深入Managed DirectX9(二十二)
  • 5915
    ming1016@gmail.com

    目录:
      第 一部分
      第 二部分
      第 三部分
      第 四部分
      第 五部分
      第 六部分
      第 七部分
      第 八部分
      第 九部分
      第 十部分
      第 十一部分
      第 十二部分
      第 十三部分
      第 十四部分
      第 十五部分
      第 十六部分
      第 十七部分
      第 十八部分
      第 十九部分
      第 二十部分
      第 二十一部分
      第 二十二部分
      第 二十三部分


    添加镜面高光(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);
            }
    }

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


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

     下 载代码

  • 请在左侧登录后再来发评论