目录:http://blog.csdn.net/tdl1001/article/details/7888182
这篇教程将介绍怎样在DirectX11中写顶点着色器和像素着色器。同时也会介绍怎样使用顶点缓存和索引缓存。这是你需要理解并在显示三维画面时使用的最基本的概念。
顶点着色器
第一个概念是顶点缓存。我们通过一个球体的三维模型来讲解:
实际上,这个球体是由几百个三角形组成的:
组成球体的每个三角形有三个点,称为顶点。为了渲染球体模型,我们需要将球体上的所有顶点保存在一个特殊的数组中,这个数组叫顶点缓存。一旦球体上所有的点都存到了顶点缓存里,我们就可以把这个缓存发送给显卡来显示这个模型了
序列缓存
与顶点缓存相关的还有序列缓存。序列缓存用于记录每个顶点在顶点缓存中的位置,GPU通过序列缓存在顶点缓存里快速找到某个特定的顶点。书本的目录可以让我们快速找到希望阅读的章节,索引缓存的作用与此类似。DirectX SDK的文档提到使用索引缓存同样可以提高在显存的快速缓存中的命中率,所以如果有好的性能一定要使用它们。
顶点着色器
顶点着色器是一段主要用来在三维空间中转换顶点缓冲里顶点而写的小程序。顶点着色器还可以执行顶点法线计算等其它运算。当顶点需要被处理时,GPU会为每个顶点调用一次顶点着色程序,例如显示一个拥有5000个多边形的模型会导致每帧调用15000次顶点着色程序。因此如果你锁定显示程序的帧率为60,那么为了显示一个有5000个三角形的程序将导致每秒调用900000次顶点着色器。现在你应该明白写一个高效的顶点着色器是多重要了吧。
像素着色器
像素着色器是用于给画出的多边形填充颜色而写的小程序。每当绘制一个可见像素时,GPU都会调用它们。像素着色器负责完成配色、贴图、照明或其它你想用于多边形面的效果。由于像素着色器也会被调用很多次,也必须被编写的很高效。
HLSL
HLSL 是用于在DirectX11中编写顶点着色程序和像素着色程序的语言。语法与C语言很相似,有一些预定于类型。HLSL程序文件包含全局变量、类型定义、顶点着色程序、像素着色程序、几何着色程序。作为第一个HLSL教程,我们将编写一个很简单的HLSL程序。
更新的框架
根据本教程的内容更新了框架。我们在GraphicsClass下添加了三个新的Class,分别是 CameraClass, ModelClass, and ColorShaderClass. CameraClass 会处理之前提到的视点矩阵。它保存了摄像机在场景中的位置,当绘制场景需要知道我们是从哪里观察这个场景时,CameraClass会将摄像机位置传递给着色器。ModelClass保存三维模型的几何结构,本章为了简单起见仅适用一个三角形。最后一个ColorShaderClass负责调用HLSL着色程序将模型绘制到屏幕。
我们先看一下程序的代码
Color.vs
这些是我们第一个着色程序。着色程序是执行模型渲染的一个小程序。这些程序用HLSL编写并保存在名叫color.vs和color.ps的源文件中。我现在把它们和.cpp、.h文件一起放在项目中。为了便于学习理解,这些着色程序仅仅绘制带颜色的三角形,现在我们先看顶点着色程序的代码:
// Filename: color.vs
在着色程序中首先出现的是全局变量。这些变量可以在外部的C++程序中被修改。你可以使用很多类型的变量例如int或float,然后在外部程序修改以便着色程序使用。通常你会把大多数类型的全局变量放在一个叫cbuffer的缓冲区对象类型中,即便只有一个变量。 由于显卡存储缓存区对象的方式,正确的组织这些缓冲区对于着色器程序的高效执行很有帮助。这里有三个matrix变量,由于我会在每帧同时更新它们,我把它们放在同一个缓冲区。
/
// GLOBALS //
/
cbuffer MatrixBuffer
{
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
};
与C语言类似我们可以创建自定义类型。为了程序更容易编写和可读,HLSL拥有多种数据类型,例如float4。本例中我们使用了拥有xyzw的向量类型和rgba的颜色类型。POSITION, COLOR, 和SV_POSITOIN是用于告诉GPU变量用途的语义词。由于顶点着色器和像素着色器的语义不一样,虽然结构体里的变量一样,但我还是要创建两个结构体。顶点着色器可识别POSITION,像素着色器可识别SV_POSITION,它们还都识别COLOR。如果你需要使用多个同一个类型的变量,需要在语义词后添加数字例如:COLOR0, COLOR1, 以此类推。
//
// TYPEDEFS //
//
struct VertexInputType
{
float4 position : POSITION;
float4 color : COLOR;
};
struct PixelInputType
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
当GPU处理传送给它的顶点缓存里的数据时,它会调用顶点着色器。处理顶点缓存中的每个顶点时都会调用我命名为ColorVertesShader的顶点着色程序。输入给顶点着色器的顶点缓存中的数据格式必须与着色器源文件中的数据结构(这里是VertexInputType)相匹配。顶点着色器的输出会被发送给像素着色器。这边输出类型在上面定义的是PixelInputType。
也许你注意到了顶点着色器的输出变量与像素着色器的输入类型相同。它会将输入顶点的位置分别乘以世界矩阵、视口矩阵和投影矩阵。这些操作会根据我们的视角将顶点放在合适的位置并最终呈现在屏幕上。最后输入的颜色会被复制到输出变量中,然后输出变量被当作像素着色器的输入变量。同时注意因为我们只能从位置得到xyz,所以我们将输入位置的w值设置成1否则它会是未定义的值
// 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类型的值。这段程序非常简单,它直接用输入参数中的color的颜色作为像素的颜色。还记得像素着色器以顶点着色器的输出值作为输入值吗
// 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类封装了3D模型的网格数据。现在我们会通过设置数据来构造一个三角形。为了能够被渲染,我们会创建顶点缓存和索引缓存
// Filename: modelclass.h
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_
//
// INCLUDES //
//
#include <d3d11.h>
#include <d3dx10math.h>
// Class name: ModelClass
class ModelClass
{
private:
这是vertex类型的定义,在ModelClass中会用它作为Vertex buffer里数据的类型。注意这边的类型定义必须与后续的ColorShaderClass相匹配。
struct VertexType
{
D3DXVECTOR3 position;
D3DXVECTOR4 color;
};
public:
ModelClass();
ModelClass(const ModelClass&);
~ModelClass();
下面的功能负责初始化和清理模型的Vertex buffer和Index buffer。Render函数将模型的网格数据传输到显卡以便后续Color shader渲染。
bool Initialize(ID3D11Device*);
void Shutdown();
void Render(ID3D11DeviceContext*);
int GetIndexCount();
private:
bool InitializeBuffers(ID3D11Device*);
void ShutdownBuffers();
void RenderBuffers(ID3D11DeviceContext*);
下面的私有变量是vertex buffer和index buffer和两个记录它们大小的int。注意它们都使用ID3D11Buffer类型,并在创建时通过buffer description来显式指明用法
private:
ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
int m_vertexCount, m_indexCount;
};
#endif
Modelclass.cpp
// Filename: modelclass.cpp
#include "modelclass.h"
构造函数设置buffer指针为0
ModelClass::ModelClass()
{
m_vertexBuffer = 0;
m_indexBuffer = 0;
}
ModelClass::ModelClass(const ModelClass& other)
{
}
ModelClass::~ModelClass()
{
}
初始化函数调用专门初始化buffer的函数。
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函数调用清理buffer的函数
void ModelClass::Shutdown()
{
// Release the vertex and index buffers.
ShutdownBuffers();
return;
}
Render函数在 GraphicsClass::Render函数中被调用。这个函数调用Renderbuffers函数将vertex buffer和index buffer发送到渲染管线,然后colorShader可以渲染出模型。
void ModelClass::Render(ID3D11DeviceContext* deviceContext)
{
// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
RenderBuffers(deviceContext);
return;
}
GetIndexCount函数返回序列数据数量。colorShader需要这些信息来渲染模型。
int ModelClass::GetIndexCount()
{
return m_indexCount;
}
InitializeBuffers函数负责创建vertex buffer和index buffer。通常我们从文件中读取模型数据然后创建buffer,但由于我们只创建一个简单的三角形所以手工设置顶点的位置。
bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
VertexType* vertices;
unsigned long* indices;
D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
D3D11_SUBRESOURCE_DATA vertexData, indexData;
HRESULT result;
首先创建两个临时的数组来保存vertex和index数据,后续使用它们创建最终的buffer。
// 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的顺序是非常重要的。作为vertex的一部分,颜色也在这里设置,我都设置为绿色。
// 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.
数组填充后我们可以使用它们创建vertex buffer和index buffer。以相同方式创建了两个buffer。首先填充了一个buffer的描述结构体,在描述体里ByteWidth(buffer的大小)和BindFlags(buffer的类型)必须要正确,填充好描述体后,需要将vertex或index数组赋值给子资源指针。填充好描述体并设置好子资源指针后,可以通过D3D设备调用CreateBuffer函数来得到创建的buffer的指针。
// Set up the description of the static vertex buffer.
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;
}
创建好vertex buffer和index buffer后,数据就已经并拷贝到buffer中了,我们可以删除用不到的数组了。
// Release the arrays now that the vertex and index buffers have been created and loaded.
delete [] vertices;
vertices = 0;
delete [] indices;
indices = 0;
return true;
}
ShutdownBuffers函数清理了在InitializeBuffers函数中创建的buffer。
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;
}
RenderBuffers函数在Render函数中被调用。这个函数的功能是将Vertex buffer和index buffer作为GPU中input assembler的可用源。一旦GPU有一个可用的vertex buffer,它就可以使用着色器(shader)来渲染buffer。这个函数也指明将buffer作为哪种类型的图元来渲染,图元有三角形、线条、三角扇等。我们在这里设置好buffer后通过IASetPrimitiveTopology函数将图元设置为三角形。
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 着色程序
// 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的类型定义,在vertex shader中会被用到。这里的类型定义必须与vertex shader里的完全一样,否则数据在渲染时无法正确匹配。
struct MatrixBufferType
{
D3DXMATRIX world;
D3DXMATRIX view;
D3DXMATRIX projection;
};
public:
ColorShaderClass();
ColorShaderClass(const ColorShaderClass&);
~ColorShaderClass();
下面的函数负责shader的初始化和清理。render函数设置shader的参数并绘制模型
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"
同样在构造函数中仅将私有指针变量设置为0
ColorShaderClass::ColorShaderClass()
{
m_vertexShader = 0;
m_pixelShader = 0;
m_layout = 0;
m_matrixBuffer = 0;
}
ColorShaderClass::ColorShaderClass(const ColorShaderClass& other)
{
}
ColorShaderClass::~ColorShaderClass()
{
}
Initialize函数会调用初始化shaders的initialization函数。我们以HLSL shader的文件名作为参数。这里我们使用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函数负责关闭 shader.
void ColorShaderClass::Shutdown()
{
// Shutdown the vertex and pixel shaders as well as the related objects.
ShutdownShader();
return;
}
Render函数首先利用SetShaderParameters设置shader内部使用的参数变量。然后调用RenderShader函数利用HLSL shader绘制绿色的三角形。
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。这个函数负责真正的shader文件加载并做一些处理使可以被DirectX和GPU使用。你同时也会看到数据内存分布的设置、vertex buffer的数据在GPU的渲染管线中怎样被解释。color.vs中定义的数据内存分布必须与modelclass.h文件里的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;
现在我们要将shader程序编译后放入buffer中。传入shader文件名、shader版本(DirectX 11中是5.0)和存放编译结果的buffer指针。如果编译失败,表示错误的一个字符串会被放在errorMessage中,我们后续可以输出这个字符串。如果失败了同时没有错误消息字符串,说明找不到文件,这边会跳出一个报错的对话框。
// 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;
}
编译完成后,我们可以用那些buffer创建shader对象了,我们后面需要通过这个shader对象控制vertex shader和pixel shader。
// 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;
}
下一步创建被shader处理的vertex数据的内存布局。因为这个shader使用positoin和color,需要在layout中创建它们并指明各自的大小。semantic是第一个需要设置的字符串,shader用它来确定这个元素的用法。我们用POSITION表示位置,用COLOR表示颜色。布局另一个重要的参数是数据格式,对于位置向量,我们使用DXGI_FORMAT_R32G32B32_FLOAT,对于颜色我们使用DXGI_FORMAT_R32G32B32A32_FLOAT。最后需要你注意的是对齐偏移量AlignedByteOffset,AlignedByteOffset表示这个元素的数据在buffer的什么地方。在这个布局中我们指定前12个字节是位置,紧跟着的16个字节是颜色。AlignedByteOffset指出每个元素从哪开始。你可以使用D3D11_APPEND_ALIGNED_ELEMENT代替明确的值,这时DX会自动计算布局。由于这里不需要其它参数,都采用默认值。
// Now setup the layout of the data that goes into the shader.
// This setup needs to match the VertexType stucture in the ModelClass and in the shader.
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设备接口创建布局了。布局创建后,vertex shader buffer和pixel shader buffer不再需要,就可以释放了。
// 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;
最后还要创建constant buffer才能用shader。之前看到在vertex shader里我们只用了一个constant buffer,所以这里我们也只需要创建一个就可以传数据给shader了。由于我们每帧都要更新buffer的值,所以在usage里我们设置为动态dynamic。绑定标识bind flags指出这个buffer是constant buffer。 CPU存取标识需要和usage相匹配,所以设定为D3D11_CPU_WRITE。设置好描述体后,我们可以创建一个constant buffer对象,然后就可以通过SetShaderParameters函数来设置shader里面的变量了。
// Setup the description of the dynamic matrix constant buffer that is in the vertex shader.
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输出在编译vertex shader和pixel shander时产生的错误消息。
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 函数用来简化设置shader里变量的操作。这里用到的matrices是在GraphicsClass创建的,然后在Render函数里通过调用这个函数将他们发送给Vertex shader。
bool ColorShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix,
D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix)
{
HRESULT result;
D3D11_MAPPED_SUBRESOURCE mappedResource;
MatrixBufferType* dataPtr;
unsigned int bufferNumber;
一定要在发送给shader前转置这些矩阵,这是DirectX11的要求。
// Transpose the matrices to prepare them for the shader.
D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);
锁定m_matrixBuffer,设置新的matrices,然后解锁。
// 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);
现在更新HLSL vertex shader里面的matrix buffer。
// 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 在此之前调用来确保所有渲染用到的参数被正确设置。
程序里第一步设置input layout来激活input assembler,设置使GPU知道vertex buffer内数据的格式。第二步设置渲染vertex buffer的vertex shader和pixel shader。一旦shader都设置完毕,我们就可以调用D3D device context的DrawIndexed函数来渲染三角面。调用这个函数会绘制一个绿色的三角形。
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 shader,知道怎么设置vertex 和index buffer,知道怎么通过ColorShaderClass调用shader来绘制保存在buffer里的图形。唯一没有提到的是设置绘制物体的视角点。我们需要一个camera class来告诉DirectX11我们是从哪里、以什么角度来观察场景。camera class会记录相机的位置和方向,并通过它们计算生成一个view matrix,这个view matrix可以传递给HLSL shader用于渲染。
// 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 的头文件非常简单,后续会使用其中的四个函数。SetPosition和SetRotation用来设置相机的位置和方向。Render会基于位置和方向创建view matrix。最后GetViewMatrix是用来从camera对象获取这个view matrix以便可以用于渲染。
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()
{
}
SetPosition 和 SetRotation 函数用来设置相机的位置和方向。
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);
}
Render 函数用相机位置和方向来创建并更新view matrix。首先我们设置上向量、位置、方向等变量。然后我们在世界原点分别按相机的X、Y、Z轴旋转相机。然后在三维空间中将相机移动到指定的位置。有了正确的position、LookAt和up向量后,我们可以使用D3DXMatrixLookAtLH函数来创建可以表示相机旋转和移动的view matrix。
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函数中创建了view matrix后,就可以通过调用GetViewMatrix函数来提供view matrix以便调用update函数。view matrix是HLSL vertex shader中使用的三个重要matrix的其中一个。
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和colorshader的对象设置为空。
GraphicsClass::GraphicsClass() { m_D3D = 0; m_Camera = 0; m_Model = 0; m_ColorShader = 0; }
Initialize函数也被更新了,它现在要创建并初始化三个新的对象。
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也添加了释放私有成员变量的功能。
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; }
Frame函数并未修改,保存原样。
bool GraphicsClass::Frame()
{
bool result;
// Render the graphics scene.
result = Render();
if(!result)
{
return false;
}
return true;
}
Render函数改变最大,它首先仍旧清空图像,这里希望清理成黑色。然后它调用camera的Render函数来创建并得到一个基于相机默认设定位置的view matrix,同样也从D3DClass的对象得到world matrix和projection matrix。然后我们调用ModelClass::Render函数将绿三角的顶点数据发送到渲染管线。顶点数据设置好后,我们就可以通知colorshader来根据顶点信息和三个matrix绘制每个顶点。绿色三角便被绘制到后缓存。场景绘制结束后,我们调用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; }
小结
你应该学习到vertex 和 index buffer怎样工作的基础知识了。还有vertex shader和pixel shader及如何用HLSL写它们。最后你也来理解了怎样将那些新的概念融入到我们的框架中来制造一个绿三角并把它绘制到窗口上。我也希望你注意到绘制一个三角形所需要的代码并不是太长以至于完全可以全部写在一个main函数里,我仍旧以框架的方式是因为这样后续我们需要使用更复杂的绘制功能时能尽可能的少改动现有的代码。