原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十四章:曲面细分阶段

代码工程地址:

https://github.com/jiabaodan/Direct12BookReadingNotes



曲面细分阶段包含渲染管线中的三个阶段,用以细分几何物体,它在顶点着色器和几何着色器之间。使用曲面细分的主要原因:

  1. 基于GPU的LOD;
  2. 物理和动画的优化,可以在低面模型上计算物理效果和动画,然后细分为高面模型用以渲染;
  3. 节省内存(硬盘,RAM,VRAM)。


学习目标

  1. 学习曲面细分使用的patch基元类型;
  2. 学习曲面细分每个阶段的作用,以及他们的输入输出;
  3. 学习通过编写hull和domain着色器来细分几何体;
  4. 学习曲面细分的不同策略,以及曲面细分的优化;
  5. 学习贝塞尔曲线和平面的数学公式,以及如何用曲面细分来实现它。


1 曲面细分基元类型

当我们使用曲面细分渲染,我们不想IA阶段提交三角形列表,我们提交具有许多控制点的patches。D3D支持patches拥有1~32个控制点,并且由下面的基元类型定义:

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,

一个三角形可以被理解为一个具有3个控制点的三角patch((D3D_PRIMITIVE_3_CONTROL_POINT_PATCH),所以你依然可以提交你的三角形网格。四边形可以被提交为(D3D_PRIMITIVE_4_CONTROL_POINT_PATCH)。这些patch最终会被曲面细分阶段细分为三角形。

当传递控制点基元类型到ID3D12GraphicsCommandList::IASetPrimitiveTopology时,设置D3D12_GRAPHICS_PIPELINE_STATE_DESC::PrimitiveTopologyType为D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH:

opaquePsoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH;

1.1 曲面细分和顶点着色器

因为我们提交的是patch的控制点,所以顶点着色器可以正常处理这些控制点(和顶点一样)。



2 HULL着色器

HULL着色器实际上包含2个着色器:常量Hull着色器(Constant Hull Shader)和控制点Hull着色器(Control Point Hull Shader)。


2.1 常量Hull着色器

常量Hull着色器针对每个patch执行,并且负责输出网格的曲面细分因子(tessellation factors);曲面细分因子命令曲面细分阶段对patch细分多少。下面是一个将拥有4个控制点的方块patch均匀细分3次的例子:

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;
}

常量Hull着色器必须输出细分因子,细分因子取决于patch的拓扑结构。

除了细分因子(SV_TessFactor和SV_InsideTessFactor),你还可以输出其他patch的信息,让domain着色器接收并使用。

细分一个方块patch包含两部分:

  1. 四条边的细分因子角色四条边怎么细分;
  2. 2个内部细分因子决定内部如何细分。

细分一个三角形patch同样包含两部分:

  1. 3条边的细分因子;
  2. 1个内部细分因子;

D3D11硬件支持的最大细分因子是64。如果所有细分因子都是0,那么当前patch就拒绝进入后面的阶段。它可以帮助我们基于patch在背面消除和视锥体裁切上实现优化。

  1. 如果这个patch不在视锥体内,可以让他拒绝进入后面的阶段;
  2. 如果这个patch是背面,可以让它拒绝进入后面的阶段;

具体裁切多少主要基于需求,不要做不需要的裁切来浪费性能。下面是一些常用的度量单位来决定裁切多少:

  1. 于相机的距离;
  2. 屏幕的覆盖率;
  3. 三角形的方向和定位;
  4. 粗糙度。

[Story10]给出了下面的优化建议:

  1. 如果细分因子是1(也就是不细分),走一遍细分阶段流程是浪费GPU开销;
  2. 因为是基于GPU实现的,不要细分一个覆盖小于8个像素的这种太小的三角形;
  3. 批量调用具有细分的绘制调用(频繁打开和关闭曲面细分非常浪费性能)。

2.1 控制点Hull着色器

控制点Hull着色器输入一系列控制点,输出一系列控制点。它每次控制点输出的时候调用一次。一个Hull着色器是改变平面的表现,比如一个将普通的三角形(拥有3个控制点)修改为立方贝塞尔三角形patch(拥有10个控制点)。这种策略称之为N-patches方案或者PN三角形方案([Vlachos01])。对于我们的第一个Demo,我们只是简单的pass-through着色器,只传递控制点,不修改(驱动可以检测和优化pass-through着色器([Bilodeau10b])):

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;
}

Hull着色器通过InputPatch输入参数传进所有控制点。系统值SV_OutputControlPointID给出控制点的索引。输入控制点的数量不需要匹配输出控制点的数量。

