Vulkan_曲面細分(Tessellation Shader)

曲面細分

本部分我們主要來看一下圖形管線中的可選功能:曲面細分。
在這裏插入圖片描述
曲面細分靠近管線的最前端,緊挨着頂點着色器之後,它是以圖元片(patch)作爲輸入:每一個都是一個用頂點表示的控制點集合,並把片段分解成更小、更簡單的片元,比如點線三角形,從而以正常的方式在管線餘下的階段中渲染。

曲面細分是一個管線中可靈活配置的固有功能模塊,並且在它的一前一後有兩個着色器階段:表面細分控制着色器(tessellation control shader)和表面細分評估着色器(tessellation evaluation shader)。

一、vulkan實現

本場景僅是加載一個簡單的模型具體步驟不在贅述,基礎的頂點和片元着色器非常簡單如下:
頂點着色器

#version 450

layout (location = 0) in vec3 inPos;

void main(void)
{
	gl_Position = vec4(inPos.xyz, 1.0);
}

片元着色器

#version 450

layout (location = 0) out vec4 outFragColor;

void main()
{
	outFragColor.rgb =  color.rgb * 1.5;
}

在創建管線的時候注意使用的是線框模式:

VkPipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo {};
pipelineRasterizationStateCreateInfo.polygonMode = VK_POLYGON_MODE_LINE;

此外還需注意的是在創建圖形管線的時候將結構體VkPipelineTessellationStateCreateInfo 填充如下:

VkPipelineTessellationStateCreateInfo pipelineTessellationStateCreateInfo {};
pipelineTessellationStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
pipelineTessellationStateCreateInfo.patchControlPoints = 3;

其中patchControlPoints會影響曲面細分狀態。
最後就是將基礎的管線和曲面細分着色器加載並傳入管線:

// Load shaders
std::array<VkPipelineShaderStageCreateInfo, 4> shaderStages;

