Clayman的专栏

It's all about XNA & GPU Programming

孙凌峰ID:soilwork
188068次访问,排名376好友0人,关注者10
soilwork的文章
原创 85 篇
翻译 15 篇
转载 0 篇
评论 324 篇
clayman的公告
嘿嘿 ^o^....
最近评论
CYF:哈哈,楼上的楼上真搞笑。
在企业应用中,WEB是主流,也是趋势。
企业应用与图形完全是两个领域,不相干的东西,何必扯在一起?完全没可比性。。。。。。。
skbaker:应该整理一下,形成系列,方便查找。
文章对入门级用户帮助很大
kobeair:每当我要放弃的时候,我都会来你的博客,回到你写这篇帖子。学习的过程很枯燥并且现在学习的时间越来越少。但是看到这篇文章有种莫名的冲动,而且不断的问自己:“别人能做到!为什么我不行?”
lijunjun:做WEB的人最终是会后悔的.
老鬼菠萝:继续~~~
文章分类
收藏
    相册
    blogs
    David Weller
    nVidia Developer blog
    Rico Mariani
    Shawn Hargreaves
    XNA Team blog
    XNA资源
    XNA Creators Club
    ZBuffer
    Ziggyware XNA Resources
    中国XNA开发网
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    翻译 【翻译】Managed DirectX(第十二章 下) 收藏

    新一篇: Managed DirectX(第十三章) | 旧一篇: 使用Managed DirectX创建三维地形

    第十二章 使用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);

        }

    }

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

    发表于 @ 2005年11月04日 05:31:00|评论(loading...)|编辑

    新一篇: Managed DirectX(第十三章) | 旧一篇: 使用Managed DirectX创建三维地形

    评论

    #ZERO 发表于2006-05-15 00:40:00  IP: 220.231.23.*
    刚开始学HLSL,有几点疑惑:

    float3 eye = EyeLocation - worldPosition;
    视向量应该是用顶点坐标减去摄像机位置坐标啊,为什么是反的?

    float3 reflection = normalize(2 * diffuse * normal - light);
    这里的light和normal都已经是单位向量了,从几何意义上说结果的向量长度应该和light相同,也就是说结果就应该已经是单位向量了,为什么还要进行normalize呢?
    我实际上也试过,加normalize得到结果和不加是不同的,但又想不明白为什么,shader又不能调试,真让人抓狂
    #clayman 发表于2006-05-15 16:05:00  IP: 218.88.0.*
    因为光是从物体折射到眼睛里,所以用EyeLocation - worldPosition矢量的方向才正确

    虽然normal和light都是单位向量,但是相减之后就不一定是单位向量了啊
    #ZERO 发表于2006-05-15 19:38:00  IP: 220.231.23.*
    我知道了,这里的normalize的确是可以省略的,画一下图就知道,实际上得到的向量和light长度是一样的,我碰到的是另外一个问题,发现在HLSL里这么写:
    float3 vNormal = normalize( tex2D( NormalSampler, texCoord ) * 2 - 1 );
    和这么写:
    float3 vTemp = tex2D( NormalSampler, texCoord ) * 2 - 1;
    float vNormal = normalize( vTemp );
    得到的结果是不一样的,还不清楚是怎么回事。
    #clayman 发表于2006-05-15 20:22:00  IP: 218.88.0.*
    float3变成float了啊~
    #ZERO 发表于2006-05-15 21:53:00  IP: 220.231.23.*
    那个是笔误,呵呵。
    但是确实有这个问题
    #clayman 发表于2006-05-22 17:30:00  IP: 218.88.2.*
    to ZERO
    第一种写法是normalize一个float4,然后转换为float3
    第二种写法是把float4先转换为float3,然后再normalize
    所以结果才会不一样吧
    #ZERO 发表于2006-05-22 19:21:00  IP: 220.231.23.*
    哦,原来是这样。有道理:)
    #胡文斌 发表于2006-09-02 10:47:00  IP: 61.191.229.*
    看不懂HLSL代码,能不能用PS,VS指令讲述一下过程。
    #clayman 发表于2006-09-03 02:44:00  IP: 222.209.130.*
    to 胡文斌:晕,hlsl的代码应该比汇编还要容易看懂吧。。。
    #messm 发表于2007-04-12 11:13:52  IP: 123.112.100.*
    问一下. 我没用hlsl实现高光.
    我是直接定了一个平行的高光.
    当物体旋转到反面的时候就没有高光了.(应该是由于法线原因)
    我现在用的模式是Cull.None.
    该怎么解决呢? (旋转的框架是用了sample framework)
    我想的是当他转到背面的时候把法线反向.但是不知道算法怎么实现
    发表评论  


    登录
    Csdn Blog version 3.1a
    Copyright © clayman