教程4:缓冲区、着色器和 HLSL

原文链接

本教程将介绍如何在 DirectX 11 中编写顶点和像素着色器,还将介绍如何在 DirectX 11 中使用顶点和索引缓冲区,这些是渲染 3D 图形时需要理解和利用的最基本概念。

顶点缓冲区

首先要理解的概念是顶点缓冲区,为了说明这个概念,让我们以球体的 3D 模型为例:

3D球体模型图1

3D 球体模型实际上由成百上千个三角形组成:

3D球体模型图2

球体模型中的每个三角形都有三个点,我们称每个点为顶点,因此,为了渲染球体模型,我们需要将构成球体的所有顶点放入一个特殊的数据数组中,我们称之为顶点缓冲区;一旦球体模型的所有点都被放置到了顶点缓冲区中,我们就可以将顶点缓冲区发送到 GPU,来将模型渲染出来。

索引缓冲区

索引缓冲区与顶点缓冲区相关,它们的目的是记录顶点缓冲区中每个顶点的位置;然后,GPU 使用索引缓冲区快速查找顶点缓冲区中的特定顶点。

索引缓冲区的概念类似于在书中使用目录的概念,它有助于以更高的速度找到您要查找的主题。

DirectX 文档表示,使用索引缓冲区还可以增加将顶点数据缓存到显存中更快位置的可能性。

因此,出于性能方面的考虑,强烈建议使用索引缓冲区。

顶点着色器

顶点着色器是一种小程序,主要用于将顶点从顶点缓冲区转换到三维空间,它还可以进行其他计算,例如计算每个顶点的法线。

GPU 将为需要处理的每个顶点调用顶点着色器程序,例如,一个 5000 个多边形组成的模型每帧将运行 15000 次你定义的顶点着色器程序,来绘制这个模型;因此,如果你将图形程序锁定为每秒 60 帧,它将每秒调用 90 万次顶点着色器来绘制 5000 个三角形。

显而易见,编写高效的顶点着色器非常重要。

像素着色器

像素着色器是为我们绘制的多边形着色而编写的小程序,它们由 GPU 为屏幕上的每个可见像素运行。

颜色、纹理、光照以及你计划对多边形面的大多数效果设置都由像素着色器程序处理。

由于 GPU 将调用像素着色器的次数,因此必须高效地编写像素着色器。

HLSL

HLSL 是我们在 DirectX 11 中用来编写这些顶点和像素着色器程序的语言,它的语法与C语言基本相同,只是有一些预定义的类型。

HLSL 程序文件由 全局变量、类型定义、顶点着色器、像素着色器和几何体着色器 组成。

由于这是第一个 HLSL 教程,我们将使用 DirectX 10 开始一个非常简单的 HLSL 程序。

更新后的框架

框架示意图

本教程的框架已经更新,在 GraphicsClass 下,我们添加了三个新类,分别称为 CameraClassModelClassColorShaderClass

CameraClass 将处理我们之前讨论过的视图矩阵,它将处理相机在世界坐标系下的位置,并在着色器需要绘制并确定我们从何处观看场景时将其传递给着色器。

ModelClass 将处理 3D 模型的几何图形,为了简单起见,在本教程中 3D 模型将只是一个三角形。

最后,ColorShaderClass 将负责调用 HLSL 着色器将模型渲染到屏幕上。

在本篇教程代码开始之前,让我们先看一下 HLSL 着色器程序。

Color.vs

这将是我们的第一个着色器程序。

着色器是对模型进行实际渲染的小程序,这些着色器用 HLSL 编写,并存储在名为 color.vs 和 color.ps 的源文件中。另外,我把这些文件和 .cpp 与 .h 文件一起放到了引擎中,请注意,你需要在 VisualStudio 中为它们创建一个新的 过滤器/文件夹。确保右键单击着色器文件并选择“属性”,在弹出窗口中,内容部分中应该没有任何内容,项目类型部分中应该显示“不参与构建”,否则你将收到关于程序主入口点的编译错误。

现在,这个着色器的目的只是绘制彩色三角形,因为我在第一个 HLSL 教程中尽可能地保持简单。以下是顶点着色器的代码:

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // Filename: color.vs
  3. ////////////////////////////////////////////////////////////////////////////////

在着色器程序中,从全局变量开始:这些全局变量可以从外部的 C++ 代码进行修改,你可以使用多种类型的变量,例如 int 或 float,然后在外部设置它们以供着色器程序使用。

通常,你会将大多数全局变量放在名为 cbuffer 的缓冲区对象类型中,即使它只是一个全局变量。逻辑地组织这些缓冲区对于着色器的有效执行以及显卡存储缓冲区的方式非常重要。

在本例中,我将三个矩阵放在同一个缓冲区中,因为我将同时更新它们的每一帧。

  1. /////////////
  2. // GLOBALS //
  3. /////////////
  4. cbuffer MatrixBuffer
  5. {
  6. matrix worldMatrix;
  7. matrix viewMatrix;
  8. matrix projectionMatrix;
  9. };

与 C 类似,我们可以创建自己的类型定义:我们将使用 HLSL 可用的不同类型,例如 float4,这使对着色器的编程更容易和可读。

在本例中,我们将创建具有 x、y、z、w 位置向量和 红、绿、蓝、alpha 颜色的类型。POSITIONCOLORSV_POSITION 是向 GPU 传递变量使用的语义。

我必须在这里创建两个不同的结构,因为顶点着色器和像素着色器的语义不同,尽管在其他方面结构相同:POSITION 适用于顶点着色器,SV_POSITION 适用于像素着色器,而 COLOR 适用于两者;如果需要多个相同类型,则必须在末尾添加一个数字,如COLOR0、COLOR1等。

  1. //////////////
  2. // TYPEDEFS //
  3. //////////////
  4. struct VertexInputType
  5. {
  6. float4 position : POSITION;
  7. float4 color : COLOR;
  8. };
  9. struct PixelInputType
  10. {
  11. float4 position : SV_POSITION;
  12. float4 color : COLOR;
  13. };