shaderStages[0] = loadShader("shaders/tessellation/base.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
shaderStages[1] = loadShader("shaders/tessellation/base.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
shaderStages[2] = loadShader("shaders/tessellation/pntriangles.tesc.spv", VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT);
shaderStages[3] = loadShader("shaders/tessellation/pntriangles.tese.spv", VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT);

接下來我們來具體詳述着色器,在此之前我們先來介紹下曲面細分中用到經典PN Triangles算法。

二、PN Triangles算法

PN triangle是一個特殊的貝塞爾曲面,它的表示形式爲:
在這裏插入圖片描述
u,v, w是重心座標,bxyz就是控制點,其中u+v+w=1,控制點的位置如下,看以看出來,b003, b030,b300就是三角形的三個頂點控制點,根據這三個控制點位置和法向,我們就可以計算出其它控制點的位置

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
PN triangles的法向通過下面的方法計算得到
在這裏插入圖片描述
在這裏插入圖片描述
從上述的計算原理我們可以看出,PN triangle的控制點信息是依據輸入三角形的頂點位置信息和法線信息計算求得,而它的質心座標則是通過細分着色器來進行插值並輸出。

三、曲面細分控制着色器(tessellation control shader)

表面細分控制着色器(tessellation control shader),它負責處理圖元的控制點,設置每個圖元片的某些參數,然後將控制權交給固定功能細分模塊。該模塊獲取到圖元后,會把圖元分解成基本的點線三角形,最終把生成的頂點數據送入評估着色器。

#version 450

// PN數據
struct PnPatch
{
 float b210;
 float b120;
 float b021;
 float b012;
 float b102;
 float b201;
 float b111;
 float n110;
 float n011;
 float n101;
};

// 細分等級可控
layout (binding = 0) uniform UBO 
{
	float tessLevel;
} ubo; 

//設置三角形輸出
layout(vertices=3) out;

layout(location = 0) in vec3 inNormal[];
layout(location = 1) in vec2 inUV[];

//location對應上三角形輸出
layout(location = 0) out vec3 outNormal[3];
layout(location = 3) out vec2 outUV[3];
layout(location = 6) out PnPatch outPatch[3];

float wij(int i, int j)
{
	return dot(gl_in[j].gl_Position.xyz - gl_in[i].gl_Position.xyz, inNormal[i]);
}

float vij(int i, int j)
{
	vec3 Pj_minus_Pi = gl_in[j].gl_Position.xyz
					- gl_in[i].gl_Position.xyz;
	vec3 Ni_plus_Nj  = inNormal[i]+inNormal[j];
	return 2.0*dot(Pj_minus_Pi, Ni_plus_Nj)/dot(Pj_minus_Pi, Pj_minus_Pi);
}

void main()
{
	// 獲取數據
	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
	outNormal[gl_InvocationID] = inNormal[gl_InvocationID];
	outUV[gl_InvocationID] = inUV[gl_InvocationID];

	// 初始化 
	float P0 = gl_in[0].gl_Position[gl_InvocationID];
	float P1 = gl_in[1].gl_Position[gl_InvocationID];
	float P2 = gl_in[2].gl_Position[gl_InvocationID];
	float N0 = inNormal[0][gl_InvocationID];
	float N1 = inNormal[1][gl_InvocationID];
	float N2 = inNormal[2][gl_InvocationID];

	// 計算控制點
	outPatch[gl_InvocationID].b210 = (2.0*P0 + P1 - wij(0,1)*N0)/3.0;
	outPatch[gl_InvocationID].b120 = (2.0*P1 + P0 - wij(1,0)*N1)/3.0;
	outPatch[gl_InvocationID].b021 = (2.0*P1 + P2 - wij(1,2)*N1)/3.0;
	outPatch[gl_InvocationID].b012 = (2.0*P2 + P1 - wij(2,1)*N2)/3.0;
	outPatch[gl_InvocationID].b102 = (2.0*P2 + P0 - wij(2,0)*N2)/3.0;
	outPatch[gl_InvocationID].b201 = (2.0*P0 + P2 - wij(0,2)*N0)/3.0;
	float E = ( outPatch[gl_InvocationID].b210
			+ outPatch[gl_InvocationID].b120
			+ outPatch[gl_InvocationID].b021
			+ outPatch[gl_InvocationID].b012
			+ outPatch[gl_InvocationID].b102
			+ outPatch[gl_InvocationID].b201 ) / 6.0;
	float V = (P0 + P1 + P2)/3.0;
	outPatch[gl_InvocationID].b111 = E + (E - V)*0.5;
	outPatch[gl_InvocationID].n110 = N0+N1-vij(0,1)*(P1-P0);
	outPatch[gl_InvocationID].n011 = N1+N2-vij(1,2)*(P2-P1);
	outPatch[gl_InvocationID].n101 = N2+N0-vij(2,0)*(P0-P2);

	// 設置細分等級
	gl_TessLevelOuter[gl_InvocationID] = ubo.tessLevel;
	gl_TessLevelInner[0] = ubo.tessLevel;
}

其中我們使用了PN Triangles算法爲低精度多邊形模型添加細節。在設置細分等級精度之前我們可以看到,我們根據PN Triangles算法將所需數據皆計算好,並傳遞給評估着色器使用。

三角形內外部細分示意圖如下:在這裏插入圖片描述
相關細分着色器內置字段及定義:

  • gl_out數組具有相同的結構體成員,不過數組大小與gl_in不同,它是由gl_PatchVerticesOut來指定的。而這個值則是在細分曲面控制器中的out這一layout限定符中設置。此外,以下標量值用於確定正在被着色的圖元和輸出頂點:

  • gl_InvocationID:當前細分曲面着色器的輸出頂點的調用索引

  • gl_PrimitiveID:當前輸入patch的圖元索引

  • gl_PatchVerticesIn:輸入patch中的頂點個數,它作爲gl_in數組變量中的元素個數

  • gl_PatchVerticesOut:輸出patch中的頂點個數,它作爲gl_out數組變量中的元素個數

如果我們需要額外的基於每個頂點的屬性值,或爲輸入或爲輸出,那麼這需要在我們的細分曲面控制着色器中將它們聲明爲in或out數組。一個輸入數組的大小需要與輸入patch大小相同,或者可以被聲明爲缺省大小的,這樣Vulkan將會爲其所有值適當地分配空間。類似地,每個頂點的輸出屬性需要與輸出patch中的頂點個數相一致,也可以爲輸出屬性聲明爲缺省大小的。輸出屬性值將會被傳遞到細分曲面計算着色器,作爲其輸入屬性值。

四、曲面細分評估着色器(tessellation evaluation shader)

表面細分控制着色器(tessellation control shader),它的作用和頂點着色器類似,只不過它處理的是所有新生成的點。

#version 450

// PN數據
struct PnPatch
{
 float b210;
 float b120;
 float b021;
 float b012;
 float b102;
 float b201;
 float b111;
 float n110;
 float n011;
 float n101;
};

layout (binding = 1) uniform UBO 
{
    mat4 projection;
    mat4 model;
    float tessAlpha;
} ubo;

//triangles:使用重心座標的一個三角形域;域座標:帶有範圍在[0, 1]內的a、b、c三個值的座標(a, b, c),這裏a+b+c=1。
//fractional_odd_spacing:值被裁減到[1, max-1]範圍內,然後取整到下一個最大奇整數值n。邊然後被劃分爲n-2條等長部分,以及兩個剩餘部分,每個在一端,剩餘部分長度可能比其它長度要短,
//ccw:表面細分環繞順序:逆時針方向
layout(triangles, fractional_odd_spacing, ccw) in;

layout(location = 0) in vec3 iNormal[];
layout(location = 3) in vec2 iTexCoord[];
layout(location = 6) in PnPatch iPnPatch[];

layout(location = 0) out vec3 oNormal;
layout(location = 1) out vec2 oTexCoord;

#define uvw gl_TessCoord

void main()
{
    vec3 uvwSquared = uvw * uvw;
    vec3 uvwCubed   = uvwSquared * uvw;

    // 提取控制點
    vec3 b210 = vec3(iPnPatch[0].b210, iPnPatch[1].b210, iPnPatch[2].b210);
    vec3 b120 = vec3(iPnPatch[0].b120, iPnPatch[1].b120, iPnPatch[2].b120);
    vec3 b021 = vec3(iPnPatch[0].b021, iPnPatch[1].b021, iPnPatch[2].b021);
    vec3 b012 = vec3(iPnPatch[0].b012, iPnPatch[1].b012, iPnPatch[2].b012);
    vec3 b102 = vec3(iPnPatch[0].b102, iPnPatch[1].b102, iPnPatch[2].b102);
    vec3 b201 = vec3(iPnPatch[0].b201, iPnPatch[1].b201, iPnPatch[2].b201);
    vec3 b111 = vec3(iPnPatch[0].b111, iPnPatch[1].b111, iPnPatch[2].b111);

    // 提取控制法線
    vec3 n110 = normalize(vec3(iPnPatch[0].n110, iPnPatch[1].n110, iPnPatch[2].n110));
    vec3 n011 = normalize(vec3(iPnPatch[0].n011, iPnPatch[1].n011, iPnPatch[2].n011));
    vec3 n101 = normalize(vec3(iPnPatch[0].n101, iPnPatch[1].n101, iPnPatch[2].n101));

    // 計算texcoords
    oTexCoord  = gl_TessCoord[2]*iTexCoord[0] + gl_TessCoord[0]*iTexCoord[1] + gl_TessCoord[1]*iTexCoord[2];

    // normal
    // 重心法線
    vec3 barNormal = gl_TessCoord[2]*iNormal[0] + gl_TessCoord[0]*iNormal[1] + gl_TessCoord[1]*iNormal[2];
    vec3 pnNormal  = iNormal[0]*uvwSquared[2] + iNormal[1]*uvwSquared[0] + iNormal[2]*uvwSquared[1]
                   + n110*uvw[2]*uvw[0] + n011*uvw[0]*uvw[1]+ n101*uvw[2]*uvw[1];
    oNormal = ubo.tessAlpha*pnNormal + (1.0-ubo.tessAlpha) * barNormal;

    // 計算插值位置
    vec3 barPos = gl_TessCoord[2]*gl_in[0].gl_Position.xyz
                + gl_TessCoord[0]*gl_in[1].gl_Position.xyz
                + gl_TessCoord[1]*gl_in[2].gl_Position.xyz;

    // 節省一些計算
    uvwSquared *= 3.0;

    // 計算PN的位置
    vec3 pnPos  = gl_in[0].gl_Position.xyz*uvwCubed[2]
                + gl_in[1].gl_Position.xyz*uvwCubed[0]
                + gl_in[2].gl_Position.xyz*uvwCubed[1]
                + b210*uvwSquared[2]*uvw[0]
                + b120*uvwSquared[0]*uvw[2]
                + b201*uvwSquared[2]*uvw[1]
                + b021*uvwSquared[0]*uvw[1]
                + b102*uvwSquared[1]*uvw[2]
                + b012*uvwSquared[1]*uvw[0]
                + b111*6.0*uvw[0]*uvw[1]*uvw[2];

    // 最終位置和法線
    vec3 finalPos = (1.0-ubo.tessAlpha)*barPos + ubo.tessAlpha*pnPos;
    gl_Position = ubo.projection * ubo.model * vec4(finalPos,1.0);
}

在表面細分控制着色器處理好之後,我們運行可以看到如下效果(此時內部和外部細分等級都是1(uboTessControl.tessLevel = 1)):

在這裏插入圖片描述
你可以設置不同的細分等級來查看對應的曲面細分程度及PN Triangles效果:

uboTessControl.tessLevel = 2:
在這裏插入圖片描述
uboTessControl.tessLevel = 5:
在這裏插入圖片描述
uboTessControl.tessLevel = 13:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章