3D空间坐标系认识

3D空间认识:

在前面章节中,在屏幕上绘制的2D图像三角形是以屏幕对齐的,就像一个三角形直接贴在屏幕上面,但是那不是每一个都适用的。这样我们就需要一个3D空间坐标系来进行描述和进行显示,在真实的世界里,每一个物品都存在于3D空间中。这也就意味着,要想放置一个物品在一个特殊的位置,我们必须要使用一个坐标系并且定义3个描述特定位置的坐标。在计算机绘图中,三维坐标系一般使用的是笛卡儿坐标系,这个在数学中有见过吧。同时还分为左手坐标系或右手坐标系,左手坐标系就是Y轴指向上方,X轴指向右方,而Z轴指向前方;和左右坐标系相同的是右手坐标系Y和X轴指向相同,Z轴的方向正好相反,如下图所示。(我自己的理解是:大拇指指向的方向就是Z轴方向,4指弯曲的方向就是指从X轴到Y轴的方向,左手和右手坐标系都可以这样判断)

下面我们来讨论一下在3D坐标系中,一个点在不同维数空间中不同的坐标。比如在一维中,如下图所示,P点在尺子5厘米的地方,当我们移动尺子时,P点的位置也就发生了变化,说不定在4厘米或3厘米的地方。虽然P点其实都没有移动过,但是它却有了新的坐标,也就是参照物不同罢了。

下面我们来看一下在三维下的情况,一个空间就需要定义一个原点和三条从原点出发的轴线:X,Y,Z。在计算机绘图中,他们包含了好几个空间下的定义:基元坐标系(Object Space)【模型几何坐标系】,世界坐标系(World Space),观察坐标系(View Space),投影坐标系(Projection Space),以及屏幕坐标系(Screen Space)。

 

基元坐标系:

这个坐标系也叫做几何模型坐标系,主要用于在使用3D建模时,所使用的坐标系。通常在创建模型时,建模人员都会将模型围绕原点进行建模,那样将有助于坐标变换,比如让模型旋转起来,在我们将坐标变换时将会进行描述。如下图,一个正方形正是定义在这样一个坐标系中,它的八个顶点将有如下坐标:(-1, 1, -1),(1,1,-1),(-1,-1,-1),(1,-1,-1),(-1,1,1),(1,1,1),(-1,-1,1),(1,-1,1) 。

因此模型几何坐标系是建模人员在设计和创建模型时定义的坐标系,在保持在磁盘里的模型当然也是存在于一个模型几何坐坐标系中。应用程序可以通过这样一个模型来建立一个顶点缓存对象,并且通过模型数据进行初始化这个缓存对象。因此一个顶点缓存对象通常都会在模型中建立,这也就说明顶点着色器将从几何坐标系中获得顶点数据信息。

 

世界坐标系:

世界坐标系是将每一个模型(基元物品)共享在一个场景中的坐标系,它通常用来描述我们期望呈现的各个模型之间的联系关系。为了虚拟一个世界空间坐标系,我们可以想象我们是站在西南的角落,并且面对着北方的一个房间里。我们把我们脚站在这个角落的地方定义为原点(0,0,0),X轴指向我们的右边,Y轴指向上方,Z轴指向前方【左手坐标系,这个也是Direct3D中使用的坐标系,听说OpenGL使用右手坐标系,不过我不懂的OpenGL】,面对的方向不变。这样确定下来后,房间里的所有东西物品都可以通过不同的坐标XYZ进行唯一定性。由此可见,之所以要使用世界坐标系,就是为了能够描述不同物品直接的联系。

 

观察坐标系:

观察坐标系,有时候我们也叫做摄影坐标系,这个有点像世界坐标系那样,只不过世界坐标系是用来描述整个场景,而观察坐标系的原点是在观察者的眼睛或摄像机。观察坐标系的Z轴就是眼睛看到的前方,Y轴就是相对于眼睛的上方,X轴位于右方,在下图就可以看到世界坐标系和观察坐标系。

