曲面细分包含三个阶段,夹在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。
下面我们实现一个能根据离摄像机距离细分一个四边面的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); }
最后效果如图
很多应用里面我们都用贝塞尔曲线来插值,得到一个更平滑的面,这里先大致介绍一下贝塞尔曲线,然后做一个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); }
最终结果如图