C++Directx11开发笔记六:3D空间坐标系变换,绘制3D图形动画

简介:

上一篇文章中我们认识了Direct3D中一些空间坐标系,其中包含了几何模型坐标系,世界坐标系,观察坐标系,投影坐标系以及屏幕坐标系,一些纯理论的知识。今天我们来了解一下这些坐标系的变换,并且通过一个例子来说明这些坐标系的关系。这些变换主要在于几何模型到世界坐标系的变化,世界坐标系到观察坐标系的变化,和观察坐标系到投影之间的变换,最后将投影所得的图像通过绘图管线在屏幕上绘制出来。

 

世界坐标系转换:

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

 

 观察坐标系转换:

所有顶点在转换到世界坐标系后,观察坐标系转换将其从世界坐标系转换为观察坐标系中。前面我们讲过,观察坐标系即是在世界坐标系内,从观测者或者摄像机角度透视所能够看到的图像,在观察坐标系内,观测者将站在原点(或者说以观测者为原点),透视的方向即是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个顶点,所以我们可以定义一个数组。并且我们还需要告诉像素着色器颜色,因此我们可以先定义一个结构,其代码如下:

复制代码
struct  SimpleVertex
{
    XMFLOAT3 Pos;
    XMFLOAT4 Color;
};
    SimpleVertex vertices[] 
=
    {
        { XMFLOAT3( 
- 1.0f 1.0f - 1.0f  ), XMFLOAT4(  0.0f 0.0f 1.0f 1.0f  ) },
        { XMFLOAT3( 
1.0f 1.0f - 1.0f  ), XMFLOAT4(  0.0f 1.0f 0.0f 1.0f  ) },
        { XMFLOAT3( 
1.0f 1.0f 1.0f  ), XMFLOAT4(  0.0f 1.0f 1.0f 1.0f  ) },
        { XMFLOAT3( 
- 1.0f 1.0f 1.0f  ), XMFLOAT4(  1.0f 0.0f 0.0f 1.0f  ) },
        { XMFLOAT3( 
- 1.0f - 1.0f - 1.0f  ), XMFLOAT4(  1.0f 0.0f 1.0f 1.0f  ) },
        { XMFLOAT3( 
1.0f - 1.0f - 1.0f  ), XMFLOAT4(  1.0f 1.0f 0.0f 1.0f  ) },
        { XMFLOAT3( 
1.0f - 1.0f 1.0f  ), XMFLOAT4(  1.0f 1.0f 1.0f 1.0f  ) },
        { XMFLOAT3( 
- 1.0f - 1.0f 1.0f  ), XMFLOAT4(  0.0f 0.0f 0.0f 1.0f  ) },
    };
复制代码

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

复制代码
WORD indices[]  =
    {
        
3 , 1 , 0 ,
        
2 , 1 , 3 ,

        
0 , 5 , 4 ,
        
1 , 5 , 0 ,

        
3 , 4 , 7 ,
        
0 , 4 , 3 ,

        
1 , 6 , 5 ,
        
2 , 6 , 1 ,

        
2 , 7 , 6 ,
        
3 , 7 , 2 ,

        
6 , 4 , 5 ,
        
7 , 4 , 6 ,
    };
复制代码

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

复制代码
 D3D11_BUFFER_DESC bd;
    ZeroMemory( 
& bd,  sizeof (bd) );
    bd.Usage 
=  D3D11_USAGE_DEFAULT;
    bd.ByteWidth 
=   sizeof ( WORD )  *   36 ;         //  36 vertices needed for 12 triangles in a triangle list
    bd.BindFlags  =  D3D11_BIND_INDEX_BUFFER;
    bd.CPUAccessFlags 
=   0 ;
    bd.MiscFlags 
=   0 ;
    InitData.pSysMem 
=  indices;
    
if ( FAILED( g_pd3dDevice -> CreateBuffer(  & bd,  & InitData,  & g_pIndexBuffer ) ) )
        
return  FALSE;
复制代码

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

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

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

复制代码
// 全局代码
XMMATRIX                g_World;
XMMATRIX                g_View;
XMMATRIX                g_Projection;

// 初始化
    
//  Initialize the world matrix
    g_World  =  XMMatrixIdentity();

    
//  Initialize the view matrix
    XMVECTOR Eye  =  XMVectorSet(  0.0f 1.0f - 5.0f 0.0f  );
    XMVECTOR At 
=  XMVectorSet(  0.0f 1.0f 0.0f 0.0f  );
    XMVECTOR Up 
=  XMVectorSet(  0.0f 1.0f 0.0f 0.0f  );
    g_View 
=  XMMatrixLookAtLH( Eye, At, Up );

    
//  Initialize the projection matrix
    g_Projection  =  XMMatrixPerspectiveFovLH( XM_PIDIV2, width  /  (FLOAT)height,  0.01f 100.0f  );
复制代码

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

复制代码
     //
    
//  Update variables
    
//
    ConstantBuffer cb;
    cb.mWorld 
=  XMMatrixTranspose( g_World );
    cb.mView 
=  XMMatrixTranspose( g_View );
    cb.mProjection 
=  XMMatrixTranspose( g_Projection );
    g_pImmediateContext