在左边的场景中,在世界坐标系中有一个人,就像一个物件一样,并且有一个摄像机在观察他,红色原点和轴线表示世界坐标系。在右边的图片中,表示了观察坐标系和世界坐标系的关系,蓝色轴线表示观察坐标系。为了更清楚的理解他们,可以看出观察坐标系的方向和世界坐标系的方向是不同的,在世界坐标系中观察的方向是红色Z轴,而观察坐标系中方向是蓝色Z轴。

 

投影坐标系:

 投影坐标系是通过观察坐标系转换而来,其实可以想象一下,就和我们拿着摄像机去拍照一样。在投影坐标系中,可见物件的X和Y坐标被描述在-1和1之间,而Z坐标被描述在0和1之间,在坐标转换时会详细讲解这里就不再多说了。

 

屏幕坐标系:

屏幕坐标系一般用来引用一个框架缓存(Frame Buffer)定位坐标列表,因为框架缓存通常是一个2D的纹理,因此屏幕坐标是一个2D坐标系。其实就是我们的显示器,左上角顶点定义为原点(0,0),X轴为横向,Y轴为纵向。在缓存中倍描述为w像素的宽度和h像素的高度,最小也就是最右下角的像素的坐标为(w-1, h-1)。

 

 

世界坐标系转换:

世界坐标系转换其实就是将顶点从几何模型坐标系移到世界坐标系中,在游戏里就是构建游戏场景,将物品放置到一个场景里。通常在世界坐标系转换时,还将通过改变大小来控制基元物件的放大或缩小,通过改变方向来设置旋转,通过改变位置来进行转变。在一个场景中,每一个物件都有他自己的世界坐标系转换矩阵,这是由于每一个物件都有其自己的大小,方向和位置。

 

 观察坐标系转换:

所有顶点在转换到世界坐标系后,观察坐标系转换将其从世界坐标系转换为观察坐标系中。前面我们讲过,观察坐标系即是在世界坐标系内,从观测者或者摄像机角度透视所能够看到的图像,在观察坐标系内,观测者将站在原点(或者说以观测者为原点),透视的方向即是Z轴方向,即观察方向为Z轴方向。

 

 很值得注意的是,虽然观察坐标空间是从观测者在世界坐标系内所能够看到的一个框架,但是观察坐标系矩阵却是由定点来填充的,而非观测者。因此,观察矩阵所填充的数据是和观测者或者摄像机里的正好相反,比如说,我们想把摄像机往-z方向移动4个单位,那么我们必须计算出观察矩阵转换的定点正好为4个单位的Z轴方向。虽然摄像机是往反方向移动,但是在摄像机里的成像却是相反的。在Direct3D中有一个方法可以用来计算这种观察矩阵,那就是XMMatrixLookAtLH()方法,我们只要告诉他观测者的位置,所观看的位置,并且告诉他观察者向上方向,就可以计算出观察矩阵。

 

投影坐标系转换:

投影坐标系转换即是将定点从3D坐标系如:世界和观察坐标系转换为投影坐标系,在投影坐标系中,一个顶点的X和Y坐标是根据在3D空间中的X/Z和Y/Z的比率获得的。首先我们来看一个图,那样有助于我们理解这个概念,如下所示:

在3D中,根据透视法,越靠近的物体越大,从上图可以看出,一棵高为h单位在远离观测点d单位的树,和一棵高为2h单位距离观测点2d位置的树是一样大的。因此,顶点在2D屏幕上呈现是依据X/Z和Y/Z的比率决定的。

在Direct3D中以一个叫做FOV(field-of-view),这个主要是通过特定方向判断特定位置的顶点是否可见。每个人都有一个FOV,当然那是在我们的前方,因为我们不可能看到后面,如果两个物体离得太近或非常远也是看不到的。在计算机绘图里,FOV包含在一个视截体里,在3D中这个视截体被定义一个六面体,有两个面是XY面平行,他们被叫做近Z视平面和远Z视平面。其它的面被定义为观测者的横向和纵向可视界面,FOV越大,视截体的体积也越大,当然容纳的物体也更多,如下图所示。