当 GPU 处理来自已发送给它的顶点缓冲区的数据时,会调用顶点着色器,这个我命名为 ColorVertexShader 的顶点着色器将为顶点缓冲区中的每个顶点调用。顶点着色器的输入必须与顶点缓冲区中的数据格式以及着色器源文件(在本例中为 VertexInputType)中的类型定义相匹配。顶点着色器的输出将被发送到像素着色器。在这种情况下,输出类型被称为 PixelInputType,这也是上面定义的。

考虑这一点,你可以看到顶点着色器创建了一个 PixelInputType 类型的输出变量。然后获取输入顶点的位置,并将其乘以 世界、视图和投影矩阵,这将根据我们的视角将顶点放置在正确的位置,以便在 3D 空间中进行渲染,然后放置到 2D 屏幕上。

之后,输出变量获取输入颜色的副本,然后返回的输出将用作像素着色器输入。还要注意,我刻意在输入时将 W 值设置为 1.0,否则它是未定义的,因为我们只读取位置的 XYZ 向量。

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // Vertex Shader
  3. ////////////////////////////////////////////////////////////////////////////////
  4. PixelInputType ColorVertexShader(VertexInputType input)
  5. {
  6. PixelInputType output;
  7. // Change the position vector to be 4 units for proper matrix calculations.
  8. input.position.w = 1.0f;
  9. // Calculate the position of the vertex against the world, view, and projection matrices.
  10. output.position = mul(input.position, worldMatrix);
  11. output.position = mul(output.position, viewMatrix);
  12. output.position = mul(output.position, projectionMatrix);
  13. // Store the input color for the pixel shader to use.
  14. output.color = input.color;
  15. return output;
  16. }

Color.ps

像素着色器为渲染到屏幕的多边形上绘制每个像素,在这个像素着色器中,它使用 PixelInputType 作为输入,并返回一个 float4 作为输出,它代表最终的像素颜色。

这个像素着色器程序非常简单,因为我们只是告诉它将像素的颜色设置为与颜色的输入值相同的颜色,请注意,像素着色器从顶点着色器输出获取其输入。

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // Filename: color.ps
  3. ////////////////////////////////////////////////////////////////////////////////
  4. //////////////
  5. // TYPEDEFS //
  6. //////////////
  7. struct PixelInputType
  8. {
  9. float4 position : SV_POSITION;
  10. float4 color : COLOR;
  11. };
  12. ////////////////////////////////////////////////////////////////////////////////
  13. // Pixel Shader
  14. ////////////////////////////////////////////////////////////////////////////////
  15. float4 ColorPixelShader(PixelInputType input) : SV_TARGET
  16. {
  17. return input.color;
  18. }

Colorshaderclass.h

我们将使用 ColorShaderClass 调用 HLSL 着色器来绘制 GPU 上的 3D 模型。

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // Filename: colorshaderclass.h
  3. ////////////////////////////////////////////////////////////////////////////////
  4. #ifndef _COLORSHADERCLASS_H_
  5. #define _COLORSHADERCLASS_H_
  6. //////////////
  7. // INCLUDES //
  8. //////////////
  9. #include <d3d11.h>
  10. #include <d3dcompiler.h>
  11. #include <directxmath.h>
  12. #include <fstream>
  13. using namespace DirectX;
  14. using namespace std;
  15. ////////////////////////////////////////////////////////////////////////////////
  16. // Class name: ColorShaderClass
  17. ////////////////////////////////////////////////////////////////////////////////
  18. class ColorShaderClass
  19. {
  20. private:

以下是将与顶点着色器一起使用的 cBuffer 类型的定义。

typedef 必须与顶点着色器中的 typedef 完全相同,因为模型数据需要与着色器中的 typedef 匹配,才能进行正确渲染。

  1. struct MatrixBufferType
  2. {
  3. XMMATRIX world;
  4. XMMATRIX view;
  5. XMMATRIX projection;
  6. };
  7. public:
  8. ColorShaderClass();
  9. ColorShaderClass(const ColorShaderClass&);
  10. ~ColorShaderClass();

这里的函数处理着色器的初始化和关闭,渲染函数设置着色器参数,然后使用着色器绘制准备好的模型顶点。

  1. bool Initialize(ID3D11Device*, HWND);
  2. void Shutdown();
  3. bool Render(ID3D11DeviceContext*, int, XMMATRIX, XMMATRIX, XMMATRIX);
  4. private:
  5. bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
  6. void ShutdownShader();
  7. void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);
  8. bool SetShaderParameters(ID3D11DeviceContext*, XMMATRIX, XMMATRIX, XMMATRIX);
  9. void RenderShader(ID3D11DeviceContext*, int);
  10. private:
  11. ID3D11VertexShader* m_vertexShader;
  12. ID3D11PixelShader* m_pixelShader;
  13. ID3D11InputLayout* m_layout;
  14. ID3D11Buffer* m_matrixBuffer;
  15. };
  16. #endif

Colorshaderclass.cpp

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // Filename: colorshaderclass.cpp
  3. ////////////////////////////////////////////////////////////////////////////////
  4. #include "colorshaderclass.h"

像往常一样,类构造函数将类中的所有私有指针初始化为空。

  1. ColorShaderClass::ColorShaderClass()
  2. {
  3. m_vertexShader = 0;
  4. m_pixelShader = 0;
  5. m_layout = 0;
  6. m_matrixBuffer = 0;
  7. }
  8. ColorShaderClass::ColorShaderClass(const ColorShaderClass& other)
  9. {
  10. }
  11. ColorShaderClass::~ColorShaderClass()
  12. {
  13. }

Initialize 函数将调用着色器的初始化函数,我们传入 HLSL 着色器文件的名称,在本教程中,文件被命名为 color.vs 和 color.ps.

  1. bool ColorShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
  2. {
  3. bool result;
  4. // Initialize the vertex and pixel shaders.
  5. result = InitializeShader(device, hwnd, L"../Engine/color.vs", L"../Engine/color.ps");
  6. if(!result)
  7. {
  8. return false;
  9. }
  10. return true;
  11. }

