6-9.添加HLSL镜面高光
问题
你想要用你的自定义HLSL效果添加镜面高光到你的3D场景。镜面高光是光源反射产生的高亮区域,如6-11.
方案
下面的讨论将帮助你决定那个像素将有镜面光部分。
6-11左边显示一个光的传播,L是一条从光源命中三角形像素的向量。同样,眼的向量是从相机朝像素方向。如果L的反射线几乎和E一样,像素应该有镜面光部分。
你可以用镜像方法通过该像素的法线来找到L的反射线。如果它们间的夹角很小这个镜像了的方向几乎和眼的方向一样。你可以通过求它们的点积来检查这两个向量间的夹角。
如果夹角是0,意味着两个方向一样且你应该添加镜面部分到光照,点积就是1。如果两个方向不同,点积会比1小。
注意 两个向量A和B间的点积只不过是(A长度)×(B长度)×(cos(它们间的夹角))。如果A和B都单位化,点积就只是(cos(它们间的夹角))。如果AB间夹角是0,点积就是1.如果两个向量互相垂直,夹角是90度,点积就是0。就像6-11右上方显示的。如果两个向量相反,夹角180度,点积是-1.
当所有光向量和眼向量的夹角少于90度时这个点积将被转成正的,如6-11的右上。你不能马上把这个值用作镜面高光,因为这将添加一个镜面光部分到所有不超过眼向量90度的反射向量。你想要缩小反射向量在10度或以下。
你可以通过给点积的结果一个高次方来得出。求这个点积的12次方,例如,将只为偏移10度之内的向量产生一个大于0的值,如6-11右下部显示。
这将使每个像素结果在单精度值,表明在该像素的镜面部分的强度。
运作
一直以来,你将想要能够设置世界,视图和投影矩阵来变换你的3D位置到2D屏幕坐标。因为这节是为点光源而写,你需要能够修改它的位置。要计算眼向量,你需要知道相机的位置。你应该能设置镜面次方数来控制镜面高光的大小。因为光照的总量可能大于1,你应该能够缩小光强度来避免过度饱和。
注意 在许多情况,你将想要缩小光源的强度。要拥有多个光时要这么做,否则引起大多数像素融进光里,浪费了照明效果。
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;
};
这个顶点着色器也将计算EyeDirection并使它对所有像素用插值替换。像素着色器仍然只需要输出每个像素的颜色。
顶点着色器
顶点着色器和上一节没啥区别。唯一新增的是计算了眼向量。通过从目标减去原点得出从一个点到另一个点的向量。
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;
}
像素着色器
像素着色器更有趣。基色固定是蓝色,因此你不必浪费心思在这。一个很好的做法是,你单位化每个你在像素着色器接收到的方向,因为它的长度可能不是1.
和以前一样,你计算通用光照。把它和xLightStrength相乘来缩小它一点。
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;
}
接着,你用反射原理通过法线镜像化光方向。因为光方向照向像素,它的反射线将向着眼。这和眼向量相反,所以你反转它。
镜面值通过眼向量和反转的反射光方向间的点积得出。使这个值有高的次方确保只为当那些两个向量差异小于10度左右的像素该值才显著的大于0。再有,这个值是和xLightStrength值相乘。
最后,环境光,着色值,镜面高光部分组合得出最终像素颜色。
注意 镜面部分添加白色到最终色。如果你的光有不同颜色,你应该用你的光的颜色乘以镜面值。