通向天才之路 : 实时环境映射贴图技术(Real-time Evironmnet Mapping)

<Made In Coollen>

我小心翼翼地剪开了包着橡胶质外皮的线,祈祷着上帝再给它一次机会。每剪一刀,手心里面就冒出一层汗……当我剪了7段的时候,终于发现了问题,原来是四条线其中的两条线断掉了……我把它插进了电脑的USB口,手里握住那两条断的线以保持电路的联通。在经过漫长的三秒钟的时间的等待之后,我兴奋的看到游戏控制器的列表里面再次出现了它的名字:PSX-BSB Adaptor。这只从高二就一直伴随着我,随我一起南征北战的游戏手柄又复活了。这再次证明了一个事实——我果然是天才!

>>>>>> 时间快进:32:02:27 >>>>>>

如果你是个赛车游戏的爱好者,你一定玩过风靡全世界的赛车游戏《Need For Speed : UnderGround2》(《极品飞车:地下狂飚2》)。在这款游戏中,玩家扮演了一个靠赛车为生的地下飚车手,在永远是夜晚的城市中飞驰。在这款游戏中大量地运用了环境映射技术(Real-time Evironmnet Mapping),在下过雨的公路上看到两旁夜景的倒映以及玩家开的车上对周围环境的真实反射都是环境映射技术的功劳。这种最酷的最华丽的特效现在已经应用在了大量的实时虚拟显示游戏中,在将来它会成为游戏中必不可少的元素。

环境映射贴图技术最典型的应用就是车体的“流光”,这种现象在现实中非常普遍。当你开着车行驶在满是霓虹灯的街道上的时候,周围的灯光都会在你的车身上投射上一个光斑或光带(前提是你的车子洗的非常干净)。当车子在街道上飞驰的时候,就好像一个个五彩的斑点在你的车体上流动,产生非常炫目的效果。甚至在当你和别人的车子擦肩而过的时候,别人的车子也会映射在你的车体上。

要实现环境贴图映射首先需要一组环境信息。假设在场景里面有一辆你开的车(本文中将一直使用这样的例子),车的环境信息就是除掉车后,你在车的位置上向四周看到的全部画面。于是你就用照相机对六个不同方向照六张照片,这样就产生了一组环境信息。车上的每个象素都和这个环境信息中的一个点有对应关系(也就是所谓的映射)。最后渲染车的时候把车身上每个点在环境中对应的点和车体本身的效果做混合处理。

归纳起来要实现环境贴图映射需要执行下列的步骤:
        A.创建环境贴图
 B.把场景中的物体渲染到环境贴图上
        C.渲染车体时候把原始贴图和环境贴图经过处理后最终渲染到屏幕上

准备好你的车子了吗?你一定想马上体验一下给你的车子打上环境贴图是什么感觉吧。不过没有了解相关的知识是难以找到正确的方法的。
 
        什么是环境贴图?
        在上面我说过用照相机照相的比喻,在这个比喻中,那六张照片就类似于一组环境贴图,那六张照片代表了立方体的六个面。你可以想象一下,当你的眼睛处于这个立方体之中的时候,你会看到周围所有的景物,所以叫作环境贴图。

一般来说我们所使用的环境贴图都是立方体的环境贴图,这种环境贴图通常叫做立方体贴图(Cube Map)。每个Cube Map含有六个面,在DirectX中分别用+X,-X,+Y,-Y,+Z,-Z来表示,它的每个面都在水平和垂直平面上都覆盖了90度的视角。
       
        我们要如何操作这组Cube Map呢?
        在我们所使用的DirectX 9中,分别使用了六个不同的标识来表示Cube Map中的哪一个面。这组标识叫做D3DCUBEMAP_FACES,其中的D3DCUBEMAP_FACE_POSITIVE_X就代表了Cube Map中的以中心点为原点的正X轴所指向的那个面,以此类推。而Cube Map的每一个面都是一个LPDIRECT3DSURFACE9对象,当你需要对其中某一个面进行某些操作的时候,就需要先调用GetCubeMapSurface函数来得到你所指定的一个面(Surface),然后像操作一般的面一样操作即可。而本文中将要对它做的操作就是把面作为渲染目标(Render Target),把周围环境中的景物全部绘制到这个Cube Map上。因为Cube Map在每个方向上有一个面,而每次绘制其中一个面,就要对场景中所有的景物进行一次渲染。这个过程的开销是十分大的,原来只需要绘制一次的场景,如果你的场景中一个物体使用了环境映射,就会增加6倍的渲染消耗!如果一个游戏原来跑70帧,当绘制一个使用了环境映射的特效的时候,马上降到了10帧!这是一个相当可怕的数字!你也许会考虑:那么这项技术会不会因为开销太大而失去使用的价值?答案是不会的。以目前人类的智慧已经可以非常好的处理好环境映射的效率问题,所以不必担心,而且在文章的最后将会讲解这些办法。