Shutdown 函数将调用着色器的关闭函数。

  1. void ColorShaderClass::Shutdown()
  2. {
  3. // Shutdown the vertex and pixel shaders as well as the related objects.
  4. ShutdownShader();
  5. return;
  6. }

Render 将首先使用 SetShaderParameters 函数设置着色器内的参数,设置参数后,它会调用 RenderShader,使用HLSL 着色器绘制绿色三角形。

  1. bool ColorShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, XMMATRIX worldMatrix, XMMATRIX viewMatrix,
  2. XMMATRIX projectionMatrix)
  3. {
  4. bool result;
  5. // Set the shader parameters that it will use for rendering.
  6. result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix);
  7. if(!result)
  8. {
  9. return false;
  10. }
  11. // Now render the prepared buffers with the shader.
  12. RenderShader(deviceContext, indexCount);
  13. return true;
  14. }

现在,我们将从本教程中一个更重要的函数开始,它叫做 InitializeShader,该函数实际加载着色器文件,并使其可用于 DirectX 和 GPU。

您还将看到布局的设置,以及顶点缓冲区数据在 GPU 图形管道上的外观,布局需要与 modelclass.h 中的 VertexType 和 color.vs 中的定义匹配。

  1. bool ColorShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
  2. {
  3. HRESULT result;
  4. ID3D10Blob* errorMessage;
  5. ID3D10Blob* vertexShaderBuffer;
  6. ID3D10Blob* pixelShaderBuffer;
  7. D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
  8. unsigned int numElements;
  9. D3D11_BUFFER_DESC matrixBufferDesc;
  10. // Initialize the pointers this function will use to null.
  11. errorMessage = 0;
  12. vertexShaderBuffer = 0;
  13. pixelShaderBuffer = 0;

这里是我们将着色器程序编译到缓冲区的地方,我们给出了 着色器文件的名称、着色器的名称、着色器版本(DirectX 11 中是 5.0)以及编译着色器的缓冲区;如果编译着色器失败,它将在 errorMessage 字符串中存入一条错误消息,我们将其发送给另一个函数以输出错误;如果它仍然失败,并且没有错误消息字符串,则意味着它无法找到着色器文件,在这种情况下,我们会弹出一个对话框说明原因。

  1. // Compile the vertex shader code.
  2. result = D3DCompileFromFile(vsFilename, NULL, NULL, "ColorVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0,
  3. &vertexShaderBuffer, &errorMessage);
  4. if(FAILED(result))
  5. {
  6. // If the shader failed to compile it should have writen something to the error message.
  7. if(errorMessage)
  8. {
  9. OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
  10. }
  11. // If there was nothing in the error message then it simply could not find the shader file itself.
  12. else
  13. {
  14. MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
  15. }
  16. return false;
  17. }
  18. // Compile the pixel shader code.
  19. result = D3DCompileFromFile(psFilename, NULL, NULL, "ColorPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0,
  20. &pixelShaderBuffer, &errorMessage);
  21. if(FAILED(result))
  22. {
  23. // If the shader failed to compile it should have writen something to the error message.
  24. if(errorMessage)
  25. {
  26. OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
  27. }
  28. // If there was nothing in the error message then it simply could not find the file itself.
  29. else
  30. {
  31. MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
  32. }
  33. return false;
  34. }

一旦顶点着色器和像素着色器代码成功编译进缓冲区,我们就可以使用这些缓冲区来创建着色器对象本身,从这一点开始,我们将使用这些指针来与顶点和像素着色器通信。

  1. // Create the vertex shader from the buffer.
  2. result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
  3. if(FAILED(result))
  4. {
  5. return false;
  6. }
  7. // Create the pixel shader from the buffer.
  8. result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
  9. if(FAILED(result))
  10. {
  11. return false;
  12. }

下一步是创建将由着色器处理的顶点数据的布局。

由于该着色器使用位置和颜色向量,我们需要在布局中创建这两个向量,并指定这两个向量的大小。

语义名称是布局中首先要填写的内容,这允许着色器确定布局中该元素的用法:因为我们有两种不同的元素,所以第一种元素命名为 POSITION,第二种元素命名为 COLOR

布局的下一个重要部分是格式,对于位置向量,我们使用 DXGI_FORMAT_R32G32B32_FLOAT;对于颜色,我们使用 DXGI_FORMAT_R32G32B32A32_FLOAT

最后需要注意的是 AlignedByteOffset,它指示数据在缓冲区中的间距:对于这个布局,我们告诉它前 12 个字节是位置,接下来 16 个字节是颜色,AlignedByteOffset 用于标注每个元素的开始位置。你可以使用 D3D11_APPEND_ALIGNED_ELEMENT,而不是自定义的值放在 AlignedByteOffset 中,它会为你计算出间距。

由于本教程中不需要其他设置,所以我现在将其设置为默认值。

  1. // Create the vertex input layout description.
  2. // This setup needs to match the VertexType stucture in the ModelClass and in the shader.
  3. polygonLayout[0].SemanticName = "POSITION";
  4. polygonLayout[0].SemanticIndex = 0;
  5. polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
  6. polygonLayout[0].InputSlot = 0;
  7. polygonLayout[0].AlignedByteOffset = 0;
  8. polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
  9. polygonLayout[0].InstanceDataStepRate = 0;
  10. polygonLayout[1].SemanticName = "COLOR";
  11. polygonLayout[1].SemanticIndex = 0;
  12. polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
  13. polygonLayout[1].InputSlot = 0;
  14. polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
  15. polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
  16. polygonLayout[1].InstanceDataStepRate = 0;

一旦布局描述被设置好,我们可以得到它的大小,然后使用 D3D 设备创建输入布局,此外,还可以释放顶点和像素着色器缓冲区,因为一旦创建布局,就不再需要它们。

  1. // Get a count of the elements in the layout.
  2. numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);
  3. // Create the vertex input layout.
  4. result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(),
  5. vertexShaderBuffer->GetBufferSize(), &m_layout);
  6. if(FAILED(result))
  7. {
  8. return false;
  9. }
  10. // Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
  11. vertexShaderBuffer->Release();
  12. vertexShaderBuffer = 0;
  13. pixelShaderBuffer->Release();
  14. pixelShaderBuffer = 0;

