|
楼主 |
发表于 2008-10-23 18:55:40
|
显示全部楼层
先是一个Vertex Shader:
///////////////////////////////////////////////////////////////////////////// // Copyright (c) FrontFree_Studio. All rights reserved. ///////////////////////////////////////////////////////////////////////////// /**************** GLOABLE VARIABLES ****************/ float4 MtrlSpec = { 1.0f, 1.0f, 1.0f, 1.0f }; // material’s specular color, white float SpecPow = 8;
float3 OmniPos : POSITION = { 0.577, 0.577, -0.577 }; // lights (world space) float4 OmniColor = { 1.0f, 1.0 f, 1.0f, 1.0f }; // light’s color, white float4 AmbLight = { 0.9f, 0.9f, 0.9f, 0.9f }; // ambient light color; float3 CameraPos : CAMERAPOSITION = { 0.0f, 3.0f, 5.0f }; // camera (world space) float4x3 matWorld : WORLD; float4x4 matViewProj : VIEWPROJ; /***** vertex shader output structure *********/ struct VS_OUTPUT { float4 Pos : POSITION; float4 Intensity : COLOR0; float4 Spec : COLOR1; float3 Texcoord : TEXCOORD; }; /**************** VERTEX SHADER ****************/ VS_OUTPUT VS( float3 InPos : POSITION, float3 InNormal : NORMAL, float2 InTexcoord : TEXCOORD ) { VS_OUTPUT Out = (VS_OUTPUT)0; //Calculate the output color by per-pixel lighting // first, calculate the diffuse component float3 P_World = mul(float4(InPos, 1), matWorld); // position to world space float3 ToLight = normalize( P_World – OmniPos ); float3 Normal = normalize( InNormal ); float4 Diff = dot( ToLight, Normal ) * OmniColor; Out.Intensity = Diff + AmbLight; // then, the specular component float3 Reflection = reflection( -ToLight, Normal ); float3 ToEye = normalize( CameraPos – P_World ); float4 Spec = pow( dot( Reflection, ToEye ), SpecPow ) * MtrlSpec * OmniColor; Out.Spec = Spec; // Output the position, texture coordination Out.Pos = mul(float4(P_World, 1), matViewProj); Out.Texcoord = InTexcoord; return Out; }
下面是个Pixel Shader:
///////////////////////////////////////////////////////////////////////////// // Copyright (c) FrontFree_Studio. All rights reserved. /////////////////////////////////////////////////////////////////////////////
/**************** TEXTURES ****************/ texture TexMap < string name = "test.dds"; >; sampler2D TexSampler = sampler_state { Texture = ; MipFilter = LINEAR; MinFilter = LINEAR; MagFilter = LINEAR; }; /**************** PIXEL SHADER ****************/ float4 PS ( VS_OUTPUT In ) : COLOR { float4 Tex = texture2D( TexSampler, In.Texcoord ); return Tex * In.Intensity + In.Spec; }
这是一个简单且无聊的例子,说它无聊是因为没有任何特效,仅仅是有高光,外加一重纹理贴图。如果不用Shader实现,仅需在D3D默认效果上多加一行代码。这个例子只是为了告诉你Shader里都需要干什么。首先是在Vertex Shader中将物体坐标转换至屏幕坐标;同时进行光照计算出顶点的亮度,这里用的是Phone模型。然后在Pixel Shader中进行纹理取样,再混合上亮度得到最终结果。高光是在Vertex Shader中计算出来并在Pixel Shader中加上去的。
光照模型 说通俗一点,光照模型就是指采用何种方式来根据光源方向、顶点(或像素)位置、法线等信息计算该顶点(或像素) 明暗信息。上面的Shader中采用的是最常见的每顶点Phone模型。即每个顶点的明暗信息由环境光(Ambient)+漫反射光 (Diffuse)+高光(Specular)组成。通常,灯光打在物体上是通过漫反射进入眼睛,因此不管你从哪个方向看一个馒头, 上面的大部分明暗只取决于背光还是向光。数学上就是取决于该顶点向光的方向(图中的向量L)和其法线之间的夹角。线数中两根标准化的 向量点乘结果就是其夹角的最好度量。因此程序里也就这么算。高光(准确讲该叫镜面反射光)就不一样了。你应该见过这样的情节 :张某疾步冲进绿柳庄,突然眼前金光一闪,“哦,倚天剑”……实际上倚天剑和阳光的位置并没有改变,张某是否看见高光 还要取决于他视线方向和光线反射方向的关系。这儿有个比较复杂的数学公式来计算高光分量,相信读者应该能把他找出来。至于 环境光,实际的场景中光线是非常复杂的,即使没有光源直接照射在物体上,它也将受到周围物体漫射出来的光线照亮。Phone 模型中,假设环境光是从四面八方来的强度一样的光,因此环境光照分量基本上是不取决于任何方向向量的常量。最后,各个分量 是加在一起最终决定明暗的。
上面只讨论光照强度,记得美术老师讲过,物体的颜色取决于光源本身的颜色,光照强度和物体材质的颜色。然后D3D老师讲过,只要把那三个东西乘到一起就得到了最终的颜色。这里的乘是指各个颜色分量(红绿蓝)分别相乘,实际上这是产生颜色过滤的方法。比如说,一个颜色值为(r,g,b,a)的颜色乘以50%灰(0.5,0.5,0.5,0.5)的结果就是(0.5r, 0.5g,0.5b,0.5a),亮度减了一半,相当于带了个50%灰的眼镜看。
由于本篇着重介绍的是HLSL语言,所以对于光照模型,笔者不打算做过多说明,将放到以后的文章中讲述。
好了,来看代码。每个Vertex Shader都要以顶点数据流中的数据作为其入口参数。但你头脑里清楚哪个参数是位置,哪个是法线,哪个是贴图坐标,你完全可以把法线叫m_Position,叫abcd,叫……D3D如何知道把数据流中的法线信息往哪儿放呢?好,Semantic。D3D与我们约定了一套Semantic:其中POSITION是位置,NORMAL、TANGENT、BINOMAL分别是法线、切线、副法线,TEXTURE0-7表示8套贴图坐标。
切线是指顶点的切线向量,副法线是与法线和切线正交的向量,都是预计算好并存在顶点数据里的。常用于凸凹贴图技术。
Vertex Shader的输出数据,也就是返回值可以包含很多信息,但是必须有一个float4是表示位置的。因为D3D认为,你可以在顶点处理过程中不作任何光照处理,但是必须要把顶点转换至正确的屏幕位置。除了位置以外,其他都将向下传入Pixel Shader作为其入口参数。这些参数也用Semantic标识。
搞清楚一点很重要,当每个顶点的颜色被计算出来之后,像素的颜色将由它所在的三角形的三个顶点插值得到。还记得Direct3D Tutorials2吗?
你仅仅指定了这个三角形顶点的颜色,而中间那些好看的渐变效果实际上是硬件自己对顶点颜色进行插值得到每个像素的颜色。当我们使用光照的时候,只不过是用光照计算出的顶点颜色代替了你手动指定的颜色。贴图坐标也是这样进行插值的。
Pixel Shader所接收的Semantic是COLOR0-7(颜色),TEXCOORD0-7(贴图坐标)。这里用个结构体来放置所有的返回值,以及Vertex Shader的函数原形一并如下所示:
struct VS_OUTPUT { float4 Pos : POSITION; float4 Intensity : COLOR0; float4 Spec : COLOR1; float3 Texcoord : TEXCOORD; }; VS_OUTPUT VS( float3 InPos : POSITION, float3 InNormal : NORMAL, float2 InTexcoord : TEXCOORD )
Shader中的处理过程无非也就是计算出四个返回值分量。
首先位置(Pos)很好说,就是将顶点的原始位置(InPos)乘上变换矩阵。
float4x3 matWorld : WORLD; float4x4 matViewProj : VIEWPROJ; … float3 P_World = mul(float4(InPos, 1), matWorld); // position to world space Out.Pos = mul(float4(P_World, 1), matViewProj);
你看到这两个全局变量被Semantic标志为WORLD和VIEWPROJ,程序中将通过它们把当前的世界变换矩阵,视矩阵和投影矩阵的乘积传进来。
这里有个优化的原则。记住Vertex Shader是每处理一个顶点运行一遍,如果某些信息对所有顶点(指同一批顶点流即同一物体中,使用同一个Shader的所有顶点)来说计算结果都一样,那最好把它在程序中计算出来直接传给Shader。例如三个变换矩阵的乘积。本例中,由于还需要把顶点位置变换置世界空间作他用,因此没要求程序把矩阵都乘起来。
同理,每顶点能够计算出来的东西就不要放在Pixel Shader中让每像素都重复一遍。
将顶点乘以矩阵用到了HLSL的内置指令mul(),实现矩阵乘法。不牵扯到投影的三维坐标变换并不需要第四维w坐标,而且在这些变来变去的过程中w会始终保持为1。因此程序传进来的顶点数据也是没有w的。四维向量才被允许乘以4x4矩阵,因此我们还要把InPos和P_World补上w坐标,即float4(InPos, 1)。
然后我们把每个顶点照亮赋给Color。这几个全局变量就是光照要用到的一些材质颜色,灯光等信息。我想应该能让你看明白都是干什么的了。SpecPow是用于计算高光的,越大,反光面积越小。其余的我想也不用多解释了吧。
float4 MtrlSpec = { 1.0f, 1.0f, 1.0f, 1.0f }; // material’s specular color, white float SpecPow = 8; float3 OmniPos : POSITION = { 0.577, 0.577, -0.577 }; // lights (world space) float4 OmniColor = { 1.0f, 1.0 f, 1.0f, 1.0f }; // light’s color, white float4 AmbLight = { 0.9f, 0.9f, 0.9f, 0.9f }; // ambient light color; // camera (world space) float3 CameraPos : CAMERAPOSITION = { 0.0f, 3.0f, 5.0f }; … float3 ToLight = normalize( P_World – OmniPos ); float3 Normal = normalize( InNormal ); float4 Diff = dot( ToLight, Normal ) * OmniColor; Out.Intensity = Diff + AmbLight; // then, the specular component float3 Reflection = reflection( -ToLight, Normal ); float3 ToEye = normalize( CameraPos – P_World ); float4 Spec = pow( dot( Reflection, ToEye ), SpecPow ) * MtrlSpec * OmniColor; Out.Spec = Spec;
至于贴图坐标,直接把它拷贝到返回值就行了。
Out.Texcoord = InTexcoord;
进入Pixel Shader,所有入口参数由Vertex Shader中的计算结果插值得到。我们只不过简单的用tex2D()进行了一下贴图取样,然后与光照强度混合得到最终结果。贴图的颜色其实才是美术老师说的“物体材质的颜色”。
float4 PS ( VS_OUTPUT In ) : COLOR { float4 Tex = texture2D( TexSampler, In.Texcoord ); return Tex * In.Intensity + In.Spec; }
总结
笔者已经进自己可能给大家讲清楚了HLSL的一些基础知识。从它的优点作用,到基本概念,到渲染流程,最后用个例子给大家串了一遍。
这只是上篇,下一次,我们将看到:
l Shader是如何与你的程序进行交互的
l Shader使用的极致——Effect File
l 介绍多种编辑、编译、调试Shader的工具
l 等等
好,文章中如有疏漏,恳请大家指出,非常欢迎各位与我交流。我的邮箱是xuantz@frontfree.net,msn是phyligefox@hotmail.com
参考文献: 《Vertex Shader 结构》 (http://www.gameres.com/Articles/Program/Visual/3D/VertexShader.htm)
《Introduction to the DirectX 9 High-Level Shader Language》Craig Peeper Microsoft Corporation, Jason L. Mitchell ATI Research (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnhlsl/html/shaderx2_introductionto.asp)
《ATI? Radeon X800 3D Architecture white paper》 |
|