29.天空球SkyDomeSceneNode类
在12.3 从游戏引擎(GameBase)开始和3D系列4.6 天空球中都是使用一个半球模型生成天空球,并在其上附加天空纹理。而我的引擎中的天空球是编程实时创建的,这样灵活性更大。代码如下:
namespace StunEngine.SceneNodes { ////// 天空球 /// public class SkyDomeSceneNode :Renderable3DSceneNode { #region 构造函数和成员变量 ////// 天空球使用的材质 /// private SingleTextureMaterial material; ////// 水平方向上的切片数量 /// private int sphereCylinders; ////// 竖直方向上的切片数量 /// private int sphereSlices; ////// 北极到球最下面的夹角,使用PI表示一个完整的球,PI/2表示半球 /// private float verticalRadians = MathHelper.PiOver2; ////// 创建一个默认天空球,半球,水平方向切片数量为16,竖直方向切片数量为32。 /// /// 引擎 /// 天空球纹理 /// 水平方向上圆周带的数量 /// 竖直方向上整个圆周的切片数量 public SkyDomeSceneNode(StunXnaGE game, Scene setScene, string cloudMap) : this(game, setScene, cloudMap, 16, 32, MathHelper.PiOver2) { } ////// 创建一个天空球。 /// /// 引擎 /// 天空球纹理 /// 水平方向上圆周带的数量 /// 竖直方向上整个圆周的切片数量 /// 天空球的上下移动偏移量,负值代表向下移动 /// 北极到球最下面的夹角,使用PI表示一个完整的球,PI/2表示半球 public SkyDomeSceneNode(StunXnaGE game,Scene setScene, string cloudMap, int cylinders, int slices, float verticalRadians) : base(game,setScene) { this.sphereCylinders = cylinders; this.sphereSlices = slices; this.verticalRadians = verticalRadians; material = new SingleTextureMaterial(engine, engine .Content .Load("Effects\\SkyDome")); this.material.Texture1Name = cloudMap; Imaterial = (IMaterial)material; //不对天空球进行剔除操作 this.DisableCulling = true; this.DisableUpdateCulling = true; } #endregion /// /// 获取天空球使用的材质 /// public SingleTextureMaterial Material { get { return material; } } ////// 初始化天空盒。 /// public override void Initialize() { this.UpdateOrder = SceneNodeOrdering.EnvironmentMap.GetValue(); base.Initialize(); //创建天空球顶点数据 this.mesh = MeshBuilder.CreateSkyDome(engine.GraphicsDevice, sphereSlices, sphereCylinders, verticalRadians); } ////// 绘制天空盒。 /// public override int Draw(GameTime gameTime,bool useReflection) { //关闭深度缓冲 engine.GraphicsDevice.RenderState.DepthBufferEnable = false; engine.GraphicsDevice.RenderState.DepthBufferWriteEnable = false; int totalPrimitives = base.Draw(gameTime, useReflection); //绘制结束后别忘了打开深度缓冲 engine.GraphicsDevice.RenderState.DepthBufferWriteEnable = true; engine.GraphicsDevice.RenderState.DepthBufferEnable = true; //返回绘制的三角形数量 return totalPrimitives; } #region 单元测试 #if DEBUG ////// 测试SkyDomeSceneNode类 /// public static void TestSkyDomeSceneNode() { SkyDomeSceneNode skyDome = null; TestGame.Start("测试SkyDomeSceneNode类", delegate { skyDome = new SkyDomeSceneNode(TestGame.engine, TestGame.scene, "Textures/SkyDome", 16, 32, MathHelper.Pi * 0.6f); TestGame.scene.AddNode(skyDome); TestGame.scene.IsShowMouse = false; }, delegate { //按空格键切换一张天空球纹理 if (Input.KeyboardSpaceJustPressed) { if (skyDome.Material.Texture1Name == "Textures/SkyDome") skyDome.Material.Texture1Name= "Textures/Grass"; else skyDome.Material.Texture1Name= "Textures/SkyDome"; } }); } #endif #endregion } }
因为SkyDomeSceneNode类使用的材质不是GenericMaterial而是SingleTextureMaterial,所以它是从Renderable3DsceneNode类继承的。
使用的effect文件也是另外的SkyDome.fx,使用的像素着色器非常简单,只是从纹理采样颜色,其实就相当于Generic.fx的SimpleTextured。代码如下:
//----------------------------------------------------------------------------------------------- // 天空球 //----------------------------------------------------------------------------------------------- shared uniform extern float4x4 gProjection : PROJECTION; // 共享的投影矩阵 uniform extern float4x4 gWorld: WORLD;; // 世界矩阵 uniform extern float4x4 gView: VIEW; // 视矩阵 uniform extern float3 gCameraPos; // 相机位置 // 天空球纹理 uniform extern texture gTexture1; sampler textureSampler = sampler_state { texture =; magfilter = LINEAR; minfilter = LINEAR; mipfilter=LINEAR; AddressU = mirror; AddressV = mirror;}; //SkyDome使用的顶点着色器输出结构 struct SkyDomeVSOut { float4 Position : POSITION0; float2 TexCoord : TEXCOORD0; }; SkyDomeVSOut SkyDomeVS(float4 position: POSITION, float2 texCoord : TEXCOORD0) { SkyDomeVSOut Output=(SkyDomeVSOut)0; //根据相机位置平移世界矩阵 gWorld[3]=float4(gCameraPos,1.0f); //将顶点坐标转换到世界坐标系 float4x4 viewProjection = mul (gView, gProjection); float4x4 worldViewProjection = mul (gWorld, viewProjection); Output.Position = mul(position, worldViewProjection); Output.TexCoord = texCoord; return Output; } float4 SkyDomePS(SkyDomeVSOut input) : COLOR0 { //采样颜色纹理 return tex2D(textureSampler, input.TexCoord); } technique SkyDome { pass Pass0 { vertexshader = compile vs_3_0 SkyDomeVS(); pixelshader = compile ps_3_0 SkyDomePS(); } }
创建天空盒顶点数据的操作由MeshBuilder类中的静态方法CreateSkyDome完成,代码如下:
////// 创建天空球顶点和索引 /// /// 图形设备 /// 水平切片数量 /// 竖直切片数量 /// 北极到球最下面的夹角,使用PI表示一个完整的球,PI/2表示半球 ///public static Mesh CreateSkyDome(GraphicsDevice g, int cylinders, int slices, float verticalRadians) { //=========================================================================== // // x = r * cos(phi) * sin(theta) // y = r * sin(phi) * sin(theta) // z = r * cos(theta) // // 这里phi的范围是0 < phi <= 2Pi,theta范围是0 < theta < Pi // 半球的theta范围是[0, Pi/2] // 因为使用单位向量r=1,所以上述公式简化为: // // x = cos(phi) * sin(theta) // y = sin(phi) * sin(theta) // z = cos(theta) // //------------------------------------------------------------------------------ // // 1) 我们必须考虑到XNA是右手坐标系,而+Z方向在屏幕之外,需要反转y/z坐标 // // 2) 我们必须注意顶点是以逆时针顺序绘制的,因为我们是从天空球内部向外看的。 // // 3) uv坐标必须正确计算。 // // // 最终我们获得以下公式: // // x = cos(phi) * sin(theta) // y = cos(theta) // z = -1 * sin(phi) * sin(theta) // //=========================================================================== int dwVertices = (slices + 1) * (cylinders + 1); int dwIndices = (3 * slices * (cylinders + 1)) * 2; int[] indices = new int[dwIndices]; VertexPositionTexture[] vertices = new VertexPositionTexture[dwVertices]; float stackAngle = verticalRadians / (float)slices; float sliceAngle = (float)(Math.PI * 2.0) / (float)cylinders; int wVertexIndex = 0; //生成顶点坐标、纹理坐标和索引 int vertcount = 0; int Indexcount = 0; for (int stack = 0; stack < (slices + 1); stack++) { float r = (float)Math.Sin((float)stack * stackAngle); float y = (float)Math.Cos((float)stack * stackAngle); for (int slice = 0; slice < (cylinders + 1); slice++) { float z = r * (float)Math.Sin((float)slice * sliceAngle) * -1; float x = r * (float)Math.Cos((float)slice * sliceAngle); vertices[vertcount].Position = new Vector3(x, y, z); vertices[vertcount].TextureCoordinate = new Vector2(1f - (float)slice / (float)cylinders, (float)stack / (float)slices); vertcount++; if (!(stack == (slices - 1))) { indices[Indexcount] = wVertexIndex + (cylinders + 1); Indexcount++; indices[Indexcount] = wVertexIndex + 1; Indexcount++; indices[Indexcount] = wVertexIndex; Indexcount++; indices[Indexcount] = wVertexIndex + (cylinders); Indexcount++; indices[Indexcount] = wVertexIndex + (cylinders + 1); Indexcount++; indices[Indexcount] = wVertexIndex; Indexcount++; wVertexIndex++; } } } //顶点声明 VertexDeclaration skyDomeVertexDeclaration = new VertexDeclaration(g, VertexPositionTexture.VertexElements); //创建顶点缓冲 VertexBuffer skyDomeVertexBuffer = new VertexBuffer(g, typeof(VertexPositionTexture), dwVertices, BufferUsage.None); skyDomeVertexBuffer.SetData(vertices, 0, dwVertices); //创建索引缓冲 IndexBuffer skyDomeIndexBuffer = new IndexBuffer(g, typeof(int), Indexcount, BufferUsage.None); skyDomeIndexBuffer.SetData(indices, 0, Indexcount); //创建mesh,使用TriangleList绘制天空球 return new Mesh(PrimitiveType.TriangleList, skyDomeVertexBuffer, skyDomeVertexDeclaration, skyDomeIndexBuffer, dwVertices, dwIndices / 3); }
以上代码与创建球体的代码是类似的,只不过索引的顺序正好反向,还可以实现半球。
你也可以参考3D系列4.19 渐变的天空球给天空添加渐变的颜色,使之更符合自然中的情况,也可以给添加另一个纹理实现云层的移动效果。
单元测试截图如下:
文件下载(已下载 1369 次)
发布时间:2010/4/26 下午4:20:01 阅读次数:7149