渲染管线(Pipeline)
image.png- Input-Assember Stage 输入装配阶段
输入:顶点缓存、顶点索引缓存
输出:三角形
该阶段为指定三角形顶点位置的阶段。
渲染三角形步骤
- 创建顶点
比如:
Vertex vertices[] =
{
// float | unsigned char
// x y r g b a
{ 0.0f,0.5f, 255, 0, 0, 0 },
{ 0.5f,-0.5f, 0, 255, 0, 0 },
{ -0.5f,-0.5f, 0, 0, 255, 0 }
};
- 创建顶点缓存
D3D11_BUFFER_DESC
描述顶点缓存,D3D11_SUBRESOURCE_DATA
存放真实顶点数据。
wrl::ComPtr<ID3D11Buffer> pVertexBuffer;
D3D11_BUFFER_DESC bd = {};
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.Usage = D3D11_USAGE_DEFAULT;
bd.CPUAccessFlags = 0u;
bd.MiscFlags = 0u;
bd.ByteWidth = sizeof( vertices );
bd.StructureByteStride = sizeof( Vertex );
D3D11_SUBRESOURCE_DATA sd = {};
sd.pSysMem = vertices; // 为缓冲区指定初始化数据
GFX_THROW_INFO( pDevice->CreateBuffer( &bd,&sd,&pVertexBuffer ) );
- 绑定顶点缓存到管线
使用ID3D11DeviceContext::IASetVertexBuffers
绑定到输入装配器阶段(Input-Assember Stage
)。
const UINT stride = sizeof( Vertex );
const UINT offset = 0u;
pContext->IASetVertexBuffers( 0u,1u,pVertexBuffer.GetAddressOf(),&stride,&offset );
- 创建顶点索引(如有必要)
- 创建顶点索引缓存(如有必要)
- 绑定顶点索引缓存到管线(如有必要)
使用ID3D11DeviceContext::IASetIndexBuffer
绑定到输入装配器阶段(Input-Assember Stage
)。 - 设置图元拓扑
图元拓扑表示告诉D3D,输入的顶点如何相连。
使用ID3D11DeviceContext::IASetPrimitiveTopology
绑定顶点的图元拓扑到输入装配器阶段(Input-Assember Stage
)。 - 编写着色器(顶点、像素)
渲染时一定需要顶点着色器。见下述的[创建着色器]。 - 编译着色器(顶点、像素)
可以选择使用D3DCompileFromFile
接口在运行时编译着色器,或者在VS中在编译阶段编译着色器(该阶段编译的着色器需使用D3DReadFileToBlob
,直接生成着色器对象)。 - 创建着色器对象(顶点、像素)
使用CreateVertexShader
/CreatePixelShader
创建顶点和像素着色器。 - 绑定着色器对象到管线
使用VSSetShader
绑定顶点着色器对象到顶点着色器管线(Vertex-Shader Stage)。
使用PSSetShader
绑定像素着色器对象到像素着色器管线(Pixel-Shader Stage)。 - 创建顶点输入布局
创建顶点输入布局是为了要让着色器知道按照何种规则读取输入的顶点数据。
使用ID3D11Device::CreateInputLayout
创建输入布局。 - 绑定顶点输入布局到管线
使用ID3D11DeviceContext::IASetInputLayout
绑定顶点输入布局到到输入装配器阶段(Input-Assember Stage
)。 - 设置视窗大小
使用RSSetViewports
设置视窗大小。通常我们会把3D场景渲染到整个后台缓冲区上,但也可以把3D场景渲染到后台缓冲区的一个子矩形区域中。 - 绑定渲染目标
像素着色器需要指定一个渲染目标视图,否则像素着色器会不知道输出到哪里。渲染管线中像素着色器之后的Output Merger
会输出到渲染目标上。使用OMSetRenderTargets
绑定渲染目标到渲染管线Output Merger
阶段。
绑定渲染目标前,需要先创建好渲染目标。使用交换链的后台缓冲创建渲染目标,如下:
wrl::ComPtr<ID3D11Resource> pBackBuffer;
pSwap->GetBuffer( 0,__uuidof(ID3D11Resource),&pBackBuffer);
pDevice->CreateRenderTargetView( pBackBuffer.Get(),nullptr,&pTarget);
- Draw
调用Draw
或者DrawIndexed
渲染到后台缓冲。 - 呈现
调用IDXGISwapChain::Present
将后台缓冲呈现到前台。
着色器
定义着色器结构
// 定义顶点着色器的输入结构体
struct VinputType
{
float4 position : POSITION; // 顶点位置
float4 color : COLOR; // 顶点颜色
};
// 定义顶点着色器的输出结构体/像素着色器的输入结构
struct PinputType
{
float4 position : SV_POSITION; // 位置。SV_POSITION像素着色器内部变量
float4 color : COLOR; // 颜色
};
编写顶点着色器
当前顶点着色器不做任何转换,直接将从输入装配阶段过来的顶点缓存数据送给像素着色器。
顶点着色器的输入是二维顶点数据,输出是一个四维向量。
只传入顶点位置时,着色器可以写作如下:
// 自定义的输入语义 | 系统的输出语义(顶点位置)
float4 main(float2 pos : Postion) : SV_Position
{
return float4(pos.x, pos.y, 0.0f, 1.0f);
}
当着色器加上颜色输入时,可以写作如下,输入顶点位置、颜色,输出则需要输出位置和颜色,此时需要定义一个输出结构,作为输出。
struct VSout
{
// | 语义
float4 pos : SV_Position;
float3 color : Color; // 传递给像素着色器
}
VSout main(float2 pos : Postion, float3 color : Color)
{
VSout out;
out.pos = float4(pos.x, pos.y, 0.0f, 1.0f);
out.color = color;
return out;
}
// 入口函数
PinputType ColorVsMain(VinputType input)
{
PinputType output;
// w分量不从CPU传入,而默认设置为1.0f
input.position.w = 1.0f;
output.position = input.position;
output.color = input.color;
return output;
}
编写像素着色器
像素着色器在每个像素上运行。光栅化决定了像素的位置,像素着色器仅仅决定每个像素的颜色,它通常不需要像素在屏幕上的位置来完成它的工作。
像素着色器返回颜色值,颜色值为一个float4
类型,包含了r
g
b
a
四分量。
输出语义SV_TARGET
表示渲染目标。
// | 输出语义
float4 ColorPsMain(PinputType input) : SV_TARGET
{
// return float4(1.0f, 1.0f, 1.0f, 1.0f); // 常量颜色
return input.color;
}
着色器环境配置
VS上创建一个shader文件夹,里面存放各类型的着色器文件。
在VS中添加一个着色器文件:
image.png
指定项目类型为HLSL编译器:
image.png
配置入口函数、着色器类型:
image.png
配置输出目录和名称:
image.png
编译着色器
方法1:运行期间编译着色器代码,生成字节码,使用D3DCompileFromFile
接口编译原始着色器文件。其他方法参考:DirectX11--HLSL编译着色器的三种方法
HRESULT D3DCompileFromFile(
LPCWSTR pFileName, // [In]要编译的.hlsl文件
CONST D3D_SHADER_MACRO* pDefines, // [In_Opt]忽略
ID3DInclude* pInclude, // [In_Opt]如何应对#include宏
LPCSTR pEntrypoint, // [In]入口函数名
LPCSTR pTarget, // [In]使用的着色器模型
UINT Flags1, // [In]D3DCOMPILE系列宏
UINT Flags2, // [In]D3DCOMPILE_FLAGS2系列宏
ID3DBlob** ppCode, // [Out]获得着色器的二进制块
ID3DBlob** ppErrorMsgs); // [Out]可能会获得错误信息的二进制块
注意如果在着色器中添加了引用头文件,则第三个参数ID3DInclude* pInclude
需要指定为:D3D_COMPILE_STANDARD_FILE_INCLUDE
例如:
ID3D10Blob* vertexShaderBuffer;
HRESULT hr = D3DCompileFromFile(vsFilename, NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE,
"TextureVsMain", // 着色器入口函数
"vs_5_0",
D3D10_SHADER_ENABLE_STRICTNESS,
0,
&vertexShaderBuffer, &errorMessage);
创建着色器对象
一旦顶点着色器和像素着色器代码成功编译到缓冲区中,我们就可以使用这些缓冲区来创建着色器对象本身。
使用CreateVertexShader
/CreatePixelShader
来创建定点着色器和像素着色器。
顶点、顶点索引
创建顶点缓冲、索引缓冲
创建缓冲有如下三步:
1.填写一个D3D11_BUFFER_DESC结构体,描述我们所要创建的缓冲区。
2.填写一个D3D11_SUBRESOURCE_DATA结构体,为缓冲区指定初始化数据。
3.调用ID3D11Device::CreateBuffer方法来创建缓冲区。
typedef struct D3D11_BUFFER_DESC{
UINT ByteWidth; // 缓冲区大小,单位字节
D3D11_USAGE Usage;
UINT BindFlags;
UINT CPUAccessFlags;
UINT MiscFlags;
UINT StructureByteStride;
} D3D11_BUFFER_DESC;
缓冲描述结构,具体含义参考:6.2 顶点缓冲
绑定顶点缓冲、索引缓冲到渲染管线
unsigned int stride = sizeof(VertexType);
unsigned int offset = 0;
// 绑定顶点缓存到渲染管线的输入装配阶段(input assembler)
deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);
// 绑定索引缓存到渲染管线的输入装配阶段(input assembler)
deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);
创建顶点输入布局
顶点数据描述结构体:
typedef struct D3D11_INPUT_ELEMENT_DESC {
LPCSTR SemanticName;
UINT SemanticIndex;
DXGI_FORMAT Format;
UINT InputSlot;
UINT AlignedByteOffset
D3D11_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} D3D11_INPUT_ELEMENT_DESC;
-
SemanticName:一个与元素相关的字符串。它可以是任何有效的语义名。语义(semantic)用于将顶点结构体中的元素映射为顶点着色器参数,如下图。
顶点结构体中的每个元素分别由D3D11_INPUT_ELEMENT_DESC数组中的对应元素描述。语义名和语义索引提供了一种将顶点元素映射为顶点着色器参数的方法。 - SemanticIndex:附加在语义上的索引值。上图说明了使用该索引的原因;举例来说,当顶点结构体包含多组纹理坐标时,我们不是添加一个新的语义名,而是在语义名的后面加上一个索引值。在着色器代码中没有指定索引的语义默认索引为0,例如,在上图中的POSITION相当于POSITION0。
- Format:一个用于指定元素格式的DXGI_FORMAT枚举类型成员。
DXGI_FORMAT_R32G32B32_FLOAT
表示传入给着色器3个4字节的数据。
更多参数说明可以参考:6.1 顶点和顶点布局
// 创建顶点数据布局
const D3D11_INPUT_ELEMENT_DESC inputElementDescs[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, // 位置分量 x/y/z
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 8u, D3D11_INPUT_PER_VERTEX_DATA, 0 }, // 颜色分量 r/g/b/a
};
device->CreateInputLayout(
inputElementDescs,
sizeof(inputElementDescs)/sizeof(D3D11_INPUT_ELEMENT_DESC),
m_vertexShaderBuffer->GetBufferPointer(),
m_vertexShaderBuffer->GetBufferSize(),
&m_inputLayout);
绑定渲染目标到渲染管线
ID3D11DeviceContext::OMSetRenderTargets()
* 绑定一个或更多渲染目标和深度模板缓冲区到管线的输出合并器阶段
* @param NumViews 将要绑定的渲染目标的数量
* @param ppRenderTargetViews 将要绑定的渲染目标视图数组中的第一个元素的指针
* @param pDepthStencilView 将要绑定到管线的深度/模板视图
为后台缓冲区和深度缓冲区创建视图后,就可以将这些视图绑定到渲染管线的输出合并器阶段(output merger stage),使些资源成为管线的渲染目标和深度/模板缓冲区。
网友评论