利器:HLSL起步教程-完整篇(8)

2019-05-27 21:23

}

//determine the distance from the light o the vertex and the direction float3 LightDir = LightPos – VertPos; float Dist = length(LightDir); LightDir = LightDir / Dist;

//Compute distance based attenuation.

float DistAttn = saturate( 1 / ( LightAttenuation.x + LightAttenuation.y * Dist + LightAttenuation.y * Dist*Dist)); //comopute angle based attenuation

float AngleAttn = saturate ( dot (VertNorm, LightDir));

// Computer the final lighting

return LightColor * DistAttn * AngleAttn;

VS_OUTPUT vs_main( float4 inPos: POSITION, { }

我把计算点光源光照的代码单独放到了Light_PointDiffuse函数中,因此,当场景中有多个点光源时,你可以复用这段代码。当然,我们在后面的章节会有这样的例子。

我们已经有了个一个漫反射点光源着色器,现在应该考虑光照方程中的高光部分了。和漫反射相比,镜面高光最大的区别就是光照亮度不但与光线到表面的角度有关,还和观察者的角度有关。

VS_OUTPUT Out; Out.TexCoord = inTxr;

float3 inNormal: NORMAL, float2 inTxr : TEXCOORD0)

Out.Pos = mul ( view_proj_matrix, inPos);

float4 Color = Light_PointDiffuse ( inPos, inNormal, Light_Position, Light1_Color, Light_Attenuation) Out.Color = Color;

return Our;

为了计算高光,我们需要一个称为中间(half)矢量的值。这个矢量其实是观察矢量和灯光矢量的中间值。为了计算观察矢量需要把观察点变换到模型空间。我们假设观察点的位置位于( 0,0,10)。把它变换到模型空间之后,加上顶点位置,就是最终的观察矢量:

HalfVect = normalize ( LightDir – EyeVetor);

把EyeVector与光源矢量混合到一起,然后进行标准化,就是中间矢量。由于EyeVector和LightDir都是标准矢量,所以

中间矢量相当于二者的均值(A + B)/ 2。

EyeVector = -normal(mul ( inv_view_matrix, float4(0,0,10,1) + inPos);

(译注:更直观的做法是在世界坐标下计算EyeVector,然后用LightDir + EyeVetor计算HalfVect)

有了中间矢量,把它和表面法线的点积进行m次幂运算,就能算出光源基于角度的光线强度。幂运算时的指数相当于物体的镜面指数,值越大,高光区域就越小也越明亮。下面的代码假设镜面指数为32。

struct VS_OUTPUT { };

float4 Light_PointSpecular( float3 VertPos, float3 VertNorm,float3 LightPos, { }

VS_OUTPUT vs_main( float4 inPos:POSITION,float3 inNormal:NORMAL,float2 inTxr:TEXCOORD0) { }

当考虑光照时,大部分人都认为逐顶点的光照足够好了。对于镶嵌度较高的模型来说是这样,但对某些复杂或的精度模型当逐顶点照亮对象时,将为每个顶点计算一次光照颜色,然后在通过顶点在多边形所覆盖的区域对像素颜色进行线形插值。

来说却不一定。出现这种效果的原因是顶点间颜色插值的方式。

现实中,光照值取决于光线角度,表面法线,和观察点(对于镜面高光来说)。与逐像素对所有光照元素进行单独插值,再计算光照相比,直接对顶点颜色进行插值所得的结果通常不够精确,特别是对面积较大的多边形来说。

VS_OUTPUT Out;

//compute the projected positon and send out the texture coordinates Out.Pos = mul(View_proj_matrix,inPos); Out.TexCoord = inTxr; //Output the ambient Color float4 Color = Light_Ambient; //Determine the eye vector

float3 EyeVector = -normlaize(mul(inv_view_matrix,float4(0,0,10,1)) + inPos); //computer the light contribution

Color = Light_PointSpecular(inPos,inNormal,Light1_Position,Light1_Color,Lihgt_Attenuation, EyeVector); //Output Final Color Out.Color = Color; return Out

float4 Pos: POSITION;

float2 texCoord: TEXCOORD0; float2 Color: COLOR;

float4 LightColor, float4 Lightattenuation, float3 EyeDir)

//Determine the Distance from the light to the vertex and the direction float3 LightDir = LightPos - VertPos; float Dis = length(lightDir); LightDir = LightDir / Dist; //Computer half vector

float3 HalfVect = normalize( LightDir = EyeDir);

//Compute distance based attenuation. this is defined as: //Attenuation = 1/(LA.x + LA.y * Dist + LA * Dist * Dist)

float DistAttn = saturate( 1 / ( LightAttenuation.x + LightAttenuation.y * Dist + LightAttenuation.z * Dist * Dist)); float SpecularAttn = pow(saturate ( dot(VertNorm,HalfVect)),32); //Compute finfal lighting

return LightColor * DistAttn * SpecularAttn;

(左图为per-pixel lighting,右图为per-vertex)

上图显示了逐像素和逐顶点光照的差别。当处理高精度多边形模型时,由于每个多边形所覆盖的区域很小,因此插值之后使用逐像素光照的另一个好处是可以在渲染时添加并不存在的表面细节。通过bump map或normal map,可以在像素级再回过头来看看漫反射光线,你需要决定哪些光照元素可以插值之后传送到像素着色器,哪些元素必须逐像素计算。从头这个点积定义了光照的强度,但是对它进行插值的结果通常不正确。因此这一步计算应该移动到像素着色器中,进行逐像表面法线和光矢量是计算点积的要素。通常矢量间的插值是正确的。因此可以在顶点着色器计算他们的值,并传递到像素

每个像素的误差也很小,所以逐顶点光照可以工作的很好。而当处理低模时,这种误差就变的很大了。 别让原本平坦的表面表现出近似的凹凸效果。

到尾,决定漫反射光照的就是表面法线和光线矢量的点积。 素计算。

着色器中,然后进行最终的点积计算。

注意

虽然对法线的插值是正确的,但是插值之后的向量将不再是标准向量。为了对此进行校正,需要在像素着色器中对法线重新进行标准化,可以使用内建的normalize函数。

为了把光照计算移动到像素着色器中,需要先添加两个变量到顶点着色器的输出结构中。可以使用TEXCOORD1和TEX

COORD2语义把这些值传递到像素着色器。代码如下:

struct VS_OUTPUT { }

VS_OUTPUT vs_main(float4 inPos:POSITION,float3 inNormal: NORMAL, {

VS_OUTPUT Out;

Out.Pos = mul( view_proj_matrix, inPos); Out.TexCoord = inTxr;

float4 Pos : POSITION; float2 TexCoord: TEXCOORD0; float3 Normal: TEXCOORD1; float3 LightDir: TEXCOORD2;

接下来修改顶点着色器代码。目前我们只考虑单光源光照的情况,所以先删除原先的光照函数把其中的代码复制到vs_ma你可能已经注意到我们还没有讨论基于距离的衰减值应该在哪里计算。虽然对距离的插值结果不是完全正确,但由于光线衰减值只是一个标量,为了把它传递给像素着色器,而又不浪费一个额外的寄存器来传值,可以把LightDir改为一个flo

in函数中。记住,我们后面回讨论多光源的情况。

的变化有足够容差范围,距离上的微小差别并不会给结果带来明显变化,所以不需要逐像素计算。 at4的矢量,把衰减值作为这个矢量的w分量。修改后的顶点着色器如下:

float2 inTxr: TEXCOORD0)

Out.Normal = inNormal;

}

//Computer and move the light direction to the pixel shader float4 LightDir;

LightDir.xyz = Light1_Position - inPosition; float Dist = length(LightDir.xyz); LightDir.xyz = LightDir.xyz / Dist; //Output the light direction Out.LightDir = lightDir; return Out;

LightDir.w = saturate( 1 / ( LightAttenuation.x + LightAttenuation.y * Dist + LightAttenuation.y * Dist*Dist));

在像素着色器中,只需要接收参数,计算点击和颜色就可以了。为了方便,把光照计算单独放到一个名为Light_PointDif

fuse的函数中。本质上来说,这里计算光照的方法和在顶点着色器中是一样的。

float4 Light_PointDiffuse(float4 LightDir, float3 Normal,Float4 LightColor) { }

float4 ps_main(float3 inNormal:TEXCOORD1, { }

很简单,对吧!接下来编写镜面高光像素着色器。整个步骤基本上和我们刚才编写漫反射像素着色器的方法差不多。这一个shader的核心是法线和中间矢量的点积,需要把它们移动到像素着色器中。这意味着需要在顶点着色器中计算光照矢量,中间矢量。先修改顶点着色器的输出结构。

struct VS_OUTPUT { }

在顶点着色器中,需要计算以下值:表面法线,光照矢量,中间矢量和基于距离的衰减值。只需要把先前编写的镜面高光

着色器代码稍作修改就行了。

VS_OUTPUT vs_main(float4 inPos:POSITION,float3 inNormal:NORMAL, float2 inTxr: TEXCOORD0) {

//compute dot product of N and L

float AngleAttn = saturate(dot(Normal,LightDir.xyz)); //compute final lighting

return LightColor * LightDir.w * AngleAttn;

float4 inLightDir : TEXCOORD2) : COLOR

//Compter the lighting contribution for this single light return Light_PointDiffuse(inLightDir,inNormal,Light_Color);

float4 pos: POSITION;

float2 TexCoord: TEXCOORD0; float3 Normal : TEXCOORD1; float4 LightDir: TEXCOORD2; float3 HalfVect: TEXCOORD3;

VS_OUT Out;

Out.Pos = mul(view_proj_matrix, inPos); Out.TexCoord = inTxr; float4 LightDir;

LightDir.xyz = Light1_Position - inPos; flaot Dist = length(LightDir.xyz); LightDir.xyz = LightDir.xyz / Dist;

}

LightDir.w = saturate( 1 / ( LightAttenuation.x + LightAttenuation.y * Dist + LightAttenuation.y * Dist*Dist)); //computer eye vector and the half vector

float3 EyeVector = -normalize(mul(inv_view_matrix,float4(0,0,10,1))+inPos); Out.HalfVect = normalize(LightDir - EyeVector); //Output normal and light direction Out.Normal = inNormal; Out.LightDir = LightDir; return Out;

对像素着色器来说,同样把计算高光的代码作为一个单独的函数:

float4 Light_PointSpecular(float3 Normal,flaot3 HalfVect,float4 LightDir,float4 LightColor) { }

float4 ps_main(float3 inNormal:TEXCOORD1,float4 LightDir:TEXCOORD2, { }

介绍HLSL或者DirectX \\ XNA的书中都很少会详细讲解Semantics,DirectX SDK对此也是草草带过,加上这个单词本身对汉语来说含义比较难懂,因此Semantics常常成了HLSL中鲜为人知的一部分。然而,对于构建复杂Effect系统来说,Semantics作为DirectX \\ XNA和shader之间传输数据的纽带,有相当重要的作用。

float SpecularAttn = pow(saturate(dot(Normal,HalfVect)),32); return LightColor * LightDir.w * SpecularAttn;

float3 HalfVect:TEXCOORD3):COLOR

//simply route teh vertex color to the output

return Light_PointSpecular(inNormal,HalfVect,LightDir,Light1_Color);

Semantics究竟是什么呢?简单来说,它是描述shader中变量的原数据,用来表示某个变量的语义。它与DirectX中D3DDECLUSAGE或者XNA中VertexElementUsage枚举的作用相当类似。

Semantics有什么用呢?Shader程序使用Semantics作为标记,实现数据从CPU到GPU的绑定。

为什么使用Semantics呢? 在Semantics出现之前,shader程序员常常直接使用寄存器名来绑定数据。这样做有两个缺点,首先,编译器无法对代码进行优化,其次,维护移植起来也相当不方便。Semantics隐藏了寄存器的复杂性,所有积存器的分配都由编译器来为你处理,同时,对外提供了一个统一的接口。

如何使用Semantics呢?先来看Semantics的语法:

[ Modifiers] ParameterType ParameterName [ : Semantic] = [ Initializers] 比如:

float4x4 WorldMatrix : WORLD;

冒号前面的部分,声明了一个变量myWorldMatrix,你也许希望用它来表示世界坐标变换。但是,编译器对此一无所知,

只会把它当作一个普通的float4x4变量来对待。然而,添加了冒号以及标准Semantics WORLD之后,无论变量叫什么名字,编译器都会知道这个变量将被当作坐标变换矩阵来使用。注意,Semantics本身并不区分大小写,只是习惯上用大写表示。

从类型上来看,Semantics可以分为vertex\\pixel semantics和普通变量semantics两种。

对于标记了vertex\\pixel semantics值的变量来说,顶点会自动把vertex shader输入顶点或输出数据中相应的值绑定到变量

上。


利器:HLSL起步教程-完整篇(8).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:人教版PEP六年级毕业考试英语模拟试题(2)有答案

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: