深入Managed DirectX9(二十二)

添加镜面高光(Specular Highlights)
  至今为止还有一种灯光没有讨论过,就是镜面高光。镜面高光让物体呈现出闪闪发亮的效果,同时也让物体看起来更加真实。虽然使用固定管道也能实现镜面高光,但这种计算是基于顶点的。这一节,我们将用编成管道来实现镜面高光。

  使用上一章渲染茶壶的例子作为开始。保留之前实现漫射光的代码,这样可以对两种光照效果做一个对比。另外,显示一些文字告诉用户当前使用的灯光类型。添加字体变量:

private Direct3D.Font font = null;

自然,在创建了茶壶之后初始化这个变量:

font = new Microsoft.DirectX.Direct3D.Font(device,new System.Drawing.Font("Arial",12.0f));

好了,现在集中注意力来编写着色代码:

float4x4 WorldViewProj : WORLDVIEWPROJECTION;
float4x4 WorldMatrix : WORLD;
float4 DiffuseDirection;
float4 EyeLocation;
const float4 MatallicColor = {0.8f,0.8f,0.8f,1.0f};
const float4 AmbientColor = {0.05f,0.05f,0.05f,1.0f};

  混合的世界、观察、投影矩阵将用于顶点变换。单独的世界矩阵用于法线位置的变换。这一次,我们不对漫射光方向硬编码,而是把它作为一个变量DiffuseDirection。最后一个变量表示观察点的位置。镜面高光是通过法线和观察点的位置来计算反射强度的。再看接下来的两个常量。由于镜面高光通常发生在金属材质表面,所以我们选择了一个类似于金属的颜色。至于最后的环境颜色在这里实际上是一个没用的量。之所以需要他只是为了满足数学公式的需要。

这个例子我们只关心每个顶点的位置和颜色:

struct VS_OUTPUT_PER_VERTEX
{
    float4 pos : POSITION;
    float4 diff : COLOR0;
};

在编写高光代码之前,先更新一下原来的漫射光着色器。每种光照类型将用独立的着色器来编写。更新代码:

VS_OUTPUT_PER_VERTEX TransformDiffuse(
    float4 inputPos : POSITION,
    float3 inputNormal : NORMAL,
    uniform bool metallic
)
{
    // Declare our return variable
    VS_OUTPUT_PER_VERTEX Out = (VS_OUTPUT_PER_VERTEX)0;
    // Transform our position
    Out.pos = mul(inputPos, WorldViewProj);
    // Transform the normal into the same coord system
    float3 Normal = normalize(mul(inputNormal, WorldViewProj));
    //make our diffuse color metallic for now
    float4 diffuseColor = MatallicColor;
    if(!metallic)
        diffuseColor.rgb = sin(Normal + inputPosition);
    //store our diffuse component
    float4 diffuse = saturate(dot(DiffuseDirection,Normal));
    //return the combined color
    Out.Color = AmbientColor + diffuseColor * diffuse;
    return Out;
}

  这次添加了一个标记为“uniform”属性的布尔变量。Uniform属性告诉Direct3D在着色程序中把这个变量当作一个常量来使用,也就是说我们不能在着色过程中改变它的值。后面的代码都很简单,进行各种变换,把漫射颜色设置为先前订一的金属颜色。另外需要注意的是我们这次添加了一些流程控制语句。HLSL支持多种流程控制机制,包括if语句,do循环,while循环以及for循环。同时,这些流程控制语句的语法和C#几乎是一样的。

  如果metallic变量为true,我们就保留金属颜色,如果不是,那么就把它换为一种动态颜色。最后,根据法线方向计算顶点颜色。由于这个着色器使用了2种类型的颜色,相应的添加两个techniques:

technique TransformDiffuseMetallic
{
    pass P0
    {
        // shaders
        VertexShader = compile vs_1_1 TransformDiffuse(true);
        PixelShader = NULL;
    }
}

technique TransformDiffuseColorful
{
    pass P0
    {
        // shaders
        VertexShader = compile vs_1_1 TransformDiffuse(false);
        PixelShader = NULL;
    }
}

这里两个techniques的区别只在于传递给着色器的参数值而已。另外还需要更新C#代码来使用新的technique:

effect.Technique = "TransformSpecularPerVertexMetallic";

接下来编写实现高光的代码:

VS_OUTPUT_PER_VERTEX TransformSpecular(
    float4 inputPosition : POSITION,
    float3 inputNormal : NORMAL,
    uniform bool metallic
)
{
    VS_OUTPUT_PER_VERTEX Out = (VS_OUTPUT_PER_VERTEX)0;
    Out.Position = mul(inputPosition, WorldViewProj);
    float3 Normal = normalize(mul(inputNormal, WorldMatrix));
    float4 diffuseColor = MetallicColor;
    float3 worldPosition = normalize(mul(inputPosition, WorldMatrix));
    float3 eye = EyeLocation - worldPosition;
    float3 normal = normalize(Normal);
    float3 light = normalize(DiffuseDirection);
    float3 eyeDirection = normalize(eye);
    if(!metallic)
        diffuseColor.rgb = cos(normal + eye);
    float4 diffuse = saturate(dot(light, normal));
    float3 reflection = normalize(2 * diffuse * normal - light);
    float4 specular = pow(saturate(dot(reflection, eyeDirection)),8);
    Out.Color = AmbientColor + diffuseColor * diffuse + specular;
    return Out;
}

  代码稍微有一点点多,我们来仔细看看。开始的部分和一前一样,对顶点和法线进行坐标变换,然后设置茶壶的漫射颜色。接下来的内容则是新的。首先,把每个顶点转换为世界坐标,接下来,用观察点位置减去这个制,获得从观察点指向顶点的矢量eye。接下来标准化所有矢量,把他们变换为单位长度。接下来检查bool变量的值,使用和刚才一样的公式,更新顶点颜色。之后,计算高光元素的值。计算高光的公式原理请参看SDK中的光照模型信息。最后,使用和之前一样的公式混合几种颜色。

同样编写相应的technique:

technique TransformSpecularPerVertexMetallic
{
    pass P0
    {
        VertexShader = compile vs_1_1 TransformSpecular(true);
        PixelShader = NULL;
    }
}

technique TransformSpecularPerVertexColorful
{
    pass P0
    {
        VertexShader = compile vs_1_1 TransformSpecular(false);
        PixelShader = NULL;
    }
}

使用这个新的technique:

effect.Technique = "TransformSpecularPerVertexMetallic";

现在运行程序看看已经可以看到闪闪发光的茶壶了。


基于像素的高光效果
  你看,茶壶现在看起来比原来真是多了。但是,由于这种计算是基于顶点的,所以在茶壶曲面上造成了一种不平滑的效果。当然,由于我们只使用了顶点找色器,所以出现这种效果也是必然的。为了达到更真实的效果,让我们使用基于像素的方法来计算灯光。由于接下来的计算需要更多指令(instructions),我们必须保证显卡可以支持pixel shader 2.0。添加如下代码检查设备性能:

if (hardware.VertexShaderVersion >= new Version(1, 1) && (hardware.PixelShader1xMaxvalue >= new Version(2,0)))

当然,我们同样需要一个vertex shader来变换顶点。

struct VS_OUTPUT_PER_VERTEX_PER_PIXEL
{
    float4 Position : POSITION;
    float3 LightDirection : TEXCOORD0;
    float3 Normal : TEXCOORD1;
    float3 EyeWorld : TEXCOORD2;
};

VS_OUTPUT_PER_VERTEX_PER_PIXEL Transform(
    float4 inputPosition : POSITION,
    float3 inputNormal : NORMAL
)
{
    VS_OUTPUT_PER_VERTEX_PER_PIXEL Out = (VS_OUTPUT_PER_VERTEX_PER_PIXEL)0;
    Out.Position = mul(inputPosition, WorldViewProj);
    Out.LightDirection = DiffuseDirection;
    Out.Normal = normalize(mul(inputNormal, WorldMatrix));
    float3 worldPosition = normalize(mul(inputPosition, WorldMatrix));
    Out.EyeWorld = EyeLocation - worldPosition;
    return Out;
}

float4 ColorSpecular(
    float3 lightDirection : TEXCOORD0,
    float3 normal : TEXCOORD1,
    float3 eye : TEXCOORD2,
    uniform bool metallic) : COLOR0
    {
        float4 diffuseColor = MetallicColor;
        if(!metallic)
            diffuseColor.rgb = cos(normal + eye);
        float3 normalized = normalize(normal);
        float3 light = normalize(lightDirection);
        float3 eyeDirection = normalize(eye);
        float4 diffuse = saturate(dot(light, normalized));
        float3 reflection = normalize(2 * diffuse * normalized - light);
        float4 specular = pow(saturate(dot(reflection, eyeDirection)), 8);
        return AmbientColor + diffuseColor * diffuse + specular;
    };
technique TransformSpecularPerPixelMetallic
{
        pass P0
        {
            // shaders
            VertexShader = compile vs_1_1 Transform();
            PixelShader = compile ps_2_0 ColorSpecular(true);
        }
}

代码同样很简单,注意顶点变换时,把灯光方向,顶点法线,以及观察点位置都作为纹理来使用。再次运行程序看看吧,现在效果就好得多了。


~~~~~~~~~~~~~第十二章完~~~~~~~~~~~~~~~~~~~~~~~

 下载代码

posted on 2007-11-07 00:34  yurow  阅读(257)  评论(0编辑  收藏  举报

导航