📄 9-7.txt
字号:
定义Mesh
虽然有很多时候,你需要手动创建顶点和索引数据,但更普遍的情况是从外部的资源加载已有的顶点数据,比如从一个文件。通常我们使用.X文件来保存这些信息。在前一章里,代码的大部分都用来创建几何体了。对于简单的三角形和立方体来说这似乎是完全可行的,但设想假如用相同的方式来创建拥有上万个顶点的物体将,所花费的时间和努力都将是很可怕的。
幸运的是,Managed DirectX里有一个可以封装并且加载顶点和索引数据的对象,这就是Mesh。Mesh可以用来储存任何类型的图形数据,但主要用来封装复杂的模型。Mesh类同样也有一些用来提高渲染物体性能的方法。所有的mesh对象都包含了一个顶点缓冲和一个索引缓存,除此之外,他还包含了一个属性缓冲(attribute buffer)——我们将会在这一章的后面讨论它。
真正的mesh对象包位于Direct3D扩展库(D3DX Direct3D Extensions library)中。添加对Direct3DX.dll程序集的引用,我们将尝试着使用mesh来创建一个旋转的立方体。首先,在声明顶点缓冲和索引缓冲成员之前添加mesh成员:
private Mesh mesh = null;
mesh类有三个构造函数,但现在还不需要用到其中的任何一个。Mesh类有几个静态方法可以用来创建或加载不同的模型。首先需要注意的就是“Box”方法,就像它的名字一样,它将创建包含了一个立方体的mesh。想想看,我们立刻就能渲染这个立方体,简直完美极了^_^(注:呵呵,可以删除之前所有与顶点缓冲、索引缓冲有关的代码了)。在创建device之后添加一下代码:
mesh = Mesh.Box(device,2.0f,2.0f,2.0f);
这个方法创建了一个包含顶点和索引的mesh,并且可以渲染为一个长、宽、高都为2的立方体。它和之前用顶点缓冲手动创建的立方体大小一样。我们已经把创建物体的代码减少为一行了,不能再简单了。虽然已经创建了mesh,但可以用原来的方法来渲染它吗,还是需要另辟途径?之前,在渲染时,我们需要调用SetStreamSource来告诉Direct3D从哪一块顶点缓冲读取数据,同样还必须设置索引以及顶点格式的属性。对于渲染mesh来说,这些都是不需要的。
(tips:mesh已经内置了所有顶点缓冲、索引缓冲以及顶点格式的信息。渲染时会自动设置stream source、索引和顶点格式的属性)
那么如何渲染mesh呢?Mesh会被分为一系列的子集(subsets)(依据属性缓冲的大小来分配),同时使用一个叫做“DrawSubset”的方法来渲染。修改DrawBox方法:
private void DrawBox(float yaw,float pitch,float roll,float x,float y,float z)
{
angle += 0.01f;
device.Transform.World = (Matrix.RotationYawPitchRoll(yaw,pitch,roll) * Matrix.Translation(x,y,z));
mesh.DrawSubset(0);
}
这里把DeawIndexedPrimitives方法改为了DrawSubset。使用Mesh类创建的普通图元总是只有一个基于0的子集。好了,这就是要让程序再次运行所作的所有改动了,出乎意料的简单。运行看看吧。
Well,再次得到了九个(在源码中是12个)旋转的盒子,但是全部变为了白色对不对?观察一下mesh中顶点的顶点格式(可以通过mesh的VertexFormat属性查看),会发现只有顶点的位置和法线数据储存在mesh中。Mesh中没有关于颜色的数据,灯光也米有打开,自然一切都是白色的。
还记得第一张中提到过,只要顶点数据包含了法线的信息,就可以使用灯光吗,既然盒子有法线数据,也许我们应该吧灯光打开。默认情况下灯光是打开的,现在可以把关闭灯光的代码删了或者设置为true。
呵呵,我们成功把白色的盒子变为黑色了-.-#。 希望你已经猜到了这是因为场景中并没有光源,所以一切都是黑色的。对于指定特定的光源而言,创建一盏能照亮整个场景的灯光将会很不错。欢迎来到环境光(ambient lighting)。
环境光为场景提供了均衡(constant)的光源。场景中所有物体都按同样的方式被照亮,因为环境光并不依赖于其它几种光源需要的因素(比如位置、方向、衰减)。甚至不需要法线数据就可以使用环境光。环境光是最高效的灯光类型,但却不能创造出真实的“世界”。但就现在而言,他就能达到我们满意的效果。在设置RendrState的地方添加如下代码:
device.RenderState.Ambient = Color.Red;
环境光完全是由ambient render state来定义的,接受一个颜色参数。这里,我们希望全局灯光是红色的,这样可以看到明显的效果。运行程序,你希望可以看到9个红色的旋转盒子,不幸的是,它们仍然为黑色。还遗漏了些什么呢?
使用材质和灯光(Using Materials and Lighting)
这里和我们以前使用灯光有什么不同呢?最大的不同点(除了使用的是mesh之外)在于顶点数珠中没有关于颜色的信息。这导致了光照失败。
为了让Direct3D正确的计算3D物体中特定点的颜色,除了灯光的颜色之外,还需要知道物体如何反射灯光的颜色。在真实的世界里,如果把红色的灯光照在淡蓝色的表面,那么它会呈献出柔和的紫色。你还需要描述我们的“表面”(我们的盒子)是如何反光的。
在Direct3D里,材质(materials)描述了这种属性。你可以指定物体如何反射环境光以及散射(diffuse)光线,镜面高光(Specular Highlights)(少后会讨论它)看起来是什么样,以及物体是否完全反射(emit)光线。在DrawBox中添加如下代码(在DrawSubset方法前):
Material boxMaterial = new Material();
boxMaterial.Ambient = Color.White;
boxMaterial.Diffuse = Color.White;
device.Material = boxMaterial;
这里创建了一个新的材质,它的环境颜色(ambient color)(注:环境颜色和环境光的颜色是不同的^_^)和散射颜色值都被设置为白色。使用白色表示它会反射所有的光线。接下来,我们把材质赋予了device的Material属性,这样Direct3D就知道渲染时使用那种材质数据。
运行程序,现在可以看到正确的结果了。修改环境光的颜色可以改变所有盒子的颜色。修改材质的环境颜色元素可以改变灯光如何照亮物体(注:后悔当年没有好好听光学课啊555~~,maya完全手册中是这样说的:环境色(ambient color),当其为黑色时,表示(环境光)不会影响材质的颜色,当环境色变浅时,它就会照亮材质,并将两种颜色混和起来,从而影响材质的颜色。如何场景中有环境光,那么这些光的颜色和亮度就会控制环境色对于最终材质颜色的影响程度)。把材质改为没有红色成分的颜色(比如绿色)会使物体再次变为黑色(注:因为此时物体不会反射红色,红色的光线被物体全部吸收了),改为含一些红色成分的颜色(比如灰色gray)会使物体呈现深灰色。
先前说过,使用这种方式渲染出来的物体不会太真实。甚至看不到每个立方体的“倒角”,好像是一些红色的类立方体斑纹一样。这是因为环境光以同样的方法来计算所有顶点。我们需要一盏真实一点点的灯,在创建环境光之后添加如下代码:
evice.Lights[0].Type = LightType.Directional;
device.Lights[0].Diffuse = Color.White;
device.Lights[0].Direction = new Vector3(0,-1,-1);
device.Lights[0].Commit();
device.Lights[0].Enabled = true;
这里创建了一盏白色的方向光,照向摄像机相同的方向。现在可以看到不同方向上光影的变化了。
创建mesh的时候,有一系列物体可以使用。使用以下一种方法来创建mesh(这些方法都要求device作为第一个参数):
(以下均使用左手坐标系)
mesh = Mesh.Box(device,2.0f,2.0f,2.0f);
Width、Height、Depth分别表示盒子在X、Y、Z轴上的尺寸
mesh = Mesh.Cylinder(device,2.0f,2.0f,2.0f,36,36);
Radius1,Radius2 表示圆柱体的下底面和上底面半径,必须为非负;Length 表示圆柱体在Z方向的高度;Slices 表示沿中心轴的片段数量,Stacks 表示沿主轴的“堆数量。(注:类似于由经、纬线分成的水平和垂直方向上的块数)
mesh = Mesh.Polygon(device,2.0f,8);
Length 表示多边形每一边的长度,Sides表示有多少条边
mesh = Mesh.Sphere(device,2.0f,36,36);
Radius表示球体的半径,Slices和Stacks的含义与Cylinder的相同。
mesh = Mesh.Torus(device,0.5f,2.0f,36,18)
InnerRadius 圆环的内径,OutterRadius 圆环的外径,Sides横截面上的面数,Rings横截面上的环数,前两个值必须为非负数,后两个必须大于等于三。
mesh = Mesh.Teapot(device)
创建一个茶壶(对一个茶壶,你没有看错^_^)。
以上每一个方法都有一个能返回阾接信息(adjacency information)的重载,每个面用三个整数来做为阾接信息,指定了相阾的三个面(Adjacency information is returned as three integers per face that specify the three neighbors of each face in the mesh)。
使用Mesh渲染复杂模型
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -