Vulkan_實例化

實例化

本次我們使用實例化功能從單個頂點緩衝區渲染具有可變參數和紋理的同一網格的許多實例(索引分層紋理)。使用輔助頂點緩衝區傳遞實例數據。

假設你有一個繪製了很多模型的場景,而大部分的模型包含的是同一組頂點數據,只不過進行的是不同的世界空間變換。想象一個充滿草的場景:每根草都是一個包含幾個三角形的小模型。你可能會需要繪製很多根草,最終在每幀中你可能會需要渲染上千或者上萬根草。因爲每一根草僅僅是由幾個三角形構成,渲染幾乎是瞬間完成的,但上千個渲染函數調用卻會極大地影響性能。

如果我們需要渲染大量物體時,代碼看起來會像這樣:

for(unsigned int i = 0; i < INSTANCE_COUNT; i++)
{
    DoSomePreparations(); //綁定描述符,綁定渲染管線等
    vkCmdDraw(drawCmdBuffers[i], 4, 1, 0, 0);;
}

如果像這樣繪製模型的大量實例(Instance),你很快就會因爲繪製調用過多而達到性能瓶頸。與繪製頂點本身相比,使用vkCmdDraw或vkCmdDrawIndexed函數告訴GPU去繪製你的頂點數據會消耗更多的性能,因爲Vulkan在繪製頂點數據之前需要做很多準備工作(比如告訴GPU該從哪個緩衝讀取數據,從哪尋找頂點屬性,而且這些都是在相對緩慢的CPU到GPU總線(CPU to GPU Bus)上進行的)。所以,即便渲染頂點非常快,大量相同數據調用命令GPU去渲染卻未必很快。

因此,我們使用vkCmdDraw或vkCmdDrawIndexed中的firstIndex和instanceCount參數來控制實例化,通過實例化,一個幾何對象的很多副本可以直接發送到圖形管線,每一個副本都可以稱爲一個實例。

案例:小行星帶

想象這樣一個場景,在宇宙中有一個大的行星,它位於小行星帶的中央。這樣的小行星帶可能包含成千上萬的巖塊,在很不錯的顯卡上也很難完成這樣的渲染。實例化渲染正是適用於這樣的場景,因爲所有的小行星都可以使用一個模型來表示。每個小行星可以再使用不同的變換矩陣來進行少許的變化。

爲了得到想要的效果,我們將會爲每個小行星生成一個變換矩陣,用作它們的模型矩陣。變換矩陣首先將小行星位移到小行星帶中的某處,我們還會加一個小的隨機偏移值到這個偏移量上,讓這個圓環看起來更自然一點。接下來,我們應用一個隨機的縮放,並且以一個旋轉向量爲軸進行一個隨機的旋轉。最終的變換矩陣不僅能將小行星變換到行星的周圍,而且會讓它看起來更自然,與其它小行星不同。最終的結果是一個佈滿小行星的圓環,其中每一個小行星都與衆不同。

1.數據準備

首先,我們需要定義每個實例對象所需的數據結構體:

	#define INSTANCE_COUNT 4096  //實例化個數
	struct InstanceData {
		glm::vec3 pos;//位置
		glm::vec3 rot;//旋轉角度
		float scale;//實例對象縮放比例
		uint32_t texIndex;//紋理索引
	};

之後我們創建一個prepareInstanceData函數來準備所需創建的實例化數據:

		std::vector<InstanceData> instanceData;
		instanceData.resize(INSTANCE_COUNT);

		std::default_random_engine rndGenerator(benchmark.active ? 0 : (unsigned)time(nullptr));
		std::uniform_real_distribution<float> uniformDist(0.0, 1.0);
		std::uniform_int_distribution<uint32_t> rndTextureIndex(0, textures.rocks.layerCount);

		// 在兩個圓環上創建位置信息
		for (auto i = 0; i < INSTANCE_COUNT / 2; i++) {		
			glm::vec2 ring0 { 5.0f, 10.0f };
			glm::vec2 ring1 { 15.0f, 19.0f };

			float rho, theta;

			// 內環數據
			rho = sqrt((pow(ring0[1], 2.0f) - pow(ring0[0], 2.0f)) * uniformDist(rndGenerator) + pow(ring0[0], 2.0f));
			theta = 2.0 * M_PI * uniformDist(rndGenerator);
			instanceData[i].pos = glm::vec3(rho*cos(theta), uniformDist(rndGenerator) * 0.5f - 0.25f, rho*sin(theta));
			instanceData[i].rot = glm::vec3(M_PI * uniformDist(rndGenerator), M_PI * uniformDist(rndGenerator), M_PI * uniformDist(rndGenerator));
			instanceData[i].scale = 1.5f + uniformDist(rndGenerator) - uniformDist(rndGenerator);
			instanceData[i].texIndex = rndTextureIndex(rndGenerator);
			instanceData[i].scale *= 0.75f;

			// 外環數據
			rho = sqrt((pow(ring1[1], 2.0f) - pow(ring1[0], 2.0f)) * uniformDist(rndGenerator) + pow(ring1[0], 2.0f));
			theta = 2.0 * M_PI * uniformDist(rndGenerator);
			instanceData[i + INSTANCE_COUNT / 2].pos = glm::vec3(rho*cos(theta), uniformDist(rndGenerator) * 0.5f - 0.25f, rho*sin(theta));
			instanceData[i + INSTANCE_COUNT / 2].rot = glm::vec3(M_PI * uniformDist(rndGenerator), M_PI * uniformDist(rndGenerator), M_PI * uniformDist(rndGenerator));
			instanceData[i + INSTANCE_COUNT / 2].scale = 1.5f + uniformDist(rndGenerator) - uniformDist(rndGenerator);
			instanceData[i + INSTANCE_COUNT / 2].texIndex = rndTextureIndex(rndGenerator);
			instanceData[i + INSTANCE_COUNT / 2].scale *= 0.75f;
		}
		...//緩存數據創建