使用着色器需要设置的最后一件事是常量缓冲区,正如你在顶点着色器中看到的,我们目前只有一个常量缓冲区,所以我们只需要在这里设置一个,这样我们就可以与着色器交互。

缓冲区用法需要设置为动态,因为我们将在每一帧更新它。

绑定标志指示此缓冲区将是一个常量缓冲区。

CPU 访问标志需要与使用情况相匹配,因此设置为 D3D11_CPU_ACCESS_WRITE

填写描述后,我们可以创建常量缓冲区接口,然后使用函数 SetShaderParameters 访问着色器中的内部变量。

  1. // Setup the description of the dynamic matrix constant buffer that is in the vertex shader.
  2. matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
  3. matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
  4. matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
  5. matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
  6. matrixBufferDesc.MiscFlags = 0;
  7. matrixBufferDesc.StructureByteStride = 0;
  8. // Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
  9. result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
  10. if(FAILED(result))
  11. {
  12. return false;
  13. }
  14. return true;
  15. }

ShutdowShader 释放在 InitializeShader 函数中设置的四个接口。

  1. void ColorShaderClass::ShutdownShader()
  2. {
  3. // Release the matrix constant buffer.
  4. if(m_matrixBuffer)
  5. {
  6. m_matrixBuffer->Release();
  7. m_matrixBuffer = 0;
  8. }
  9. // Release the layout.
  10. if(m_layout)
  11. {
  12. m_layout->Release();
  13. m_layout = 0;
  14. }
  15. // Release the pixel shader.
  16. if(m_pixelShader)
  17. {
  18. m_pixelShader->Release();
  19. m_pixelShader = 0;
  20. }
  21. // Release the vertex shader.
  22. if(m_vertexShader)
  23. {
  24. m_vertexShader->Release();
  25. m_vertexShader = 0;
  26. }
  27. return;
  28. }

OutputShaderErrorMessage 输出编译顶点着色器或像素着色器时生成的错误消息。

  1. void ColorShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
  2. {
  3. char* compileErrors;
  4. unsigned long long bufferSize, i;
  5. ofstream fout;
  6. // Get a pointer to the error message text buffer.
  7. compileErrors = (char*)(errorMessage->GetBufferPointer());
  8. // Get the length of the message.
  9. bufferSize = errorMessage->GetBufferSize();
  10. // Open a file to write the error message to.
  11. fout.open("shader-error.txt");
  12. // Write out the error message.
  13. for(i=0; i<bufferSize; i++)
  14. {
  15. fout << compileErrors[i];
  16. }
  17. // Close the file.
  18. fout.close();
  19. // Release the error message.
  20. errorMessage->Release();
  21. errorMessage = 0;
  22. // Pop a message up on the screen to notify the user to check the text file for compile errors.
  23. MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK);
  24. return;
  25. }

SetShaderVariables 函数的存在使在着色器中设置全局变量变得更容易,该函数中使用的矩阵是在 GraphicsClass 中创建的,在 Render 函数调用期间将它们从这里发送到顶点着色器。

  1. bool ColorShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, XMMATRIX worldMatrix, XMMATRIX viewMatrix,
  2. XMMATRIX projectionMatrix)
  3. {
  4. HRESULT result;
  5. D3D11_MAPPED_SUBRESOURCE mappedResource;
  6. MatrixBufferType* dataPtr;
  7. unsigned int bufferNumber;

确保在将矩阵发送到着色器之前对其进行转置,这是 DirectX 11 的要求。

  1. // Transpose the matrices to prepare them for the shader.
  2. worldMatrix = XMMatrixTranspose(worldMatrix);
  3. viewMatrix = XMMatrixTranspose(viewMatrix);
  4. projectionMatrix = XMMatrixTranspose(projectionMatrix);

锁定 m_matrixBuffer,在其中设置新矩阵,然后解锁。

  1. // Lock the constant buffer so it can be written to.
  2. result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
  3. if(FAILED(result))
  4. {
  5. return false;
  6. }
  7. // Get a pointer to the data in the constant buffer.
  8. dataPtr = (MatrixBufferType*)mappedResource.pData;
  9. // Copy the matrices into the constant buffer.
  10. dataPtr->world = worldMatrix;
  11. dataPtr->view = viewMatrix;
  12. dataPtr->projection = projectionMatrix;
  13. // Unlock the constant buffer.
  14. deviceContext->Unmap(m_matrixBuffer, 0);

现在在 HLSL 顶点着色器中设置更新后的矩阵缓冲区。

  1. // Set the position of the constant buffer in the vertex shader.
  2. bufferNumber = 0;
  3. // Finanly set the constant buffer in the vertex shader with the updated values.
  4. deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);
  5. return true;
  6. }

RenderShader 是在 Render 函数中调用的第二个函数,在此之前调用 SetShaderParameters,以确保着色器参数设置正确。

此函数的第一步是在输入装配器中将我们的输入布局设置为活动,这可以让 GPU 知晓顶点缓冲区中数据的格式。

第二步是设置我们将用于渲染该顶点缓冲区的顶点着色器和像素着色器,设置着色器后,我们使用 D3D 设备上下文调用 DrawIndexed 这个 DirectX 11 函数来渲染三角形,调用此函数后,绿色的三角形就被渲染出来了。

  1. void ColorShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
  2. {
  3. // Set the vertex input layout.
  4. deviceContext->IASetInputLayout(m_layout);
  5. // Set the vertex and pixel shaders that will be used to render this triangle.
  6. deviceContext->VSSetShader(m_vertexShader, NULL, 0);
  7. deviceContext->PSSetShader(m_pixelShader, NULL, 0);
  8. // Render the triangle.
  9. deviceContext->DrawIndexed(indexCount, 0, 0);
  10. return;
  11. }

Modelclass.h

如前所述,ModelClass 负责封装3D模型的几何体。

