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 渐变的天空球给天空添加渐变的颜色,使之更符合自然中的情况,也可以给添加另一个纹理实现云层的移动效果。
单元测试截图如下:

发布时间:2010/4/26 下午4:20:01 阅读次数:7723
