基本的 3D 几何学

下载 : Graph_05.Zip [14 Kb]

原著:克·霍杰兹利

这堂课的内容:
1 .介绍
2 .3D 图形理论
3 .深度缓冲
4 .几何顶点缓冲
5 .矩阵
6 .渲染

  在这本教程结束你也许还也不能创造你的第一 3D 世界,但是它是向着正确的方向前进。这堂课将大有3个内容介绍: 3D 世界空间,顶点缓冲区,矩阵,深度缓冲区。

2. 3D graphics theory

3D空间
1)世界变换
我们在建立三维实体的数学模型时,通常以实体的某一点为坐标原点,比如一个球体,很自然就用球心做原点,这样构成的坐标系称为本地坐标系(Local Coordinates)。实体总是位于某个场景(World Space)中,而场景采用世界坐标系(World Coordinates),因此需要把实体的本地坐标变换成世界坐标,这个变换被称为世界变换(World Transformation)。
图8
在Direct3D中,坐标变换通过一个4x4矩阵来实现,对于世界变换,只要给出实体在场景中的位置信息,就可以借助Direct3D函数得到变换矩阵,具体的计算步骤如下:
首先把实体放置在在世界坐标系原点,使两个坐标系重合;
在世界空间中,对实体进行平行移动,其对应的平移变换阵TT可由函数D3DXMatrixTranslation求得;
把平移后的实体沿自身的Z轴旋转一个角度(角度大于0,表示从Z轴的正向朝原
Direct3D9初级教程-祝晓鹰余锋点看去,旋转方向为顺时针;反之为逆时针,下同),对应的旋转变换阵TZ用D3DXMatrixRotationZ计算;
把实体沿自身的Y轴旋转一个角度,用D3DXMatrixRotationY求出变换阵TY;
把实体沿自身的X轴旋转一个角度,用D3DXMatrixRotationX求出变换阵TX;
最后对实体进行缩放,假设三个轴的缩放系数分别为sx、sy、sz,该操作对应的变换阵TS可由函数D3DXMatrixScaling求得;
最终的世界变换矩阵TW = TS·TX·TY·TZ·TT ,在Direct3D中,矩阵乘法用函数D3DXMatrixMultiply实现,注意相乘顺序为操作的逆序。
从以上描述中,我们很容易得出:实体的运动可以通过不断改变世界变换矩阵来实现。
2)视角变换
实体确定后,接下来要确定观察者在世界坐标系中的方位,换句话说,就是在世界坐标系中如何放置摄像机。观察者(摄像机)所看到的景象,就是Direct3D窗口显示的内容。
确定观察者需要三个量:
观察者的点坐标;
视线方向,为一个矢量,不过Direct3D用视线上的一个点来替代,此时视线方向就是从观察者指向该目标点,这样表示更直观一些;
上方向,通俗地说,就是观察者的头顶方向,用一个矢量表示。
确定后,以观察者为原点,视线为Z轴,上方向或它的一个分量为Y轴(X轴可由左手法则得出,为右方向),构成了视角坐标系,我们需要把实体从世界空间转换到视角空间,这个坐标变换被称为视角变换(View Transformation)。
与世界变换相比,视角变换矩阵的获取要容易得多,只需调用一个函数D3DXMatrixLookAtLH,其输入参数就是决定观察者的那三个量。
3)投影变换
实体转换到视角空间后,还要经过投影变换(Projection Transformation),三维的实体才能显示在二维的计算机屏幕上。打个比方,如果把屏幕看做照相机中的胶卷,那么投影变换就相当于照相机的镜头。
Direct3D使用透视投影变换(Perspective Transformation),此时在视角空间中,可视区域是一个以视线为轴心的棱台(Viewing Frustum),如图10所示。想象一下你处在一个伸手不见五指的房间里,面前有一扇窗户,你可以透过窗户看到各种景物。窗户就是棱台的前裁剪平面,天空、远山等背景是后裁剪平面,其间的可视范围是景深。投影变换把位于可视棱台内的景物投影到前裁剪平面,由于采用透视投影,距离观察者远的对象会变小,从而更具有真实感。在Direct3D中,前裁剪平面被映射到程序窗口,最终形成了我们在屏幕上看到的画面。
透视投影变换由四个量决定:
前裁剪平面的宽度w;
前裁剪平面的高度h;
前裁剪平面到原点的距离z1;
后裁剪平面到原点的距离z2。

