Chinaunix首页 | 论坛 | 博客
  • 博客访问: 571889
  • 博文数量: 208
  • 博客积分: 3286
  • 博客等级: 中校
  • 技术积分: 1780
  • 用 户 组: 普通用户
  • 注册时间: 2007-09-24 20:38
文章分类

全部博文(208)

文章存档

2012年(7)

2011年(28)

2010年(21)

2009年(76)

2008年(65)

2007年(11)

我的朋友

分类: C/C++

2008-02-20 13:07:01

第十二章 使用Hight Level Shader Language(下)

本文版权归原作者所有,仅供个人学习使用,请勿转载,勿用于任何商业用途。
由于本人水平有限,难免出错,不清楚的地方请大家以原著为准。欢迎大家和我多多交流。
翻译:clayman
Blog:http://blog.csdn.net/soilwork
  

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

    }

}

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

阅读(851) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~