在本教程中,我们将手动设置单个绿色三角形的数据,我们还将为三角形创建顶点和索引缓冲区,以便可以对其进行渲染。

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // Filename: modelclass.h
  3. ////////////////////////////////////////////////////////////////////////////////
  4. #ifndef _MODELCLASS_H_
  5. #define _MODELCLASS_H_
  6. //////////////
  7. // INCLUDES //
  8. //////////////
  9. #include <d3d11.h>
  10. #include <directxmath.h>
  11. using namespace DirectX;
  12. ////////////////////////////////////////////////////////////////////////////////
  13. // Class name: ModelClass
  14. ////////////////////////////////////////////////////////////////////////////////
  15. class ModelClass
  16. {
  17. private:

下面是我们的顶点类型的定义,它将与这个 ModelClass 中的顶点缓冲区一起使用,还需要注意的是,这个类型定义必须与 ColorShaderClass 中的布局相匹配,这将在本教程后面介绍。

  1. struct VertexType
  2. {
  3. XMFLOAT3 position;
  4. XMFLOAT4 color;
  5. };
  6. public:
  7. ModelClass();
  8. ModelClass(const ModelClass&);
  9. ~ModelClass();

这里的函数用于处理初始化和关闭模型的顶点和索引缓冲区,Render 函数将模型几何体放置在显卡上,以准备由色彩着色器绘制。

  1. bool Initialize(ID3D11Device*);
  2. void Shutdown();
  3. void Render(ID3D11DeviceContext*);
  4. int GetIndexCount();
  5. private:
  6. bool InitializeBuffers(ID3D11Device*);
  7. void ShutdownBuffers();
  8. void RenderBuffers(ID3D11DeviceContext*);

ModelClass 中的私有变量是顶点和索引缓冲区以及两个整数,这两个整数用于跟踪每个缓冲区的大小。

请注意,所有 DirectX 11 缓冲区通常使用通用 ID3D11Buffer 类型,并且在第一次创建时通过缓冲区描述更清楚地标识。

  1. private:
  2. ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
  3. int m_vertexCount, m_indexCount;
  4. };
  5. #endif

Modelclass.cpp

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // Filename: modelclass.cpp
  3. ////////////////////////////////////////////////////////////////////////////////
  4. #include "modelclass.h"

类构造函数将顶点和索引缓冲区指针初始化为空。

  1. ModelClass::ModelClass()
  2. {
  3. m_vertexBuffer = 0;
  4. m_indexBuffer = 0;
  5. }
  6. ModelClass::ModelClass(const ModelClass& other)
  7. {
  8. }
  9. ModelClass::~ModelClass()
  10. {
  11. }

Initialize 函数将调用顶点和索引缓冲区的初始化函数。

  1. bool ModelClass::Initialize(ID3D11Device* device)
  2. {
  3. bool result;
  4. // Initialize the vertex and index buffers.
  5. result = InitializeBuffers(device);
  6. if(!result)
  7. {
  8. return false;
  9. }
  10. return true;
  11. }

Shutdown 函数将调用顶点和索引缓冲区的关闭函数。

  1. void ModelClass::Shutdown()
  2. {
  3. // Shutdown the vertex and index buffers.
  4. ShutdownBuffers();
  5. return;
  6. }

Render 函数被 GraphicsClass:Render 函数调用,此函数调用 RenderBuffers 将顶点和索引缓冲区放置在图形管道上,以便色彩着色器能够渲染它们。

  1. void ModelClass::Render(ID3D11DeviceContext* deviceContext)
  2. {
  3. // Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
  4. RenderBuffers(deviceContext);
  5. return;
  6. }

GetIndexCount 返回模型中的索引数,色彩着色器将需要此信息来绘制此模型。

  1. int ModelClass::GetIndexCount()
  2. {
  3. return m_indexCount;
  4. }

InitializeBuffers 函数是我们创建顶点和索引缓冲区的地方,通常,你会读入一个模型,并从该数据文件中创建缓冲区;在本教程中,我们将手动设置顶点和索引缓冲区中的点,因为它仅仅是一个三角形。

  1. bool ModelClass::InitializeBuffers(ID3D11Device* device)
  2. {
  3. VertexType* vertices;
  4. unsigned long* indices;
  5. D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
  6. D3D11_SUBRESOURCE_DATA vertexData, indexData;
  7. HRESULT result;

首先创建两个临时数组来保存顶点和索引数据,稍后我们将使用它们填充最终的缓冲区。

  1. // Set the number of vertices in the vertex array.
  2. m_vertexCount = 3;
  3. // Set the number of indices in the index array.
  4. m_indexCount = 3;
  5. // Create the vertex array.
  6. vertices = new VertexType[m_vertexCount];
  7. if(!vertices)
  8. {
  9. return false;
  10. }
  11. // Create the index array.
  12. indices = new unsigned long[m_indexCount];
  13. if(!indices)
  14. {
  15. return false;
  16. }

现在用三角形的三个点以及每个点的索引填充顶点和索引数组,请注意,我按顺时针顺序创建点,如果逆时针进行此操作,D3D 就会认为三角形朝向相反的方向,并且因为背面剔除而不会绘制三角形。始终记住,将顶点发送到GPU的顺序非常重要。

颜色也在此处设置,因为它是顶点描述的一部分,在这里我把颜色设置为绿色。

  1. // Load the vertex array with data.
  2. vertices[0].position = XMFLOAT3(-1.0f, -1.0f, 0.0f); // Bottom left.
  3. vertices[0].color = XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f);
  4. vertices[1].position = XMFLOAT3(0.0f, 1.0f, 0.0f); // Top middle.
  5. vertices[1].color = XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f);
  6. vertices[2].position = XMFLOAT3(1.0f, -1.0f, 0.0f); // Bottom right.
  7. vertices[2].color = XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f);
  8. // Load the index array with data.
  9. indices[0] = 0; // Bottom left.
  10. indices[1] = 1; // Top middle.
  11. indices[2] = 2; // Bottom right.

