分类: C/C++
2008-02-20 13:01:50
static void
{
using (Form1 frm = new Form1())
{
frm.Show();
if(!frm.InitializeGraphics())
MessageBox.Show("your card does not support cube maps.");
else
Application.Run(frm);
}
}
这里没有什么特别内容,接下来看看LoadMesh方法。和之前的方法差不多。加载了Mesh,纹理,材质之后,保证数据包含法线信息。之后,返回mesh。添加代码:
private Mesh LoadMesh(string file,ref Material[] meshMaterials,ref Texture[] meshTextures)
{
ExtendedMaterial[] mtrl;
Mesh mesh = Mesh.FromFile(file, MeshFlags.Managed, device, out mtrl);
if ((mtrl != null) && (mtrl.Length > 0))
{
meshMaterials = new Material[mtrl.Length];
meshTextures = new Texture[mtrl.Length];
for (int i = 0; i < mtrl.Length; i++)
{
meshMaterials[i] = mtrl[i].Material3D;
if ((mtrl[i].TextureFilename != null) && (mtrl[i].TextureFilename != string.Empty))
meshTextures[i] = TextureLoader.FromFile(device, @"..\..\" + mtrl[i].TextureFilename);
}
}
if((mesh.VertexFormat & VertexFormats.Normal) != VertexFormats.Normal)
{
Mesh tempMesh = mesh.Clone(mesh.Options.Value,mesh.VertexFormat | VertexFormats.Normal,device);
tempMesh.ComputeNormals();
mesh.Dispose();
mesh = tempMesh;
}
return mesh;
}
接下来,就像上一个例子,创建渲染目标表面来储存环境贴图。在OnDeviceReset方法中实现:
private void OnDeviceReset(object sender, EventArgs e)
{
Device dev = (Device)sender;
rte = new RenderToEnvironmentMap(dev, CubeMapSize, 1, Format.X8R
environment = new CubeTexture(dev, CubeMapSize, 1,Usage.RenderTarget, Format.X8R
}
这里同时创建了助手对象和立体纹理。这里使用的尺寸越大(这里使用了CubeMapSize常理),环境贴图的细节就越丰富。通常情况下,把环境贴图保存为静态的会更加高兴,这样就可以直接通过一个文件(或其他数据源)加载立体纹理。这里为了展示如何在渲染时动态创建环境贴图才这么做,接下来创建所渲染的目标纹理。
和上一个知渲染一个纹理的例子差不多,这里我们需要在场景中进行多次渲染。这里需要额外的六次渲染。(立方体的每个面一次),添加以下代码来渲染环境贴图):
private void RenderSceneIntoEnvMap
{
Matrix matProj;
matProj = Matrix.PerspectiveFovLH((float)Math.PI *
Matrix matViewDir = ViewMatrix;
matViewDir.M41 =
if(environment != null)
rte.BeginCube(environment);
for(int i=0;i<6;i++)
{
rte.Face((CubeMapFace)i,1);
Matrix matView = Matrix.Multiply(matViewDir,GetCubeMapViewMatrix((CubeMapFace)i));
enderScene(matView,matProj,false);
}
rte.End(1);
}
这个方法完成了很多工作。首先,我们创建了一个视野范围为90度的投影矩阵,因为立方体每个面之间的夹角恰好为90度。接下来保存这个矩阵,并且修改了最后一行,以保证对于每个面,都可以使用这个矩阵和观察矩阵相乘。
接下来,调用助手类的BeginCube方法,让系统知道我们即将开始创建立体的环境贴图。这个类还有一些其他的方法可以用来创建不同类型的环境贴图,包括BeginHemisphere、BeginParabolic(它将使用2个面,一个在z轴正向,一个位于z轴负方向)、以及BeginSphere(使用一个单独的面)。
在准备好了渲染环境贴图之后,对每个面作一次迭代:对每个面来调用face方法,这个方法和渲染时的BeginScene方法类似,表示上一个面已经处理完毕,可以渲染新的面了。之后,把当前矩阵与通过GetCubeMapViewMatrix获得的矩阵相乘。这个方法的定义如下:
private Matrix GetCubeMapViewMatrix(CubeMapFace face)
{
Vector3 vEyept = new Vector3();
Vector3 vLookDir = new Vector3();
Vector3 vUpDir = new Vector3();
switch(face)
{
case CubeMapFace.PositiveX:
vLookDir = new Vector3(
vUpDir = new Vector3(
break;
case CubeMapFace.NegativeX:
vLookDir = new Vector3(
vUpDir = new Vector3(
break;
case CubeMapFace.PositiveY:
vLookDir = new Vector3(
vUpDir = new Vector3(
break;
case CubeMapFace.NegativeY:
vLookDir = new Vector3(
vUpDir = new Vector3(
break;
case CubeMapFace.PositiveZ:
vLookDir = new Vector3(
vUpDir = new Vector3(
break;
case CubeMapFace.NegativeZ:
vLookDir = new Vector3(
vUpDir = new Vector3(
break;
}
Matrix matView = Matrix.LookAtLH(vEyePt,vLookDir,vUpDir);
return matView;
}
根据不同的面,我们修改观察参数,最后依据这些点返回观察矩阵。到这里,做完了这些之后,就可以开始渲染一个还没有车的场景了。添加RenderScene方法:
private void RenderScene(Matrix View,Matrix Project,bool shouldRenderCar)
{
device.Transform.World = Matrix.Scaling(
Matrix matView = View;
matView.M41 = matView.M42 = matView.M43 =
device.Transform.View = matView;
device.Transform.Projection = Profect;
device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;
device.TextureState[0].ColorOperation = TextureOperation.SelectArg1;
device.SamplerState[0].MinFilter = TextureFilter.Linear;
device.SamplerState[0].MagFilter = TextureFilter.Linear;
device.SamplerState[0].AddressU = TextureAddress.Mirror;
device.SamplerState[0].AddressV = TextureAddress.Mirror;
device.RenderState.ZBufferFunction = Compare.Always;
DrawSkyBox();
device.RenderState.ZBufferFunction = Compare.LessEqual;
if(shouldRenderCar)
{
device.Transform.View = View;
device.Transform.Projection = Project;
using(VertexBuffer vb = car.VertexBuffer)
{
using(IndexBuffer ib = car.IndexBuffer)
{
device.SetStreamSource(0,vb,0,VertexInformation.GetFormatSize(car.VertexFormat));
device.VertexFormat = car.VertexFormat;
device.Indces = ib;
device.SetTexture(0,environment);
device.SamplerState[0].MinFilter = TextureFilter.Linear;
device.SamplerState[0].MagFilter = TextureFilter.Linear;
device.SamplerState[0].AddressV = TextureAddress.Clamp;
device.SamplerState[0].AddressU = TextureAddress.Clamp;
device.SamplerState[0].AddressW = TextureAddress.Clamp;
device.TextureState[0].ColorOperation = TextureOperation.SelectArg1;
device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;
device.TextureState[0].TextureCoordinateIndex =(int)TextureCoordinateIndex.CameraSpaceReflectionVector;
device.TextureState[0].TextureTransform = TextureTransform.Count3;
device.Transform.World = Matrix.RotationYawPitchRoll(angel/(float)Math.PI,angel/(float)Math.PI*
angle/(float)Math.PI/
angle +=
device.DrawIndexedPrimitives(PrimitiveType.TriangleList,0,0,car.NumberVertices,0,car.NumberFaces);
}
}
}
来看看这个方法干了些什么。首先,渲染天空盒。由于知道天空盒看起来有点小,因此先把它放大10倍。然后把TextureState以及SamplerState设置为渲染天空盒纹理所必须的值(对于每个状态的具体用法请查阅MSDN)。
注意到没有,我们没有对device进行任何clear操作。因为我们不想渲染每个面时都调用这个方法,所以在渲染天空盒(它的深度值将是场景中最大的)之前改变RenderState,允许渲染天空盒的时候总是跳过深度缓冲。之后,就渲染天空盒,并且让深度缓冲功能恢复到正常状态。渲染天空盒的方法如下:
private void DrawSkyBox()
{
for(int i=0;i
{
device.Material = skyboxMaterial[i];
device.SetTexture(0,skyboxTextures[i]);
skybox.DrawSubset(i);
}
}
渲染了天空和之后,我们查看否需要渲染赛车。如果需要,就重置观察和投影变换。这里,由于我们需要控制自动纹理坐标的生成(automatic texture coordinate generation),所以不使用mesh的DrawSubset方法,而是直接使用顶点和索要缓冲,并且通过device的DrawIndexedPrimitiv方法。首先获得这两个缓冲,确保device为渲染mesh做好准备。
首先,把流的目标设置为mesh的顶点缓冲。根据mesh把顶点格式设置为正确类型,填充device的索引缓冲。最后,在设置其它状态和渲染之前,把立体纹理设置为第一状态。
这里最重要的是下面这两行代码:
device.TextureState[0].TextureCoordinateIndex =(int)TextureCoordinateIndex.CameraSpaceReflectionVector;
device.TextureState[0].TextureTransform = TextureTransform.Count3;
由于在赛车的mesh里没有定义纹理坐标(实际上就算定义过也一样),第一行代码表示不使用任何预先定义的纹理坐标,而使用(已经变换为摄像机坐标的)反射向量(reflection vector)作为纹理坐标。这些信息都是通过固定功能渲染管道使用定点位置和法线来自动产生的,所以之前须要保证mesh包含法线信息。最后,由于使用3D立体映射,所以纹理变换告诉纹理使用3D纹理坐标。
纹理映射完成了之后,就可以更新世界坐标变换,同时调用绘制图元的方法了。马上就完成了,添加如下代码:
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
RenderSceneIntoEnvMap();
device.BeginScene();
RenderScene(ViewMatrix,Matrix.PerspectiveFovLH((float)Math.PI/4,this.Width/this.Height,
device.EndScene();
device.Present();
this.Invalidate();
}
对每次渲染来说,先渲染环境贴图,接下来才真正渲染场景:再次渲染环境贴图,以及赛车。运行程序看看吧。