使用shader最大的好处是每一步的渲染管道都可自定义支精Word文件下载.docx
《使用shader最大的好处是每一步的渲染管道都可自定义支精Word文件下载.docx》由会员分享,可在线阅读,更多相关《使用shader最大的好处是每一步的渲染管道都可自定义支精Word文件下载.docx(34页珍藏版)》请在冰豆网上搜索。
想要得到更好的效果还能使用ParallaxMapping(视差映射),OffsetMapping或DisplacementMapping(位移映射,ShaderModel4支持,但代价昂贵)。
这些技术较为复杂,除了diffuse贴图和NormalMap贴图外还需要额外的heightmap(高度图)。
但使用NormalMapping已经足够好了。
如果您有一块带shader功能的图形卡,NormalMapping并不难实现。
在老的GeForce3(shader1.1版)也能工作,就现今支持PixelShader2.0的硬件可以更好地实现反光,效果会更好。
通过前一章的学习你已经有了基本的基础,包括如何创造shader、如何转换顶点、像素如何最终显示在屏幕上。
NormalMapping需要额外的纹理。
diffuse纹理是用来显示基本的材质,颜色等等。
NormalMap纹理通过提供顶点向量添加更多的3D细节。
图7-2
请注意,如果您打开仙人掌的三维模型的kaktusseg.dds和kaktussegnormal.dds贴图,会发现kaktussegnormal.dds看上去不同,这是因为正NormalMap是压缩的,并已经被Alpha和红色通道反转,这样可以在没失去很多细节的前提下把纹理压缩至1:
4,看起来仍不错。
Moredetailsaboutthistechniqueinasecond-justrememberthatinternallytheNormalMaptexturelookslikethetexturein图7-2;
thecompressedversionjuststoresthedatadifferently。
计算每个像素的光线是要用到NormalMapping纹理中的数据,NormalMap中的每一个像素代表了多边形表面的法线向量。
这一数据是储存在正切空间,可参见图7-4。
从贴图中的颜色数据获得法线向量要用到公式(eachr,gandbvalue-0.5)/0.5(见图7-3),NormalMap中的RGB颜色数据以浮点数的形式被用在公式里,要将RGB颜色数据转换成浮点数,只需除以255,这在PixelShader里是自动完成的。
比起位图文件的字节数据,浮点数更容易使用。
NormalMap中最多的颜色是淡蓝(RGB128,128,255),这意味着大多数向量是向上的,即vector3(0,0,1)。
如果整个纹理都是浅蓝色的,这意味着每个向量都朝上,那么,NormalMap将没有任何效果。
这些NormalMap像素通常存放在切线空间,这意味着法线数据和多边形表面是有联系的。
这是所谓的切空间,通过每个顶点的切线和binormal向量你才能建立切线矩阵,而binormal可以通过叉乘法线向量和切线向量得到(见图7-4)。
在上一章,你只有每一点的位置,法线和纹理坐标数据,这些数据对NormalMapping来说是不够的,您至少需要每个顶点的切线矢量去建立这个切线矩阵,如果没有切线数据的话NormalMapping图像显示将不正确。
你可以把切向和binormal向量看成指向下一个顶点x和y方向的向量,buttheyarestilloctagonal(ina90degreeangle)tothenormalvector。
这意味着,即使您的三维模型没有任何切线数据,您也可以遍历所有顶点自己创建(不容易,但是可行的)。
问题
问题是,往往顶点可用于一个以上的多边形,有时纹理坐标合并得也不好。
对diffusemapping来说这问题不大,因为您只使用法线向量计算光线,但如果你通过切线空间转换每个像素的法线,就要求数据必须配合。
这意味最好在三维程序中生成切线数据,三维程序知道如何在NormalMappingshader中更好地使用哪些切线和输出法线。
在vertexshader中你只能访问到目前您正在处理的顶点,而不能访问下一个顶点,或建立一个新的向量指向下一个顶点。
这意味着,将有效的切线数据传送到vertexshader(顶点着色引擎)是很重要的。
如果你只是定义了支持切线的vertexinputstructure,也不意味着应用程序能够处理有效的切线数据。
通常您在内容管道有.x或.fbx文件,它没有任何切线的数据(见图7-5)。
图7-5
这意味着你仅有法线数据而没有切线数据,这样就无法为NormalMapping创建切线空间。
对于简单的对象比如这个苹果问题不大,但如何模型有更多的曲线和纹理坐标就会出现显示错误。
图7-6显示的就是切线错误的苹果模型。
图7-6
学习了以上知识后你就可以开始处理3D模型和shader了。
Asteroids(小行星)!
错误的游戏,或者是什么呢?
要建立一个象图7-1一样的3D模型并不容易,一个精细模型,可能会有几百万的多边形,建模往往会花掉很多时间,你可能还会花很多时间优化shader以便能让您的游戏引擎能在同一时间内高帧速率显示许多对象。
为了简化问题,你将只使用一些很酷的小行星模型,其中有几百万多边形的精细版本,也有1000多个多边形的低精度版本。
这些模型将被用于在下一章的RocketCommander游戏。
为了进一步优化性能,也制作了500,200,甚至50个多边形的小行星,NormalMaps在高低精度版本中都能用。
在性能测试中200个多边形的版本是最有效率的,它几乎和50个多边形版本的一样快,但看起来更好。
500个多边形的版本不见得看好多少,特别是对低端电脑上用时较长,。
因此,我们使用1000个多边形和200个多边形版本的小行星(见图7-7)。
图7-8显示了小行星使用的三个纹理。
因为所有小行星模型使用parallaxmappingshader(视差映射着色),您需要为每个对象建立一个diffuse(散射),normal(法线),height(高度)贴图。
正如前面所提到的,法线贴图被压缩,这意味着将看到红色而不是默认的蓝色法线贴图。
压缩的法线贴图是红色的是因为红色通道完全是白色、而储存在原始红色通道中的Alpha通道是不可见。
更多详情请参阅下一章的parallaxmapping.fxshader和打开NormalMapping纹理。
我在2005年写了一个名为normalmapcompressor的小工具(见图7-9),可以从我的博客上下载。
它可以用来压缩NormalMapping纹理,你将可节省75%的磁盘和纹理空间,而质量下降得很少。
这是真的很酷,在使用NormalMapping或parallaxmapping时可解决很多显卡内存不足引起的问题。
而在过去,你只有diffuse纹理和小如128×
128,256×
256的分辨率,有时512×
512更常见。
7-9
以每通道(rgba)32位、未经压缩位图的形式储存的分辨率为128×
128的纹理要占128*128*4个字节=64KB的空间,而同样尺寸压缩为dxt1DDS的文件只有8kB大小,一个JPG文件只有约5到10KB。
256×
256分辨率大小则为128×
128的四倍(未压缩256KB,dxt1为32KB,JPEG格式约20KB),若512×
512,再次四倍,即便如此,未压缩的贴图大小也仅有1MB。
但今天的纹理尺寸普遍都有1024×
1024,2048×
2048,甚至4096×
4096更为常见。
即使你只用1024×
1024的贴图,未压缩diffuse贴图也要占用4MB的空间,因为你也需要NormalMapping贴图和height贴图实现Shader效果,只是一个单一的纹理就要高达12MB的空间。
如果您的游戏使用了数百个不同的纹理,这是完全不切实际的。
你必须使用压缩纹理和只有加载需要的数据。
这就是为什么console游戏往往会重新加载关卡的数据和一般不要使用非常高的纹理,如果不这样做,对Xbox360和老的显卡来说会消耗太多显存,。
diffuse纹理可以很容易地压缩为dxt1,rgba节省8倍空间,RGB可节省6倍。
如果您使用需要Alpha数据的透明物体,您可以使用dxt5,比起未压缩rgba数据可节省4倍。
你也许会说“为什么不使用.jpg或.png文件?
”“好,您可以为节省磁盘空间使用.jpg文件,但在显存中仍需要解压缩文件并将其存储在显存中。
使用dds文件的好处是因为DXT格式更好地兼容了今天的图形硬件和节省显存。
对NormalMaps来说,最主要的问题是DXT压缩算法是针对颜色数据设计的,绿色通道占用大部分的压缩空间,因为绿色的颜色更明显。
蓝色通道对NormalMaps并不重要,因为它通常是十分明亮的,(见图7-3),但红色通道经过dxt1压缩后看起来就像crap,特别是在PixelShader1.1中,你不能再次将法线归一化,许多情况下向量长度将完全错误,NormalMap的照明看起来有错误,而且很暗(见图7-10)。
图7-10
Itisreallyhardtoexplainwithoutswitchingthecompressiononandoff,屏幕上视觉效果的不同非常明显。
Normalmapcompressor通过dxt5和将红色通道储存在Alpha通道内的方法解决了这个问题,因为红色通道都是白的,这使得绿色和蓝色有更好一点的压缩比。
Intheshaderyoujusthavetoswitchtheredandalphachannelandyouaregoodtogo。
如果你仔细观察normalmapcompressor(图7-9),你可以看到,红色通道具有最高的variation(左侧的imformation面板),这意味着红色通道具有最不同的颜色观和variation。
蓝色通道通常很低。
对于diffuse贴图和高度贴图,您可以使用dxt1。
如果你只是使用NormalMapping,你不需要高度贴图,可以节省更多的显存。
现在1024×
1024的diffuse贴图只需要512KB,1MB用于法线(dxt5需要两倍多的空间),和可选的512KB的高度贴图,如果需要,如果您使用mip-mapping,还要增加25%的空间。
用去2~2.5MB空间,看起来就能与未压缩1024×
1024的12MB大小的纹理几乎一样好了。
使用NormalMapping或parallaxmapping效果往往看上去太锐化,你可以像我在RocketCommander游戏中的那样,用512×
512的纹理代替1024×
1024的纹理,能避免锐化问题。
令人吃惊的是,RocketCommander游戏包括两个音道,许多声音效果,5个小行星模型,五个item模型,火箭模型等,纹理和特殊效果和四个关卡,只有不到10MB(译者注:
?
源文件有60MB,安装文件16MB)。
对Xbox360再大一点,因为不能播放mp3文件,但它还是很不错,以少于15MB大小的支持1920×
1080的分辨率(HDTV),比大多数Xbox360上的商业游戏看起来更清晰。
接下来的章节,本书会继续讨论如何使用为自定义的三维数据使用NormalMapping效果,使用NormalMap后图像看起来很棒,而且因为使用了压缩技术并没用占据太多的纹理空间。
7.2Shader如何运行?
现在让我们开始使用写代码,先在FXComposer(见第6章介绍)中设计Shader,这里将使用前一节图7-8的小行星的纹理。
如果您愿意,您可以按照下面所描述的步骤,象上一章的simpleshader一样编写自己的NormalMapshader。
法线映射是一个相当酷的效果,但微调要占用大量的时间。
你可以给模型的艺术家留下一些调整选项,编写一些不同的NormalMapshader以便可以选择为哪个材质使用哪个效果。
举例来说,金属应该和石头或木材材质看起来有很大不同。
打开FXComposer及normalmapping.fx文件,shader文件的布局类似于simpleshader.fx文件,但您可以使用更多的注释。
这本书以后的shader中都使用了类似的文件结构。
基本文件布局是:
●注释和Shader说明
●矩阵(world,worldviewproj,viewinverse)
●其他的全局变量如时间、测试值等
●材质数据,首先是材质的颜色,然后是所有使用的材质与采样器
●顶点结构,最重要的是vertexinput结构,通常使用你已经定义在引擎中的tangentvertex,通常使用如//-----------------的注释行为每个technique分隔数据块。
●VertexShader
●PixelShader
●Technique将shader合在一起,通常使用相同的顶点shader。
为了简单,我只解释本书接下来游戏都用到的NormalMapping效果的顶点和像素shader,在这本书里被命名为specular20,还有一个Technique被称为specular,它以同样的方式工作在ShaderModel1.1,由于PixelShader1.1有8条指令的限制,一些功能被关闭或减弱。
打开shader文件后请浏览一下文件头和参数,基本结构和上一章的simpleshader.fx是相似的。
与上一章vertexinput格式看起来很类似,但它多了切线数据。
在之前您已经遇到了.x和.fbx文件的问题,在这一章中后面将加以解决。
假设现在您有有效的切线数据可用于shader。
幸运的是FXComposer总能为标准的测试对象(如球,茶壶,立方体等等)给出有效的切线数据。
//Vertexinputstructure(usedforALLtechniqueshere!
)
structVertexInput
{
float3pos:
POSITION;
float2texCoord:
TEXCOORD0;
float3normal:
NORMAL;
float3tangent:
TANGENT;
};
在simpleshader.fx您只需开始编码,一旦运行正常就无需重构Shader的代码了。
听起自不错,但你shader写得越多,您会越想重用代码。
一个方法是直接在shader文件中定义最常用的方法:
//Commonfunctions
float4TransformPosition(float3pos)//float4pos)
returnmul(mul(float4(pos.xyz,1),world),viewProj);
}//TransformPosition(.)
float3GetWorldPos(float3pos)
returnmul(float4(pos,1),world).xyz;
}//GetWorldPos(.)
float3GetCameraPos()
returnviewInverse[3].xyz;
}//GetCameraPos()
float3CalcNormalVector(float3nor)
returnnormalize(mul(nor,(float3x3)world));
}//CalcNormalVector(.)
//Getlightdirection
float3GetLightDir()
returnlightDir;
}//GetLightDir()
float3x3ComputeTangentMatrix(float3tangent,float3normal)
//Computethe3x3tranformfromtangentspacetoobjectspace
float3x3worldToTangentSpace;
worldToTangentSpace[0]=
mul(cross(normal,tangent),world);
worldToTangentSpace[1]=mul(tangent,world);
worldToTangentSpace[2]=mul(normal,world);
returnworldToTangentSpace;
}//ComputeTangentMatrix(..)
另一种方式类似于C++的头文件,在单独的fxh文件中储存方法,参数和常量。
我不喜欢这种方法,因为FXComposer只能一次使用一个源文件,在游戏你仍要改变很多代码。
第一个函数是transformposition,它将顶点shader中的3D顶点位置转化到屏幕。
除了无需再合并worldviewproj矩阵,transformposition的工作方式类似于上一章。
不过你用一个世界矩阵来代替,但viewproj矩阵是新的。
将world矩阵与viewproj矩阵相乘你会再次获得worldviewproj矩阵,这一步不在代码而在shader中执行是因为这样做可以节省你一个的顶点shader指令。
将world矩阵从worldviewproj矩阵分离出来能让你只关心单个矩阵。
Shader中使用的数据越多,花费的时间越长,如果你重复设置参数和开始shader将明显拖慢游戏。
只建立Shader一次,然后批量改变数以千计的物体的世界矩阵要好得多,您可以一次建立一个存储了20个或更多的世界矩阵的数组,然后遍历全部,这项技术叫instancing,我用在了下面的shooter游戏中以优化性能(如果很多对象使用相同的Shader)。
在较早版本的RocketCommander游戏中我也用过,也支持固定功能管道,但要在所有的Shader模型(1.1,2.0和3.0)中正常工作代价不菲。
不过没关系,在优化了所以其他Shader后性能很好。
其他辅助方法相当简单,浏览后你应该很快就能弄懂,除了最后一个,这是用来建立我曾谈及的切线空间矩阵的。
再次看一下图7-4和7-5,看看是哪个向量。
calculatetangentmatrix在顶点shader引擎中(如所有其他辅助方法),用来计算每个顶点的切线与法线向量。
从vertexinput结构可以看出,Binormal向量从叉乘法线和切线向量获得。
您可以用您的右手重建这个矩阵-中指是法线向量,食指是切线向量,拇指代表binormal向量,binormal向量是中指和食指的叉乘,因此互成90度夹角。
切线空间矩阵对快速将法线和光线的方向从世界空间向转化到切线空间是非常有用的,对PixelShaderNormalMapping计算也是必须的。
您需要切线空间矩阵的原因是要确保所有向量在同一个空间。
这个空间是直接指向多边形的顶部和方向向上(即z方向),就像法线向量的X和Y描述切线和binormal一样。
使用切线空间是PixelShader中最容易和最快的方法,你必须获得正确的binormal和切线order去构建切线空间矩阵。
此order和binormal的叉乘也能被转化成左手坐标系。
使用单元测试以计算出哪个方式是正确的;
您也可以看一下原始版本RocketCommander游戏(左手系)中的shader,看一看与XNA版本(右手系)的区别。
Vertexshaders和矩阵
借助于所有辅助方法,现在应该可以很容易地编写顶点shader了。
首先你应该定义vertexoutput结构。
我这里只解释的PixelShader2.0版本,因为支持PixelShader1.1版本太复杂,并使用了很多汇编代码,已超出了本书的范围。
如果你真的想支持的PixelShader1.1,建议你找一本关于shader的书,以了解更多shader的细节和shader汇编语言,我推荐ProgrammingVertexandPixelShader,GPUGems,或ShaderX系列。
Shader专家Wolfga