§6.2将Shader导入到您的引擎

感谢DirectX和Xna,加载fx文件和设置所有必需的参数是很容易的(上一章在绘制2D直线和3D直线时你已经这样做) 。在下一章您会使用一个更一般的类,它可以接受许多不同的shader,并采用了更优化的方式设置所有必需的参数,但对于这一章在引擎中只使用simpleshader.fx文件。

与以往一样,你通过定义单元测试开始。在命名空间Shader中创建一个新文件simpleshader.cs并写入以下代码:

#region Unit Testing

 public static void TestSimpleShader()

{

SimpleShader shader = null;

Model testModel = null;

Texture testTexture = null;

TestGame.Start("Test SimpleShader.fx",

delegate {

shader = new SimpleShader();

testModel = new Model("Apple");

testTexture = new Texture("Marble");

},

delegate {

// Render model

shader.RenderModel(testModel.XnaModel, testTexture);

}

);

} // TestSimpleShader

#endregion

该单元测试还没有Rendermodel的方法,让我们创建它:

public void RenderModel(XnaModel someModel, Texture texture) {

} // RenderModel(someModel, texture)

现在,编译后您就可以看到一个空白屏幕。

编译Shader

此单元测试中要在屏幕上显示点什么,你首先必须加载并编译Shader。正如你加载模型和纹理一样,您使用xna的内容管道(content pipeline)。只需将.fx文件拖动到您的项目(同其他材质和模型一样放至正确的目录),这样在项目创建时会自动编译shader,如果Shader并没有编译你还会获取Shader编译的错误信息。同时,你也要确保marble.dds纹理被正确加载,因为您的单元测试中要用到。

加载effect和加载纹理一样简单,只需定义一个effect变量,并在simpleshader构造函数中加载它:

#region Variables

Effect effect = null;

#endregion

#region Constructor

public SimpleShader() {

 effect = BaseGame.Content.Load( Path.Combine(Directories.ContentDirectory, "SimpleShader"));

} // SimpleShader()

#endregion

如果您是在Windows平台上,您也可以动态加载shader,对于在游戏中测试和改变shader是很有用的。我通常使用un-compiled shader文件,我不想改变shader了。下面的代码是用来编译和加载shader的。请注意,这些类和方法只适用于在Windows平台上。如果您想对Xbox 360使用这些代码,请将#if !XBOX360 #endif围绕这些程序行。

 CompiledEffect compiledEffect = Effect.CompileEffectFromFile(Path.Combine("Shaders", shaderContentName + ".fx"),null, null, CompilerOptions.None,TargetPlatform.Windows);

effect=new Effect(BaseGame.Device,compiledEffect.GetEffectCode(), CompilerOptions.None, null);

使用参数

在你学习建立Shader的过程中,世界,观察和投影矩阵对转换三维数据,并在屏幕得到正确显示是非常重要的。你应在rendermodel方法设置所有这些Shader的参数,并由您的单元测试调用。

BaseGame.WorldMatrix =Matrix.CreateScale(0.25f, 0.25f, 0.25f);

effect.Parameters["worldViewProj"].SetValue(BaseGame.WorldMatrix *BaseGame.ViewMatrix *BaseGame.ProjectionMatrix);

effect.Parameters["world"].SetValue(BaseGame.WorldMatrix);

effect.Parameters["viewInverse"].SetValue(BaseGame.InverseViewMatrix);

effect.Parameters["lightDir"].SetValue(BaseGame.LightDirection);

effect.Parameters["diffuseTexture"].SetValue(texture.XnaTexture);

在此代码中首先是设置世界矩阵。这是非常重要的,因为如果世界矩阵没有设置,也许从以往的操作中会产生无法预料的数值,你当然不希望三维模型处于一些随机的位置。因为您的apple.x是相当大的,你应该把它缩放得小一些以适合您上一章建立的simplecamera类。

然后计算worldviewproj矩阵和设置的其他矩阵,lightdir、diffusetexture也很重要,因为FX Composer中会自动加载贴图,而程序中不会,你必须自己设置。如果你从内容管道加载模型,Xna会从模型数据自动加载所有使用的纹理。所以在单元测试中,应加载marble.dds纹理。

顶点格式

在您渲染你的3D苹果模型前必须确保您的程序和shader知道使用哪种顶点格式。在DirectX可以使用一个预定义的固定功能顶点格式,但在xna中不行。你必须用类似于shader中vertexinput结构的方式定义顶点声明。因为你使用内置的vertexpositionnormaltexture结构,所以没无需定义每个值,但在下一章你将学到如何使用您自定义的tangentvertex格式。

// Use the VertexPositionNormalTexture vertex format in SimpleShader.fx

BaseGame.Device.VertexDeclaration =new VertexDeclaration(BaseGame.Device, VertexPositionNormalTexture.VertexElements);

您无需在每次调用rendermodel时创建一个新的顶点声明,但为了保持简洁,你每次调用都建立一个新的顶点声明。它以图形设备作为第一个参数,顶点元素的数组vertexpositionnormaltexture结构作为第二个参数。如需详细资讯请参阅第7章。

渲染与着色

现在要渲染苹果,您首先应指定您想要使用的technique(默认设置为第一个technique,但最好知道如何设置technique)。你将一直使用effect类的currenttechnique属性。您为每一个technique中的pass(正如我以前说过,通常只要有一个pass)渲染三维数据。渲染苹果并不容易,因为xna只提供一个mesh.draw的方法,可参见model类的代码。

XNA Framework另一个缺少的功能是创造盒,球,或茶壶的mesh辅助类。你也会注意到大部分的direct3dx名字空间的功能在xna中不存在。当您处理自己的content processor时只能使用部分的方法,对您的引擎或测试模型,网格,或shader并没有帮助。因为所有的顶点和索引缓冲区是只写的,所以加载模型时您也不能处理任何的顶点或索引的数据,这虽然有利于快速硬件访问,但不够灵活。在您代码中你只是模拟的mesh.draw方法,只是使用了自己的effect类。

effect.CurrentTechnique = effect.Techniques["SpecularPerPixel"];

effect.Begin();

foreach (EffectPass pass in effect.CurrentTechnique.Passes)

{

pass.Begin();

 // Render all meshes

foreach (ModelMesh mesh in someModel.Meshes)

{

// Render all mesh parts

foreach (ModelMeshPart part in mesh.MeshParts)

 {

// Render data our own way BaseGame.Device.Vertices[0].SetSource(mesh.VertexBuffer,part.StreamOffset, part.VertexStride);

 BaseGame.Device.Indices = mesh.IndexBuffer;

// And render

 BaseGame.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList,part.BaseVertex,0, part.NumVertices, part.StartIndex, part.PrimitiveCount);

} // foreach

 } // foreach

pass.End();

 } // for effect.End();

详细地说这意味着你遍历了每一个pass(这里只有一个)渲染所有网格(meshes)(这里也只有一个),然后你制定的所有mesh parts(仍然又只有一个)最后,您调用drawindexedprimitives方法渲染所有shader中的顶点。然后pass和shader关闭,你终于可以看到带有marble.dds纹理的苹果显示在屏幕上 。(见图6-16)

图6-16

测试的Shader 现在程序可以运行了,你可以试着测试一下其他贴图,材质或渲染模式。比如要实现线框的效果,你可以看一下前面的图6-5,您可以在Shader开始前改变fillmode:

basegame.device.renderstate.fillmode = fillmode.wireframe ;

或者可以载入的另一个纹理或另一个模型,Shader类允许这样做。最简单的方式是修改环境光,散射光和镜面光的值来渲染一个怪苹果(见图6-17)。

图6-17

effect.Parameters["ambientColor"].SetValue(Color.Blue.ToVector4());

effect.Parameters["diffuseColor"].SetValue(Color.Orange.ToVector4());

effect.Parameters["specularColor"].SetValue(Color.Orchid.ToVector4());

请注意,您必须将颜色值转换为vector4。当您在effect.Begin()和effect.Ene()之间设定shader参数,你必须调用Effect.Commitchanges()方法,以确保将您所做的更改发送给GPU,但如果象在simpleshader类中的一样,设置参数后才开始effect.Begin()就无需如此。

如果你通过shader参数名去设置参数,当你不当心拼写错参数很容易发生错误,在下一章我们将学习如何更有效率地去设置参数。


发布时间:2008/9/10 下午1:57:21  阅读次数:6827

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号