GPU会过滤视截体外部的东东以至于不会浪费那些不需要显示的部分,这个被称为裁剪,GPU将会将顶点转换为投影顶点,那样就可以知道是否在视截体内。在Direct3D 11中,这些换行都被一个方法完成,那就是XMMatrixPerspectiveFovLH(),我们将提高4个参数,FOVy,比率,Zn和Zf即可以获得投影矩阵。其中FOVy就是Y方向的投影角度,比率就是宽和高比率,Zn和Zf分别是近视面和远视面的大小。

 

绘制3D图形:

有了以上理论知识,我们就可以在屏幕上绘制3D图形了。在前面的例子中我们学会了如何绘制三角形,这里我们将绘制一个立方体。根据我们前面的例子知道,计算机绘图中需要告诉GPU三角形的顶点,因此我们需要先定义一下立方体的顶点,由于立方体有8个顶点,所以我们可以定义一个数组。并且我们还需要告诉像素着色器颜色,因此我们可以先定义一个结构,其代码如下:

复制代码
  1. struct SimpleVertex  
  2. {  
  3.     XMFLOAT3 Pos;  
  4.     XMFLOAT4 Color;  
  5. };  
  6.     SimpleVertex vertices[] =  
  7.     {  
  8.         { XMFLOAT3( -1.0f, 1.0f, -1.0f ), XMFLOAT4( 0.0f, 0.0f, 1.0f, 1.0f ) },  
  9.         { XMFLOAT3( 1.0f, 1.0f, -1.0f ), XMFLOAT4( 0.0f, 1.0f, 0.0f, 1.0f ) },  
  10.         { XMFLOAT3( 1.0f, 1.0f, 1.0f ), XMFLOAT4( 0.0f, 1.0f, 1.0f, 1.0f ) },  
  11.         { XMFLOAT3( -1.0f, 1.0f, 1.0f ), XMFLOAT4( 1.0f, 0.0f, 0.0f, 1.0f ) },  
  12.         { XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT4( 1.0f, 0.0f, 1.0f, 1.0f ) },  
  13.         { XMFLOAT3( 1.0f, -1.0f, -1.0f ), XMFLOAT4( 1.0f, 1.0f, 0.0f, 1.0f ) },  
  14.         { XMFLOAT3( 1.0f, -1.0f, 1.0f ), XMFLOAT4( 1.0f, 1.0f, 1.0f, 1.0f ) },  
  15.         { XMFLOAT3( -1.0f, -1.0f, 1.0f ), XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) },  
  16.     };  

我们知道,GPU识别的最小几何单位是三角形(点线除外),我们告诉GPU立方体的顶点是绘制不出来的,所以我们要转换一下。立方体有6个面,也就是12个三角形,那就是有36个顶点,上面我们才定义了八个顶点,是不是不够呢?前面一章节我们也知道,多边形可以共用一边,即共用两个顶点,因此我们可以通过定义一个索引来描述顶点,其代码如下:

  1. WORD indices[] =  
  2.     {  
  3.         3,1,0,  
  4.         2,1,3,  
  5.   
  6.         0,5,4,  
  7.         1,5,0,  
  8.   
  9.         3,4,7,  
  10.         0,4,3,  
  11.   
  12.         1,6,5,  
  13.         2,6,1,  
  14.   
  15.         2,7,6,  
  16.         3,7,2,  
  17.   
  18.         6,4,5,  
  19.         7,4,6,  
  20.     };  

上面的数字就是表示vertices的下标,8个顶点就是0-7。创建索引缓存和创建顶点缓存很像,顶点缓存前面已经描述过就不再写了,创建索引缓存如下:

  1. D3D11_BUFFER_DESC bd;  
  2.    ZeroMemory( &bd, sizeof(bd) );  
  3.    bd.Usage = D3D11_USAGE_DEFAULT;  
  4.    bd.ByteWidth = sizeofWORD ) * 36;        // 36 vertices needed for 12 triangles in a triangle list  
  5.    bd.BindFlags = D3D11_BIND_INDEX_BUFFER;  
  6.    bd.CPUAccessFlags = 0;  
  7.    bd.MiscFlags = 0;  
  8.    InitData.pSysMem = indices;  
  9.    if( FAILED( g_pd3dDevice->CreateBuffer( &bd, &InitData, &g_pIndexBuffer ) ) )  
  10.        return FALSE;  