控制点Hull着色器介绍了一些属性:

  1. domain:patch类型:tri,quad或者isoline;
  2. partitioning:指定细分的模式:

    a、integer:新顶点添加/删除值根据整形细分因子,小数部分会无视;

    b、Fractional((fractional_even/fractional_odd)):新顶点添加/删除值根据整形细分因子,但是通过小数部分滑动。
  3. outputtopology:细分后的三角形的缠绕顺序,triangle_cw(顺时针)、triangle_ccw(逆时针)、line(针对线段的细分);
  4. outputcontrolpoints:Hull着色器执行的次数,每次输出一个控制点。SV_OutputControlPointID给出输出点在Hull着色器中的索引。
  5. patchconstantfunc:常量Hull着色器函数的名称;
  6. maxtessfactor:提示驱动指定你的着色器使用的最大细分因子。这个可以让硬件有一个潜在的优化(让硬件知道最大细分因子)。D3D11硬件支持的最大细分因子是64.


3 曲面细分阶段

作为程序员,我们无法控制曲面细分阶段,它是由硬件完成的,基于常量Hull着色器程序输出的细分因子对Patch进行细分,下面是一些基于不同因子细分的例子:


3.1 方块patch的细分例子:


3.2 三角形patch的细分例子:



4 DOMAIN着色器

曲面细分阶段输出了所有新的顶点和三角形。DOMAIN着色器对每个新创建的顶点进行调用。当曲面细分开启的时候,顶点着色器运行与每个控制点,Hull着色器是每个细分patch的顶点着色器。Domain着色器中对每个细分完成的patch变换到其次裁切空间。

对于方块patch,Domain着色器输入细分因子(常量Hull着色器的输出),细分顶点位置(u, v)的坐标参数,所有从控制点hull着色器输出的控制点。Domain并不给你每个顶点的实际位置,而是patch空间的(u, v),顶点的位置通过双线性差值得到:

struct DomainOut
{
float4 PosH : SV_POSITION;
}; // The domain shader is called for every vertex created by the tessellator.
// It is like the vertex shader after tessellation.
[domain("quad")]
DomainOut DS(PatchTess patchTess,
float2 uv : SV_DomainLocation,
const OutputPatch<HullOut, 4> quad)
{
DomainOut dout; // Bilinear interpolation.
float3 v1 = lerp(quad[0].PosL, quad[1].PosL, uv.x);
float3 v2 = lerp(quad[2].PosL, quad[3].PosL, uv.x);
float3 p = lerp(v1, v2, uv.y); float4 posW = mul(float4(p, 1.0f), gWorld);
dout.PosH = mul(posW, gViewProj);
return dout;
}

三角形patch类似,只是坐标从(u, v)变为三维重心(u, v, w)坐标。修改为重心坐标系原因是贝塞尔三角形patches通过重心坐标系定义的。



5 细分一个平面方块

作为本章中的一个Demo,我们提交一个方块patch,然后根据和摄像机的距离进行细分,然后根据数学公式对顶点进行偏移(类似之前“hills”Demo)。

顶点缓冲保存4个控制点,创建如下:

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[geo->Name] = std::move(geo);
}

渲染物体创建如下:

void BasicTessellationApp::BuildRenderItems()
{
auto quadPatchRitem = std::make_unique<RenderItem>(); quadPatchRitem->World = MathHelper::Identity4x4();
quadPatchRitem->TexTransform = MathHelper::Identity4x4();
quadPatchRitem->ObjCBIndex = 0;
quadPatchRitem->Mat = mMaterials["whiteMat"].get();
quadPatchRitem->Geo = mGeometries["quadpatchGeo"].get();
quadPatchRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST;
quadPatchRitem->IndexCount = quadPatchRitem->Geo->DrawArgs["quadpatch"].IndexCount;
quadPatchRitem->StartIndexLocation = quadPatchRitem->Geo->DrawArgs["quadpatch"].StartIndexLocation;
quadPatchRitem->BaseVertexLocation = quadPatchRitem->Geo->DrawArgs["quadpatch"].BaseVertexLocation; mRitemLayer[(int)RenderLayer::Opaque].push_back(quadPatchRitem.mAllRitems.push_back(std::move(quadPatchRitem));
}

Hull着色器和前面介绍的基本一致,不同的地方在于,根据和摄像机的距离决定细分多少;并且它是一个pass-through着色器:

struct VertexIn
{
float3 PosL : POSITION;
};
struct VertexOut
{
float3 PosL : POSITION;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
vout.PosL = vin.PosL;
return vout;
}
struct PatchTess
{
float EdgeTess[4] : SV_TessFactor;
float InsideTess[2] : SV_InsideTessFactor;
}; PatchTess ConstantHS(InputPatch<VertexOut, 4> patch, uint patchID : SV_PrimitiveID)
{
PatchTess pt;
float3 centerL = 0.25f*(patch[0].PosL +
patch[1].PosL +
patch[2].PosL +
patch[3].PosL); float3 centerW = mul(float4(centerL, 1.0f), gWorld).xyz;
float d = distance(centerW, gEyePosW); // Tessellate the patch based on distance from the eye such that
// the tessellation is 0 if d >= d1 and 64 if d <= d0. The interval
// [d0, d1] defines the range we tessellate in.
const float d0 = 20.0f;
const float d1 = 100.0f;
float tess = 64.0f*saturate( (d1-d)/(d1-d0) ); // Uniformly tessellate the patch.
pt.EdgeTess[0] = tess;
pt.EdgeTess[1] = tess;
pt.EdgeTess[2] = tess;
pt.EdgeTess[3] = tess;
pt.InsideTess[0] = tess;
pt.InsideTess[1] = tess;
return pt;
} 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;
}

