码迷,mamicode.com
首页 > 其他好文 > 详细

Direct3D 11第四节 3D Spaces

时间:2016-04-29 19:35:52      阅读:265      评论:0      收藏:0      [点我收藏+]

标签:

引言

这节,我们将深入研究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 );

Direct3D 11第四节 3D Spaces

标签:

原文地址:http://blog.csdn.net/zty1012620/article/details/51236021

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!