Direct3D 11第四节 3D Spaces

  • 引言
  • 3D 坐标系
    • Object Space对象坐标系
    • World Space世界坐标系
    • View Space视坐标系
  • 坐标系转换
    • World Transformation世界变换
    • View Transformation视变换
    • Projection Transformation投影变换
  • 使用变换
    • 修改顶点缓存
    • 修改顶点着色器
    • 设置矩阵
    • 更新常量缓存

引言

这节,我们将深入研究3D位置和转换。这节的目标就是将一个3D物体绘制到屏幕上。

3D 坐标系

为了在世界中的某个位置放一个物体,我们需要使用坐标系统,然后定义三个与位置相关的坐标。在电脑绘图中,3D坐标系在笛卡尔积坐标系中是最普遍的。在这个坐标系中,有X、Y、Z轴,彼此互相垂直。左手系:X->右,Y->上,Z->前;右手系:X->右,Y->上,Z->后。

在3D中,坐标系可以由一个原点,和X,Y,Z轴确定。在电脑绘图中,有几个常用的坐标系:object space, world space, view space, projection space, and screen space.

Object Space(对象坐标系)

对象坐标系,也叫模型坐标系(艺术家用来设计3D模型)。以物体中心为原点。

World Space(世界坐标系)

场景中每个物体共用的坐标系就是世界坐标系。它通常被用来定义我们希望去渲染的对象集合之间的坐标系关系。为了使世界坐标系更形象,我们可以想象我们站在一个长方形房间里的西南角,面朝北。我们的脚站的位置定义为(0,0,0)。X轴->right,Y轴->上,Z轴->前(和我们的朝向一致)。这样房间中的某个点都可以用XYZ轴描述下来。所以,世界坐标系就是告诉我们世界中物体彼此之间的相对位置。

View Space(视坐标系)

有时也叫相机坐标系,和世界坐标系有一点相似之处就是也是被整个场景所使用。然而,在视坐标系中原点是观察者或相机。观察方向定义为Z轴正向,被应用定义的“上”就是Y轴正向。

坐标系转换

转换通常就是将顶点从一种坐标系表示转换成另外一种坐标系表示。在3D电脑图形中,图形管线中有几种典型的转换:world transformation, view transformation, and 投影变换(projection transformation)

World Transformation(世界变换)

世界变换,顾名思义,就是将对象坐标系转换成世界坐标系(坐标变换)。包括缩放、旋转、移动,场景中的每个物体都有它的世界变换矩阵,因为每个物体都有它的大小、方向、位置。

View Transformation(视变换)

顶点被转成世界坐标系后,视转换再将这些顶点从世界坐标系转换成视坐标系。在视坐标系中,观察者站在原点,沿着Z轴正向向远方看去。

视变换矩阵是应用到顶点上,不是观察者身上,因此,视变换矩阵要给观察者或相机做相反的变换。举个例子,我们将相机沿着Z轴负方向移动一段距离,我们需要计算一个视矩阵能够将顶点沿着Z轴正向移动相应的距离。XNA Math中**XMMatrixLookAtLH()**API通常用来计算一个视矩阵。我们只要简单的告诉它观察者在哪儿,朝向,观察者向上的方向,就可以获得相应的视矩阵。

Projection Transformation(投影变换)

投影变换将3D坐标系中的顶点,比如视坐标系、世界坐标系转换成投影坐标系。在投影坐标系中,顶点的XY轴可以通过顶点在3D坐标系中的X/Z、Y/Z比率获取。

3D空间中,事物以透视的方式出现,越近的东西看起来越大。因此,2D屏幕上的点和X/Z、Y/Z比率有直接关系。

FOV,可视区域,截头锥体。

GPU将FOV外的物体过滤掉,不必渲染不用显示,这个过程叫剪裁clipping。裁剪过程相当复杂,因为GPU要比较每一个顶点(与平面比较)。

在Direct3D 11中,获得一个投影矩阵最简单的方法就是调用XMMatrixPerspectiveFovLH() 方法。我们只需提供四个参数:FOVy、Aspect、Zn、Zf,FOVy 是Y方向上的显示域,Aspect是视坐标系的纵横比,Zn和Zf分别是近处的Z值和远处的Z值。

使用变换

上节我们在屏幕上简单的画了个简单的三角形,当我们创建顶点缓存时,我们是直接使用了投影坐标系的顶点位置,所以不必做任何转换,现在我们对3D坐标系和转换有了一个基本的理解,我们将顶点缓存定义在对象坐标系,然后我们再修改顶点着色器将对象坐标系转换成投影坐标系。

修改顶点缓存

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 ) },
    };

如果细心会发现,我们所做的就是明确正方体上的八个点,但事实上我们没有描述三角形,如果我们将这个原样传入,输出结果将不是我们所期待的,我们需要明确通过这八个点构成正方体的三角形。

一个正方体,许多三角形将共用同样的顶点,一次又一次去重复定义同样的顶点是一种空间浪费,因此,Direct3D 可以明确应该选择哪些点去构成三角形。这是通过索引缓存完成的,索引缓存中包含我们的顶点列表,代码如下:

