A Basic Tessellation Demo
把前面几节所讲的triangle和quad tessellation shaders集成到应用程序中并不需要编写特别复杂的C++代码。总的来说,首先创建对应的materials与这两个shaders进行交互,然后创建用于表示单个triangle和quad的vertex buffers,并添加一些成员变量用于保存tessellation factors值。在该示例中,可以使用键盘操作,在quad和triangle tessellation之间进行切换,并修所有的tessellation factors值,可以使这些factors值保持一致或各不相同。绘制一个细分的object与之前示例程序中建立的设计方法基本相同,但是需要指定primitive topology为control point patch list topologies中的某一种。其中,对于triangles使用D3D11_PRIMITIVE_TOPOTOGY_3_CONTROL_POINT_PATCHLIST,
而quads则使用D3D11_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST。
列表21.9中该示例程序中渲染部分的核心代码。
列表21.9 Draw Code for the Basic Tessellation Demo
ID3D11DeviceContext* direct3DDeviceContext = mGame->Direct3DDeviceContext();
direct3DDeviceContext->RSSetState(RasterizerStates::Wireframe);
if (mShowQuadTopology)
{
direct3DDeviceContext->IASetInputLayout(mQuadInputLayout);
direct3DDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST);
UINT stride = mQuadMaterial->VertexSize();
UINT offset = 0;
direct3DDeviceContext->IASetVertexBuffers(0, 1, &mQuadVertexBuffer, &stride, &offset);
mQuadMaterial->WorldViewProjection() << mCamera->ViewMatrix() * mCamera->ProjectionMatrix();
mQuadMaterial->TessellationEdgeFactors() << mTessellationEdgeFactors;
mQuadMaterial->TessellationInsideFactors() << mTessellationInsideFactors;
mQuadPass->Apply(0, direct3DDeviceContext);
direct3DDeviceContext->Draw(4, 0);
}
else
{
direct3DDeviceContext->IASetInputLayout(mTriInputLayout);
direct3DDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST);
UINT stride = mTriMaterial->VertexSize();
UINT offset = 0;
direct3DDeviceContext->IASetVertexBuffers(0, 1, &mTriVertexBuffer, &stride, &offset);
std::vector<float> tessellationEdgeFactors(mTessellationEdgeFactors.begin(), mTessellationEdgeFactors.end() - 1);
mTriMaterial->WorldViewProjection() << mCamera->ViewMatrix() * mCamera->ProjectionMatrix();
mTriMaterial->TessellationEdgeFactors() << tessellationEdgeFactors;
mTriMaterial->TessellationInsideFactor() << mTessellationInsideFactors[0];
mTriPass->Apply(0, direct3DDeviceContext);
direct3DDeviceContext->Draw(3, 0);
}
Displacement Tessellated Vertices
使用表面细分功能主要是为了从low-fidelity(低精度)模型中产生high-fidelity(高精度)的渲染。但是在同一个平面上进行三角形细分或四边形细分并不能提供这种细节的渲染;一个经过细分的平面还是一个平面。要使用该过程达到真正的细分效果需要对新创建的vertices进行适当的移位。这种思想与第9章“Normal Mapping和Displacement Mapping”所讲的,使用一个纹理存储displacement值以及一个缩放因子的方法类似。列表21.10中列出了四边形细分的shader代码,该四边形与xz平面对齐,并根据一个displacement纹理图对vertices的y坐标值进行移位。
列表21.10 The QuadHeightmapTessellation.fx Effect
/************* Resources *************/
static const float4 ColorWheat = { 0.961f, 0.871f, 0.702f, 1.0f };
cbuffer CBufferPerFrame
{
float TessellationEdgeFactors[4];
float TessellationInsideFactors[2];
}
cbuffer CBufferPerObject
{
float4x4 WorldViewProjection;
float4x4 TextureMatrix;
float DisplacementScale;
}
Texture2D Heightmap;
SamplerState TrilinearSampler
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = WRAP;
AddressV = WRAP;
};
/************* Data Structures *************/
struct VS_INPUT
{
float4 ObjectPosition : POSITION;
float2 TextureCoordinate: TEXCOORD;
};
struct VS_OUTPUT
{
float4 ObjectPosition : POSITION;
float2 TextureCoordinate: TEXCOORD;
};
struct HS_CONSTANT_OUTPUT
{
float EdgeFactors[4] : SV_TessFactor;
float InsideFactors[2] : SV_InsideTessFactor;
};
struct HS_OUTPUT
{
float4 ObjectPosition : POSITION;
float2 TextureCoordinate: TEXCOORD;
};
struct DS_OUTPUT
{
float4 Position : SV_Position;
};
/************* Vertex Shader *************/
VS_OUTPUT vertex_shader(VS_INPUT IN)
{
VS_OUTPUT OUT = (VS_OUTPUT)0;
OUT.ObjectPosition = IN.ObjectPosition;
OUT.TextureCoordinate = mul(float4(IN.TextureCoordinate, 0, 1), TextureMatrix).xy;
return OUT;
}
/************* Hull Shaders *************/
HS_CONSTANT_OUTPUT constant_hull_shader(InputPatch<VS_OUTPUT, 4> patch, uint patchID : SV_PrimitiveID)
{
HS_CONSTANT_OUTPUT OUT = (HS_CONSTANT_OUTPUT)0;
[unroll]
for (int i = 0; i < 4; i++)
{
OUT.EdgeFactors[i] = TessellationEdgeFactors[i];
}
OUT.InsideFactors[0] = TessellationInsideFactors[0];
OUT.InsideFactors[1] = TessellationInsideFactors[1];
return OUT;
}
[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(4)]
[patchconstantfunc("constant_hull_shader")]
HS_OUTPUT hull_shader(InputPatch<VS_OUTPUT, 4> patch, uint controlPointID : SV_OutputControlPointID)
{
HS_OUTPUT OUT = (HS_OUTPUT)0;
OUT.ObjectPosition = patch[controlPointID].ObjectPosition;
OUT.TextureCoordinate = patch[controlPointID].TextureCoordinate;
return OUT;
}
/************* Domain Shader *************/
[domain("quad")]
DS_OUTPUT domain_shader(HS_CONSTANT_OUTPUT IN, float2 uv : SV_DomainLocation, const OutputPatch<HS_OUTPUT, 4> patch)
{
DS_OUTPUT OUT;
float4 v0 = lerp(patch[0].ObjectPosition, patch[1].ObjectPosition, uv.x);
float4 v1 = lerp(patch[2].ObjectPosition, patch[3].ObjectPosition, uv.x);
float4 objectPosition = lerp(v0, v1, uv.y);
float2 texCoord0 = lerp(patch[0].TextureCoordinate, patch[1].TextureCoordinate, uv.x);
float2 texCoord1 = lerp(patch[2].TextureCoordinate, patch[3].TextureCoordinate, uv.x);
float2 textureCoordinate = lerp(texCoord0, texCoord1, uv.y);
objectPosition.y = (2 * Heightmap.SampleLevel(TrilinearSampler, textureCoordinate, 0).x - 1) * DisplacementScale;
OUT.Position = mul(float4(objectPosition.xyz, 1.0f), WorldViewProjection);
return OUT;
}
/************* Pixel Shader *************/
float4 pixel_shader(DS_OUTPUT IN) : SV_Target
{
return ColorWheat;
}
/************* Techniques *************/
technique11 main11
{
pass p0
{
SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
SetHullShader(CompileShader(hs_5_0, hull_shader()));
SetDomainShader(CompileShader(ds_5_0, domain_shader()));
SetPixelShader(CompileShader(ps_5_0, pixel_shader()));
}
}
在该shader中有一些非常有趣的部分。首先可以观察到在domain shader中纹理坐标值的计算方法与vertex坐标值的推导方法完全一样。然后需要注意的是,如何采样displacement值,并把该值缩放调整到介于范围[-1, 1]之间,在该范围内灰度值为0.5表示不移位。这样就可以使用displacement纹理图中的暗色值(小于0.5)表示表面的凹陷,而亮色值(大于0.5)表示表面的凸起。接下来还要使用shader变量DisplacementScale对displacement值作进一步调整。
最后仔细分析在vertex shader中是如何对纹理坐标执行变换的。通过这种变换可使texture形成动画效果。例如,在应用程序中使用一个 随着时间变化不断更新的平移矩阵(包括水平和垂直方向的平移)赋值给TextureMatrix变量,可以使细分后的四边形看起来像是波浪起伏的效果。图21.6中显示了quad heightmap tessellation示例的输出结果,其中在屏幕的左下角渲染了displacement纹理图。在本书的配套网站上提供了该示例程序的完整代码。
图21.6 Output of the quad heightmap tessellation demo.
Dynamic Levels of Detail
在实际应用程序中,我们并不需要拖动设置tessellation factors,而是根据表面到camera的距离动态计算tessellation数量。这就是一种动态的LOD系统。
列表21.11列出了该LOD系统中的hull和domain shaders代码。
列表21.11 The Hull and Domain Shaders for a Dynamic Tessellation Effect
/************* Resources *************/
static const float4 ColorWheat = { 0.961f, 0.871f, 0.702f, 1.0f };
cbuffer CBufferPerFrame
{
float TessellationEdgeFactors[3];
float TessellationInsideFactor;
float3 CameraPosition : CAMERAPOSITION;
int MaxTessellationFactor = 64;
float MinTessellationDistance = 2.0f;
float MaxTessellationDistance = 20.0f;
}
cbuffer CBufferPerObject
{
float4x4 WorldViewProjection : WORLDVIEWPROJECTION;
float4x4 World : WORLD;
}
Texture2D ColorTexture;
SamplerState TrilinearSampler
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = WRAP;
AddressV = WRAP;
};
/************* Data Structures *************/
struct VS_INPUT
{
float4 ObjectPosition : POSITION;
float2 TextureCoordinate: TEXCOORD;
};
struct VS_OUTPUT
{
float4 ObjectPosition : POSITION;
float2 TextureCoordinate: TEXCOORD;
};
struct HS_CONSTANT_OUTPUT
{
float EdgeFactors[3] : SV_TessFactor;
float InsideFactor : SV_InsideTessFactor;
};
struct HS_OUTPUT
{
float4 ObjectPosition : POSITION;
float2 TextureCoordinate: TEXCOORD;
};
struct DS_OUTPUT
{
float4 Position : SV_Position;
float2 TextureCoordinate: TEXCOORD;
};
/************* Vertex Shader *************/
VS_OUTPUT vertex_shader(VS_INPUT IN)
{
VS_OUTPUT OUT = (VS_OUTPUT)0;
OUT.ObjectPosition = IN.ObjectPosition;
OUT.TextureCoordinate = IN.TextureCoordinate;
return OUT;
}
/************* Hull Shaders *************/
HS_CONSTANT_OUTPUT constant_hull_shader(InputPatch<VS_OUTPUT, 3> patch, uint patchID : SV_PrimitiveID)
{
HS_CONSTANT_OUTPUT OUT = (HS_CONSTANT_OUTPUT)0;
[unroll]
for (int i = 0; i < 3; i++)
{
OUT.EdgeFactors[i] = TessellationEdgeFactors[i];
}
OUT.InsideFactor = TessellationInsideFactor;
return OUT;
}
[domain("tri")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("constant_hull_shader")]
HS_OUTPUT hull_shader(InputPatch<VS_OUTPUT, 3> patch, uint controlPointID : SV_OutputControlPointID)
{
HS_OUTPUT OUT = (HS_OUTPUT)0;
OUT.ObjectPosition = patch[controlPointID].ObjectPosition;
OUT.TextureCoordinate = patch[controlPointID].TextureCoordinate;
return OUT;
}
HS_CONSTANT_OUTPUT distance_constant_hull_shader(InputPatch<VS_OUTPUT, 3> patch, uint patchID : SV_PrimitiveID)
{
HS_CONSTANT_OUTPUT OUT = (HS_CONSTANT_OUTPUT)0;
// Caclulate the center of the patch
float3 objectCenter = (patch[0].ObjectPosition.xyz + patch[1].ObjectPosition.xyz + patch[2].ObjectPosition.xyz) / 3.0f;
float3 worldCenter = mul(float4(objectCenter, 1.0f), World).xyz;
// Calculate uniform tessellation factor based on distance from the camera
float tessellationFactor = max(min(MaxTessellationFactor, (MaxTessellationDistance - distance(worldCenter, CameraPosition)) / (MaxTessellationDistance - MinTessellationDistance) * MaxTessellationFactor), 1);
[unroll]
for (int i = 0; i < 3; i++)
{
OUT.EdgeFactors[i] = tessellationFactor;
}
OUT.InsideFactor = tessellationFactor;
return OUT;
}
[domain("tri")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("distance_constant_hull_shader")]
HS_OUTPUT distance_hull_shader(InputPatch<VS_OUTPUT, 3> patch, uint controlPointID : SV_OutputControlPointID)
{
HS_OUTPUT OUT = (HS_OUTPUT)0;
OUT.ObjectPosition = patch[controlPointID].ObjectPosition;
OUT.TextureCoordinate = patch[controlPointID].TextureCoordinate;
return OUT;
}
/************* Domain Shader *************/
[domain("tri")]
DS_OUTPUT domain_shader(HS_CONSTANT_OUTPUT IN, float3 uvw : SV_DomainLocation, const OutputPatch<HS_OUTPUT, 3> patch)
{
DS_OUTPUT OUT = (DS_OUTPUT)0;
float3 objectPosition = uvw.x * patch[0].ObjectPosition.xyz + uvw.y * patch[1].ObjectPosition.xyz + uvw.z * patch[2].ObjectPosition.xyz;
float2 textureCoordinate = uvw.x * patch[0].TextureCoordinate + uvw.y * patch[1].TextureCoordinate + uvw.z * patch[2].TextureCoordinate;
OUT.Position = mul(float4(objectPosition, 1.0f), WorldViewProjection);
OUT.TextureCoordinate = textureCoordinate;
return OUT;
}
/************* Pixel Shader *************/
float4 pixel_shader(DS_OUTPUT IN) : SV_Target
{
return ColorTexture.Sample(TrilinearSampler, IN.TextureCoordinate);
}
float4 solid_color_pixel_shader(DS_OUTPUT IN) : SV_Target
{
return ColorWheat;
}
/************* Techniques *************/
technique11 textured_manual_factors
{
pass p0
{
SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
SetHullShader(CompileShader(hs_5_0, hull_shader()));
SetDomainShader(CompileShader(ds_5_0, domain_shader()));
SetPixelShader(CompileShader(ps_5_0, pixel_shader()));
}
}
technique11 solid_color_manual_factors
{
pass p0
{
SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
SetHullShader(CompileShader(hs_5_0, hull_shader()));
SetDomainShader(CompileShader(ds_5_0, domain_shader()));
SetPixelShader(CompileShader(ps_5_0, solid_color_pixel_shader()));
}
}
technique11 solid_color_distance_factors
{
pass p0
{
SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
SetHullShader(CompileShader(hs_5_0, distance_hull_shader()));
SetDomainShader(CompileShader(ds_5_0, domain_shader()));
SetPixelShader(CompileShader(ps_5_0, solid_color_pixel_shader()));
}
}
另外,在constant hull shader中首先计算三角形patch的中心坐标点,然后把该坐标点变换到world space中。接下来,根据表面细分中定义的最大和最小距离(允许执行细分的距离范围)计算一个统一的tessellation factor。当object到camera的距离超过了最大距离,tessellation factor就会被clamp为1(表示不执行细分)。当object到camera的距离小于最小距离,tessellation factor就会被clmap为由应用程序输入的MaxTessellationFactor变量值。当object到camera的距离介于最大和最小距离之间,tesselllation factor值随着距离的增加而减小。图21.7中显示对一个sphere模型执行自动细分的输出结果,从图中可以看出,距离camera越近的表面比距离越远的表面执行更多的细分。
图21.7 Output of the dynamic tessellation demo.
注意
从性能上来讲,当object到camera的距离足够远以至于不会发生细分的情况下,最好是禁用tessellation阶段。要实现这种功能,只需要在CPU应用程序中根据object的bounding volume(用于表示模型物理碰撞范围的包围体)与camera之间的距离,动态切换启用和禁用tessellation的两个shader techniques。
总结
在本章我们主要学习了geometry和tessellation shaders。并实现了一个point sprite系统用于演示geometry shaders,以及多个使用硬件tessellation的示例程序。
Exercises
- Experiment with geometry shaders. Modify the point sprite demo to output different vertex topologies.
- Explore the three tessellation demos on the book’s companion website. For the basic tessellation shader demo (whose output is shown in Figures 21.4 and 21.5), vary the edge and inside tessellation factors uniformly and nonuniformly, and observe the results. For the dynamic level-of-detail tessellation demo, experiment with the max tessellation factor and minimum and maximum tessellation distances, and observe the results. For the heightmap tessellation demo, alter the heightmap and the transformation for animating the UVs, and observe the results.
- Modify the dynamic level-of-detail tessellation shader to use an icosaheron model (included on the companion website). Run the demo and observe how the shape is not made more spherical as tessellation increases. Then modify the associated domain shader to displace the vertices along a unit sphere (to normalize the computed object position), and observe the results.
1、测试geometry shader的使用方法,并修改point sprite示例程序,使其输出不同的vertex topologies。
2、测试本书配套网站上提供的3个tessellation示例程序。在basic tessellation shader示例中(该示例的输出如图21.4和21.5),统一改变patch的边缘和内部细分因子以及分别改变各个因子值,并观察输出结果。对于dynamic level-of-detail tessellation示例,测试使用不同max tessellation factor值,以及tessellation的最大和最小距离值,并观察输出结果。对于heigthmap tessellation示例中,改变heightmap以及用于动态修改UVs的变换矩阵,并观察显示结果。
本章配套学习代码:
Render Engine及全部示例代码
示例所需要的全部Assets资源文件,需要把Assets目录及文件复制到Render Engine的根目录下。