- 注意!本文是在下几年前入门期间所写(young and naive),其中许多表述可能不正确,为防止误导,请各位读者仔细鉴别。
硬件曲面细分
曲面细分阶段
曲面细分包含三个阶段,夹在VS和GS之间,如图
如果要用硬件曲面细分,那么顶点着色器输出就不再是顶点,而是control point的patch,一个patch包含多个control point,VS输出patch给HS用,这就要一种新的拓扑类型,如下
D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST = 33,
D3D_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST = 34,
D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST = 35,
D3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST = 36,
.
.
.
D3D_PRIMITIVE_TOPOLOGY_31_CONTROL_POINT_PATCHLIST = 63,
D3D_PRIMITIVE_TOPOLOGY_32_CONTROL_POINT_PATCHLIST = 64,
此外,如果要传primitive type到ID3D12GraphicsCommandList::IASetPrimitiveTopology,那么PSO也必须设置D3D12_GRAPHICS_PIPELINE_STATE_DESC::PrimitiveTopologyType为D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH。
Hull Shader
Hull Shader分Constant Hull Shader和Control Point Hull Shader。下面先介绍Constant Hull Shader,一个例子如下
struct PatchTess
{
float EdgeTess[4] : SV_TessFactor;
float InsideTess[2] : SV_InsideTessFactor;
// Additional info you want associated per patch.
};
PatchTess ConstantHS(InputPatch<VertexOut, 4> patch,
uint patchID : SV_PrimitiveID)
{
PatchTess pt;
// Uniformly tessellate the patch 3 times.
pt.EdgeTess[0] = 3; // Left edge
pt.EdgeTess[1] = 3; // Top edge
pt.EdgeTess[2] = 3; // Right edge
pt.EdgeTess[3] = 3; // Bottom edge
pt.InsideTess[0] = 3; // u-axis (columns)
pt.InsideTess[1] = 3; // v-axis (rows)
return pt;
}
首先要输出的有两个系统值,SV_TessFactor和SV_InsideTessFactor,前者是边的细分等级,后者是面片中心的细分等级,前者有几条边就要输出几个,后者要输出两个,分别对应的是u和v方向。
然后输入ConstantHS的InputPatch是一个模板类,对应的是顶点着色器的输出,如果这里要用曲面细分的话,就不能在VS中乘以世界和摄像机投影矩阵,而是要留到细分结束后再变换(如果有几何着色器的话就留到几何着色器阶段再变换)。然后这里可以用SV_PrimitiveID获取Primitive的id。
这里可以把细分等级和离摄像机的远近、屏幕覆盖范围、朝向、粗糙度等挂钩,而不是写一个定的值,可以起到优化的效果。
接下来介绍Control Point Hull Shader,这个部分是每个control point都调用一次,因此和顶点着色器类似,只不过对象是control point,在这个阶段我们可以改变曲面的表达形式,比如把输入的三角面(三个control point)变成由包含十个control point的patch控制的贝塞尔曲线输出,等等。
一个例子如下
struct HullOut
{
float3 PosL : POSITION;
};
[domain(“quad”)]
[partitioning(“integer”)]
[outputtopology(“triangle_cw”)]
[outputcontrolpoints(4)]
[patchconstantfunc(“ConstantHS”)]
[maxtessfactor(64.0f)]
HullOut HS(InputPatch<VertexOut, 4> p,
uint i : SV_OutputControlPointID,
uint patchId : SV_PrimitiveID)
{
HullOut hout;
hout.PosL = p[i].PosL;
return hout;
}
Control Point HS要定义不少属性,其中domain是patch type,可选的有tri,quad或者isoline。
partitioining是细分模式,integer的细分等级会突变,而fractional_odd或者fractional_even的细分等级会渐变,顶点会逐渐移动直到消失,而不会突然pop出来或者消失。
outputtopology输出的三角面的winding order,有triangle_cw,triangle_ccw,line这三个选项
outputcontrolpoints是输出顶点的数量,也就是hs的执行次数,可以用SV_OutputControlPointID获取当前Control Point的ID。
patchconstantfunc则是constantHS的名字
maxtessfactor是最大细分数量,dx11最高支持到64,这里可以手动设置得更低。
The Tessellation Stage
HullShader结束之后就是细分阶段,这部分由硬件完成,效果如下
Domain Shader
域着色器的输入是细分好了的曲面,但是不会是直接给定位置,而是只给uv,我们要做的是根据uv来算出顶点的位置,然后如果没有几何着色器的话,我们需要在这个阶段把顶点变换到屏幕坐标里。
这里说的给定uv是针对四边面,如果用的是三个control point的patch,那么这里给定的是质心坐标系下的uvw。
Quad曲面细分Demo
下面我们实现一个能根据离摄像机距离细分一个四边面的demo,并列出其中关键部分的代码:
编译shader
void BasicTessellationApp::BuildShadersAndInputLayout()
{
mShaders["tessVS"] = d3dUtil::CompileShader(L"Shaders\\Tessellation.hlsl", nullptr, "VS", "vs_5_0");
mShaders["tessHS"] = d3dUtil::CompileShader(L"Shaders\\Tessellation.hlsl", nullptr, "HS", "hs_5_0");
mShaders["tessDS"] = d3dUtil::CompileShader(L"Shaders\\Tessellation.hlsl", nullptr, "DS", "ds_5_0");
mShaders["tessPS"] = d3dUtil::CompileShader(L"Shaders\\Tessellation.hlsl", nullptr, "PS", "ps_5_0");
mInputLayout =
{
{
"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
定义一个quad
void BasicTessellationApp::BuildQuadPatchGeometry()
{
std::array<XMFLOAT3,4> vertices =
{
XMFLOAT3(-10.0f, 0.0f, +10.0f),
XMFLOAT3(+10.0f, 0.0f, +10.0f),
XMFLOAT3(-10.0f, 0.0f, -10.0f),
XMFLOAT3(+10.0f, 0.0f, -10.0f)
};
std::array<std::int16_t, 4> indices = {
0, 1, 2, 3 };
const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);
const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);
auto geo = std::make_unique<MeshGeometry>();
geo->Name = "quadpatchGeo";
ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU));
CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);
ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);
geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader);
geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader);
geo->VertexByteStride = sizeof(XMFLOAT3);
geo->VertexBufferByteSize = vbByteSize;
geo->IndexFormat = DXGI_FORMAT_R16_UINT;
geo->IndexBufferByteSize = ibByteSize;
SubmeshGeometry quadSubmesh;
quadSubmesh.IndexCount = 4;
quadSubmesh.StartIndexLocation = 0;
quadSubmesh.BaseVertexLocation = 0;
geo->DrawArgs["quadpatch"] = quadSubmesh;
mGeometries