A Basic Tessellation Demo

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

  1. Experiment with geometry shaders. Modify the point sprite demo to output different vertex topologies.
  2. 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.
  3. 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的根目錄下。

發佈了5 篇原創文章 · 獲贊 27 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章