// Create index buffer
    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,
    };

正如你所见,第一个三角形由索引点3,1,0构成,即( -1.0f, 1.0f, 1.0f ),( 1.0f, 1.0f, -1.0f ), 和 ( -1.0f, 1.0f, -1.0f ),正方体有六个面,每个面由两个三角形构成,所以这里一共定义了12个三角形。

索引缓冲区的创建和顶点缓冲区的创建很类似,这里我们明确参数的大小和类型,然后调用方法CreateBuffer。类型是D3D11_BIND_INDEX_BUFFER,因为我们声明数组的类型是WORD,我们将使用sizeof(WORD),代码如下:

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;

一旦我们创建了这个缓存,我们需要设置它以至于Direct3D 知道在形成三角形集合的时候参考索引缓存。我们明确了缓存指针,格式,偏移。

// Set index buffer
    g_pImmediateContext->IASetIndexBuffer( g_pIndexBuffer, DXGI_FORMAT_R16_UINT, 0 );

修改顶点着色器

上节的顶点着色器,我们输入顶点位置再输出顶点位置,没有做任何修改。我们之所以能这么做,是因为顶点位置已经被定义在投影坐标系中了。现在,因为我们的输入顶点被定义在对象坐标系,在顶点着色器输出结果前必须将它转换。一共需三步:对象坐标系->世界坐标系->视坐标系->投影坐标系。1.声明三个缓存变量,缓存变量用来存储需要传给着色器的数据,渲染之前,应用通常会将重要的数据写到缓存,渲染过程中数据就可以读给着色器,在FX文件中,常量缓存被声明得像C++结构中的全局变量。这三个变量就是世界、视、投影变换矩阵。

一旦我们声明了需要的矩阵,我们就可以用顶点着色器通过矩阵转换输入顶点位置。代码如下:

cbuffer ConstantBuffer : register( b0 )
    {
        matrix World;
        matrix View;
        matrix Projection;
    }

    //
    // 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;
    }

设置矩阵

我们已经让顶点着色器去通过矩阵做转换,但我们也需要在我们的程序中定义三个矩阵。当我们渲染时这三个矩阵将把使用的转换保存下来。渲染之前,我们复制这三个矩阵给着色器常量缓存,然后,当我们调用Draw()初始化渲染时,顶点着色器就可以读取常量缓存上储存好的矩阵。除了矩阵,我们也需要ID3D11Buffer 对象表示常量缓存,因此,代码如下:

ID3D11Buffer* g_pConstantBuffer = NULL;
    XMMATRIX g_World;
    XMMATRIX g_View;
    XMMATRIX g_Projection;

为了创建ID3D11Buffer 对象,我们使用ID3D11Device::CreateBuffer()

D3D11_BUFFER_DESC bd;
    ZeroMemory( &bd, sizeof(bd) );
    bd.Usage = D3D11_USAGE_DEFAULT;
    bd.ByteWidth = sizeof(ConstantBuffer);
    bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    bd.CPUAccessFlags = 0;
    if( FAILED(g_pd3dDevice->CreateBuffer( &bd, NULL, &g_pConstantBuffer ) ) )
        return hr;

下面我们需要做的事情就是提出将用来做转换的数组。我们想要三角形坐落在原点并与XY平面平行,这事实上也是它在对象坐标系中怎样存储在顶点缓存中的。因此,世界变换什么也不需要做,我们经世界数组初始化为一个单位数组。我们想要设置相机在[0 1 -5]的位置,看向 [0 1 0]。我们可以调用方法XMMatrixLookAtLH()很方便的计算一个视矩阵通过使用向上的向量[0 1 0] ,因为我们想要Y方向一致保持向上。最后,为了产生投影转换数组,我们调用XMMatrixPerspectiveFovLH(),with a 90 degree vertical field of view (pi/2),640/480的屏幕纵横比,Z轴的远近值分别为0.1和110,也就是说任何物体比0.1近比110远的都不会在屏幕上显示。这就是存储在全局的三个数组g_World, g_View, g_Projection。

更新常量缓存

有了矩阵,渲染时就要把它们写入常量数据缓冲区,这样GPU才能读到它们。为了更新缓存,我们可以调用**ID3D11DeviceContext::UpdateSubresource()**API,然后以和着色器常量缓冲区一样的顺序将矩阵存储的位置传给该函数。为了做到这一点,我们将创建一个结构,这个结构和着色器中的常量缓冲区有一样的布局。而且,因为在C++和HLSL中矩阵在内存中的安排不同,所以我们在更新他们之前必须要转置一下。

We have the matrices, and now we must write them to the constant buffer when rendering so that the GPU can read them. To update the buffer, we can use the ID3D11DeviceContext::UpdateSubresource() API and pass it a pointer to the matrices stored in the same order as the shader’s constant buffer. To help do this, we will create a structure that has the same layout as the constant buffer in the shader. Also, because matrices are arranged differently in memory in C++ and HLSL, we must transpose the matrices before updating them.

//
    // 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 );
时间: 2024-08-02 07:27:59