创建了索引缓存后需要灌水GPU这个索引缓存,那样他才知道如何使用,其代码如下:

g_pImmediateContext -> IASetIndexBuffer( g_pIndexBuffer, DXGI_FORMAT_R16_UINT, 0  );

万事具备,当然接下来我们要确定的就是坐标系的确立,将其移动到世界坐标系内,我们先看一下如下代码:

[html]  view plain copy print ?
  1. //全局代码  
  2. XMMATRIX                g_World;  
  3. XMMATRIX                g_View;  
  4. XMMATRIX                g_Projection;  
  5.   
  6. //初始化  
  7.     // Initialize the world matrix  
  8.     g_World = XMMatrixIdentity();  
  9.   
  10.     // Initialize the view matrix  
  11.     XMVECTOR Eye = XMVectorSet( 0.0f, 1.0f, -5.0f, 0.0f );  
  12.     XMVECTOR At = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f );  
  13.     XMVECTOR Up = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f );  
  14.     g_View = XMMatrixLookAtLH( Eye, At, Up );  
  15.   
  16.     // Initialize the projection matrix  
  17.     g_Projection = XMMatrixPerspectiveFovLH( XM_PIDIV2, width / (FLOAT)height, 0.01f, 100.0f );  

从上面的代码中可以确立顶点坐标矩阵的确立,为了更能够突出3维的效果,我们先让他转动起来,即通过游戏时间让立方体绕自己的Y轴移动某个角度,其方法为:XMMatrixRotationY。其主要代码如下所示:

   
[html]  view plain copy print ?
  1. //  
  2.    // Update variables  
  3.    //  
  4.    ConstantBuffer cb;  
  5.    cb.mWorld = XMMatrixTranspose( g_World );  
  6.    cb.mView = XMMatrixTranspose( g_View );  
  7.    cb.mProjection = XMMatrixTranspose( g_Projection );  
  8.    g_pImmediateContext->UpdateSubresource( g_pConstantBuffer, 0, NULL, &cb, 0, 0 );  
  9.   
  10.    //  
  11.    // Renders a triangle  
  12.    //  
  13.    g_pImmediateContext->VSSetShader( g_pVertexShader, NULL, 0 );  
  14.    g_pImmediateContext->VSSetConstantBuffers( 0, 1, &g_pConstantBuffer );  
  15.    g_pImmediateContext->PSSetShader( g_pPixelShader, NULL, 0 );  
  16.    g_pImmediateContext->DrawIndexed( 36, 0, 0 );        // 36 vertices needed for 12 triangles in a triangle list  

 

 HLSL编写:

[html]  view plain copy print ?
  1. //--------------------------------------------------------------------------------------  
  2. // Constant Buffer Variables  
  3. //--------------------------------------------------------------------------------------  
  4. cbuffer ConstantBuffer : register( b0 )  
  5. {  
  6.     matrix World;  
  7.     matrix View;  
  8.     matrix Projection;  
  9. }  
  10.   
  11. //--------------------------------------------------------------------------------------  
  12. struct VS_OUTPUT  
  13. {  
  14.     float4 Pos : SV_POSITION;  
  15.     float4 Color : COLOR0;  
  16. };  
  17.   
  18. //--------------------------------------------------------------------------------------  
  19. // Vertex Shader  
  20. //--------------------------------------------------------------------------------------  
  21. VS_OUTPUT VS( float4 Pos : POSITION, float4 Color : COLOR )  
  22. {  
  23.     VS_OUTPUT output = (VS_OUTPUT)0;  
  24.     output.Pos = mul( Pos, World );  
  25.     output.Pos = mul( output.Pos, View );  
  26.     output.Pos = mul( output.Pos, Projection );  
  27.     output.Color = Color;  
  28.     return output;  
  29. }  
  30.    
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值