顶点缓冲
顶点缓冲对于材质是非常重要的存放重要的数据。Vertex buffers are similiar to textures in that they store pre-made data. Using vertex buffers can help a great deal when it comes to generating complicated scenes - instead of remembering 100's of different arrays for each part you can compile it all into a vertex buffer and then render it with very few calls. These aren't complicated to setup or use - but you should know that they exist.

深度缓冲
深度缓冲很容易设置,当你渲染两个三角形,您任何知道哪个三角形在前面?还是相交?深度缓冲帮你解决了这个问题These are very useful indeed; easy to set up, and once set up require no additional attention - they just sit there and work. 


3. 深度缓冲

它实际上就是一个数组,储存着物体离观察者眼睛的远近:0.0f是最近,1.0f是最远。我们都知道近的东西可以挡住远的东西(除非它是透明的),不过以前的显示卡是不懂这个道理的,一切都要你自己计算。现在好了,绝大多数显示卡都支持这项功能,不会再出现古怪的图像(什么?你喜欢以前的样子?faint)。为了实现不显示被挡住的点,在Z Buffer中要开辟一块区域,即Stencil Buffer,储存是否显示该点。比如说,在32位的Z Buffer中放置8位的Stencil Buffer。所以通过修改Stencil Buffer,我们可以控制要显示哪些点,实现镂空、简单的淡入淡出(把一个个点改为图像的颜色或黑色)等效果。

'## INITIALISE() CODE ##

D3DWindow.EnableAutoDepthStencil = 1
D3DWindow.AutoDepthStencilFormat = D3DFMT_D16 '//16 bit Z-Buffer
16位深度缓冲

'//Then, after you've created the D3DDevice:
’在您创建D3D设备后

'//We need to enable our Z Buffer
允许深度缓冲
D3DDevice.SetRenderState D3DRS_ZENABLE, 1

作的好,有一件事你要确认,你的硬件要支持 Z-Buffer(除非你用软件模拟)Well done, you now have a functioning depth buffer attached to you're render device. If you remember to do this each time you'll be fine. The only thing to note is that you must have hardware support for the selected Z-Buffer depth (if you're using a hardware device). If you select a depth that isn't available two things will happen : 1) the device falls back to a mode it supports or 2) it disables depth buffering. Both of which are unpreferable. To check if the device supports the Z-Buffer setup you want you can use the following code:

If D3D.CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, DispMode.Format, D3DUSAGE_DEPTHSTENCIL, _
        D3DRTYPE_SURFACE, D3DFMT_D16) = D3D_OK Then
'We can use a 16 bit Z-Buffer
核查硬件信息是否能用16位深度缓冲
    D3DWindow.AutoDepthStencilFormat = D3DFMT_D16 '//16 bit Z-Buffer
Else
'We could now check for different modes available...
End If

4. 几何学顶点缓冲

我们都知道,为了确定空间中的一个点的位置,我们需要使用三个坐标。在DXGraphics中,三根坐标轴的伸展方向如图9.1所示。

我们还知道,目前的3D游戏中的物体实际上都是由一个个有着贴图的三角形构成的。事实上,在DXGraphics中的3D物体也可以由一个个点或一条条线组成。而线和三角形又是由顶点(Vertex)构成的。无论是点、线还是三角形,在DXGraphics中都叫Primitive。DX8中3D于2D的A差别仅在于三个尺寸与两个尺寸

'//We're using 3D vertices, but we're not using Lighting just yet...
'   which means we need to specify the vertex colour自定义格式

Private Type LITVERTEX
    x As Single
'x坐标
    y As Single
'y坐标
    z As Single
'z坐标
    color As Long
'颜色
    Specular As Long
’高光
    tu As Single
‘贴图坐标
    tv As Single
‘贴图坐标
End Type

'//The Descriptor for this vertex format...
顶点的描述格式
Const Lit_FVF = (D3DFVF_XYZ Or D3DFVF_DIFFUSE Or D3DFVF_SPECULAR Or D3DFVF_TEX1)

Dim Cube(35) As LITVERTEX

’站长注:渲染同时要自定义与顶点描述格式

我们现在要通过几何写入信息We'll now re-write our InitGeometry( ) function so that it creates a cube. Two things to note with this code - 1) I didn't bother with checking the the vertex order (for culling) and 2) all vertices are generated around the origin - the middle of the cube is going to be [0, 0, 0] and the rest of the vertices are equally distributed around this (+- 1m); when you've finished reading about matrices you'll understand why.

Code In This Font/Size in these tables.

'//I used the Immediate window to
'   get the RGB() values for each of these...