2.管線處理

最後在創建繪製命令緩衝的時候綁定實例化的數據:

		// 實例化創建
		for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
		{
			...
			//綁定管線及描述符
			vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets.instancedRocks, 0, NULL);
			vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.instancedRocks);
			// 綁定位置 0 : 每個實例的緩存
			vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &models.rock.vertices.buffer, offsets);
			// 綁定位置 1 : 實例化數據的緩存
			vkCmdBindVertexBuffers(drawCmdBuffers[i], 1, 1, &instanceBuffer.buffer, offsets);
			//綁定索引緩衝
			vkCmdBindIndexBuffer(drawCmdBuffers[i], models.rock.indices.buffer, 0, VK_INDEX_TYPE_UINT32);

			// 渲染繪製實例對象
			vkCmdDrawIndexed(drawCmdBuffers[i], models.rock.indexCount, INSTANCE_COUNT, 0, 0, 0);
			...
		}

此處在創建渲染管線中頂點屬性綁定的時候,注意:每個頂點和每個實例屬性的着色器聲明是相同的,不同的是輸入只存儲在綁定中:

std::vector<VkVertexInputAttributeDescription> attributeDescriptions = {
			// 頂點屬性
			// 頂點着色器獲取的每個頂點數據
			vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 0, VK_FORMAT_R32G32B32_SFLOAT, 0),					// Location 0: Position			
			vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3),	// Location 1: Normal			
			vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 6),		// Location 2: Texture coordinates			
			vks::initializers::vertexInputAttributeDescription(VERTEX_BUFFER_BIND_ID, 3, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 8),	// Location 3: Color
			// 實例化數據屬性,這些是爲每個呈現的實例獲取的
			vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 4, VK_FORMAT_R32G32B32_SFLOAT, 0),					// Location 4: Position
			vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 5, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3),	// Location 5: Rotation
			vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 6, VK_FORMAT_R32_SFLOAT,sizeof(float) * 6),			// Location 6: Scale
			vks::initializers::vertexInputAttributeDescription(INSTANCE_BUFFER_BIND_ID, 7, VK_FORMAT_R32_SINT, sizeof(float) * 7),			// Location 7: Texture array layer index
		};

接下來我們重點看一下頂點着色器,針對每一個實例化的構件,都會執行這個頂點着色器:

#version 450

// Vertex attributes
layout (location = 0) in vec3 inPos;
layout (location = 1) in vec3 inNormal;
layout (location = 2) in vec2 inUV;
layout (location = 3) in vec3 inColor;

// Instanced attributes
layout (location = 4) in vec3 instancePos;
layout (location = 5) in vec3 instanceRot;
layout (location = 6) in float instanceScale;
layout (location = 7) in int instanceTexIndex;

layout (binding = 0) uniform UBO 
{
	mat4 projection;
	mat4 modelview;
	vec4 lightPos;
	float locSpeed;
	float globSpeed;
} ubo;

layout (location = 0) out vec3 outNormal;
layout (location = 1) out vec3 outColor;
layout (location = 2) out vec3 outUV;
layout (location = 3) out vec3 outViewVec;
layout (location = 4) out vec3 outLightVec;

void main() 
{
	outColor = inColor;
	outUV = vec3(inUV, instanceTexIndex);

	vec4 locPos = vec4(inPos.xyz, 1.0);
	vec4 pos = vec4((locPos.xyz * instanceScale) + instancePos, 1.0);

	gl_Position = ubo.projection * ubo.modelview * pos;
	outNormal = mat3(ubo.modelview) * inNormal;

	pos = ubo.modelview * vec4(inPos.xyz + instancePos, 1.0);
	vec3 lPos = mat3(ubo.modelview) * ubo.lightPos.xyz;
	outLightVec = lPos - pos.xyz;
	outViewVec = -pos.xyz;		
}

至於片元着色器就很簡單了,裏面僅有一個光照處理:

#version 450

layout (binding = 1) uniform sampler2DArray samplerArray;

layout (location = 0) in vec3 inNormal;
layout (location = 1) in vec3 inColor;
layout (location = 2) in vec3 inUV;
layout (location = 3) in vec3 inViewVec;
layout (location = 4) in vec3 inLightVec;

layout (location = 0) out vec4 outFragColor;

void main() 
{
	vec4 color = texture(samplerArray, inUV) * vec4(inColor, 1.0);	
	vec3 N = normalize(inNormal);
	vec3 L = normalize(inLightVec);
	vec3 V = normalize(inViewVec);
	vec3 R = reflect(-L, N);
	vec3 diffuse = max(dot(N, L), 0.1) * inColor;
	vec3 specular = (dot(N,L) > 0.0) ? pow(max(dot(R, V), 0.0), 16.0) * vec3(0.75) * color.r : vec3(0.0);
	outFragColor = vec4(diffuse * color.rgb + specular, 1.0);		
}

編譯着色器,運行可見:
在這裏插入圖片描述
你也可以將實例化個數調整到十萬來看一下,在GPU強大的計算能力下,幾乎運行時間不會過於冗長:
在這裏插入圖片描述

可以看到,在合適的環境下,實例化渲染能夠大大增加顯卡的渲染能力。正是出於這個原因,實例化渲染通常會用於渲染草、植被、粒子,以及上面這樣的場景,基本上只要場景中有很多重複的形狀,都能夠使用實例化渲染來提高性能。

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