您的位置:首页 > 其它

在场景中添加光线——添加HLSL镜面高光

2011-02-14 14:52 260 查看
问题 你想使用自定义的HLSL effect在场景中添加镜面高光。镜面高光是位于反光位置的高亮度区域,如图6-11所示。

解决方案 下面的讨论将帮助你判断哪个像素具有高光分量。

图6-11的左图显示了一条光线L,从光源指向三角形中的一个像素。在左图中还显示了 eye向量,从相机指向像素。如果L的反射向量与E相同,那么这个像素就有一个高光分量。





图6-11 使用靠近eye向量的光线方向检测像素

你可以通过求L关于像素法线的镜像获取L的反射向量。如果镜像方向与eye向量夹角很小则这两个方向几乎是相同的。你可以通过点乘这两个向量检测两者的夹角(可参见教程 6-8)。

如果角度为0,则这两个方向是相同的,你需要添加一个高光分量,这时点乘的结果为1。如果两个方向不同,则点乘结果小于1。

注意:两个向量A和B的点乘结果等于(A的长度)*(B的长度)*(两者夹角的余弦)。如果A和B都已经进行了归一化,点乘结果会变为(两者夹角的余弦)。如果A和B的夹角为0,则余弦值为1。如果两者垂直,夹角为90度,余弦值为0,如图6-11的右图所示。如果两个向量方向相反,夹角为180度,余弦值为-1。 当反射的方向与eye向量的方向夹角小于90度时,点乘结果为正。

你还不能立即使用这个值判断高光,因为这样做会在所有反射向量与eye向量的夹角小于90度的像素上添加高光,而你想在夹角小于10度时才添加高光。

这可以通过对点乘结果进行一个高次幂实现。例如,将点乘结果进行12次方的操作,会使角度小于10度的情况下这个值才会大于0,如图6-11右下图所示。

每个像素的运算结果是一个single值,表示高光强度。

工作原理 和以往一样,你需要首先设置World,View和Projection矩阵将3D位置转换到2D屏幕位置。因为这个教程用的是一个点光源,你还需指定它的位置。要计算eye向量,你需要知道相机的位置。你还需能够设置光照强度控制高光大小。因为光照强度可能大于1,因此需要缩小光照强度避免饱和(saturation)。

注意:在大多数情况中,你需要缩小光源的强度。在多光源的情况中大多数像素的光照会饱和,浪费光照effect。

float4x4 xWorld;
float4x4 xView;
float4x4 xProjection;
float3 xLightPosition;
float3 xCameraPos;
float xAmbient;
float xSpecularPower;
float xLightStrength;

struct SLVertexToPixel
{
float4 Position : POSITION;
float3 Normal : TEXCOORD0;
float3 LightDirection : TEXCOORD1;
float3 EyeDirection : TEXCOORD2;
};

struct SLPixelToFrame
{
float4 Color : COLOR0;
};


vertex shader还计算了EyeDirection并进行插值。pixel shader仍然只输出每个像素的颜色。

Vertex Shader

vertex shader与前面的教程没有太大的不同。唯一一个新的东西就是eye向量在vertex shader中进行计算。从一个点指向另一个点的向量可以通过将终点减去起点实现。

SLVertexToPixel SLVertexShader(float4 inPos: POSITION0, float3 inNormal: NORMAL0)
{
SLVertexToPixel Output = (SLVertexToPixel)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;
Output.EyeDirection = final3DPos - xCameraPos;

float3x3 rotMatrix = (float3x3)xWorld;
float3 rotNormal = mul(inNormal, rotMatrix);
Output.Normal = rotNormal;

return Output;
}


Pixel Shader

pixel shader更加有趣。基本颜色是蓝色的,无需关注太多。在pixel shader中归一化每个方向,因为它的长度可能不是1 (见教程6-3)。

与以往一样,你计算了光照,将它乘以xLightStrength缩小一点(xLightStrength小于1)。

SLPixelToFrame SLPixelShader(SLVertexToPixel PSIn) : COLOR0
{
SLPixelToFrame Output = (SLPixelToFrame)0;

float4 baseColor = float4(0,0,1,1);
float3 normal = normalize(PSIn.Normal);
float3 lightDirection = normalize(PSIn.LightDirection);
float shading = dot(normal, -lightDirection);
shading *= xLightStrength;

float3 reflection = -reflect(lightDirection, normal);
float3 eyeDirection = normalize(PSIn.EyeDirection);
float specular = dot(reflection, eyeDirection);
specular = pow(specular, xSpecularPower);
specular *= xLightStrength;

Output.Color = baseColor*(shading+xAmbient)+specular;

return Output;
}


然后,使用reflect 函数计算光线方向的镜像。因为光线方向是指向像素的,它的反射方向将指向眼睛,反射方向与eye向量相反,所以需要取负值。

Specular的值可以通过点乘eye向量和反射方向获取,将这个值进行高次幂计算,使这两个向量的夹角小于10度的像素高光值才会大于0。这个值需要通过乘以xLightStrength 变得小一点。

最后,ambient,shading和specular分量组合在一起获得像素最后的颜色。

注意:specular分量在最终颜色中添加白色。如果光线有不同的颜色,你需要将specular值乘以光线的颜色。

定义Technique

下面是technique定义:

technique SpecularLighting
{
pass Pass0
{
VertexShader = compile vs_2_0 SLVertexShader();
PixelShader = compile ps_2_0 SLPixelShader();
}
}


代码

因为所有HLSL代码前面已经写过了,下面只是XNA代码:

effect.CurrentTechnique = effect.Techniques["SpecularLighting"];
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["xLightStrength"].SetValue(0.5f);
effect.Parameters["xLightPosition"].SetValue(new Vector3(5.0f, 2.0f, -15.0f));
effect.Parameters["xCameraPos"].SetValue(fpsCam.Position);
effect.Parameters["xSpecularPower"].SetValue(128.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();




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