6-5.添加HLSL顶点着色
6-5.添加HLSL顶点着色
问题
BasicEffect使用你配置的照明将很好的渲染你的场景。但是,如果你想要定义一些你自己的花哨的效果,第一件事是实施正确的照明。本节,你将学习如何写一个基本HLSL效果执行每顶点着色。
方案
在显卡中传递每个顶点的3D位置和法线给你的效果。显卡中的顶点着色器需要为每个顶点做2件事。首先,作为总是在绘制的三维世界,应该用世界矩阵,视图矩阵和投影矩阵将每个顶点的三维位置转变成相应的二维屏幕坐标。第二,顶点光照特有的,每个顶点应该根据光的方向和顶点法线的点积计算顶点得到的光照量。
如何运作
提示 HLSL代码放在一个单独文件中,后缀.fx。如果你不确定把代码放在文件的什么位置,查看本节源代码。
你先要在你的XNA项目中定义你的顶点。很明显,你要在每个顶点中存储3D位置。让您在您的顶点着色器计算正确的照明,你还需要为每个顶点提供法线。在你的XNA项目创建一个新的.fx文件,添加如下代码。他包含你将能在XNA应用程序中改变的HLSL变量。
float4×4 xWorld;
float4×4 xView;
float4×4 xProjection;
float xAmbient;
float3 xLightDirection;
当要转变3D坐标到2D屏幕位置,你需要视图矩阵和投影矩阵。因为你也想要在场景中移动3D对象,你还要世界矩阵。因为本节研究光照,你想要可以定义光的方向。环境光值允许你设置最低水平光照;即使对象不是直接被光源照亮,它仍将是依稀可见。
深入顶点着色器和像素着色器之前,你应该先定义它们的输出结构。首先,顶点着色器的输出(也是像素着色器的输入)必须一直包含每个顶点的2D屏幕坐标。第二,你的顶点着色器也将计算每个顶点的光照值。
在顶点着色器和像素着色器之间,这些值将被插值替换因此每个像素得到插值。像素着色器只计算每个像素的最终颜色。
Struct VSVertexToPixel
{
float4 Position:POSITION;
float4 LightingFactor:TEXCOORD0;
};
Strcut VSPixelToFrame
{
float4 Color:COLOR0;
};
顶点着色器
你的顶点着色器将(总是)组合世界,视图和投影矩阵到一个矩阵,你将用它来转变每个3D顶点到2D屏幕坐标。
在一个顶点规定全局光方向和法线方向,你想要你的顶点着色器依照6-7计算着色量。光和法线的夹角越小,得到的光照越多。反之亦然。
你可以通过两个方向间的点积得出这个值。点积返回0到1间(如果每个向量长度都为1)的单精度值。
但是,求它们的点积前你应该反转其中一个的方向,因为这两个方向是互相相反的。例如,6-7最右边的法线和光方向是相反的,这将得到负的点积。
VSVertexToPixel VSVertexShader(float4 inPos: POSITION0, float3 inNormal: NORMAL0)
{
VSVertexToPixel Output = (VSVertexToPixel)0;
float4x4 preViewProjection = mul(xView, xProjection);
float4x4 preWorldViewProjection = mul(xWorld, preViewProjection);
Output.Position = mul(inPos, preWorldViewProjection);
float3 normal = normalize(inNormal);
float3x3 rotMatrix = (float3x3)xWorld;
float3 rotNormal = mul(normal, rotMatrix);
Output.LightFactor = dot(rotNormal, -xLightDirection);
return Output;
}
点积的结果是单精度浮点值,基于两个向量之间的夹角和两个向量的长度。在几乎所有情况,你将想要你的光照只基于法线和光方向的夹角。这意味着你要确保所有法线和光方向有相同的长度;否则,顶点的法线越长越亮。你可以单位化这些向量为1.
使用世界矩阵时确保正确的照明
如果你设置你的对象的世界矩阵为单位矩阵刚才的代码将工作良好,这意味着对象将被渲染在3D原点(0,0,0)周围。
不管怎样,在许多情况,你将想要设置另一个世界矩阵,允许你在3D世界中移动,旋转和缩放你的对象。
像6-1看到的,如果你旋转你的对象,你想要你的法线跟着转。这意味着所有法线也要被世界矩阵转变。世界矩阵中包含的任何缩放操作对光照计算没有多少影响。你在顶点着色器中单位化你的法线,确保返回向量的长度为1。
但是,如果你的世界矩阵包含位移,你就麻烦了。这是因为法线是最大长度为1的向量。举个例子,如果你用一个包含大于两个单位的移动的矩阵改变一个法线的话,所有法线将指向移动方向。
这是6-8显示的,一个物体用包含向右移动几个单位的世界矩阵渲染。结果,顶点的位置(还有物体)将向右移动。当用世界矩阵平移,现在一切将指向右,而他们应保持不变!所以用世界矩阵变换你的法线前,你要去掉平移部分。
矩阵是包含4×4数字的表。你只需要用世界矩阵中的旋转来变换法线,但不用移动。你可以剥离出矩阵的旋转部分,它存储在矩阵的左上部3×3数字。简单的把4×4矩阵转为3×3矩阵,你只提取旋转信息。代码如下:
float3 normal = normalize(inNormal);
float3×3 rotMatrix = (float3×3)xWorld;
float3 rotNormal = mul(mormal,rotMatrix);
Output.LightFactor = dot(rotNormal,-xLightDirection);
像素着色
首先,顶点着色器处理三角形的三个顶点,计算光照值。然后,对于要被渲染的三角形的每个像素,光照值是三个顶点间的插值。这个插值光照值传给像素着色器。
在这个简单情况,你将给你的对象蓝色做基色。添加着色到这个三角形,把基色乘以lightfactor(已在顶点着色器算出)和环境光线(通过XNA应用程序的xAmbient变量设置)的总和。
环境光因子确保没有物体是全黑的,而LightFactor表示光照依据光方向:
VSPixelToFrame VSPixelShader(VSVertexToPixel PSIn) : COLOR0
{
VSPixelToFrame Output = (VSPixelToFrame)0;
float4 baseColor = float4(0,0,1,1);
Output.Color = baseColor*(PSIn.LightFactor+xAmbient);
return Output;
}
技术定义
最后,定义你的技术和用那个顶点着色器和像素着色器:
technique VertexShading
{
pass Pass0
{
VertexShader = compile vs_2_0 VSVertexShader();
PixelShader = compile ps_2_0 VSPixelShader();
}
}
转载于:https://www.cnblogs.com/XNAconglele/archive/2009/09/22/1571501.html