填写完顶点数组和索引数组后,我们现在可以使用它们来创建顶点缓冲区和索引缓冲区,创建两个缓冲区的方式相同:

首先填写缓冲区的描述,在描述中,ByteWidth(缓冲区大小)和 BindFlags(缓冲区类型)是你需要确保正确填写的内容。

填写描述后,还需要填写一个子资源指针,该指针将指向先前创建的顶点或索引数组。

使用描述和子资源指针,你可以使用 D3D 设备调用 CreateBuffer,它将返回指向新缓冲区的指针。

  1. // Set up the description of the static vertex buffer.
  2. vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
  3. vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
  4. vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
  5. vertexBufferDesc.CPUAccessFlags = 0;
  6. vertexBufferDesc.MiscFlags = 0;
  7. vertexBufferDesc.StructureByteStride = 0;
  8. // Give the subresource structure a pointer to the vertex data.
  9. vertexData.pSysMem = vertices;
  10. vertexData.SysMemPitch = 0;
  11. vertexData.SysMemSlicePitch = 0;
  12. // Now create the vertex buffer.
  13. result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
  14. if(FAILED(result))
  15. {
  16. return false;
  17. }
  18. // Set up the description of the static index buffer.
  19. indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
  20. indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
  21. indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
  22. indexBufferDesc.CPUAccessFlags = 0;
  23. indexBufferDesc.MiscFlags = 0;
  24. indexBufferDesc.StructureByteStride = 0;
  25. // Give the subresource structure a pointer to the index data.
  26. indexData.pSysMem = indices;
  27. indexData.SysMemPitch = 0;
  28. indexData.SysMemSlicePitch = 0;
  29. // Create the index buffer.
  30. result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
  31. if(FAILED(result))
  32. {
  33. return false;
  34. }

创建顶点缓冲区和索引缓冲区后,可以删除顶点和索引数组,因为数据复制到缓冲区后便不再需要它们。

  1. // Release the arrays now that the vertex and index buffers have been created and loaded.
  2. delete [] vertices;
  3. vertices = 0;
  4. delete [] indices;
  5. indices = 0;
  6. return true;
  7. }

ShutdownBuffers 函数只是释放 InitializeBuffers 函数中创建的顶点缓冲区和索引缓冲区。

  1. void ModelClass::ShutdownBuffers()
  2. {
  3. // Release the index buffer.
  4. if(m_indexBuffer)
  5. {
  6. m_indexBuffer->Release();
  7. m_indexBuffer = 0;
  8. }
  9. // Release the vertex buffer.
  10. if(m_vertexBuffer)
  11. {
  12. m_vertexBuffer->Release();
  13. m_vertexBuffer = 0;
  14. }
  15. return;
  16. }

RenderBuffers 是从 Render 函数调用的,此函数的目的是在 GPU 的输入装配器上将顶点缓冲区和索引缓冲区设置为活动,一旦 GPU 有一个活动的顶点缓冲区,它就可以使用着色器渲染该缓冲区。

该函数还定义了如何绘制这些缓冲区,例如三角形、直线、扇形等。

在本教程中,我们在输入装配器上将顶点缓冲区和索引缓冲区设置为活动状态,并告诉 GPU 应该使用 IASetPrimitiveTopology 这个 DirectX 函数将缓冲区绘制为三角形。

  1. void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
  2. {
  3. unsigned int stride;
  4. unsigned int offset;
  5. // Set vertex buffer stride and offset.
  6. stride = sizeof(VertexType);
  7. offset = 0;
  8. // Set the vertex buffer to active in the input assembler so it can be rendered.
  9. deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);
  10. // Set the index buffer to active in the input assembler so it can be rendered.
  11. deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);
  12. // Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
  13. deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
  14. return;
  15. }

Cameraclass.h

我们已经研究了如何编写 HLSL 着色器,如何设置顶点和索引缓冲区,以及如何使用 ColorShaderClass 调用 HLSL 着色器来绘制这些缓冲区。然而,我们还没有做的事情是:我们要从何处观察来绘制他们。

为此,我们需要一个摄像机类来让 DirectX 11 知道我们在哪里以及如何观看场景,摄像机类将跟踪相机的位置及其当前旋转,它将使用位置和旋转信息生成视图矩阵,该矩阵将被传递到 HLSL 着色器进行渲染。

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // Filename: cameraclass.h
  3. ////////////////////////////////////////////////////////////////////////////////
  4. #ifndef _CAMERACLASS_H_
  5. #define _CAMERACLASS_H_
  6. //////////////
  7. // INCLUDES //
  8. //////////////
  9. #include <directxmath.h>
  10. using namespace DirectX;
  11. ////////////////////////////////////////////////////////////////////////////////
  12. // Class name: CameraClass
  13. ////////////////////////////////////////////////////////////////////////////////
  14. class CameraClass
  15. {
  16. public:
  17. CameraClass();
  18. CameraClass(const CameraClass&);
  19. ~CameraClass();
  20. void SetPosition(float, float, float);
  21. void SetRotation(float, float, float);
  22. XMFLOAT3 GetPosition();
  23. XMFLOAT3 GetRotation();
  24. void Render();
  25. void GetViewMatrix(XMMATRIX&);
  26. private:
  27. float m_positionX, m_positionY, m_positionZ;
  28. float m_rotationX, m_rotationY, m_rotationZ;
  29. XMMATRIX m_viewMatrix;
  30. };
  31. #endif

CameraClass 头文件非常简单,只需使用四个函数:SetPositionSetRotation 功能将用于设置相机对象的位置和旋转;Render 将用于基于相机的位置和旋转创建视图矩阵;最后,GetViewMatrix 将用于从摄影机对象检索视图矩阵,以便着色器可以使用它进行渲染。

Cameraclass.cpp

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // Filename: cameraclass.cpp
  3. ////////////////////////////////////////////////////////////////////////////////
  4. #include "cameraclass.h"

类构造函数将初始化相机在场景原点的位置和旋转。

  1. CameraClass::CameraClass()
  2. {
  3. m_positionX = 0.0f;
  4. m_positionY = 0.0f;
  5. m_positionZ = 0.0f;
  6. m_rotationX = 0.0f;
  7. m_rotationY = 0.0f;
  8. m_rotationZ = 0.0f;
  9. }
  10. CameraClass::CameraClass(const CameraClass& other)
  11. {
  12. }
  13. CameraClass::~CameraClass()
  14. {
  15. }

SetPositionSetRotation 函数用于设置摄像机的位置和旋转量。

  1. void CameraClass::SetPosition(float x, float y, float z)
  2. {
  3. m_positionX = x;
  4. m_positionY = y;
  5. m_positionZ = z;
  6. return;
  7. }
  8. void CameraClass::SetRotation(float x, float y, float z)
  9. {
  10. m_rotationX = x;
  11. m_rotationY = y;
  12. m_rotationZ = z;
  13. return;
  14. }

GetPositionGetRotation 函数将相机的位置和旋转量返回给调用函数。

  1. XMFLOAT3 CameraClass::GetPosition()
  2. {
  3. return XMFLOAT3(m_positionX, m_positionY, m_positionZ);
  4. }
  5. XMFLOAT3 CameraClass::GetRotation()
  6. {
  7. return XMFLOAT3(m_rotationX, m_rotationY, m_rotationZ);
  8. }

Render 函数使用相机的位置和旋转量来构建和更新视图矩阵。

我们首先设置向上、位置、旋转等变量,然后在场景的原点,我们根据相机的 x、y和z 旋转来旋转相机。

一旦它正确旋转,我们就可以将相机平移到三维空间中的位置。

positionlookAt 和 向上向量 中输入正确的值后,我们可以使用 XMMatrixLookAtLH 函数创建视图矩阵,以表示当前相机的旋转和平移。

  1. void CameraClass::Render()
  2. {
  3. XMFLOAT3 up, position, lookAt;
  4. XMVECTOR upVector, positionVector, lookAtVector;
  5. float yaw, pitch, roll;
  6. XMMATRIX rotationMatrix;
  7. // Setup the vector that points upwards.
  8. up.x = 0.0f;
  9. up.y = 1.0f;
  10. up.z = 0.0f;
  11. // Load it into a XMVECTOR structure.
  12. upVector = XMLoadFloat3(&up);
  13. // Setup the position of the camera in the world.
  14. position.x = m_positionX;
  15. position.y = m_positionY;
  16. position.z = m_positionZ;
  17. // Load it into a XMVECTOR structure.
  18. positionVector = XMLoadFloat3(&position);
  19. // Setup where the camera is looking by default.
  20. lookAt.x = 0.0f;
  21. lookAt.y = 0.0f;
  22. lookAt.z = 1.0f;
  23. // Load it into a XMVECTOR structure.
  24. lookAtVector = XMLoadFloat3(&lookAt);
  25. // Set the yaw (Y axis), pitch (X axis), and roll (Z axis) rotations in radians.
  26. pitch = m_rotationX * 0.0174532925f;
  27. yaw = m_rotationY * 0.0174532925f;
  28. roll = m_rotationZ * 0.0174532925f;
  29. // Create the rotation matrix from the yaw, pitch, and roll values.
  30. rotationMatrix = XMMatrixRotationRollPitchYaw(pitch, yaw, roll);
  31. // Transform the lookAt and up vector by the rotation matrix so the view is correctly rotated at the origin.
  32. lookAtVector = XMVector3TransformCoord(lookAtVector, rotationMatrix);
  33. upVector = XMVector3TransformCoord(upVector, rotationMatrix);
  34. // Translate the rotated camera position to the location of the viewer.
  35. lookAtVector = XMVectorAdd(positionVector, lookAtVector);
  36. // Finally create the view matrix from the three updated vectors.
  37. m_viewMatrix = XMMatrixLookAtLH(positionVector, lookAtVector, upVector);
  38. return;
  39. }

调用 Render 函数创建视图矩阵后,我们可以使用 GetViewMatrix 函数向调用函数提供更新后的视图矩阵,视图矩阵将是 HLSL 顶点着色器中使用的三个主要矩阵之一。

  1. void CameraClass::GetViewMatrix(XMMATRIX& viewMatrix)
  2. {
  3. viewMatrix = m_viewMatrix;
  4. return;
  5. }

Graphicsclass.h

GraphicsClass 现在添加了三个新类:CameraClassModelClassColorShaderClass 在这里添加了头文件以及私有成员变量。

请记住,GraphicsClass 是通过调用项目所需的所有类对象来渲染场景的主类。

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // Filename: graphicsclass.h
  3. ////////////////////////////////////////////////////////////////////////////////
  4. #ifndef _GRAPHICSCLASS_H_
  5. #define _GRAPHICSCLASS_H_
  6. ///////////////////////
  7. // MY CLASS INCLUDES //
  8. ///////////////////////
  9. #include "d3dclass.h"
  10. #include "cameraclass.h"
  11. #include "modelclass.h"
  12. #include "colorshaderclass.h"
  13. /////////////
  14. // GLOBALS //
  15. /////////////
  16. const bool FULL_SCREEN = false;
  17. const bool VSYNC_ENABLED = true;
  18. const float SCREEN_DEPTH = 1000.0f;
  19. const float SCREEN_NEAR = 0.1f;
  20. ////////////////////////////////////////////////////////////////////////////////
  21. // Class name: GraphicsClass
  22. ////////////////////////////////////////////////////////////////////////////////
  23. class GraphicsClass
  24. {
  25. public:
  26. GraphicsClass();
  27. GraphicsClass(const GraphicsClass&);
  28. ~GraphicsClass();
  29. bool Initialize(int, int, HWND);
  30. void Shutdown();
  31. bool Frame();
  32. private:
  33. bool Render();
  34. private:
  35. D3DClass* m_Direct3D;
  36. CameraClass* m_Camera;
  37. ModelClass* m_Model;
  38. ColorShaderClass* m_ColorShader;
  39. };
  40. #endif

Graphicsclass.cpp

  1. ////////////////////////////////////////////////////////////////////////////////
  2. // Filename: graphicsclass.cpp
  3. ////////////////////////////////////////////////////////////////////////////////
  4. #include "graphicsclass.h"

GraphicsClass 的第一个更改是将类构造函数中的 摄影机、模型和颜色着色器对象 初始化为空。

  1. GraphicsClass::GraphicsClass()
  2. {
  3. m_Direct3D = 0;
  4. m_Camera = 0;
  5. m_Model = 0;
  6. m_ColorShader = 0;
  7. }
  8. GraphicsClass::GraphicsClass(const GraphicsClass& other)
  9. {
  10. }
  11. GraphicsClass::~GraphicsClass()
  12. {
  13. }

Initialize 函数也已更新,以创建和初始化三个新对象。

  1. bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
  2. {
  3. bool result;
  4. // Create the Direct3D object.
  5. m_Direct3D = new D3DClass;
  6. if(!m_Direct3D)
  7. {
  8. return false;
  9. }
  10. // Initialize the Direct3D object.
  11. result = m_Direct3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
  12. if(!result)
  13. {
  14. MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
  15. return false;
  16. }
  17. // Create the camera object.
  18. m_Camera = new CameraClass;
  19. if (!m_Camera)
  20. {
  21. return false;
  22. }
  23. // Set the initial position of the camera.
  24. m_Camera->SetPosition(0.0f, 0.0f, -5.0f);
  25. // Create the model object.
  26. m_Model = new ModelClass;
  27. if (!m_Model)
  28. {
  29. return false;
  30. }
  31. // Initialize the model object.
  32. result = m_Model->Initialize(m_Direct3D->GetDevice());
  33. if (!result)
  34. {
  35. MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
  36. return false;
  37. }
  38. // Create the color shader object.
  39. m_ColorShader = new ColorShaderClass;
  40. if (!m_ColorShader)
  41. {
  42. return false;
  43. }
  44. // Initialize the color shader object.
  45. result = m_ColorShader->Initialize(m_Direct3D->GetDevice(), hwnd);
  46. if (!result)
  47. {
  48. MessageBox(hwnd, L"Could not initialize the color shader object.", L"Error", MB_OK);
  49. return false;
  50. }
  51. return true;
  52. }

Shutdown 函数也会更新来关闭并释放这三个新对象。

  1. void GraphicsClass::Shutdown()
  2. {
  3. // Release the color shader object.
  4. if (m_ColorShader)
  5. {
  6. m_ColorShader->Shutdown();
  7. delete m_ColorShader;
  8. m_ColorShader = 0;
  9. }
  10. // Release the model object.
  11. if (m_Model)
  12. {
  13. m_Model->Shutdown();
  14. delete m_Model;
  15. m_Model = 0;
  16. }
  17. // Release the camera object.
  18. if (m_Camera)
  19. {
  20. delete m_Camera;
  21. m_Camera = 0;
  22. }
  23. // Release the D3D object.
  24. if(m_Direct3D)
  25. {
  26. m_Direct3D->Shutdown();
  27. delete m_Direct3D;
  28. m_Direct3D = 0;
  29. }
  30. return;
  31. }

Frame 函数与先前的教程保持一致。

  1. bool GraphicsClass::Frame()
  2. {
  3. bool result;
  4. // Render the graphics scene.
  5. result = Render();
  6. if(!result)
  7. {
  8. return false;
  9. }
  10. return true;
  11. }

正如你所料,我们对于 Render 函数的更改最多。

它仍然从清空场景开始,并且只是清空为黑色。

之后,它会调用摄影机对象的 Render 函数,以根据在初始化函数中设置的摄影机位置创建视图矩阵。

创建视图矩阵后,我们将从摄像机类中获取它的副本,我们还从 D3DClass 对象获取 世界和投影矩阵 的副本。

然后我们调用 ModelClass:Render 函数,将绿色三角形模型几何体放到图形管线上。

现在准备好顶点后,我们调用颜色着色器,使用模型信息和用于定位每个顶点的三个矩阵绘制顶点,绿色三角形现在就被绘制到后缓冲区了。

这样,场景绘制完成后,我们调用 EndScene 将其显示在屏幕上。

  1. bool GraphicsClass::Render()
  2. {
  3. XMMATRIX worldMatrix, viewMatrix, projectionMatrix;
  4. bool result;
  5. // Clear the buffers to begin the scene.
  6. m_Direct3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
  7. // Generate the view matrix based on the camera's position.
  8. m_Camera->Render();
  9. // Get the world, view, and projection matrices from the camera and d3d objects.
  10. m_Direct3D->GetWorldMatrix(worldMatrix);
  11. m_Camera->GetViewMatrix(viewMatrix);
  12. m_Direct3D->GetProjectionMatrix(projectionMatrix);
  13. // Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
  14. m_Model->Render(m_Direct3D->GetDeviceContext());
  15. // Render the model using the color shader.
  16. result = m_ColorShader->Render(m_Direct3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix);
  17. if (!result)
  18. {
  19. return false;
  20. }
  21. // Present the rendered scene to the screen.
  22. m_Direct3D->EndScene();
  23. return true;
  24. }

总结

总之,你应该已经了解了顶点和索引缓冲区的工作原理,你还应该了解顶点和像素着色器的基本知识,以及如何使用 HLSL 编写它们;最后,你应该了解我们是如何将这些新概念融入到我们的框架中,从而产生一个绿色三角形,呈现在屏幕上。

我还想提到的是,我意识到仅仅画一个三角形的代码就相当长,而且都挤在 main() 函数中;然而,我这样做是为了搭建一个合适的框架,所以接下来的教程只需要对代码进行很少的修改就可以完成更复杂的图形。

绿色三角形

练习

  1. 编译并运行教程,确保它在屏幕上绘制一个绿色三角形,按 escape 键退出。

  2. 将三角形的颜色更改为红色。

  3. 把三角形变成正方形。

  4. 将相机向后移动 10 个单位。

  5. 更改像素着色器以输出亮度减半的颜色。(重要提示:将 ColorPixelShader 中的某些内容乘以 0.5f)