Direct3D 11第四节 3D Spaces的相关文章

火云开发课堂 - 《使用Cocos2d-x 开发3D游戏》系列 第四节:3D公告板

<使用Cocos2d-x 开发3D游戏>系列在线课程 第四节:3D公告板 视频地址:http://edu.csdn.net/course/attend/1330/20804 交流论坛:mod=viewthread&tid=5&page=1&extra=#pid5" target="_blank">http://www.firestonegames.com/bbs/forum.php? project下载地址:mod=viewthrea

Direct3D 11 Tutorial 4: 3D Spaces_Direct3D 11 教程4:3D空间

概述 在上一个教程中,我们在应用程序窗口的中心成功渲染了一个三角形. 我们没有太注意我们在顶点缓冲区中拾取的顶点位置. 在本教程中,我们将深入研究3D位置和转换的细节. 本教程的结果将是渲染到屏幕的3D对象. 虽然之前的教程侧重于将2D对象渲染到3D世界,但在这里我们展示了一个3D对象. 资源目录 (SDK root)\Samples\C++\Direct3D11\Tutorials\Tutorial04 Github仓库 3D空间 在上一个教程中,三角形的顶点被有策略地放置,以在屏幕上完美地对

Direct3D 11 Tutorial 5: 3D Transformation_Direct3D 11 教程5:3D转型

概述 在上一个教程中,我们从模型空间到屏幕渲染了一个立方体. 在本教程中,我们将扩展转换的概念并演示可以通过这些转换实现的简单动画. 本教程的结果将是围绕另一个轨道运行的对象. 展示转换以及如何将它们组合以实现期望的效果将是有用的. 在我们介绍新概念时,未来的教程将在此基础上构建. 资源目录 (SDK root)\Samples\C++\Direct3D11\Tutorials\Tutorial05 Github 转型 在3D图形中,变换通常用于对顶点和矢量进行操作. 它还用于将它们在一个空间中

Direct3D 11 Tutorial 2: Rendering a Triangle_Direct3D 11 教程2:渲染一个三角形

概要 在之前的教程中,我们建立了一个最小的Direct3D 11的应用程序,它用来在窗口上输出一个单一颜色.在本次教程中,我们将扩展这个应用程序,在屏幕上渲染出一个单一颜色的三角形.我们将通过设置数据机构的过程关联到三角形. 这个教程的输出结果是在窗口中央渲染出一个三角形. 资源目录 (SDK root)\Samples\C++\Direct3D11\Tutorials\Tutorial02 Github-LearnDirectX-DX3D11 tutorial02 (源码已上传至Github)

第二十四节,自定义函数

第二十四节,自定义函数函数是将要实现的功能写在函数里,在要使用此功能的地方调用此函数即可实现功能,这样大大减少编程重复书写同样的代码,在多个要使用同样功能的地方调用函数即可不需要重复写同样的代码函数式编程最重要的是增强代码的重用性和可读性 函数的定义主要有如下要点: def:表示函数的关键字函数名:函数的名称,日后根据函数名调用函数函数体:函数中进行一系列的逻辑计算,如:发送邮件.计算出 [11,22,38,888,2]中的最大数等...参数:为函数体提供数据return:返回值:当函数执行完毕

03. Initializing Direct3D 11

全局变量 IDXGISwapChain* SwapChain; ID3D11Device* d3d11Device; ID3D11DeviceContext* d3d11DevCon; ID3D11RenderTargetView* renderTargetView; float red = 0.0f; float green = 0.0f; float blue = 0.0f; int colormodr = 1; int colormodg = 1; int colormodb = 1; 函

Direct3D 11 Tutorial 3: Shaders and Effect System_Direct3D 11 教程3:着色器和效果系统

概述 在上一个教程中,我们设置了一个顶点缓冲区并将一个三角形传递给GPU. 现在,我们将逐步完成图形管道并查看每个阶段的工作原理. 将解释着色器和效果系统的概念. 请注意,本教程与前一个源代码共享相同的源代码,但将强调不同的部分. 资源目录 (SDK root)\Samples\C++\Direct3D11\Tutorials\Tutorial03 Github仓库 图形管道 在上一个教程中,我们设置顶点缓冲区,然后将顶点布局与顶点着色器相关联. 现在,我们将解释着色器是什么以及它是如何工作的.

centos LNMP第二部分nginx、php配置 第二十四节课

centos  LNMP第二部分nginx.php配置  第二十四节课 上半节课 下半节课 f

第三百六十四节,Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的mapping映射管理

第三百六十四节,Python分布式爬虫打造搜索引擎Scrapy精讲-elasticsearch(搜索引擎)的mapping映射管理 1.映射(mapping)介绍 映射:创建索引的时候,可以预先定义字段的类型以及相关属性elasticsearch会根据json源数据的基础类型猜测你想要的字段映射,将输入的数据转换成可搜索的索引项,mapping就是我们自己定义的字段数据类型,同时告诉elasticsearch如何索引数据以及是否可以被搜索 作用:会让索引建立的更加细致和完善 类型:静态映射和动态