原英文版地址:http://www.rastertek.com/dx11tut04.html
本教程将介绍如何在Directx11中编写顶点和像素遮影器。它还将介绍如何在Directx11中使用顶点和索引缓冲区。这些是最基本的概念,你需要理解和利用来渲染三维图形。
顶缓冲
要理解的第一个概念是顶点缓冲区。为了说明这个概念,让我们以球体的三维模型为例:
三维球体模型实际上由数百个三角形组成:
球体模型中的每个三角形都有三个点,我们称每个点为顶点。因此,为了渲染球体模型,我们需要将构成球体的所有顶点放入一个特殊的数据数组中,我们称之为顶点缓冲区。一旦球体模型的所有点都在顶点缓冲区中,我们就可以将顶点缓冲区发送到GPU,以便它可以渲染模型。
索引缓冲区
索引缓冲区与顶点缓冲区相关。它们的目的是记录顶点缓冲区中每个顶点的位置。然后,GPU使用索引缓冲区在顶点缓冲区中快速查找特定顶点。索引缓冲区的概念类似于在书中使用索引的概念,它有助于以更高的速度找到要查找的主题。DirectxSDK文档说,使用索引缓冲区还可以增加在视频内存中更快位置缓存顶点数据的可能性。因此,出于性能方面的考虑,强烈建议使用这些。
顶点着色
顶点明暗器是主要用于将顶点从顶点缓冲区转换为三维空间的小型程序。还可以进行其他计算,例如计算每个顶点的法线。GPU将为需要处理的每个顶点调用顶点着色程序。例如,一个5000多边形模型将在每帧运行15000次顶点着色程序来绘制单个模型。因此,如果将图形程序锁定为每秒60帧,它将每秒调用顶点着色程序900000次,以绘制5000个三角形。正如您所知道的,编写高效的顶点着色非常重要。
象素渲染管线
像素着色程序是为我们绘制的多边形着色而编写的小程序。它们由GPU为将被绘制到屏幕上的每个可见像素运行。着色、纹理、照明和大多数其他您计划对多边形面执行的效果都由像素着色程序处理。像素遮影器必须有效地写入,因为它们将被GPU调用的次数。
HLSL
HLSL是我们在Directx11中用来编码这些小顶点和像素着色程序的语言。语法与带有一些预定义类型的C语言几乎完全相同。HLSL程序文件由全局变量、类型定义、顶点着色、像素着色和几何着色组成。因为这是第一个HLSL教程,所以我们将使用Directx11执行一个非常简单的HLSL程序。
更新的框架
已经为本教程更新了框架。在graphicsClass下,我们添加了三个新类,分别是cameraclass、modelclass和colorshaderclass。摄像机将处理我们之前讨论过的视图矩阵。它将处理摄像机在世界上的位置,并在需要绘制和确定我们从何处观看场景时将其传递给着色程序。ModelClass将处理我们的3D模型的几何图形,在本教程中,为了简单起见,3D模型将只是一个三角形。最后,colorshaderclass将负责将模型呈现到调用HLSL明暗器的屏幕上。
我们将首先从HLSL明暗器程序开始教程代码。
色度
这将是我们的第一个着色程序。明暗器是执行模型实际渲染的小程序。这些明暗器是用hlsl编写的,并存储在名为color.vs和color.ps的源文件中。我暂时将带有.cpp和.h文件的文件放在引擎中。这个明暗器的目的只是绘制彩色三角形,因为我在第一个HLSL教程中尽可能地保持简单。下面是顶点着色程序的代码:
// Filename: color.vs
在着色程序中,您从全局变量开始。这些全局变量可以从C++代码外部修改。您可以使用许多类型的变量,如int或float,然后在外部设置这些变量,以便着色程序使用。通常,您将把大多数全局变量放在称为“cBuffer”的缓冲区对象类型中,即使它只是一个全局变量。从逻辑上组织这些缓冲区对于高效执行着色以及图形卡如何存储缓冲区很重要。在这个例子中,我将三个矩阵放在同一个缓冲区中,因为我将同时更新每一帧。
/
// GLOBALS //
/
cbuffer MatrixBuffer
{
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
};
与C类似,我们可以创建自己的类型定义。我们将使用HLSL可用的不同类型,如float4,这使得编程着色器更容易和可读。在这个例子中,我们创建的类型有X、Y、Z、W位置向量和红、绿、蓝、阿尔法颜色。位置、颜色和sv_位置是向gpu传递变量使用的语义。我必须在这里创建两个不同的结构,因为顶点和像素遮影器的语义是不同的,即使结构在其他方面是相同的。位置适用于顶点着色,SV_位置适用于像素着色,而颜色适用于两者。如果您想要多个相同类型的,那么您必须在末尾添加一个数字,例如color0、color1等等。
//
// TYPEDEFS //
//
struct VertexInputType
{
float4 position : POSITION;
float4 color : COLOR;
};
struct PixelInputType
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
当GPU处理已发送给它的顶点缓冲区的数据时,它会调用顶点明暗器。我命名为colorVertexShader的顶点明暗器将为顶点缓冲区中的每个顶点调用。顶点明暗器的输入必须与顶点缓冲区中的数据格式以及明暗器源文件中的类型定义相匹配,在本例中,该文件是VertexInputType。顶点明暗器的输出将发送到像素明暗器。在这种情况下,输出类型称为pixelinputtype,上面也定义了它。
考虑到这一点,您可以看到顶点明暗器创建了一个pixelinputtype类型的输出变量。然后它获取输入顶点的位置,并将其乘以世界、视图,然后再乘以投影矩阵。这将根据我们的视图将顶点放置在正确的位置,以便在三维空间中进行渲染,然后放置在二维屏幕上。之后,输出变量将获取输入颜色的副本,然后返回将用作像素明暗器输入的输出。还要注意,我确实将输入位置的w值设置为1.0,否则它是未定义的,因为我们只在xyz向量中读取位置。
// Vertex Shader
PixelInputType ColorVertexShader(VertexInputType input)
{
PixelInputType output;
// Change the position vector to be 4 units for proper matrix calculations.
input.position.w = 1.0f;
// Calculate the position of the vertex against the world, view, and projection matrices.
output.position = mul(input.position, worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);
// Store the input color for the pixel shader to use.
output.color = input.color;
return output;
}
Color.ps
像素明暗器在将渲染到屏幕的多边形上绘制每个像素。在此像素明暗器中,它使用pixelinputtype作为输入,并返回float4作为输出,该输出表示最终像素颜色。这个像素着色程序非常简单,因为我们只是告诉它将像素着色为与颜色的输入值相同的颜色。请注意,像素明暗器从顶点明暗器输出获取其输入。
// Filename: color.ps
//
// TYPEDEFS //
//
struct PixelInputType
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
// Pixel Shader
float4 ColorPixelShader(PixelInputType input) : SV_TARGET
{
return input.color;
}
Modelclass.h
如前所述,ModelClass负责封装三维模型的几何图形。在本教程中,我们将手动设置单个绿色三角形的数据。我们还将为三角形创建顶点和索引缓冲区,以便对其进行渲染。
// Filename: modelclass.h
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_
//
// INCLUDES //
//
#include <d3d11.h>
#include <d3dx10math.h>
// Class name: ModelClass
class ModelClass
{
private:
这里是我们的顶点类型的定义,将与这个模型类中的顶点缓冲区一起使用。还要注意,这个typedef必须与colorshaderclass中的布局相匹配,这将在本教程后面介绍。
struct VertexType
{
D3DXVECTOR3 position;
D3DXVECTOR4 color;
};
public:
ModelClass();
ModelClass(const ModelClass&);
~ModelClass();
这里的函数处理模型顶点和索引缓冲区的初始化和关闭。“渲染”功能将模型几何图形放置在视频卡上,以准备由颜色明暗器绘制。
bool Initialize(ID3D11Device*);
void Shutdown();
void Render(ID3D11DeviceContext*);
int GetIndexCount();
private:
bool InitializeBuffers(ID3D11Device*);
void ShutdownBuffers();
void RenderBuffers(ID3D11DeviceContext*);
ModelClass中的私有变量是顶点和索引缓冲区以及两个整数,用于跟踪每个缓冲区的大小。请注意,所有Directx11缓冲区通常使用通用的ID3D11缓冲区类型,并且在第一次创建缓冲区时,通过缓冲区描述更清楚地标识这些缓冲区。
private:
ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
int m_vertexCount, m_indexCount;
};
#endif
Modelclass.cpp
// Filename: modelclass.cpp
#include "modelclass.h"
类构造函数将顶点和索引缓冲区指针初始化为空。
ModelClass::ModelClass()
{
m_vertexBuffer = 0;
m_indexBuffer = 0;
}
ModelClass::ModelClass(const ModelClass& other)
{
}
ModelClass::~ModelClass()
{
}
初始化函数将调用顶点和索引缓冲区的初始化函数。
bool ModelClass::Initialize(ID3D11Device* device)
{
bool result;
// Initialize the vertex and index buffer that hold the geometry for the triangle.
result = InitializeBuffers(device);
if(!result)
{
return false;
}
return true;
}
shutdown函数将调用顶点和索引缓冲区的shutdown函数。
void ModelClass::Shutdown()
{
// Release the vertex and index buffers.
ShutdownBuffers();
return;
}
从graphicsClass::render函数调用render。此函数调用render buffers将顶点和索引缓冲区放置在图形管道上,以便颜色明暗器能够渲染它们。
void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
RenderBuffers(deviceContext);
return;
}
GetIndexCount返回模型中的索引数。颜色明暗器将需要此信息来绘制此模型。
int ModelClass::GetIndexCount()
{
return m_indexCount;
}
initializeBuffers函数是我们处理创建顶点和索引缓冲区的地方。通常,您将读入一个模型并从该数据文件创建缓冲区。对于本教程,我们只需手动设置顶点和索引缓冲区中的点,因为它只是一个三角形。
bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
VertexType* vertices;
unsigned long* indices;
D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
D3D11_SUBRESOURCE_DATA vertexData, indexData;
HRESULT result;
首先创建两个临时数组来保存顶点和索引数据,稍后我们将使用这些数据填充最终缓冲区。
// Set the number of vertices in the vertex array.
m_vertexCount = 3;
// Set the number of indices in the index array.
m_indexCount = 3;
// Create the vertex array.
vertices = new VertexType[m_vertexCount];
if(!vertices)
{
return false;
}
// Create the index array.
indices = new unsigned long[m_indexCount];
if(!indices)
{
return false;
}
现在用三角形的三个点以及每个点的索引填充顶点和索引数组。请注意,我按照绘制点的顺时针顺序创建点。如果你逆时针这样做,它会认为三角形是面向相反的方向,而不是因为背面剔除绘制它。请始终记住,将顶点发送到GPU的顺序非常重要。这里也设置了颜色,因为它是顶点描述的一部分。我把颜色设为绿色。
// Load the vertex array with data.
vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f); // Bottom left.
vertices[0].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);
vertices[1].position = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // Top middle.
vertices[1].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);
vertices[2].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f); // Bottom right.
vertices[2].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);
// Load the index array with data.
indices[0] = 0; // Bottom left.
indices[1] = 1; // Top middle.
indices[2] = 2; // Bottom right.
完成顶点数组和索引数组后,我们现在可以使用它们来创建顶点缓冲区和索引缓冲区。创建两个缓冲区的方式相同。首先填写缓冲区的描述。在描述中,字节宽度(缓冲区的大小)和bindFlags(缓冲区的类型)是您需要确保正确填写的内容。完成描述后,您还需要填写一个子资源指针,该指针将指向您先前创建的顶点或索引数组。通过描述和子资源指针,您可以使用d3d设备调用createbuffer,它将返回指向新缓冲区的指针。
//设置静态顶点缓冲区的描述。
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;
// Give the subresource structure a pointer to the vertex data.
vertexData.pSysMem = vertices;
vertexData.SysMemPitch = 0;
vertexData.SysMemSlicePitch = 0;
// Now create the vertex buffer.
result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
if(FAILED(result))
{
return false;
}
// Set up the description of the static index buffer.
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
indexBufferDesc.StructureByteStride = 0;
// Give the subresource structure a pointer to the index data.
indexData.pSysMem = indices;
indexData.SysMemPitch = 0;
indexData.SysMemSlicePitch = 0;
// Create the index buffer.
result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
if(FAILED(result))
{
return false;
}
创建顶点缓冲区和索引缓冲区后,可以删除顶点和索引数组,因为数据已复制到缓冲区中,因此不再需要它们。
//在创建并加载顶点和索引缓冲区后,释放数组。
delete [] vertices;
vertices = 0;
delete [] indices;
indices = 0;
return true;
}
shutdownbuffers函数只释放在initializeBuffers函数中创建的顶点缓冲区和索引缓冲区。
void ModelClass::ShutdownBuffers()
{
// Release the index buffer.
if(m_indexBuffer)
{
m_indexBuffer->Release();
m_indexBuffer = 0;
}
// Release the vertex buffer.
if(m_vertexBuffer)
{
m_vertexBuffer->Release();
m_vertexBuffer = 0;
}
return;
}
从render函数调用renderbuffers。此函数的目的是在GPU中的输入汇编程序上将顶点缓冲区和索引缓冲区设置为活动的。一旦GPU有了活动的顶点缓冲区,它就可以使用着色器来渲染该缓冲区。此函数还定义了应该如何绘制这些缓冲区,如三角形、线条、风扇等。在本教程中,我们在输入汇编程序中将顶点缓冲区和索引缓冲区设置为活动的,并告诉GPU应该使用iasetPrimitiveTopology directx函数将缓冲区绘制为三角形。
void ModelClass::RenderBuffers(ID3D11DeviceContext* deviceContext)
{
unsigned int stride;
unsigned int offset;
// Set vertex buffer stride and offset.
stride = sizeof(VertexType);
offset = 0;
// Set the vertex buffer to active in the input assembler so it can be rendered.
deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);
// Set the index buffer to active in the input assembler so it can be rendered.
deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);
// Set the type of primitive that should be rendered from this vertex buffer, in this case triangles.
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
return;
}
Colorshaderclass.h
colorshaderclass是我们将用来调用HLSL着色器来绘制GPU上的3D模型的工具。
// Filename: colorshaderclass.h
#ifndef _COLORSHADERCLASS_H_
#define _COLORSHADERCLASS_H_
//
// INCLUDES //
//
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;
// Class name: ColorShaderClass
class ColorShaderClass
{
private:
下面是将与顶点明暗器一起使用的cBuffer类型的定义。此typedef必须与顶点明暗器中的typedef完全相同,因为模型数据需要与明暗器中的typedef匹配才能正确呈现。
struct MatrixBufferType
{
D3DXMATRIX world;
D3DXMATRIX view;
D3DXMATRIX projection;
};
public:
ColorShaderClass();
ColorShaderClass(const ColorShaderClass&);
~ColorShaderClass();
这里的函数处理着色器的初始化和关闭。render函数设置明暗器参数,然后使用明暗器绘制准备好的模型顶点。
bool Initialize(ID3D11Device*, HWND);
void Shutdown();
bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX);
private:
bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
void ShutdownShader();
void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);
bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX);
void RenderShader(ID3D11DeviceContext*, int);
private:
ID3D11VertexShader* m_vertexShader;
ID3D11PixelShader* m_pixelShader;
ID3D11InputLayout* m_layout;
ID3D11Buffer* m_matrixBuffer;
};
#endif
Colorshaderclass.cpp
// Filename: colorshaderclass.cpp
#include "colorshaderclass.h"
和往常一样,类构造函数将类中的所有私有指针初始化为空。
ColorShaderClass::ColorShaderClass()
{
m_vertexShader = 0;
m_pixelShader = 0;
m_layout = 0;
m_matrixBuffer = 0;
}
ColorShaderClass::ColorShaderClass(const ColorShaderClass& other)
{
}
ColorShaderClass::~ColorShaderClass()
{
}
初始化函数将调用着色器的初始化函数。我们传入HLSL明暗器文件的名称,在本教程中,它们被命名为color.vs和color.ps。
bool ColorShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
bool result;
// Initialize the vertex and pixel shaders.
result = InitializeShader(device, hwnd, L"../Engine/color.vs", L"../Engine/color.ps");
if(!result)
{
return false;
}
return true;
}
shutdown函数将调用着色器的shutdown。
void ColorShaderClass::Shutdown()
{
// Shutdown the vertex and pixel shaders as well as the related objects.
ShutdownShader();
return;
}
render将首先使用setshaderParameters函数设置着色器内的参数。一旦设置了参数,它就会调用rendershader使用hlsl明暗器绘制绿色三角形。
bool ColorShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix,
D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
bool result;
// Set the shader parameters that it will use for rendering.
result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix);
if(!result)
{
return false;
}
// Now render the prepared buffers with the shader.
RenderShader(deviceContext, indexCount);
return true;
}
现在,我们将从本教程中一个更重要的函数开始,该函数称为InitializeShader。这个函数实际上加载了着色器文件,并使其可用于DirectX和GPU。您还将看到布局的设置以及顶点缓冲区数据将如何在GPU中的图形管道上查看。布局将需要modelclass.h文件中的匹配vertextype以及color.vs文件中定义的匹配vertextype。
bool ColorShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
HRESULT result;
ID3D10Blob* errorMessage;
ID3D10Blob* vertexShaderBuffer;
ID3D10Blob* pixelShaderBuffer;
D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
unsigned int numElements;
D3D11_BUFFER_DESC matrixBufferDesc;
// Initialize the pointers this function will use to null.
errorMessage = 0;
vertexShaderBuffer = 0;
pixelShaderBuffer = 0;
这里是我们将着色程序编译为缓冲区的地方。我们给它指定了明暗器文件的名称、明暗器的名称、明暗器版本(Directx11中的5.0)和将明暗器编译为的缓冲区。如果它未能编译着色器,它将在错误消息字符串中放入一条错误消息,我们将该字符串发送给另一个函数以写出错误。如果它仍然失败,并且没有错误消息字符串,那么它意味着它找不到着色器文件,在这种情况下,我们会弹出一个对话框,这样说。
// Compile the vertex shader code.
result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "ColorVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
&vertexShaderBuffer, &errorMessage, NULL);
if(FAILED(result))
{
// If the shader failed to compile it should have writen something to the error message.
if(errorMessage)
{
OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
}
// If there was nothing in the error message then it simply could not find the shader file itself.
else
{
MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
}
return false;
}
// Compile the pixel shader code.
result = D3DX11CompileFromFile(psFilename, NULL, NULL, "ColorPixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, NULL,
&pixelShaderBuffer, &errorMessage, NULL);
if(FAILED(result))
{
// If the shader failed to compile it should have writen something to the error message.
if(errorMessage)
{
OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
}
// If there was nothing in the error message then it simply could not find the file itself.
else
{
MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
}
return false;
}
一旦顶点明暗器和像素明暗器代码成功编译到缓冲区中,我们就可以使用这些缓冲区自己创建明暗器对象。我们将使用这些指针从这一点向前与顶点和像素明暗器接口。
// Create the vertex shader from the buffer.
result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &m_vertexShader);
if(FAILED(result))
{
return false;
}
// Create the pixel shader from the buffer.
result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &m_pixelShader);
if(FAILED(result))
{
return false;
}
下一步是创建将由明暗器处理的顶点数据的布局。由于此明暗器使用位置和颜色向量,因此我们需要在布局中创建这两个向量,并指定这两个向量的大小。语义名称是布局中首先要填写的内容,这允许明暗器确定布局中此元素的用法。因为我们有两个不同的元素,所以第一个元素使用位置,第二个元素使用颜色。布局的下一个重要部分是格式。对于位置矢量,我们使用dxgi_格式_r32g32b32_浮点,对于颜色,我们使用dxgi_格式_r32g32b32a32_浮点。最后一件需要注意的事情是对齐的byteoffset,它指示数据在缓冲区中的间距。对于这个布局,我们告诉它前12个字节是位置,接下来的16个字节是颜色,AlignedByteOffset显示每个元素的起始位置。您可以使用d3d11_append_-aligned_元素,而不是将自己的值放入AlignedByteOffset中,它将为您计算出间距。由于本教程中不需要这些设置,所以我现在已经将它们设为默认设置。
//现在设置进入明暗器的数据的布局。
//此设置需要与ModelClass和明暗器中的VertexType结构匹配。
polygonLayout[0].SemanticName = "POSITION";
polygonLayout[0].SemanticIndex = 0;
polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
polygonLayout[0].InputSlot = 0;
polygonLayout[0].AlignedByteOffset = 0;
polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
polygonLayout[0].InstanceDataStepRate = 0;
polygonLayout[1].SemanticName = "COLOR";
polygonLayout[1].SemanticIndex = 0;
polygonLayout[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
polygonLayout[1].InputSlot = 0;
polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
polygonLayout[1].InstanceDataStepRate = 0;
设置好布局描述之后,我们就可以得到它的大小,然后使用d3d设备创建输入布局。同时释放顶点和像素着色缓冲区,因为在创建布局后不再需要它们。
// Get a count of the elements in the layout.
numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);
// Create the vertex input layout.
result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(),
vertexShaderBuffer->GetBufferSize(), &m_layout);
if(FAILED(result))
{
return false;
}
// Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
vertexShaderBuffer->Release();
vertexShaderBuffer = 0;
pixelShaderBuffer->Release();
pixelShaderBuffer = 0;
最后一件需要设置的事情是使用着色程序是常数缓冲区。正如您在顶点着色中看到的,我们目前只有一个常量缓冲区,因此我们只需要在这里设置一个常量缓冲区,这样我们就可以与着色程序进行交互。缓冲区的使用需要设置为动态,因为我们将在每帧更新它。绑定标志指示此缓冲区将是一个常量缓冲区。CPU访问标志需要与使用情况匹配,因此设置为d3d11_cpu_access_write。完成描述后,我们可以创建常量缓冲区接口,然后使用该接口使用函数setshaderParameters访问着色器中的内部变量。
//设置顶点明暗器中的动态矩阵常量缓冲区的描述。
matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
matrixBufferDesc.MiscFlags = 0;
matrixBufferDesc.StructureByteStride = 0;
// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
if(FAILED(result))
{
return false;
}
return true;
}
shutdownshader释放在initializeshader函数中设置的四个接口。
void ColorShaderClass::ShutdownShader()
{
// Release the matrix constant buffer.
if(m_matrixBuffer)
{
m_matrixBuffer->Release();
m_matrixBuffer = 0;
}
// Release the layout.
if(m_layout)
{
m_layout->Release();
m_layout = 0;
}
// Release the pixel shader.
if(m_pixelShader)
{
m_pixelShader->Release();
m_pixelShader = 0;
}
// Release the vertex shader.
if(m_vertexShader)
{
m_vertexShader->Release();
m_vertexShader = 0;
}
return;
}
OutputShaderErrorMessage会写出编译顶点着色或像素着色时生成的错误消息。
void ColorShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
char* compileErrors;
unsigned long bufferSize, i;
ofstream fout;
// Get a pointer to the error message text buffer.
compileErrors = (char*)(errorMessage->GetBufferPointer());
// Get the length of the message.
bufferSize = errorMessage->GetBufferSize();
// Open a file to write the error message to.
fout.open("shader-error.txt");
// Write out the error message.
for(i=0; i<bufferSize; i++)
{
fout << compileErrors[i];
}
// Close the file.
fout.close();
// Release the error message.
errorMessage->Release();
errorMessage = 0;
// Pop a message up on the screen to notify the user to check the text file for compile errors.
MessageBox(hwnd, L"Error compiling shader. Check shader-error.txt for message.", shaderFilename, MB_OK);
return;
}
setshadervariables函数的存在是为了使在明暗器中设置全局变量更容易。此函数中使用的矩阵是在graphicsClass内创建的,在此之后调用此函数,以便在渲染函数调用期间将它们从该类发送到顶点明暗器中。
bool ColorShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix,
D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
HRESULT result;
D3D11_MAPPED_SUBRESOURCE mappedResource;
MatrixBufferType* dataPtr;
unsigned int bufferNumber;
Make sure to transpose matrices before sending them into the shader, this is a requirement for DirectX 11.
// Transpose the matrices to prepare them for the shader.
D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);
Lock the m_matrixBuffer, set the new matrices inside it, and then unlock it.
// Lock the constant buffer so it can be written to.
result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
if(FAILED(result))
{
return false;
}
// Get a pointer to the data in the constant buffer.
dataPtr = (MatrixBufferType*)mappedResource.pData;
// Copy the matrices into the constant buffer.
dataPtr->world = worldMatrix;
dataPtr->view = viewMatrix;
dataPtr->projection = projectionMatrix;
// Unlock the constant buffer.
deviceContext->Unmap(m_matrixBuffer, 0);
Now set the updated matrix buffer in the HLSL vertex shader.
// Set the position of the constant buffer in the vertex shader.
bufferNumber = 0;
// Finanly set the constant buffer in the vertex shader with the updated values.
deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);
return true;
}
rendershader是render函数中调用的第二个函数。在此之前调用setshaderParameters以确保正确设置了明暗器参数。
此函数的第一步是在输入汇编程序中将输入布局设置为活动。这让GPU知道顶点缓冲区中数据的格式。第二步是设置顶点明暗器和像素明暗器,我们将使用它们来渲染这个顶点缓冲区。一旦设置了明暗器,我们就通过使用d3d设备上下文调用drawindexed directx 11函数来呈现三角形。一旦调用此函数,它将呈现绿色三角形。
void ColorShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
// Set the vertex input layout.
deviceContext->IASetInputLayout(m_layout);
// Set the vertex and pixel shaders that will be used to render this triangle.
deviceContext->VSSetShader(m_vertexShader, NULL, 0);
deviceContext->PSSetShader(m_pixelShader, NULL, 0);
// Render the triangle.
deviceContext->DrawIndexed(indexCount, 0, 0);
return;
}
Cameraclass.h
我们研究了如何编写HLSL明暗器代码,如何设置顶点和索引缓冲区,以及如何调用HLSL明暗器以使用ColorShaderClass绘制这些缓冲区。然而,我们遗漏的一件事是从中得出它们的观点。为此,我们需要一个摄像头类来让Directx11知道我们在哪里以及如何观看场景。Camera类将跟踪摄影机的位置及其当前旋转。它将使用位置和旋转信息生成一个视图矩阵,该矩阵将传递到HLSL明暗器中进行渲染。
// Filename: cameraclass.h
#ifndef _CAMERACLASS_H_
#define _CAMERACLASS_H_
//
// INCLUDES //
//
#include <d3dx10math.h>
// Class name: CameraClass
class CameraClass
{
public:
CameraClass();
CameraClass(const CameraClass&);
~CameraClass();
void SetPosition(float, float, float);
void SetRotation(float, float, float);
D3DXVECTOR3 GetPosition();
D3DXVECTOR3 GetRotation();
void Render();
void GetViewMatrix(D3DXMATRIX&);
private:
float m_positionX, m_positionY, m_positionZ;
float m_rotationX, m_rotationY, m_rotationZ;
D3DXMATRIX m_viewMatrix;
};
#endif
cameraclass头非常简单,只使用四个函数。set position和set rotation函数将用于设置相机对象的位置和旋转。渲染将用于根据相机的位置和旋转创建视图矩阵。最后,GetViewMatrix将用于从相机对象中检索视图矩阵,以便着色者可以使用它进行渲染。
Cameraclass.cpp
// Filename: cameraclass.cpp
#include "cameraclass.h"
类构造函数将初始化摄像机的位置和旋转,使其位于场景的原点。
CameraClass::CameraClass()
{
m_positionX = 0.0f;
m_positionY = 0.0f;
m_positionZ = 0.0f;
m_rotationX = 0.0f;
m_rotationY = 0.0f;
m_rotationZ = 0.0f;
}
CameraClass::CameraClass(const CameraClass& other)
{
}
CameraClass::~CameraClass()
{
}
设置位置和设置旋转功能用于设置摄像机的位置和旋转。
void CameraClass::SetPosition(float x, float y, float z)
{
m_positionX = x;
m_positionY = y;
m_positionZ = z;
return;
}
void CameraClass::SetRotation(float x, float y, float z)
{
m_rotationX = x;
m_rotationY = y;
m_rotationZ = z;
return;
}
getposition和getrotation函数将相机的位置和旋转返回到调用函数。
D3DXVECTOR3 CameraClass::GetPosition()
{
return D3DXVECTOR3(m_positionX, m_positionY, m_positionZ);
}
D3DXVECTOR3 CameraClass::GetRotation()
{
return D3DXVECTOR3(m_rotationX, m_rotationY, m_rotationZ);
}
渲染函数使用相机的位置和旋转来构建和更新视图矩阵。我们首先为向上、位置、旋转等设置变量。然后在场景的原点,我们首先根据相机的X、Y和Z旋转来旋转相机。一旦它正确旋转,然后将相机转换到三维空间中的位置。如果位置、注视和向上的值正确,则可以使用d3dxMatrixLookatlh函数创建视图矩阵,以表示当前相机旋转和平移。
void CameraClass::Render()
{
D3DXVECTOR3 up, position, lookAt;
float yaw, pitch, roll;
D3DXMATRIX rotationMatrix;
// Setup the vector that points upwards.
up.x = 0.0f;
up.y = 1.0f;
up.z = 0.0f;
// Setup the position of the camera in the world.
position.x = m_positionX;
position.y = m_positionY;
position.z = m_positionZ;
// Setup where the camera is looking by default.
lookAt.x = 0.0f;
lookAt.y = 0.0f;
lookAt.z = 1.0f;
// Set the yaw (Y axis), pitch (X axis), and roll (Z axis) rotations in radians.
pitch = m_rotationX * 0.0174532925f;
yaw = m_rotationY * 0.0174532925f;
roll = m_rotationZ * 0.0174532925f;
// Create the rotation matrix from the yaw, pitch, and roll values.
D3DXMatrixRotationYawPitchRoll(&rotationMatrix, yaw, pitch, roll);
// Transform the lookAt and up vector by the rotation matrix so the view is correctly rotated at the origin.
D3DXVec3TransformCoord(&lookAt, &lookAt, &rotationMatrix);
D3DXVec3TransformCoord(&up, &up, &rotationMatrix);
// Translate the rotated camera position to the location of the viewer.
lookAt = position + lookAt;
// Finally create the view matrix from the three updated vectors.
D3DXMatrixLookAtLH(&m_viewMatrix, &position, &lookAt, &up);
return;
}
调用render函数创建视图矩阵后,我们可以使用此getviewmatrix函数向调用函数提供更新的视图矩阵。视图矩阵将是HLSL顶点着色器中使用的三个主要矩阵之一。
void CameraClass::GetViewMatrix(D3DXMATRIX& viewMatrix)
{
viewMatrix = m_viewMatrix;
return;
}
Graphicsclass.h
graphicsClass现在添加了三个新类。cameraclass、modelclass和colorshaderclass在这里添加了头和私有成员变量。记住,graphicsClass是用于通过调用项目所需的所有类对象来渲染场景的主要类。
// Filename: graphicsclass.h
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_
///
// MY CLASS INCLUDES //
///
#include "d3dclass.h"#include "cameraclass.h"
#include "modelclass.h"
#include "colorshaderclass.h"
/
// GLOBALS //
/
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;
// Class name: GraphicsClass
class GraphicsClass
{
public:
GraphicsClass();
GraphicsClass(const GraphicsClass&);
~GraphicsClass();
bool Initialize(int, int, HWND);
void Shutdown();
bool Frame();
private:
bool Render();
private:
D3DClass* m_D3D; CameraClass* m_Camera;
ModelClass* m_Model;
ColorShaderClass* m_ColorShader;
};
#endif
Graphicsclass.cpp
// Filename: graphicsclass.cpp
#include "graphicsclass.h"
对graphicsClass的第一个更改是将类构造函数中的camera、model和color shader对象初始化为空。
GraphicsClass::GraphicsClass()
{
m_D3D = 0; m_Camera = 0;
m_Model = 0;
m_ColorShader = 0;
}
初始化函数也被更新以创建和初始化三个新对象。
bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
bool result;
// Create the Direct3D object.
m_D3D = new D3DClass;
if(!m_D3D)
{
return false;
}
// Initialize the Direct3D object.
result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
if(!result)
{
MessageBox(hwnd, L"Could not initialize Direct3D.", L"Error", MB_OK);
return false;
}
// Create the camera object.
m_Camera = new CameraClass;
if(!m_Camera)
{
return false;
}
// Set the initial position of the camera.
m_Camera->SetPosition(0.0f, 0.0f, -10.0f);
// Create the model object.
m_Model = new ModelClass;
if(!m_Model)
{
return false;
}
// Initialize the model object.
result = m_Model->Initialize(m_D3D->GetDevice());
if(!result)
{
MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
return false;
}
// Create the color shader object.
m_ColorShader = new ColorShaderClass;
if(!m_ColorShader)
{
return false;
}
// Initialize the color shader object.
result = m_ColorShader->Initialize(m_D3D->GetDevice(), hwnd);
if(!result)
{
MessageBox(hwnd, L"Could not initialize the color shader object.", L"Error", MB_OK);
return false;
}
return true;
}
Shutdown也会更新为Shutdown并释放三个新对象。
void GraphicsClass::Shutdown()
{ // Release the color shader object.
if(m_ColorShader)
{
m_ColorShader->Shutdown();
delete m_ColorShader;
m_ColorShader = 0;
}
// Release the model object.
if(m_Model)
{
m_Model->Shutdown();
delete m_Model;
m_Model = 0;
}
// Release the camera object.
if(m_Camera)
{
delete m_Camera;
m_Camera = 0;
}
// Release the Direct3D object.
if(m_D3D)
{
m_D3D->Shutdown();
delete m_D3D;
m_D3D = 0;
}
return;
}
框架函数与前面的教程保持相同。
bool GraphicsClass::Frame()
{
bool result;
// Render the graphics scene.
result = Render();
if(!result)
{
return false;
}
return true;
}
正如您所期望的,render函数对它的更改最多。它仍然从清理场景开始,除非它被清除为黑色。之后,它调用相机对象的渲染函数,根据在初始化函数中设置的相机位置创建视图矩阵。一旦创建了视图矩阵,我们就可以从camera类中获取它的副本。我们还从d3dclass对象中获取了世界和投影矩阵的副本。然后我们调用modelClass::render函数将绿色三角形模型几何图形放到图形管道上。现在准备好顶点后,我们调用颜色明暗器,使用模型信息和用于定位每个顶点的三个矩阵绘制顶点。绿色三角形现在绘制到后缓冲区。这样,场景就完成了,我们调用endscene将其显示到屏幕上。
bool GraphicsClass::Render()
{ D3DXMATRIX viewMatrix, projectionMatrix, worldMatrix;
bool result;
// Clear the buffers to begin the scene.
m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);
// Generate the view matrix based on the camera's position.
m_Camera->Render();
// Get the world, view, and projection matrices from the camera and d3d objects.
m_Camera->GetViewMatrix(viewMatrix);
m_D3D->GetWorldMatrix(worldMatrix);
m_D3D->GetProjectionMatrix(projectionMatrix);
// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
m_Model->Render(m_D3D->GetDeviceContext());
// Render the model using the color shader.
result = m_ColorShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix);
if(!result)
{
return false;
}
// Present the rendered scene to the screen.
m_D3D->EndScene();
return true;
}
总结
因此,总而言之,您应该已经学习了顶点和索引缓冲区如何工作的基本知识。您还应该学习顶点和像素着色器的基本知识,以及如何使用HLSL编写它们。最后,你应该理解我们是如何将这些新概念融入到我们的框架中,从而产生一个呈现到屏幕上的绿色三角形。我还想提到的是,我意识到代码相当长,只是为了画一个三角形,它可能都卡在一个main()函数中。不过,我是通过适当的框架来完成的,所以接下来的教程只需要对代码进行很少的修改就可以完成更复杂的图形。
做练习
1。编译并运行教程。确保它在屏幕上画一个绿色三角形。按Escape退出。
2。将三角形的颜色改为红色。
三。将三角形改为正方形。
4。把相机向后移10个单位。
5。更改像素明暗器以将颜色的一半输出为亮。(大提示:将彩色像素阴影中的某些内容乘以0.5f)
(如果要看其他课的中文翻译版,请到我博客目录查找,我会抽时间把后续的课目都翻译出来,这取决于我有空闲时间。)
时间仓促,只是粗略翻译,可能有多处失误,请谅解。朋友如有发现哪里有错误,欢迎指正,联·系w新licheng16886