原文: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. LINUX查询用户命令

    W 可显示开机多久,当前登录的所有用户,平均负载 Who 显示当前登录的所有用户 Last 显示每个用户最后的登录时间 Lastlog 显示每个用户最后的登录时间

  2. 修改代码150万行!与 Blink 合并后的 Apache Flink 1.9.0 究竟有哪些重大变更?

    8月22日,Apache Flink 1.9.0 正式发布,早在今年1月,阿里便宣布将内部过去几年打磨的大数据处理引擎Blink进行开源并向 Apache Flink 贡献代码.当前 Flink 1. ...

  3. BZOJ 1099 树网的核

    题面 解题思路 搞了三个多小时.... noip时的数据很水,直接暴力n^3过. 我们考虑优化,首先可以贪心,我们要在直径上选肯定越插长越好,所以n^2其实就可以解决.但这还不够,根据直径的最长性,我 ...

  4. clientHeight、offsetHeight 区别 笔记

    一张图 说明全部 clientHeight和clientWidth用于描述元素内尺寸,是指元素内容+内边距大小,不包括边框(低版本IE下实际包括).外边距.滚动条部分 offsetHeight和off ...

  5. spring springmvc 展示图片,静态资源的处理

    jsp中显示一张照片 <img alt="静态图片" src="static/目录.png"> 然后在springmvc的配置中加上 <!-- ...

  6. IE9没有内置鼠标手势,还要自己写

    写了个IE插件,然后获取鼠标,信息, 模拟了鼠标手势,在虚拟机里面测试,完全好使,但是现在又不敢在Win7上用了. 愁死了... 为了实现一个鼠标手势. 写的那破玩意,竟然50多K.....太大了.. ...

  7. oracle 监听配置文件路径

    app\Administrator\product\11.2.0\dbhome_1\NETWORK\ADMIN listener.ora tnsnames.ora 配置监听直接在 tnsnames.o ...

  8. JS Ajax跨域访问

    js ajax跨域访问报"No 'Access-Control-Allow-Origin' header is present on the requested resource 如果请求的 ...

  9. 集训队日常训练20180518-DIV2

    A.3232 n个物品,换取要花积分,问刚好花完积分能换最大多少价值的物品. 多重背包. #include <bits/stdc++.h> using namespace std; ]; ...

  10. js实现自由落体

    实现自由落体运动需要理解的几个简单属性: clientHeight:浏览器客户端整体高度 offsetHeight:对象(比如div)的高度 offsetTop:对象离客户端最顶端的距离 <!d ...