如何使用Cube Map?
        当你渲染好了你的贴图坐标,下一步就是如何使用的问题了。一张贴图要绘制到屏幕上就要通过坐标和模型的顶点对应起来。我们都知道,对于2D贴图来说使用u和v两个坐标来表示顶点对应的贴图坐标。而在Cube Map中,仅有2个量是无法表示一个点在立方体中的位置的,所以,Cube Map的贴图坐标是由3个数的向量来表示的,你可以简单把这个贴图坐标对应的颜色的理解为是从盒子中心向这个3D向量的方向前进和盒子的交点所在的点的象素颜色值。比如你要让场景中的一个球映射出周围的环境,最简单的只要把球的每个点的法线做为贴图坐标传给图形处理芯片就能让球有种金属球的感觉。不过,仅仅这样做的话,绘制出的图形是和现实世界中的映射有些出入。

现在你可以把你的车子开到你的车库里面,停下车,把你车库的墙壁和天花板以及地面铺上放大了的照片,就像做环境映射一样……错了,接下来应该是看看实际操作应该怎么做了。

微软DirectX 9为了应用程序开发方便提供了一个ID3DXRenderToEnvMap的接口,通过这个帮助类接口我们可以十分快速地完成对环境贴图的操作。当然,作为一个通用的接口,它不仅含有对Cube Map的操作,还支持其他一些环境贴图,比如Sphere Map等。我们可以通过查看DirectX 9的相关文档了解更多信息。在以下的代码中实现的是创建环境贴图的工作:

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

#define CUBEMAP_SIZE 128
       
        IDirect3DCubeTexture9* m_pCubeMap;
        ID3DXRenderToEnvMap m_pRenderToEnvMap;

// 创建RenderToEnvMap对象。其中m_pd3dDevice和m_d3dsdBackBuffer分别是指向Direct3D设备和显示缓存页面的指针。CUBEMAP_SIZE这个宏指定了Cube Map的边长,它关系到环境贴图的大小,值越大占用空间越多,绘制也越慢。

if( FAILED( D3DXCreateRenderToEnvMap( m_pd3dDevice, CUBEMAP_SIZE, 1,
            m_d3dsdBackBuffer.Format, TRUE, D3DFMT_D16, &m_pRenderToEnvMap ) ) )
        {
            return E_FAIL;
        }
       
        // 创建空的Cube Map,D3DUSAGE_RENDERTARGET说明创建的Cube Map是一个渲染目标。

if( m_d3dCaps.TextureCaps & D3DPTEXTURECAPS_CUBEMAP )
        {
            if( FAILED( D3DXCreateCubeTexture( m_pd3dDevice, CUBEMAP_SIZE, 1,
                D3DUSAGE_RENDERTARGET, m_d3dsdBackBuffer.Format, D3DPOOL_DEFAULT, &m_pCubeMap ) ) )
            {
                return E_FAIL;
            }
        }
        else
        {
               return E_FAIL;
        }

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
       
        绘制的过程需要先得到一个新的投影矩阵(Projection Matrix),这个矩阵设置了摄像机的视角和场景深度范围等。另外还要计算出一个新的观察矩阵(View Matrix),这样Cube Map才会在“车”上随着视角的改变而变化。
       
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

HRESULT RenderSceneIntoEnvMap()
        {
            HRESULT hr;

// 设置投影矩阵
            D3DXMATRIXA16 matProj;
            D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI * 0.5f, 1.0f, 0.5f, 1000.0f );

// 得到当前观察矩阵
            D3DXMATRIXA16 matViewDir( m_matView );
            matViewDir._41 = 0.0f; matViewDir._42 = 0.0f; matViewDir._43 = 0.0f;

// 把场景绘制到Cube Map上
            if( m_pCubeMap )
                hr = m_pRenderToEnvMap->BeginCube( m_pCubeMap );

if(FAILED(hr))
                return hr;

for( UINT i = 0; i < 6; i++ )
            {
                // 设置Cube Map中的一个面为当前的渲染目标
                m_pRenderToEnvMap->Face( (D3DCUBEMAP_FACES) i, 0 );
   
                // 计算新的观察矩阵
                D3DXMATRIXA16 matView;
                matView = D3DUtil_GetCubeMapViewMatrix( (D3DCUBEMAP_FACES) i );
                D3DXMatrixMultiply( &matView, &matViewDir, &matView );

// 设置投影和观察矩阵并渲染场景(省略)。注意:这里的渲染场景中的物体并不包括使用环境贴图的物体。
              ... ...
            }

m_pRenderToEnvMap->End( 0 );
            return S_OK;
        }

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
       
        渲染出环境贴图之后就需要把这个环境贴图使用在目标物体上,也就是上面一直都作为例子的那辆车子。我们在渲染场景(并不是上面一步中说到的渲染场景,而是真正绘制到屏幕的时候)中使用一个特效(Effect)来绘制。特效可以从内存中一段字串创建,也可以从一个特效文件(后缀名为fx,由DirectX9 SDK自带的Effect Edit生成)来创建。特效的使用方法可以查看SDK中的相关文档和例子,因为涉及的东西非常多,所以不在这里废话了。特效文件的代码如下,这里给出了基于固定渲染管道(Fixed Function Pipeline)的实现代码,除此之外还可以使用可编程渲染管道(Programmable Pipeline)来实现。
               
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

textureCUBE texCubeMap;

matrix matWorld;
        matrix matView;
        matrix matProject;

technique Cube
        {
            pass P0
            {
                // Vertex state
                VertexShader = null;
                WorldTransform[0] = <matWorld>;
                ViewTransform = <matView>;
                ProjectionTransform = <matProject>;

// Pixel state
                Texture[0] = <texCubeMap>;
           
                MinFilter[0] = Linear;
                MagFilter[0] = Linear;

AddressU[0] = Clamp;
                AddressV[0] = Clamp;
                AddressW[0] = Clamp;

ColorOp[0] = SelectArg1;
                ColorArg1[0] = Texture;

TexCoordIndex[0] = CameraSpaceReflectionVector;
                TextureTransformFlags[0] = Count3;
            }
        }

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

NOTE:
        1.用静态环境贴图代替实时渲染
        除了实时的渲染环境贴图之外,另外一个比较常见的做法是从文件中读取一个张预先生成的环境贴图。使用静态贴图实现起来比实时渲染简单,它不需要把场景绘制到Cube Map上,只需要在创建的时候从硬盘文件中载入即可。使用静态贴图可以大大降低渲染开销,是一种适合在低端环境中使用的技术。不过使用静态贴图会大大降低游戏真实性,举例来说,车体上的贴图不会随环境的改变而变化。

2.减少实时渲染的渲染开销
        除了使用静态环境贴图外,还可以使用其他方式来降低系统开销。比如《地下狂飚2》是使用了限制环境贴图刷新率的办法来实现实时渲染,就是说每秒环境贴图只渲染一定的次数,而不是每帧都做渲染。而且这个频率可以让玩家来调整。另外一个方法是限制场景中渲染的物体的数量。比如车子开到路段A上,就只会对路段A两边的建筑进行绘制;或者可以只绘制一部分能够被清晰映射的物体。除此之外还可以减小环境贴图的大小,这会对渲染效果产生影响,车体上的环境映射效果会比较模糊。

3.使用可编程渲染管道
        使用可编程渲染管道大大提高了显卡编程的灵活性,随着显卡性能的逐日提高,可以由显卡绘制出更真实的特效。对于环境贴图来说,上面的代码是使用顶点计算贴图坐标,如果使用了逐象素(Per-Pixel)计算,将会把效果表现的更加真实,当然这样做的开销太大,目前仅限于顶级显卡。

4.BLOG
 访问Coollen的Blog可以获得更多信息。(Code::Life http://blog.coollen.com)

>>>>>> 时间快进:342:34:47 >>>>>>
       
        火焰纹章是SLG的,手柄是修好的,玩起来是爽的。
        音乐是钢琴的,音箱是25块的,听起来是不爽的。
        台风是公测的,Bug是要改的,加班是没钱的。
        写程序是要命的,时间是没有的,女友是跑掉的。
        成绩是倒数的,技术是菜鸟的,前途是没有的。

通向天才之路 : 实时环境映射贴图技术(Real-time Evironmnet Mapping)相关推荐

  1. 通向KDE4之路(十一):Amarok2开辟起步

      Troy Unrau 本周我们未来看看Amarok2将泛起的浩繁特征中的一部分,Amarok2是KDE4中的Amarok开辟分支.我们在此所会商的一切特征的开辟已靠近完成.下面是关于Amarok的 ...

  2. 乔治亚州立大学如何利用算法来帮助学生通向大学之路?

    文章来源:ATYUN AI平台 随着AI的不断发展,对其潜力的一项重大考验将是它能否以个性化.复杂的方式取代人类的判断.在乔治亚州立大学(GSU),我们调查了一个测试案例,在这个案例中,AI帮助高中学 ...

  3. 搜索双链路实时计算体系@双11实战

    该文章来自阿里巴巴技术协会(ATA)精选集 0. 前言 何为双链路实时计算体系? 微观实时计算链路 a) 最细粒度商品/店铺/用户数据的实时 b) 底层模型的实时 宏观实时计算链路 相比微观实时,宏观 ...

  4. Asp.net mvc 实时生成缩率图到硬盘

    之前对于缩率图的处理是在图片上传到服务器之后,同步生成两张不同尺寸的缩率供前端调用,刚开始还能满足需求,慢慢的随着前端展示的多样化,缩率图已不能前端展示的需求,所以考虑做一个实时生成图片缩率图服务. ...

  5. echarts折线图怎么从y轴开始_基于echarts的双y轴实时更新折线图

    一款基于echarts的双y轴实时更新折线图效果,页面加载后开始自动更新数据并绘制对应的折线图,可以点击右上角的按钮:显示数据视图.刷新数据和将数据存储为png的图片. 查看演示 下载资源: 46 次 ...

  6. <Linux开发> ubuntu开发工具-Ubuntu测试网速及实时网速图

    <Linux开发> ubuntu开发工具-Ubuntu测试网速及实时网速图 一.查看网线上行.下行网速 1.安装speedtest-cli工具 water@water-Tower-PC:~ ...

  7. 混在中国,财富保值的必要性,读《金砖四国之梦:通向2050之路》有感

    [写这个专栏blog的由来] 1.  从07年开始,我一直有在断断续续的买基金,学习了一些基金知识,自己摸索了一些投资思路,慢慢形成了自己的投资风格,可能有点怪异,希望能与大家分享. 2.  过去的几 ...

  8. 阿里云副总裁杨名:“通向智能之路”

    原文链接 6月15日,第十五届中国软交会在大连开幕.在当日举行的2017全球软件和信息服务高峰论坛上,阿里云计算有限公司副总裁杨名以"通向智能之路"为题,回顾了阿里云的成长壮大历程 ...

  9. 伦敦金天天实时行情走势图

    伦敦金天天的走势图走势图中都有交易的机会,但高质量的交易信号和进场时机不是经常出现,如果能够过滤掉不佳的交易信号,大家的投资绩效就有望大幅提升.在每天的实时行情走势图中,长影线K线是高胜率的信号,它代 ...

最新文章

  1. 做接口测试最重要的知识点
  2. 高通平台耳机插拔检测
  3. 【数据结构与算法】之深入解析“穿过迷宫的最少移动次数”的求解思路与算法示例
  4. java socket编程实现聊天程序_java Socket编程 聊天程序 服务器端和客户端
  5. 我到底要选择一种什么样的生活方式,度过这一辈子呢:人生自由与职业发展方向(下)...
  6. SVN文件上感叹号、加号、问号等图标的原因
  7. 驱动中定时器,taskle,工作队列编程
  8. String、StringBuffer与StringBuilder的区别
  9. 用Python对全国火车站数量进行分析,发现东北三省竟然占了2成
  10. Foreign Language_english_补语
  11. 软件测试培训一般多少钱?
  12. 【博文笔记】Attentive Reader\Impatient Reader:机器阅读理解之开山之作Teaching Machines to Read and Comprehend
  13. assoc 和 ftype
  14. GitHub个人Blog完全攻略
  15. 【Unity】Unity中影响性能的几个因素
  16. TTTTTTTTTTT 400D Dima and Bacteria 细菌 最短路
  17. go语言交叉编译 - 附xgo踩坑之旅
  18. 密评复习(选择+简答)
  19. mysql查询数据库文件信息_查询数据库信息
  20. 使用Scrum进行敏捷项目管理

热门文章

  1. notepad转换json_Notepad++的Json格式化插件
  2. python操作鼠标进行点击
  3. JavaScript也可以制作颜色拾取工具了
  4. 【转】很全的英语短语
  5. 120行python代码解锁10000分微信跳一跳
  6. ES6 之显示Unicode
  7. docker和vm不兼容遇到的坑
  8. 基于easyui 1.3.6设计的后台管理系统模板界面
  9. 关于VSCode以及DEV-C++在进行网络编程时出现的WS2_32链接问题
  10. API接口自动化测试框架搭建(十三)-优化operate_conf.py并创建用户数据目录data