Const C000 As Long = 255     '//Red
Const C001 As Long = 65280  '//Green
Const C100 As Long = 16711680   '//Blue
Const C101 As Long = 16711935 '//Magenta
Const C010 As Long = 65535 '//Yellow
Const C011 As Long = 16776960 '//Cyan
Const C110 As Long = 16777215 '//White
Const C111 As Long = 8421631 '//Orange


'//1. Fill the structures with the data
填充数据

        'Front
前面

        Cube(0) = CreateLitVertex(-1, 1, 1, C011, 0, 0, 0)
        Cube(1) = CreateLitVertex(1, 1, 1, C111, 0, 0, 0)
        Cube(2) = CreateLitVertex(-1, -1, 1, C001, 0, 0, 0)
        Cube(3) = CreateLitVertex(1, 1, 1, C111, 0, 0, 0)
        Cube(4) = CreateLitVertex(-1, -1, 1, C001, 0, 0, 0)
        Cube(5) = CreateLitVertex(1, -1, 1, C101, 0, 0, 0)
        
        'Back
背面
        Cube(6) = CreateLitVertex(-1, 1, -1, C010, 0, 0, 0)
        Cube(7) = CreateLitVertex(1, 1, -1, C110, 0, 0, 0)
        Cube(8) = CreateLitVertex(-1, -1, -1, C000, 0, 0, 0)
        Cube(9) = CreateLitVertex(1, 1, -1, C110, 0, 0, 0)
        Cube(10) = CreateLitVertex(-1, -1, -1, C000, 0, 0, 0)
        Cube(11) = CreateLitVertex(1, -1, -1, C100, 0, 0, 0)
        
        'Right
右面
        Cube(12) = CreateLitVertex(-1, 1, -1, C010, 0, 0, 0)
        Cube(13) = CreateLitVertex(-1, 1, 1, C011, 0, 0, 0)
        Cube(14) = CreateLitVertex(-1, -1, -1, C000, 0, 0, 0)
        Cube(15) = CreateLitVertex(-1, 1, 1, C011, 0, 0, 0)
        Cube(16) = CreateLitVertex(-1, -1, -1, C000, 0, 0, 0)
        Cube(17) = CreateLitVertex(-1, -1, 1, C001, 0, 0, 0)
        
        'Left
左面
        Cube(18) = CreateLitVertex(1, 1, -1, C110, 0, 0, 0)
        Cube(19) = CreateLitVertex(1, 1, 1, C111, 0, 0, 0)
        Cube(20) = CreateLitVertex(1, -1, -1, C100, 0, 0, 0)
        Cube(21) = CreateLitVertex(1, 1, 1, C111, 0, 0, 0)
        Cube(22) = CreateLitVertex(1, -1, -1, C100, 0, 0, 0)
        Cube(23) = CreateLitVertex(1, -1, 1, C101, 0, 0, 0)
        
        'Top
顶面
        Cube(24) = CreateLitVertex(-1, 1, 1, C011, 0, 0, 0)
        Cube(25) = CreateLitVertex(1, 1, 1, C111, 0, 0, 0)
        Cube(26) = CreateLitVertex(-1, 1, -1, C010, 0, 0, 0)
        Cube(27) = CreateLitVertex(1, 1, 1, C111, 0, 0, 0)
        Cube(28) = CreateLitVertex(-1, 1, -1, C010, 0, 0, 0)
        Cube(29) = CreateLitVertex(1, 1, -1, C110, 0, 0, 0)
        
        '

        Cube(30) = CreateLitVertex(-1, -1, 1, C001, 0, 0, 0)
        Cube(31) = CreateLitVertex(1, -1, 1, C101, 0, 0, 0)
        Cube(32) = CreateLitVertex(-1, -1, -1, C000, 0, 0, 0)
        Cube(33) = CreateLitVertex(1, -1, 1, C101, 0, 0, 0)
        Cube(34) = CreateLitVertex(-1, -1, -1, C000, 0, 0, 0)
        Cube(35) = CreateLitVertex(1, -1, -1, C100, 0, 0, 0)

Looks great doesn't it. Just imagine how complicated it'd be to make a sphere or a person... which is why you'll love using externally created objects and loading them straight in. You'll notice that I've used constants for all the colours - a cube is made up of 8 corners, so I designed 8 constants for colour - a colour for each corner. As 3-4 vertices can share the same point I just placed the constant as the colour; if you change the constant all required vertices will change colour as well.

'//建立点缓冲