最后在domain着色器中对顶点的y坐标进行偏移:

struct DomainOut
{
float4 PosH : SV_POSITION;
}; // The domain shader is called for every vertex created by the tessellator.
// It is like the vertex shader after tessellation.
[domain("quad")]
DomainOut DS(PatchTess patchTess,
float2 uv : SV_DomainLocation,
const OutputPatch<HullOut, 4> quad)
{
DomainOut dout; // Bilinear interpolation.
float3 v1 = lerp(quad[0].PosL, quad[1].PosL, uv.x);
float3 v2 = lerp(quad[2].PosL, quad[3].PosL, uv.x);
float3 p = lerp(v1, v2, uv.y); // Displacement mapping
p.y = 0.3f*( p.z*sin(p.x) + p.x*cos(p.z) );
float4 posW = mul(float4(p, 1.0f), gWorld);
dout.PosH = mul(posW, gViewProj); return dout;
} float4 PS(DomainOut pin) : SV_Target
{
return float4(1.0f, 1.0f, 1.0f, 1.0f);
}


6 立方贝塞尔方块PATCHES

本节我们通过描述立方贝塞尔方块Patches来展示如何通过大量控制点构成一个表面。


6.1 贝塞尔曲线

有三个不共线的控制点p0, p1, 和p2定义一个贝塞尔曲线,那么如果要求曲线上的点p(t)的位置,首先对p0、p1和p1、p2根据t进行线性插值:



然后p(t)点就可以通过基于t的线性插值得到:



将上面两组方程结合起来,就得到贝塞尔曲线方程:



类似的方式,如果是4个控制点(p0, p1, p2,和p3):



第一次插值:



第二次插值:



第三次插值:



将上面方程结合起来,最终公式为:



通常情况下都只用到3个点,因为已经足够光滑,和控制表面。

针对N维的贝塞尔曲线方程是Bernstein basis functions,可以定义为:

对于三维曲线Bernstein basis functions是:

相比于之前4个控制点的最终方程,我们可以将贝塞尔曲线方程写为:

然后求出三次Bernstein basis functions的偏导数:



三次贝塞尔曲线的偏导数就是:



偏导数方程对求表面的切线方向很有用。


6.2 三次贝塞尔平面

对于一个具有4x4个控制点的patch,我可以将每一行定义为一个具有4个控制点的三次贝塞尔曲线;那么第i行的贝塞尔曲线为:



如果我们在u0的位置求这些贝塞尔曲线的值,那么我们会得到从列方向上的4个点。我们可以使用这4个点定义另一条贝塞尔曲线:



如果我们让U正常变化,我们就会扫出一组类似的贝塞尔曲线,组成一个贝塞尔平面。



它的偏导数用以求切线和法线向量:


6.3 三次贝塞尔平面求解代码

本节给出三次贝塞尔平面求解代码,为了方便,先给出完整公式:



代码直接映射到上面给出的公式:

float4 BernsteinBasis(float t)
{
float invT = 1.0f - t; return float4( invT * invT * invT,
3.0f * t * invT * invT,
3.0f * t * t * invT,
t * t * t );
} float3 CubicBezierSum(const OutputPatch<HullOut, 16> bezpatch, float4 basisU, float4 basisV)
{
float3 sum = float3(0.0f, 0.0f, 0.0f);
sum = basisV.x * (basisU.x*bezpatch[0].PosL + basisU.y*bezpatch[1].PosL + basisU.z*bezpatch[2].PosL + basisU.w*bezpatch[3].PosL );
sum += basisV.y * (basisU.x*bezpatch[4].PosL + basisU.y*bezpatch[5].PosL + basisU.z*bezpatch[6].PosL + basisU.w*bezpatch[7].PosL );
sum += basisV.z * (basisU.x*bezpatch[8].PosL + basisU.y*bezpatch[9].PosL + basisU.z*bezpatch[10].PosL + basisU.w*bezpatch[11].PosL);
sum += basisV.w * (basisU.x*bezpatch[12].PosL + basisU.y*bezpatch[13].PosL + basisU.z*bezpatch[14].PosL + basisU.w*bezpatch[15].PosL); return sum;
} float4 dBernsteinBasis(float t)
{
float invT = 1.0f - t; return float4( -3 * invT * invT,
3 * invT * invT - 6 * t * invT,
6 * t * invT - 3 * t * t,
3 * t * t );
}

6.4 定义Patch几何

我们的顶点缓冲保存16个控制点:

void BezierPatchApp::BuildQuadPatchGeometry()
{
std::array<XMFLOAT3,16> vertices =
{
// Row 0
XMFLOAT3(-10.0f, -10.0f, +15.0f),
XMFLOAT3(-5.0f, 0.0f, +15.0f),
XMFLOAT3(+5.0f, 0.0f, +15.0f),
XMFLOAT3(+10.0f, 0.0f, +15.0f),
// Row 1
XMFLOAT3(-15.0f, 0.0f, +5.0f),
XMFLOAT3(-5.0f, 0.0f, +5.0f),
748
XMFLOAT3(+5.0f, 20.0f, +5.0f),
XMFLOAT3(+15.0f, 0.0f, +5.0f),
// Row 2
XMFLOAT3(-15.0f, 0.0f, -5.0f),
XMFLOAT3(-5.0f, 0.0f, -5.0f),
XMFLOAT3(+5.0f, 0.0f, -5.0f),
XMFLOAT3(+15.0f, 0.0f, -5.0f),
// Row 3
XMFLOAT3(-10.0f, 10.0f, -15.0f),
XMFLOAT3(-5.0f, 0.0f, -15.0f),
XMFLOAT3(+5.0f, 0.0f, -15.0f),
XMFLOAT3(+25.0f, 10.0f, -15.0f)
}; std::array<std::int16_t, 16> indices =
{
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15
}; 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 = (UINT)indices.size();
quadSubmesh.StartIndexLocation = 0;
quadSubmesh.BaseVertexLocation = 0; geo->DrawArgs["quadpatch"] = quadSubmesh;
mGeometries[geo->Name] = std::move(geo);
}

我们的渲染物体创建和定义如下:

