您的位置:首页 > 其它

在场景中添加光线——添加HLSL逐像素光照

2011-02-14 14:48 239 查看
问题 如教程6-3所示,要获得最好的光照效果应该使用逐像素光照,特别是对那些由大三角形构成的曲线的情况中。你想使用自己的effect添加逐像素光照。

解决方案 前两个教程中,你在每个顶点中计算明暗值(shading value,也可以翻译成着色值)。三角形三个顶点的明暗值会进行线性以获取每个像素的明暗值。

在逐像素光照中,你想对三个顶点的法线进行插值以获取每个像素的法线,这样就可以基于每个像素的法线计算光照因子。但是,当从一个顶点到另一个顶点进行法线插值时结果是有缺陷的。如图6-9中的左图所示,图中的水平直线表示一个顶点包含法线的三角形。当你在像素上对这左右两个顶点的法线进行插值时,插值的法线总会沿着虚线。导致在三角形中间的法线法线是正确的,其他位置的法线会小于实际值,如图所示。





图6-9 从顶点到像素的线性插值是错误的

解决方法是在pixel shader中处理这个插过值的法线。因为它的方向是正确的,你可以归一化这个向量。这是因为每个像素的缩放因子是不同的,所以这一步需要在pixel shader中进行。

注意:要理解为什么将法线长度变为1,可参见教程6-1中的“归一化法线”一节。

当你想让光照强度只取决于法线和入射光夹角时,更小的法线导致更小的光照强度。

光照方向也会遇到同样的问题,如图6-9右图所示。插过值的光线方向的长度沿着虚线曲线,这会导致向量比实际的小。你仍需要归一化这个插过值的光线方向。

工作原理 和以往一样,你的项目必须从至少包含3D位置和法线的顶点中与显卡进行交互。你想让XNA代码可以设置World,View,Projection矩阵,光源的3D位置和环境光:

float4x4 xWorld; float4x4 xView;
float4x4 xProjection;
float3 xLightPosition;
float xAmbient;

struct PPSVertexToPixel
{
float4 Position: POSITION;
float3 Normal: TEXCOORD0;
float3 LightDirection: TEXCOORD1;
};

struct PPSPixelToFrame
{
float4 Color: COLOR0;
};


如前所述,vertex shader会输出法线,这个法线已经进行了插值,光线方向也进行了插值。pixel shader只需计算每个像素最后的颜色。

注意:在单向光的简单例子中,光源的方向是XNA-to-HLSL变量,对顶点和像素来说都是不变的。所以vertex shader无需计算这个值。

Vertex Shader

vertex shader从顶点中接受法线,根据世界矩阵中的旋转值旋转这个法线(见教程6-5),并将它传递到pixel shader。 在vertex shader中还通过将顶点位置减去点光源的位置计算了光线方向(见教程6-5)。根据当前世界矩阵获取顶点的最终3D位置。

PPSVertexToPixel PPSVertexShader(float4 inPos: POSITION0, float3 inNormal: NORMAL0)
{
PPSVertexToPixel Output = (PPSVertexToPixel)0;

float4x4 preViewProjection = mul(xView, xProjection);
float4x4 preWorldViewProjection = mul(xWorld, preViewProjection);

Output.Position = mul(inPos, preWorldViewProjection);
float3 final3DPos = mul(inPos, xWorld);
Output.LightDirection = final3DPos - xLightPosition;
float3x3 rotMatrix = (float3x3)xWorld;
float3 rotNormal = mul(inNormal, rotMatrix);
Output.Normal = rotNormal;

return Output;
}


Pixel Shader

法线和光线方向在三个顶点间进行插值,作用在三角形的所有像素上。如前所述,因为被插值的向量的长度会比实际的小,所以会发生错误。你可以通过归一化操作解决这个问题。将两个方向归一化之后,就可以点乘两者获取光照因子:

PPSPixelToFrame PPSPixelShader(PPSVertexToPixel PSIn) : COLOR0
{
PPSPixelToFrame Output = (PPSPixelToFrame)0;
float4 baseColor = float4(0,0,1,1);
float3 normal = normalize(PSIn.Normal);
float3 lightDirection = normalize(PSIn.LightDirection);
float lightFactor = dot(normal, -lightDirection);

Output.Color = baseColor*(lightFactor+xAmbient);

return Output;
}


定义Technique

这个technique需要Shader 2.0–compatible的显卡:

technique PerPixelShading
{
pass Pass0
{
VertexShader = compile vs_2_0 PPSVertexShader();
PixelShader = compile ps_2_0 PPSPixelShader();
}
}


代码

前面已经写过. fx文件中的HLSL代码了,所以下面只是绘制三角形的XNA代码:

effect.CurrentTechnique = effect.Techniques["PerPixelShading"];
effect.Parameters["xWorld"].SetValue(Matrix.Identity);
effect.Parameters["xView"].SetValue(fpsCam.ViewMatrix);
effect.Parameters["xProjection"].SetValue(fpsCam.ProjectionMatrix);
effect.Parameters["xAmbient"].SetValue(0.0f);
effect.Parameters["xLightPosition"].SetValue(new Vector3(6.0f, 1.0f, -5.0f));

effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Begin();
device.VertexDeclaration = myVertexDeclaration;
device.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleStrip, vertices, 0, 6);
pass.End();
}
effect.End();


你可以试着改变光源的位置查看效果。本例中的光源前后移动。



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: