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

概述

在上一个教程中,我们从模型空间到屏幕渲染了一个立方体。 在本教程中,我们将扩展转换的概念并演示可以通过这些转换实现的简单动画。

本教程的结果将是围绕另一个轨道运行的对象。 展示转换以及如何将它们组合以实现期望的效果将是有用的。 在我们介绍新概念时,未来的教程将在此基础上构建。

资源目录

(SDK root)\Samples\C++\Direct3D11\Tutorials\Tutorial05

Github

转型

在3D图形中,变换通常用于对顶点和矢量进行操作。 它还用于将它们在一个空间中转换为另一个空间。 通过与矩阵相乘来执行变换。 通常有三种类型的原始变换可以在顶点上执行:平移(相对于原点位于空间中),旋转(相对于x,y,z帧的方向)和缩放(距离 起源)。 除此之外,投影变换用于从视图空间到投影空间。 XNA Math库包含的API可以方便地构建矩阵,用于多种用途,例如平移,旋转,缩放,世界到视图转换,视图到投影转换等。 然后,应用程序可以使用这些矩阵来转换其场景中的顶点。 需要对矩阵变换有基本的了解。 我们将简要介绍下面的一些示例。

平移

平移是指在空间中移动或移位一定距离。 在3D中,用于翻译的矩阵具有形式。

    1  0  0  0
    0  1  0  0
    0  0  1  0
    a  b  c  1

其中(a,b,c)是定义移动方向和距离的向量。 例如,要沿X轴(负X方向)移动顶点-5单位,我们可以将其与此矩阵相乘:

    1  0  0  0
    0  1  0  0
    0  0  1  0
   -5  0  0  1

如果我们将此应用于以原点为中心的立方体对象,则结果是该框向负X轴移动5个单位,如图5所示,在应用平移之后。

图1.平移的影响

在3D中,空间通常由原点和来自原点的三个唯一轴定义:X,Y和Z.计算机图形中通常使用多个空间:对象空间,世界空间,视图空间,投影空间和屏幕空间。

图2.在对象空间中定义的立方体

旋转

旋转是指围绕穿过原点的轴旋转顶点。 三个这样的轴是空间中的X,Y和Z轴。 2D中的示例是逆时针旋转矢量[1 0] 90度。 旋转的结果是向量[0 1]。 用于围绕Y轴顺时针旋转1度的矩阵看起来像这样:

    cosΐ  0  -sinΐ   0
     0    1     0    0
    sinΐ  0   cosΐ  0
     0    0     0    1

图6显示了围绕Y轴旋转以原点为中心45度的立方体的效果。

图3.围绕Y轴旋转的效果

缩放

缩放是指沿轴方向放大或缩小矢量分量的大小。 例如,矢量可以沿所有方向按比例放大或仅沿X轴按比例缩小。 为了扩展,我们通常在下面应用缩放矩阵:

    p  0  0  0
    0  q  0  0
    0  0  r  0
    0  0  0  1

其中p,q和r分别是沿X,Y和Z方向的比例因子。 下图显示了沿X轴缩放2并沿Y轴缩放0.5的效果。

图4.缩放的效果

多重转换

要将多个变换应用于矢量,我们可以简单地将矢量乘以第一个变换矩阵,然后将得到的矢量乘以第二个变换矩阵,依此类推。 因为向量和矩阵乘法是关联的,我们也可以先将所有矩阵相乘,然后将向量乘以乘积矩阵,得到相同的结果。 下图显示了如果我们将旋转和平移转换结合在一起,立方体将如何结束。

图5.旋转和平移的效果

创建轨道

在本教程中,我们将转换两个多维数据集。 第一个将旋转到位,而第二个将围绕第一个旋转,同时在其自己的轴上旋转。 这两个立方体将具有与其关联的自己的世界变换矩阵,并且该矩阵将在渲染的每个帧中重新应用于该矩阵。

XNA Math中有一些函数可以帮助创建旋转,平移和缩放矩阵。

  • 围绕X,Y和Z轴执行的旋转分别使用函数XMMatrixRotationX,XMMatrixRotationY和XMMatrixRotationZ来完成。 它们创建围绕主轴之一旋转的基本旋转矩阵。 围绕其他轴的复杂旋转可以通过将它们中的几个相乘来完成。
  • 可以通过调用XMMatrixTranslation函数来执行转换。 此函数将创建一个矩阵,用于转换参数指定的点。
  • 使用XMMatrixScaling完成缩放。 它仅沿主轴缩放。 如果需要沿任意轴缩放,则可以将缩放矩阵与适当的旋转矩阵相乘以实现该效果。

第一个立方体将旋转到位,并作为轨道的中心。 立方体沿Y轴旋转,应用于相关的世界矩阵。 这是通过调用以下代码中显示的XMMatrixRotationY函数来完成的。 立方体每帧旋转一定量。 由于立方体被假设为连续旋转,因此旋转矩阵所基于的值随每帧递增。

// 1st Cube: Rotate around the origin
    g_World1 = XMMatrixRotationY( t );

第一个立方体将旋转到位,并作为轨道的中心。 立方体沿Y轴旋转,应用于相关的世界矩阵。 这是通过调用以下代码中显示的XMMatrixRotationY函数来完成的。 立方体每帧旋转一定量。 由于立方体被假设为连续旋转,因此旋转矩阵所基于的值随每帧递增。

// 2nd Cube:  Rotate around origin
    XMMATRIX mSpin = XMMatrixRotationZ( -t );
    XMMATRIX mOrbit = XMMatrixRotationY( -t * 2.0f );
    XMMATRIX mTranslate = XMMatrixTranslation( -4.0f, 0.0f, 0.0f );
    XMMATRIX mScale = XMMatrixScaling( 0.3f, 0.3f, 0.3f );
    g_World2 = mScale * mSpin * mTranslate * mOrbit;

需要注意的一点是,这些操作不是可交换的。 应用转换的顺序很重要。 试验转化顺序并观察结果。

由于所有变换函数都将根据参数创建新矩阵,因此它们旋转的量必须递增。 这是通过更新“时间”变量来完成的。

// Update our time
    t += XM_PI * 0.0125f;

在进行渲染调用之前,必须为着色器更新常量缓冲区。 请注意,世界矩阵对于每个多维数据集都是唯一的,因此会为每个传递给它的对象进行更改。

//
    // Update variables for the first cube
    //
    ConstantBuffer cb1;
    cb1.mWorld = XMMatrixTranspose( g_World1 );
    cb1.mView = XMMatrixTranspose( g_View );
    cb1.mProjection = XMMatrixTranspose( g_Projection );
    g_pImmediateContext->UpdateSubresource( g_pConstantBuffer, 0, NULL, &cb1, 0, 0 );

    //
    // Render the first cube
    //
    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 );

    //
    // Update variables for the second cube
    //
    ConstantBuffer cb2;
    cb2.mWorld = XMMatrixTranspose( g_World2 );
    cb2.mView = XMMatrixTranspose( g_View );
    cb2.mProjection = XMMatrixTranspose( g_Projection );
    g_pImmediateContext->UpdateSubresource( g_pConstantBuffer, 0, NULL, &cb2, 0, 0 );

    //
    // Render the second cube
    //
    g_pImmediateContext->DrawIndexed( 36, 0, 0 );

深度缓冲区

本教程还有另外一个重要的补充,那就是深度缓冲区。 没有它,较小的轨道立方体在围绕后者的后部时仍会被绘制在较大的中心立方体的顶部。 深度缓冲区允许Direct3D跟踪绘制到屏幕的每个像素的深度。 Direct3D 11中深度缓冲区的默认行为是检查屏幕上绘制的每个像素与屏幕空间像素的深度缓冲区中存储的值。 如果正在渲染的像素的深度小于或等于深度缓冲器中已经存在的值,则绘制像素并且将深度缓冲器中的值更新为新绘制的像素的深度。 另一方面,如果正在绘制的像素的深度大于深度缓冲器中已经存在的值,则丢弃该像素并且深度缓冲器中的深度值保持不变。

示例中的以下代码创建深度缓冲区(DepthStencil纹理)。 它还创建深度缓冲区的DepthStencilView,以便Direct3D 11知道将其用作深度模板纹理。

 // Create depth stencil texture
    D3D11_TEXTURE2D_DESC descDepth;
    ZeroMemory( &descDepth, sizeof(descDepth) );
    descDepth.Width = width;
    descDepth.Height = height;
    descDepth.MipLevels = 1;
    descDepth.ArraySize = 1;
    descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    descDepth.SampleDesc.Count = 1;
    descDepth.SampleDesc.Quality = 0;
    descDepth.Usage = D3D11_USAGE_DEFAULT;
    descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    descDepth.CPUAccessFlags = 0;
    descDepth.MiscFlags = 0;
    hr = g_pd3dDevice->CreateTexture2D( &descDepth, NULL, &g_pDepthStencil );
    if( FAILED(hr) )
        return hr;

    // Create the depth stencil view
    D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
    ZeroMemory( &descDSV, sizeof(descDSV) );
    descDSV.Format = descDepth.Format;
    descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
    descDSV.Texture2D.MipSlice = 0;
    hr = g_pd3dDevice->CreateDepthStencilView( g_pDepthStencil, &descDSV, &g_pDepthStencilView );
    if( FAILED(hr) )
        return hr;

为了使用这个新创建的深度模板缓冲区,教程必须将其绑定到设备。 这是通过将深度模板视图传递给OMSetRenderTargets函数的第三个参数来完成的。

g_pImmediateContext->OMSetRenderTargets( 1, &g_pRenderTargetView, g_pDepthStencilView );

与渲染目标一样,我们还必须在渲染之前清除深度缓冲区。 这可确保先前帧的深度值不会错误地丢弃当前帧中的像素。 在下面的代码中,教程实际上是将深度缓冲区设置为最大量(1.0)。

    //
    // Clear the depth buffer to 1.0 (max depth)
    //

最终效果

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏专知

【干货】计算机视觉实战系列02——用Python做图像处理

【导读】在当今互联网蓬勃发展的时代,图像处理技术也随着人们的需求不断进步,专知成员Hui计划推出一系列计算机视觉入门实战讲解,参照Jan Erik Solem编...

1.9K130
来自专栏Code_iOS

OpenGL ES 2.0 (iOS)[02]:修复三角形的显示

从图可以看出,这三个数据形成的其实是一个等边直角三角形,而在 iOS 模拟器中通过 OpenGL ES 绘制出来的是直角三角形,所以是有问题的,三角形被拉伸了。

16510
来自专栏计算机视觉与深度学习基础

【深度学习】写诗机器人tensorflow实现

代码地址:https://github.com/hjptriplebee/Chinese_poem_generator, 欢迎fork, star 机器人命名M...

2.8K60
来自专栏章鱼的慢慢技术路

利用MATLAB进行曲线拟合

47130
来自专栏大数据风控

Python中的交叉分析pivot_table

交叉分析 通常用于分析两个或两个以上,分组变量之间的关系,以交叉表形式进行变量间关系的对比分析; 从数据的不同维度,综合进行分组细分,进一步了解数据的构成、分...

36980
来自专栏PPV课数据科学社区

【学习】ggplot2绘图入门系列之二:图层控制与直方图

如前文所述,ggplot2使用图层将各种图形元素逐步添加组合,从而形成最终结果。第一层必须是原始数据层,其中data参数控制数据来源,注意数据形式...

27460
来自专栏小鹏的专栏

身份证识别——生成身份证号和汉字

还是直接代码吧(genIDCard.py),代码中有注释很容易读懂,原理跟验证码识别一样(tf20: CNN—识别字符验证码),都属于定长字符串识别,接下来也...

53590
来自专栏数值分析与有限元编程

CSR存储刚度矩阵

CSR(Compressed Sparse Row Storage Format)是一种非常有效的稀疏矩阵的存储方法,它按行将稀疏矩阵存储在一个一维实型数组中,...

34850
来自专栏深度学习之tensorflow实战篇

r语言ggplot2包学习笔记(上代码+注释)

#ggplot2学习笔记##第一节:尝试ggplot library(ggplot2) #使用的是R内置数据(mpg) qplot(displ,hwy,data...

52950
来自专栏chenjx85的技术专栏

leetcode-48-旋转图像

你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

10430

扫码关注云+社区

领取腾讯云代金券