永远也不完美的程序

不断学习,不断实践,不断的重构……

常用链接

统计

积分与排名

好友链接

最新评论

Shader示例

上一章里,我们详细讨论了HLSL着色语言的各方面。但并没有实际展示如何编写shader。虽然本书不是关于如何编写shader的,但还是有必要编写几个简单的shader,帮你深入了解HLSL。此外,在学习effect framework时,我们还会用到这些例子来阐述一些核心概念。

 

       记住,对创建一个完整的shader来说,不仅仅是编写shader代码,还包括用适当的语义符设置一系列渲染状态和变量。当然,由于目前你还缺乏编写完整shader的一些知识,所以,这里只讨论前者:也就是顶点和像素着色程序代码。本书的后面会对这些代码进行扩展。

 

最简单的Shader

 

       对于把物体渲染到屏幕上来说,有几个基本的步骤是必须完成的。首先,需要接收输入顶点的位置(顶点在世界坐标中的位置)并把它们转变为屏幕坐标。通常使用world-view-projection矩阵来完成这个任务,它包含了把顶点从局部坐标映射为最终屏幕坐标的所有信息。现在开始,我们假设已经有这样一个矩阵变量,并且名称为view_proj_matrix

 

       先来定义一个把数据从顶点着色器传递给像素着色器的结构。我们把这个结构称为VS_OUTPUT,当然,也可以是任何你喜欢的名字。目前,只需要把顶点位置数据添加到这个结构中。

 

struct VS_OUTPUT

{

     float4 Pos: POSITION;

};

 

       你应该注意到我们把POSITION语义连接到了Pos变量上,它将告诉effect系统如何把这个变量传递到像素着色器中。我会在下一章讲解语义。现在只差顶点着色器代码了。顶点着色器接收顶点位置,并使用view_proj_matrix矩阵对它进行变换,可以用内建的mul函数来完成这一步计算。我们把顶点着色器代码放到一个名为vs_main的函数中:

 

VS_OUTPUT vs_main ( float4 inPos : POSITION)

{

     VS_OUTPUT Out;

     // output a transformed and projected vertex position

   Out.Pos = mul ( view_proj_matrix , inPos);

     return Out;

}

 

       这里同样使用了POSITION语义修饰输入参数inPos。它将告诉顶点着色器把几何体数据流信息映射为这个参数的输入值。接下来进入完成这个简单shader的第二步。现在你知道了顶点在屏幕上的位置,可以定义顶点的颜色了。最简单的方法就是把所有顶点的颜色都设置为一个常量。通常像素着色器将返回一个float4类型的值来表示当前像素在屏幕上的颜色值,float4分量分别表示红色,绿色,蓝色和alpha值。我们把像素着色器代码放到一个名为ps_main的函数中:

 

float4 ps_main ( void ) : COLOR

{

     //Output constant color

     float4 Color;

     color[0] = color[3] = 1.0; // red and alpha on

     color[1] = color[2] = 0.0;// Green and Blue off

     return color;

}

 

       这几乎是最简单的代码了,注意我们用COLOR语义修饰了函数的返回值,它将告诉编译器把函数返回值作为当前像素的颜色值。

 

着色

 

       我们已经有渲染物体所需的最基本代码了,如何把纹理映射到几何体上,让物体看起来更加真实呢?对于需要使用纹理的shader来说,需要有一个sampler类型的全局变量。在后面的章节中,我会教你如何使用语义和effect framework来设置纹理状态。目前我们假设已经设置了好了纹理状态:

 

       sampler Texture0;

 

       使用纹理之前,还需要知道知道对纹理的哪一部份进行采样映射,因此,每个像素都必须有相应的纹理坐标。一般情况下,纹理坐标将作为几何体信息的一部分输入到顶点着色器中,经由顶点着色器计算处理之后,传入到像素着色器中。通常使用TEXCOORDx语义来修饰作为参数传递的纹理坐标。这个语义将会告诉硬件如何在顶点和像素着色器之间交换数据。以下是修改之后的顶点着色器代码:

 

struct VS_OUTPUT

{

        float4 Pos :     POSITION;

        float2 Txr1:    TEXCOORD0;

}

 

VS_OUTPUT vs_main(

        float4 inPos : POSITION;

        float2 Txr1 : TEXCOORD0)

{

        VS_OUTPUT Out;

        //Output our transformed and projected vertex position and texture coordinate

        Out.Pos = mul ( view_proj_matrix, inPos);

        Out.Txr1 = Txr1;

        returen Out;

}

 

       像素着色器也同样简单。在创建了sampler变量之后,可以使用HLSL的内建函数tex2D来对纹理进行采样,代码如下:

 

sampler Texture0;

float4 ps_main(

     float4 inDiffuse : COLOR0,

     float2 inTxr1 : TEXCOORD0) : COLOR0

{

     //Output the color taken from our texture

     return tex2D ( Texture0, inTxr1);

}

 

添加光照

 

       虽然添加了纹理的对象看起来不错,但显然还不够真实。在增加场景真实度的过程中,很重要的一步就是为对象添加光照。真实世界中,从太阳到灯泡,充满了各种光。没有了光线,就什么都看不到了。

 

       虽然光照本身是一个相当复杂的主题,但在计算机图形领域中,光通常被简化为几种基本类型:

 

l         环境光(Ambient lighting:场景中所有光源经过多次放射和折射之后,对场景总亮度贡献的近似模拟。通常用它来减少场景中所需光源的数量,模拟出多光源下的照明效果。环境光通常是一个常量,对所有物体的作用效果都一样。

l         漫反射光(Diffuse lighting):材质的微观粗糙表面将导致在有所方向上均匀的反射入射光线。在任何角度接收到的反射光线强度都是相同的。

l         镜面高光( Specular lighting):当材质表面相当光滑,粗糙度很低时,将以一种非均匀的方式反射光线。对镜面高光来说,光线强度不但与入射光角度有关,和观察者的角度也有关。

 

除了知道光线如何影响物体之外,你还需要如何对光源本身分类。虽然光总是由某个表面发出,比如太阳或灯泡表面,但你也可以把它们看作来自某个方向或某个点。

 

光照技术中,方向光是最简单的类型。它们没有位置信息,并且假设所有光线之间都是平行的,指向同一个方向。哪一种光源是这样的呢?现实中并没有这样的光源。方向光是假设光源离物体无限远时,照射到物体上的光线将近似于平行而得出的。

 

方向光最好的例子就是阳光。如果把太阳看作一个离地球上亿千米的点光源,那么当阳光到达地球表面时已经近似于平行了,完全可以看作是方向光。

 

此外没有位置信息表示方向光不随距离而衰减。对方向光来说,要考虑的因素只有两个:方向和光的颜色。看到这里你可能会问光线是如何影响物体表面的。如图所示,光线照射到物体表面的强度只与入射光线和表面法线的角度有关。

       知道了这些基础知识,就可以用入射光的方向矢量和表面法线的点积以及灯光的颜色因子计算出物体表面上任意一点的光照强度和颜色。这让我们得出了以下代码:

 

Color = Light_Color * saturate ( dot ( Light_Direction, inNormal ) );

 

       注意在上面的代码中我们使用了saturate函数。它保证对于背对光线的面来说,获得的光照强度不会为负值。当然,你也可以使用clamp函数,但是对于把值限制在01之间来说,saturate函数要更加高效。

 

       一般来说,场景中大多数的光都来自于灯泡,火炬或类似的光源。仔细观察一下这类光源,它们通常由一个很小的有限点发出,并且位于场景中的某个特定位置。简化一下,你可以把这些光源都看作场景中的一个点,这就是点光源。

 

       对这类光源来说,光线呈放射状发出。这意味着只要物体和光源的距离相等,那么无论在哪个方向,所受到的影响都相同。由于表面的光照强度与光线和物体表面法线之间的关系有关,因此我们所要做的第一步就是计算出光线的方向。显然,对于表面上的任意点来说,光线方向就等于从当前点的位置指向光源位置的矢量。对点光源来说,随角度的衰减值如下:

 

//compute the normalized light direction vector and use it to determine the angular light attenuation

float3 Light_Direction = normal ( inPos – Light_Position);

float AngleAttn = saturate ( dot ( inNormal, Light_Direction) );

 

       此外对于点光源来说,还需要考虑它在距离上的衰减。自然,需要计算光源到当前点的距离,使用如下代码:

 

float Distance = length ( inPos – Light_Position);

 

       通常情况下,点光源的衰减因子随距离的平方成反比。但是为了获得很多的可控性,可以调整公式,让衰减和距离的二次多项式成反比,代码如下:

 

//compute distance based attenuation. this is defined as

// attenuatin = 1 / ( a + b*distance + c * disctance * distance)

float DistAttn = saturate( 1 / ( LightAttenuation.x + LightAttenuation.y * Dist +

LightAttenuation.z * Dist));

 

现在把前面的代码集成到顶点着色器中吧:

 

struct VS_OUTPUT

{

        float4 Pos:      POSITION;

        float2 TexCoord: TEXCOORD0;

        float2 Color:    COLOR0;

};

 

float4 Light_PointDiffuse( float3 VertPos, float3 VertNorm, float3 LightPos, float4 LightColor,

                                           float4 LightAttenuation)

{

        //determine the distance from the light o the vertex and the direction

        float3 LightDir = LightPos – VertPos;

        float Dist = length(LightDir);

        LightDir = LightDir / Dist; 

        //Compute distance based attenuation.

        float DistAttn = saturate( 1 / ( LightAttenuation.x + LightAttenuation.y * Dist +

               LightAttenuation.y * Dist*Dist));

        //comopute angle based attenuation

        float AngleAttn = saturate ( dot (VertNorm, LightDir));

        // Computer the final lighting

        return LightColor * DistAttn * AngleAttn;

}

 

VS_OUTPUT vs_main( float4 inPos: POSITION,

        float3 inNormal: NORMAL,

        float2 inTxr : TEXCOORD0)

{

        VS_OUTPUT Out;   

        Out.Pos = mul ( view_proj_matrix, inPos);

        Out.TexCoord = inTxr;

        float4 Color = Light_PointDiffuse ( inPos, inNormal, Light_Position, Light1_Color, Light_Attenuation)

        Out.Color = Color;

        return Our;

}

 

       我把计算点光源光照的代码单独放到了Light_PointDiffuse函数中,因此,当场景中有多个点光源时,你可以复用这段代码。当然,我们在后面的章节会有这样的例子。

posted on 2008-08-08 08:53 狂烂球 阅读(1834) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理