-> UpdateSubresource( g_pConstantBuffer,  0 , NULL,  & cb,  0 0  );

    
//
    
//  Renders a triangle
    
//
    g_pImmediateContext -> VSSetShader( g_pVertexShader, NULL,  0  );
    g_pImmediateContext
-> VSSetConstantBuffers(  0 1 & g_pConstantBuffer );
    g_pImmediateContext
-> PSSetShader( g_pPixelShader, NULL,  0  );
    g_pImmediateContext
-> DrawIndexed(  36 0 0  );         //  36 vertices needed for 12 triangles in a triangle list
复制代码

 

 HLSL编写:

复制代码
// --------------------------------------------------------------------------------------
//  Constant Buffer Variables
// --------------------------------------------------------------------------------------
cbuffer ConstantBuffer : register( b0 )
{
    matrix World;
    matrix View;
    matrix Projection;
}

// --------------------------------------------------------------------------------------
struct  VS_OUTPUT
{
    float4 Pos : SV_POSITION;
    float4 Color : COLOR0;
};

// --------------------------------------------------------------------------------------
//  Vertex Shader
// --------------------------------------------------------------------------------------
VS_OUTPUT VS( float4 Pos : POSITION, float4 Color : COLOR )
{
    VS_OUTPUT output 
=  (VS_OUTPUT) 0 ;
    output.Pos 
=  mul( Pos, World );
    output.Pos 
=  mul( output.Pos, View );
    output.Pos 
=  mul( output.Pos, Projection );
    output.Color 
=  Color;
    
return  output;
}
复制代码

文章不能写太长,到后面就有点写不下去的感觉,这篇就到这吧,因为下面我们要学的将深入一点看这个空间转换,并且将不同的3D物品联系起来!!!!大学的时候只顾着玩游戏了,代数几何没学好,现在感觉很亏啊,假如上天再给我一次机会..........

本文转自网魂小兵博客园博客,原文链接:http://www.cnblogs.com/xdotnet/archive/2011/08/03/direct3d11_trasformations.html,如需转载请自行联系原作者

相关实践学习
基于阿里云DeepGPU实例,用AI画唯美国风少女
本实验基于阿里云DeepGPU实例,使用aiacctorch加速stable-diffusion-webui,用AI画唯美国风少女,可提升性能至高至原性能的2.6倍。
相关文章
|
24天前
|
开发框架 Linux C语言
C、C++、boost、Qt在嵌入式系统开发中的使用
C、C++、boost、Qt在嵌入式系统开发中的使用
31 1
|
29天前
|
存储 编译器 C语言
C++入门: 类和对象笔记总结(上)
C++入门: 类和对象笔记总结(上)
34 0
|
1月前
|
网络协议 C++
C++ Qt开发:QTcpSocket网络通信组件
`QTcpSocket`和`QTcpServer`是Qt中用于实现基于TCP(Transmission Control Protocol)通信的两个关键类。TCP是一种面向连接的协议,它提供可靠的、双向的、面向字节流的通信。这两个类允许Qt应用程序在网络上建立客户端和服务器之间的连接。Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用`QTcpSocket`组件实现基于TCP的网络通信功能。
38 8
C++ Qt开发:QTcpSocket网络通信组件
|
1月前
|
存储 Linux 程序员
【Linux C/C++ 堆内存分布】深入理解Linux进程的堆空间管理
【Linux C/C++ 堆内存分布】深入理解Linux进程的堆空间管理
76 0
|
1月前
|
监控 C++
C++ Qt开发:QProcess进程管理模块
Qt是一个跨平台的C++图形库,简化了窗体应用开发,支持通过拖放组件提升效率。本章节关注`QProcess`组件,它用于控制和管理进程,例如执行命令、运行可执行文件及与外部进程通信。`QProcess`提供多种方法如`start`、`waitForStarted`和`waitForFinished`等,实现启动、监控和交互。示例展示了如何使用`QProcess`获取系统进程和信息,通过`tasklist`和`systeminfo`命令,并将结果展示在`QTreeWidget`中。
29 0
C++ Qt开发:QProcess进程管理模块
|
1月前
|
存储 编译器 C++
C/C++ 函数的存储位置和占用空间
C/C++ 函数的存储位置和占用空间
16 0
|
1月前
|
编译器 测试技术 API
C++库开发之道:实践和原则(三)
C++库开发之道:实践和原则
73 0
|
1月前
|
存储 缓存 安全
C++库开发之道:实践和原则(二)
C++库开发之道:实践和原则
46 0
|
1月前
|
安全 API C++
C++库开发之道:实践和原则(一)
C++库开发之道:实践和原则
46 0
|
1月前
|
存储 C++ 网络架构
C++ Qt开发:QUdpSocket实现组播通信
Qt教程:使用`QUdpSocket`实现UDP组播通信。通过设置套接字选项、绑定端口、加入和离开组播组,以及发送和接收数据报,简化跨平台窗体应用开发。关键函数包括`setSocketOption`设置多播TTL,`bind`绑定地址和端口,`joinMulticastGroup`加入组播,`leaveMulticastGroup`退出,`writeDatagram`发送,和`readDatagram`接收数据报。
26 1
C++ Qt开发:QUdpSocket实现组播通信