这一章讲的是纹理,也是Direct3D中必不可少的一个技术:你见过那种所有物体基本都是单色的游戏吗?
以前我在这里一直不明白光照和纹理的关系。更具体地说,是在启用光照和材质之后,模型本身已经有了颜色,那么再用纹理给模型增添颜色,这几种颜色的关系是怎么样的呢?它们如何共存呢?
以前看非shader方法(就是固定管道渲染方法)的时候老是搞不懂,现在用shader方法全明白了,因为我可以看见实现的细节了!事实上,是这样的:
ambient color一般设为灰度光,然后在VertexShader中,与根据光照和材质得到的diffuse color相加得到的结果作为新的diffuse color,接着在Pixel Shader中,这个新的diffuse color会与材质的颜色进行modulate运算,得到一个调色后的效果,作为最后的diffuse color。而specular color一直是单独处理的,最后在Pixel Shader中与上面提到的最后的diffuse color做加法,从而得到最终的颜色。
这一章的内容并不简单,讲了很多比较高端的知识。事实上,纹理是Direct3D中比较难的部分,以至于本书的第III部分还会有两章内容来讲述纹理映射的高级技术。
这一章最令人纳闷的恐怕就是第4节Mipmaps了,不过幸好,在我们的这些简单程序示例中,并不需要太关注这一点。
下面开始讲习题吧!
习题1部分:
===============================================================================
这道题很简单,就是让我们画一个金字塔形的物体,然后在它的每个面上贴上板条箱的纹理。具体写起来有点麻烦,但是是纯体力活,没什么技术含量。
本来我打算在每个面上贴上不同的纹理的,但是我发现没办法很方便地实现这一点,因为这表示你必须将每个面作为一个单独的物体进行绘制,而这显然很麻烦;当然你也可以将需要的几种纹理拼凑在一起形成一张大的纹理图,然后在填充顶点数据的时候注意一下就好了;不过这样还是很麻烦。咳!看起来还是有3D建模软件比较好啊!
不管怎么说,这个程序做出来了,下面是运行时截图:
然后是程序的代码:
===============================================================================
习题2部分:
===============================================================================
这道题让我们在SDK文档中查找D3DXCreateTextureFromFileEx函数的用法。这道题想必大家都会做,所以俺就略过了。
不过值得一提的是,这个函数使用起来很复杂,除非有特殊需要,或者你已经很熟练了,否则还是使用简单的D3DXCreateTextureFromFile函数吧!
===============================================================================
习题3-5部分:
===============================================================================
习题3、4和5 都是针对TiledGround demo 的,所以我就把它们放在一起做了,结果成了一个比较大、但是十分给力的程序!
为了好玩,我让程序具有运行时改变MaxAnisotropy 值、texScale 值以及address mode 的功能。MaxAnisotropy 的值要改变很简单,把它的取值作为一个effect parameter传递进.fx 文件中就行了。至于 texScale 的值,其实类似;不过值得注意的是,我把它进行了不小的变动,就是从texScale 变成了texTiledTimes,也就是在u(或者也可以说是v)方向上tile 的次数。这样,当texTiledTimes=1.0 的时候,纹理没有进行重复;而当texTiledTimes =10.0 时,地面被tile了10×10=100次。
至于address mode,这个稍微难办一些,因为你不应该把它当作数字来处理,而是当成一个指标,然后利用switch语句(或者if/else语句)进行匹配。注意到我定义了3个sampler。这是因为在.fx文件中,只有在vertex shader和pixel shader中才可以出现if/else这样的高级语法,其他地方,比如说sampler内部,甚至是“全局空间”,都不可以出现这样的语句。在在某种程度上来说是很令人郁闷的。
最后值得一提的是我用来改变texTiledTimes(还有addressmode)的方法。我设定是按下Q键,则会增大texTiledTimes;而按下E键,则会减小texTiledTimes。我本来觉得这个很简单,每次增大或减小1就行了。然而我错了:事实上,我尝试了之后,发现不管你按得多么快,它增加或减少的值都会远远高于1!后来我整明白了:由于帧率比较高,所以在你觉得你只是按下一瞬间的时候,那个updateScene 程序可能已经运行了10次,从而会让texTiledTimes 变动10个单位!既然如此,那么别让它每次变动1个单位,让它变动dt 就是啦!这个想法看上去很诱人,不过我尝试了之后,又发现了更奇怪的问题,让我不知所措。大家可以试试。总之,这个问题让我很苦恼。不过昨天,我终于想到了解决办法了!
我的解决办法就是设定一个辅助变量,一个static float 类型的dtexTiledTimes,它表示你想要对texTiledTimes 进行的改变。它在每次调用updateScene 函数时只会变动dt,这样当updateScene 函数累计运行1秒钟之后,dtexTiledTimes 才会变动1,而这时候我们就对texTiledTimes 进行相应的增减操作,再让dtexTiledTimes 归零就行了!哦也!我是不是很聪明呢?
不过我觉得最好的方法就是产生一个用户界面,在这里可以通过直接输入或者下拉菜单的形式进行数字的输入。不过这个实现起来有点太麻烦了,而且我暂时也不会。
此外有一个神奇的问题:当keyDown的参数位四个方向键(DIK_UP、DIK_DOWN、DIK_LEFT、DIK_RIGHT)时,编译的时候会发出警告,然后虽然有时候仍然可以正常运行,但是有时候按键就不灵了。这让我百思不得其解:究竟是什么原因使得这四个键如此特殊呢?
好了,来欣赏一下运行时的截图吧!
第3题的效果:
第4题的效果:
第5题的效果:
下面是第3-5题的答案下载地址:
===============================================================================
习题6部分:
===============================================================================
这道题让我们来进行纹理的混合。其实跟书上多重纹理那一节差不多,但是不同的是,这里只有一张color texture。
所以具体实现上就有一些区别啦。我是这样做的,觉得这也应该是作者的本意:那张flarealpha 的图上每个像素的灰度代表的是flare这张图上相应像素的权重,也就是说,如果flarealpha 上(1/4, 1/4) 位置处的颜色为(122, 122, 122),那么在最终得到的混合纹理上,(1/4, 1/4) 处的颜色值为flare 这张图上(1/4, 1/4) 处的颜色值的(122+122+122) / (255+255+255)。由于flarealpha 是一张黑白图,所以那个权重也可以这样计算:122/255。
不过在具体计算的时候,需要注意一点:在vertex shader和pixel shader中,颜色是D3DXCOLOR类型的,也就是说颜色的每个成分值是0.0到1.0之间的浮点数!所以如果B(i, j) 是flarealpha 上位于(i, j) 处的D3DXCOLOR 类型的颜色,那么权重应该这样计算:
(B.r + B.g + B.b)/3.0
或者,因为是黑白图,所以直接用B.r 的值就行了!
下面是运行时的一个截图:
奉上答案下载地址:
===============================================================================
习题7-8部分:
===============================================================================
这两题让我们分别利用球形纹理和柱状纹理来给茶壶和圆锥贴上纹理图。这个很简单,按照书上的来做就行了。
值得注意的是,书上提过在计算球形纹理坐标的时候,在两极位置会出现偏差(distortion),但是并没有对这种偏差进行处理。我为了图省事,也没有进行处理,所以在运行的时候,你们可以看见两极位置的图案有点怪怪的。
运行时截图:
答案下载地址:
===============================================================================