Set VBuffer = D3DDevice.CreateVertexBuffer(Len(Cube(0)) * 36, 0, Lit_FVF, D3DPOOL_DEFAULT)
If VBuffer Is Nothing Then Exit Function '//Error handler


'//3. Fill the created vertex buffer with the data
填充数据到缓冲区
D3DVertexBuffer8SetData VBuffer, 0, Len(Cube(0)) * 36, 0, Cube(0)

5. 矩阵

3D图形编程中我们要大量地使用矩阵对3D物体的坐标进行转换。你可以把最常用的4阶矩阵看作16个数排成的4x4的方阵一个4x4的矩阵可以和一个空间中的坐标(x,y,z)进行乘法运算,得出一个新的坐标(x',y',z')。运算法则是这样的:

x'=(x*M11)+(y*M21)+(z*M31)+M41

y'=(x*M12)+(y*M22)+(z*M32)+M42

z'=(x*M13)+(y*M23)+(z*M33)+M43

DXGraphics中,空间中的一个点要依次经过三个矩阵的转换才能正确地按要求显示在屏幕上,这三个矩阵分别是WorldViewProjection。这三个矩阵都可以使三维形体平移、旋转和放缩,但一般说来World矩阵负责把形体放到场景中正确的位置,View矩阵负责根据观察者的位置和看的方向调整形体,而Projection矩阵负责设置视角和透视矫正(使物体近大远小)等。如果要手工填充这几个矩阵那就太麻烦了,DXGraphics当然提供了方便我们创建这些矩阵的函数,下面我们来看看例子:

 

世界 矩阵

世界 矩阵是你最常用到的通过原点(0,0,0)This matrix alters the vertices - this is the one you'll use the most often. All transformations of vertices work around the origin [0,0,0] - it rotates vertices around the origin, scales vertices around the origin. This is why (earlier) we created our cube around the origin. As already mentioned it is possible to combine multiple matrices to form a single all-in-one matrix; but as you also should remember, the order you multiply them matters. If you rotate something around the X axis and the Z axis you'll not get the same result if you'd rotated around the Z then the X. For example, if you have a car wheel and rotate it around the X axis you'll get it rotating like a wheel should, if you then rotate it around the Z axis it'll appear to be tilted in one direction - but it'll still rotate like a wheel should. If you rotate around the Z axis the wheel will appear to tilt, if you then rotate it around the X axis the wheel will seem to wobble as it goes around; the effect is best seen when animated though.渲染出来的3D物体的位置和方向、大小等似乎是无法控制的。但办法总是有的,我们可以借助World矩阵完成对3D物体的改变。方法很简单:将模型平移到世界矩阵,旋转世界矩阵,改变模型位置

Dim matWorld As D3DMATRIX
Dim matTemp As D3DMATRIX

D3DXMatrixIdentity matWorld '
复位世界矩阵
    
D3DXMatrixIdentity matTemp
'临时矩阵
D3DXMatrixRotationX matTemp, RotateAngle * (pi / 180)
D3DXMatrixMultiply matWorld, matWorld, matTemp
    
D3DXMatrixIdentity matTemp
D3DXMatrixRotationZ matTemp, RotateAngle * (pi / 180)
D3DXMatrixMultiply matWorld, matWorld, matTemp
    
       
D3DDevice.SetTransform D3DTS_WORLD, matWorld

变换时要设置一个临时矩阵,不然你的世界矩阵想要复原到原来位置就麻烦了okay, not too complicated really. First we create two matrices, one is our "Master" matrix, the other is a temporary one, because we're doing more than one transformation we need something to store each step before multiplying it. Next we reset our master matrix - this is very important as matrix multiplying and transforming is a cumulative thing - if you alter it each frame without reseting it very quickly things will go very strange. The Identity matrix isn't actually a matrix where everything is set to '0', all the X=Y entries are filled with '1's [1,1] [2,2] [3,3] [4,4]. After we've reset our matrix we start manipulating it; the available transformations are:

'//Normal Rotation绕X,Y,Z轴旋转
D3DXMatrixRotationX(Out As D3DMATRIX, angle As Single)
D3DXMatrixRotationY(Out As D3DMATRIX, angle As Single)
D3DXMatrixRotationZ(Out As D3DMATRIX, angle As Single)

'//Rotation through a custom axis
绕自定义轴旋转
' You must specify an axis using the VAxis setting
' FYI: X=[1,0,0] Y=[0,1,0] and Z=[0,0,1]

D3DXMatrixRotationAxis(MOut As D3DMATRIX, VAxis As D3DVECTOR, angle As Single)


'//Misc Transforms
'This scales vertices by the amounts specified
缩放顶点
D3DXMatrixScaling(MOut As D3DMATRIX, x As Single, y As Single, z As Single)
'this moves vertices by the amounts specified
  平移顶点
D3DXMatrixTranslation(MOut As D3DMATRIX, x As Single, y As Single, z As Single)

'Others are available, and are listed in the object browser - and can be found by going
DxVBLibA -> D3DXMATH_MATRIX

观察矩阵
观察矩阵使用方
法很简单,但你要知道如果在屏幕没有看到,是因为你The view matrix can be thought of as the camera; whilst the projection matrix (see later) controls some of the more complicated parts of the "camera", the view matrix is the one you alter if you wish to move the camera around (as most games do). Setting the view matrix is extremely simple, but you need to think about it a little first - if nothing is appearing on screen then the chances are that you've got the camera set up wrong...

 观察矩阵可以被看成摄象机或您的眼睛所在的位置,设置代码如下:

'//The function prototype
D3DXMatrixLookAtLH(MOut As D3DMATRIX, VEye As D3DVECTOR, VAt As D3DVECTOR, VUp As D3DVECTOR)

'//And this is an example:
Dim matView as D3DMATRIX
Dim vecFrom as D3DVECTOR
Dim vecTo as D3DVECTOR
Dim vecUP as D3DVECTOR

'
摄象机在3D空间所在的位置
vecFrom.X = 0
vecFrom.Y = 10
vecFrom.Z = 10

'
摄象机观察的点(物体)
vecTo.X = 0
vecTo.Y = 0
vecTo.Z = 0

'//Unless you want to do something clever you
'
摄象机向上的方向,需要这个方向才能形成完整的约束
vecUp.X = 0
vecUp.Y = 1 
vecUp.Z = 0

'
生成摄象机矩阵

D3DXMatrixLookAtLH matView, vecFrom, vecTo, vecUp

'设置摄象机

D3DDevice.SetTransform D3DTS_VIEW, matView

D3DXMatrixLookAtLH函数最后一个参数,你可以这样理解它:如果我们站在坐标原点处,那么它就是指明我们的头朝着哪一个方向。一般来说,我们的头是向着y轴方向的,所以一般把它定为(0,1,0)

投影矩阵
投影矩阵一般不改变,除非你要作一些奇特的镜头Finally we come to the projection matrix; this matrix isn't used as often as the other two, but it's equally as important. The projection matrix controls how the scene appears when we look through our camera - you can set the field of view (the closest and furthest objects visible) and the view angle (wide angle or tele-photo). Unless you're intending to make a game with a zoom feature or some weird head-messing-up game you're unlikely to set this matrix very often; normally it's setup during initialisation and left.

屏幕的2D图形要通过投影矩阵转换,并将前后面剪裁(加快速度)

'//The function prototype:
D3DXMatrixPerspectiveFovLH( MOut As D3DMATRIX, fovy As Single, aspect As Single, zn As Single, zf As Single)
'Where Mout is the result
'fovy is the view angle
 
'Aspect is the aspect ratio
'zn is the nearest boundary for polygons - anything between here and the camera position are not rendered
'zf is the furthest boundary - anything beyond here is not drawn



'
生成投影 矩阵(视角90度,高宽比1,前后剪裁面0.1, 500)
D3DXMatrixPerspectiveFovLH matProj, pi / 4, 1, 0.1, 500

'
投影 矩阵加入到设备中
D3DDevice.SetTransform D3DTS_PROJECTION, matProj

6. 渲染

'//我们最好先将场景清空
D3DDevice.Clear 0, ByVal 0, D3DCLEAR_TARGET Or D3DCLEAR_ZBUFFER, 0, 1#, 0 

’这条指令可以只清除指定的几个矩形中的屏幕内容,第一个参数的意义就是要清除多少个矩形中的内容,而第二个参数指向一个装满了这些矩形的数组。第三个参数说明要清除那些内容,包括渲染后的场景 ( D3DCLEAR_TARGET )Z Buffer ( D3DCLEAR_ZBUFFER ) Stencil Buffer ( D3DCLEAR_STENCIL )。第四、五、六个参数分别表示场景要被清为什么颜色,Z Buffer要被清为什么值,Stencil要被清为什么值。



'
然后,设置渲染流
D3DDevice.SetStreamSource 0, VBuffer, Len(Cube(0))

'渲染
D3DDevice.DrawPrimitive D3DPT_TRIANGLELIST, 0, 12