这个例子演示了在XNA应用vertex shader渲染,先来看一看HLSL着色器.

着色器允许用户自定义一段可以在GPU上执行的程序,以代替directx固定管道技术中的Vertex Processing和Pixel Processing阶段,这样我们可以用编程的方式灵活地做出渲染效果。下面看一段简单的HLSL着色器代码,我们可以直接把HLSL着色器代码作为一长串字符串编写进我们的应用程序源文件中,但是,更加方便和模块化的方法是把着色器的代码从应用程序代码中分离出来。该着色器完成顶点的世界变换、观察变换和投影变幻,并将顶点颜色设定为指定的颜色。

 

Code

首先是两个全局变量,matrix WVPMatrix;vector color;变量WVPMatrix是一个矩阵类型,它包含了世界、观察、投影的合矩阵,用于对顶点进行坐标变换;
变量color是一个向量类型,它用于设定顶点颜色;代码中并没有对全局变量进行初始化,这是因为我们对全局变量的初始化过程将在应用程序中进行,全局变量在应用程序中赋值而在着色器程序中使用,这是应用程序和着色器通信的关键所在。具体赋值可以在后面的XNA程序中看到.

然后是两个输入输出结构,

struct VS_INPUT
{
vector position : POSITION;
};
struct VS_OUTPUT
{
vector position : POSITION;
vector color : COLOR;
};

自定义的结构可以采用任意名称,结构不过是一种组织数据的方式,并不是强制的.用于输入输出的变量采用用一种特殊的声明方式:Type VariableName : Semantic .这个特殊的冒号语法表示一个语义,冒号后面的标志符用来指定变量的用途,如,POSITION标志符表明该变量表示顶点位置,另外还有诸如COLOR、NORMAL等很多表示其他意义的标志符。这里的输入输出其实是指着色器代码和编译器、GPU之间的通信,和应用程序是无关的,所以这些变量不需要在应用程序中进行赋值,标志符告诉编译器各个输入输出变量的用途(顶点位置、法线、颜色等).

程序中还定义了一个函数SetColor:
OUTPUT SetColor(INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.position = mul(input.position, WVPMatrix);
output.color = color;
return output;
}

该函数以input和output类型作为输入输出,使全局变量WVPMatrix和input.position相乘,以完成顶点的世界、观察、投影变换,并把结果赋值到output.position.output.position = mul(input.position, WVPMatrix);再将全局变量color的值赋给output.color.output.color = color;在同一个着色器代码文件中,可以有多个用户自定义函数,因此在应用程序中需要指定一个入口函数,相当于windows程序的WinMain函数,本程序只包含SetColor一个函数而且它将被做为入口函数使用.

 着色器代码的结构大致都如此,至于象素着色器pixelshader,会多出纹理的变量texture,以及纹理采样器sampler,着色器使用纹理采样器对纹理进行采样,得到采样颜色值后进行插值之类的处理.具体使用可以在pixelshader的代码或者directx文档里面看到.(之后可能再跟进写..)

最后对于HLSL再讲下效果框架,即effect(.fx)文件.其实效果框架就是对于顶点着色器和象素着色器进行封装组织,以方便应用程序使用以及不同设备(支持着色器与不支持的)实现相同效果的不同版本(就是说着色器(shader)支持的显卡用户能够利用着色器实现,而那些不支持着色器的用户仍然可以使用固定管线实现).

一种效果effect,可以使用多种技术technique,而每种技术中可能使用多个过程pass进行渲染,这样就构成了上述effect包含多个technique,technique又包含多个pass的代码结构。具体代码可以在.fx文件中看到.

Code

现在,通过XNA中vertexlighting这个例子简单地看下XNA怎么应用着色器。仅看下vertexlighting.cs文件的关键几个部分。

首先是类的私有变量,分别能对应到.fx文件中的变量

 private Matrix world, view, projection;
 private Vector3 diffuseLightDirection;
 private Vector4 diffuseLightColor;
 private Vector4 ambientLightColor;

然后声明effct相关的变量

private Effect noLightingEffect;      //没有光效果的effect
private Effect vertexLightingEffect;    //有光效果的effet
private EffectParameter projectionParameter;
private EffectParameter viewParameter;
private EffectParameter worldParameter;
private EffectParameter lightColorParameter;
private EffectParameter lightDirectionParameter;
private EffectParameter ambientColorParameter;

构造函数中指明了shader的支持版本,

graphics.MinimumVertexShaderProfile = ShaderProfile.VS_2_0;

graphics.MinimumPixelShaderProfile = ShaderProfile.PS_2_0;

然后看LoadContent()初始化操作,

先要加载effect(.fx)文件,

noLightingEffect = Content.Load<Effect>("FlatShaded");
vertexLightingEffect = Content.Load<Effect>("VertexLighting");

然后获取effect中的参数,

worldParameter = noLightingEffect.Parameters["world"];
viewParameter = noLightingEffect.Parameters["view"];
projectionParameter = noLightingEffect.Parameters["projection"];

lightColorParameter = vertexLightingEffect.Parameters["lightColor"];
lightDirectionParameter = vertexLightingEffect.Parameters["lightDirection"];
ambientColorParameter = vertexLightingEffect.Parameters["ambientColor"];

设置projection和world变量,

projection = Matrix.CreatePerspectiveFieldOfView(fov,aspectRatio, .1f, 1000f);
world = Matrix.Identity;

再设置漫反射光方向及颜色和环境光颜色,

diffuseLightDirection = new Vector3(-1, -1, -1);

diffuseLightDirection.Normalize();

diffuseLightColor = Color.CornflowerBlue.ToVector4();

ambientLightColor = Color.DarkSlateGray.ToVector4();

Update中只需处理键盘和摄像机的更新操作,

最后看Draw()函数,

首先设置effect变量的值,

projectionParameter.SetValue(projection);
viewParameter.SetValue(view);
worldParameter.SetValue(world);

ambientColorParameter.SetValue(ambientLightColor);
lightColorParameter.SetValue(diffuseLightColor);
lightDirectionParameter.SetValue(diffuseLightDirection);

然后利用顶点/索引缓存Draw模型,接着使用effect效果,

effect.Begin(SaveStateMode.None);
            for (int i = 0; i < effect.CurrentTechnique.Passes.Count; i++)
            {
                effect.CurrentTechnique.Passes[i].Begin();
                graphics.GraphicsDevice.DrawIndexedPrimitives(
                    PrimitiveType.TriangleList, meshPart.BaseVertex, 0,
                    meshPart.NumVertices, meshPart.StartIndex, meshPart.PrimitiveCount);

                effect.CurrentTechnique.Passes[i].End();
            }
effect.End();

begin()函数封装了directx的操作,使得着色器程序执行,再调用graphics的函数进行渲染。由上面程序代码可以看到在XNA下使用着色器比使用directx里面的接口函数要简化很多。

之后会继续跟进象素着色器以及多光源例子的分析,以上有不正确的请各位立即指正~~。

 

 

 

 

 

posted on 2009-03-24 20:51  中大信息中心数媒部  阅读(332)  评论(0)    收藏  举报