void BezierPatchApp::BuildRenderItems()
{
auto quadPatchRitem = std::make_unique<RenderItem>();
quadPatchRitem->World = MathHelper::Identity4x4();
quadPatchRitem->TexTransform = MathHelper::Identity4x4();
quadPatchRitem->ObjCBIndex = 0;
quadPatchRitem->Mat = mMaterials["whiteMat"].get();
quadPatchRitem->Geo = mGeometries["quadpatchGeo"].get();
quadPatchRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_16_CONTROL_POINT_PATCHLIST;
quadPatchRitem->IndexCount = quadPatchRitem->Geo->DrawArgs["quadpatch"].IndexCount;
quadPatchRitem->StartIndexLocation = quadPatchRitem->Geo->DrawArgs["quadpatch"].StartIndexLocation;
quadPatchRitem->BaseVertexLocation = quadPatchRitem->Geo->DrawArgs["quadpatch"].BaseVertexLocation;
mRitemLayer[(int)RenderLayer::Opaque].push_back(quadPatchRitem.mAllRitems.push_back(std::move(quadPatchRitem));
}


7 总结

  1. 曲面细分阶段是渲染流水线中的一个可选的阶段,它包含Hull着色器,曲面细分,Domain着色器;曲面细分完全由硬件完成,其他两个阶段是可编程的;
  2. 曲面细分可以优化内存,也可以减少物理和动画运算(在低模上计算),可以实现LOD(以前只能放到CPU);
  3. 提交曲面细分控制点要使用新的基元类型;单个基元D3D12支持1到32个控制点,由枚举D3D_PRIMITIVE_1_CONTROL_POINT_PATCH到D3D_PRIMITIVE_32_CONTROL_POINT_PATCH定义;
  4. 启用曲面细分后,顶点着色器输入控制点,对每个控制点进行传统的动画和物理计算;Hull着色器包含常量Hull着色器(Constant Hull Shader)和控制点Hull着色器(Control Point Hull Shader)。常量Hull着色器针对每个Patch执行,输出度每个Patch细分多少的细分因子(tessellation factors)(也可以添加其他可选数据)。控制点Hull着色器在每次控制点输出的时候调用一次,它修改了表面的表达方式。比如一个有3个控制点的三角形,可以输出为有10个控制点的贝塞尔三角面;
  5. Domain着色器对每个细分生成的顶点调用一次,在这里对每个顶点投射到其次裁切空间;
  6. 如果不需要细分物体,就不要开启细分阶段,因为会有性能开销。避免细分太多覆盖小于8像素的三角形。将需要细分的绘制放到一起,不要在同一帧中频繁开启和关闭细分。Hull着色器中使用背面消除和视锥体消除屏蔽看不到的Patch;
  7. 用参数方程定义的贝塞尔曲线和平面,可以用来表示平滑的曲线或表面。它们通过控制点在确定形状。为了让我们可以直接绘制平滑的曲线和表面,贝塞尔表面被很多流行的硬件细分算法使用,比如PN Triangles 和 Catmull-Clark approximations。


8 练习

本章内容我目前用不到,练习暂时不做。

Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十四章:曲面细分阶段的更多相关文章

  1. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十二章:四元数(QUATERNIONS)

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十二章:四元数(QUATERNIONS) 学习目标 回顾复数,以及 ...

  2. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十九章:法线贴图

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十九章:法线贴图 学习目标 理解为什么需要法线贴图: 学习法线贴图如 ...

  3. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十六章:实例化和截头锥体裁切

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十六章:实例化和截头锥体裁切 代码工程地址: https://git ...

  4. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引 代码工程地址: https://g ...

  5. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十二章:几何着色器(The Geometry Shader)

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十二章:几何着色器(The Geometry Shader) 代码工 ...

  6. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十八章:立方体贴图

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十八章:立方体贴图 代码工程地址: https://github.c ...

  7. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- Direct12优化

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- Direct12优化 第一章:向量代数 1.向量计算的时候,使用XMV ...

  8. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 全书总结

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 全书总结 本系列文章中可能有很多翻译有问题或者错误的地方:并且有些章节 ...

  9. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十三章:角色动画

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十三章:角色动画 学习目标 熟悉蒙皮动画的术语: 学习网格层级变换 ...

随机推荐

  1. Idea SSH框架整合基础代码

    第一步 pom.xml注入jar包依赖 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=" ...

  2. HDFS 冗余数据保存

  3. BZOJ2982: combination Lucas模板

    2982: combination Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 734  Solved: 437[Submit][Status][Di ...

  4. PyCharm软件代码配色和字体设置

    配置效果图: 1.字体设置: 2.tab键设置: 3.代码颜色配置: 注释颜色为: 类名称: 函数: 关键字: 关键字参数: 函数参数: 字符串:

  5. 【python之路41】web框架

    一.web框架介绍 一共有两种web框架 1.既包含socket又能逻辑处理 tornado框架 2.只能处理逻辑 Django bottle flask  二.web框架的本质 众所周知,对于所有的 ...

  6. TZ_05_Spring_基于AOP的xml配置

    1.分析    1>首先我们有一个Service需要增强 将Service增加一个日志(Logger)          2>写了一个日志的通知并且它可以对Service进行日志增强   ...

  7. androidstudio实现增量更新步骤

    本文demo和参考例子参考-传送  门:http://blog.csdn.net/duguang77/article/details/17676797 一.增量更新优点:节省客户端和服务器端流量 增量 ...

  8. Spring这棵大树

    目前项目中用的框架大多数都是Spring,一直想找时间对这个框架做一个全面的了解. 今天先以导图的形式画出来Spring都包含的主要模块,即使有些模块目前用不上,但说不定在将来的应用场景时想到Spri ...

  9. UML类图解释

    那个动物矩形框,它就代表一个类(Class).类图分三层,第一层显示类的名称,如果是抽象类,则就用斜体显示.第二层是类的特性,通常是字段和属性.第三层是类的操作,通常是方法或行为.注意前面的符号,“+ ...

  10. flexget安装

    首先新建一个文件夹给flexget下载种子 Flexgetdownloads 注意:如果先安装flexget再创建文件夹则需要手动去分配权限 然后通过勾选我要试用beta版本,来获取flexget 找 ...