硬件曲面细分
曲面细分阶段
曲面细分包含三个阶段,夹在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[geo->Name] = std::move(geo);
}
创建PSO要设置拓扑类型
void BasicTessellationApp::BuildPSOs()
{
D3D12_GRAPHICS_PIPELINE_STATE_DESC opaquePsoDesc;
//
// PSO for opaque objects.
//
ZeroMemory(&opaquePsoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
opaquePsoDesc.InputLayout = { mInputLayout.data(), (UINT)mInputLayout.size() };
opaquePsoDesc.pRootSignature = mRootSignature.Get();
opaquePsoDesc.VS =
{
reinterpret_cast<BYTE*>(mShaders["tessVS"]->GetBufferPointer()),
mShaders["tessVS"]->GetBufferSize()
};
opaquePsoDesc.HS =
{
reinterpret_cast<BYTE*>(mShaders["tessHS"]->GetBufferPointer()),
mShaders["tessHS"]->GetBufferSize()
};
opaquePsoDesc.DS =
{
reinterpret_cast<BYTE*>(mShaders["tessDS"]->GetBufferPointer()),
mShaders["tessDS"]->GetBufferSize()
};
opaquePsoDesc.PS =
{
reinterpret_cast<BYTE*>(mShaders["tessPS"]->GetBufferPointer()),
mShaders["tessPS"]->GetBufferSize()
};
opaquePsoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
opaquePsoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_WIREFRAME;
opaquePsoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
opaquePsoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
opaquePsoDesc.SampleMask = UINT_MAX;
opaquePsoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH;
opaquePsoDesc.NumRenderTargets = 1;
opaquePsoDesc.RTVFormats[0] = mBackBufferFormat;
opaquePsoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
opaquePsoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
opaquePsoDesc.DSVFormat = mDepthStencilFormat;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&opaquePsoDesc, IID_PPV_ARGS(&mPSOs["opaque"])));
}
然后render item里也要设置下拓扑类型为D3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST。
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.get());
mAllRitems.push_back(std::move(quadPatchRitem));
}
然后shader部分,在hull shader里根据摄像机距离算出细分等级,然后在域着色器里根据uv算出顶点位置,这里我们用的是之前demo里用过的山的曲面函数。
// Include structures and functions for lighting.
#include "LightingUtil.hlsl"
Texture2D gDiffuseMap : register(t0);
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);
// Constant data that varies per frame.
cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
float4x4 gTexTransform;
};
// Constant data that varies per material.
cbuffer cbPass : register(b1)
{
float4x4 gView;
float4x4 gInvView;
float4x4 gProj;
float4x4 gInvProj;
float4x4 gViewProj;
float4x4 gInvViewProj;
float3 gEyePosW;
float cbPerObjectPad1;
float2 gRenderTargetSize;
float2 gInvRenderTargetSize;
float gNearZ;
float gFarZ;
float gTotalTime;
float gDeltaTime;
float4 gAmbientLight;
float4 gFogColor;
float gFogStart;
float gFogRange;
float2 cbPerObjectPad2;
// Indices [0, NUM_DIR_LIGHTS) are directional lights;
// indices [NUM_DIR_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHTS) are point lights;
// indices [NUM_DIR_LIGHTS+NUM_POINT_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHT+NUM_SPOT_LIGHTS)
// are spot lights for a maximum of MaxLights per object.
Light gLights[MaxLights];
};
cbuffer cbMaterial : register(b2)
{
float4 gDiffuseAlbedo;
float3 gFresnelR0;
float gRoughness;
float4x4 gMatTransform;
};
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;
}
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);
}
最后效果如图
贝塞尔曲线Demo
很多应用里面我们都用贝塞尔曲线来插值,得到一个更平滑的面,这里先大致介绍一下贝塞尔曲线,然后做一个16个control point的贝塞尔曲线插值demo。
贝塞尔曲线是怎么来的呢,首先假如我们有三个点,p0,p1和p2,我们先在p0和p1,p1和p2之间线性插出两个点
书上把p的上标叫做degree。
然后得到这两个点之后再用同样的t线性插出一个点p,这个点的曲线就是三个control point的贝塞尔曲线。
接下来考虑四个点的(Cubic Bezier Curve),插值过程如图
得到曲线方程
然后在介绍一下Bernstein基函数:
那么三次贝塞尔曲线可以写成
对t的导数则是
那么假如有16个4x4的控制点,首先我们横着插4条贝塞尔曲线,然后竖着用这四个贝塞尔曲线的同一个u值的四个点作为控制点(固定u)再插一条关于v的贝塞尔曲线出来,那么就得到了一个关于(u,v)的三次贝塞尔曲面。
求切线则是求对u和v的偏导
然后法线根据切线叉乘出来。
然后是代码实现,首先我们有
把上式写成代码:
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 );
}
接下来给出实现贝塞尔曲面细分的代码的关键部分。
创建控制点
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),
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);
}
其他部分和上面的quad细分的demo差不多,接下来是完整的shader部分代码:
// Include structures and functions for lighting.
#include "LightingUtil.hlsl"
Texture2D gDiffuseMap : register(t0);
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);
// Constant data that varies per frame.
cbuffer cbPerObject : register(b0)
{
float4x4 gWorld;
float4x4 gTexTransform;
};
// Constant data that varies per material.
cbuffer cbPass : register(b1)
{
float4x4 gView;
float4x4 gInvView;
float4x4 gProj;
float4x4 gInvProj;
float4x4 gViewProj;
float4x4 gInvViewProj;
float3 gEyePosW;
float cbPerObjectPad1;
float2 gRenderTargetSize;
float2 gInvRenderTargetSize;
float gNearZ;
float gFarZ;
float gTotalTime;
float gDeltaTime;
float4 gAmbientLight;
float4 gFogColor;
float gFogStart;
float gFogRange;
float2 cbPerObjectPad2;
// Indices [0, NUM_DIR_LIGHTS) are directional lights;
// indices [NUM_DIR_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHTS) are point lights;
// indices [NUM_DIR_LIGHTS+NUM_POINT_LIGHTS, NUM_DIR_LIGHTS+NUM_POINT_LIGHT+NUM_SPOT_LIGHTS)
// are spot lights for a maximum of MaxLights per object.
Light gLights[MaxLights];
};
cbuffer cbMaterial : register(b2)
{
float4 gDiffuseAlbedo;
float3 gFresnelR0;
float gRoughness;
float4x4 gMatTransform;
};
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, 16> patch, uint patchID : SV_PrimitiveID)
{
PatchTess pt;
// Uniform tessellation for this demo.
pt.EdgeTess[0] = 25;
pt.EdgeTess[1] = 25;
pt.EdgeTess[2] = 25;
pt.EdgeTess[3] = 25;
pt.InsideTess[0] = 25;
pt.InsideTess[1] = 25;
return pt;
}
struct HullOut
{
float3 PosL : POSITION;
};
// This Hull Shader part is commonly used for a coordinate basis change,
// for example changing from a quad to a Bezier bi-cubic.
[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(16)]
[patchconstantfunc("ConstantHS")]
[maxtessfactor(64.0f)]
HullOut HS(InputPatch<VertexOut, 16> p,
uint i : SV_OutputControlPointID,
uint patchId : SV_PrimitiveID)
{
HullOut hout;
hout.PosL = p[i].PosL;
return hout;
}
struct DomainOut
{
float4 PosH : SV_POSITION;
};
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 );
}
// 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, 16> bezPatch)
{
DomainOut dout;
float4 basisU = BernsteinBasis(uv.x);
float4 basisV = BernsteinBasis(uv.y);
float3 p = CubicBezierSum(bezPatch, basisU, basisV);
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);
}
最终结果如图