博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十四章:曲面细分阶段...
阅读量:5078 次
发布时间:2019-06-12

本文共 15022 字,大约阅读时间需要 50 分钟。

原文:

代码工程地址:



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

  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
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
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
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
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
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
(); 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
(); 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
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
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
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
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
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
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
(); 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
(); 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 练习

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

posted on
2019-05-05 23:37 阅读(
...) 评论(
...)

转载于:https://www.cnblogs.com/lonelyxmas/p/10817178.html

你可能感兴趣的文章
JS实现背景图按时切换或者每次更新
查看>>
字符编码笔记:ASCII,Unicode和UTF-8
查看>>
C Linux read write function extension
查看>>
指针、数组和指针算术
查看>>
[Node.js] ECMAScript 6中的生成器及koa小析
查看>>
自定义图表技术解析
查看>>
c语言打开一个程序
查看>>
图像显示特效
查看>>
apollo配置中心初探
查看>>
elasticsearch配置
查看>>
jquery attr与prop的区别与联系
查看>>
swift基本运算符
查看>>
VMware Ubuntu安装
查看>>
斐波那契数列
查看>>
Web容量规划的艺术-要点
查看>>
c#高级编程第六版读书笔记二(委托)
查看>>
Linux打包压缩与安装卸载
查看>>
C++回调函数和静态成员函数。。。。
查看>>
study1
查看>>
python socket 模拟tcp通讯
查看>>