建設(shè)廳特種作業(yè)證件查詢官網(wǎng)網(wǎng)站優(yōu)化推廣公司
1.著色器模塊介紹
? ? ? ?Vulkan著色器代碼一定要用字節(jié)碼格式,而不是人類可讀的語法如GLSL和HLSL。這個(gè)字節(jié)碼就是SPIR-V,設(shè)計(jì)用于Vulkan和OpenCL。這是一個(gè)可以用于編寫圖形和計(jì)算著色器的格式,但是我們主要關(guān)注的是Vulkan的圖形管線。使用字節(jié)碼格式的優(yōu)點(diǎn)之一是GPU廠商寫的編譯器將著色器代碼轉(zhuǎn)化為原生代碼會(huì)非常簡單。過去的經(jīng)驗(yàn)表明,人類易讀的語法如GLSL,某些GPU廠商是能很便捷地解讀這些標(biāo)準(zhǔn)的。但是如果你碰巧寫了不一般的著色器,那可能會(huì)導(dǎo)致廠家的著色器因?yàn)槟愕恼Z法錯(cuò)誤而拒絕執(zhí)行,甚至更糟,就是能執(zhí)行,卻因?yàn)榫幾g器bug得到的是錯(cuò)誤的結(jié)果。直接用字節(jié)碼格式就能避免這些問題。
? ? ? ?Khronos已經(jīng)發(fā)行了他們自己的廠商無關(guān)的編譯器,能夠?qū)LSL編譯到SPIR-V格式。這個(gè)編譯器就是用來驗(yàn)證你的著色器都是和標(biāo)準(zhǔn)兼容的,它會(huì)產(chǎn)生一個(gè)SPIR-V的二進(jìn)制輸出,可以和你的程序一同發(fā)行。該編譯器包含在LunarG SDK中了,也就是glslangValidator.exe,所以不用額外下載任何內(nèi)容。
? ? ? ?GLSL是C語法風(fēng)格的著色器語言,用它寫的程序有一個(gè)main方法來讓每個(gè)對象調(diào)用。沒有用參數(shù)作為輸入,返回一個(gè)值作為輸出這種做法,GLSL使用了全局變量來處理輸入和輸出。該語言包含了許多特性以便于圖形編程,比如內(nèi)建的向量和矩陣原型。叉乘,矩陣-向量相乘,向量反射之類操作用的函數(shù)都包括在內(nèi)。
? ? ? ? 向量類型叫做vec,后面跟著一個(gè)數(shù)字表示元素個(gè)數(shù)。比如一個(gè)3D位置應(yīng)該存儲(chǔ)為vec3。可以用類似.x的方式獲取其單獨(dú)的組件,但是也可能會(huì)創(chuàng)建一個(gè)新的變量,比如vec3(1.0, 2.0, 3.0).xy就會(huì)得到一個(gè)vec2。向量的構(gòu)造器也可以接受向量對象的組合以及標(biāo)量值,比如vec3可以用vec3(vec2(1.0, 2.0), 3.0)構(gòu)造。
2.著色器例子
? ? ? ? 寫一個(gè)頂點(diǎn)著色器和片段著色器,以便將三角形顯示到屏幕上。下面兩部分會(huì)介紹每一部分的GLSL代碼,之后我會(huì)介紹如何產(chǎn)生兩份SPIR-V二進(jìn)制文件并加載到程序中。
2.1 定點(diǎn)著色器
頂點(diǎn)著色器處理每個(gè)到來的頂點(diǎn),用其屬性如世界坐標(biāo),顏色,法線和材質(zhì)坐標(biāo)等作為輸入。輸出是最終在裁剪坐標(biāo)的位置和需要傳遞給片段著色器的屬性,比如顏色和材質(zhì)坐標(biāo)。這些值會(huì)被片段著色器根據(jù)光柵器插值,產(chǎn)生平滑的梯度。
裁剪坐標(biāo)是來自頂點(diǎn)著色器的四維向量,隨后被通過最后一個(gè)元素除以整個(gè)向量轉(zhuǎn)變成一個(gè)歸一化設(shè)備坐標(biāo)。這些歸一化設(shè)備坐標(biāo)是齊次坐標(biāo),將幀緩沖映射到縱橫都是[-1, 1]的坐標(biāo)系,如下:
?
我們第一個(gè)三角形不用任何變換,我們就直接明確三個(gè)點(diǎn)的位置作為歸一化設(shè)備坐標(biāo),來創(chuàng)建如下的三角形:
直接輸出歸一化設(shè)備坐標(biāo),做法就是將他們作為裁剪坐標(biāo)從頂點(diǎn)著色器輸出,最后一個(gè)部分置為1。這樣變換裁剪坐標(biāo)到歸一化設(shè)備坐標(biāo)時(shí)候的除法操作就什么都保持不變。
通常這些坐標(biāo)會(huì)存儲(chǔ)在頂點(diǎn)緩沖中,但是創(chuàng)建頂點(diǎn)緩沖并填充數(shù)據(jù)在Vulkan中并非微不足道。因此我們決定先將其推遲,直到我們滿足地看到三角形繪制到了屏幕上。同時(shí)我們還要做一些不太正統(tǒng)的東西:直接將坐標(biāo)包含在頂點(diǎn)著色器中。代碼如下
#version 450vec2 positions[3] = vec2[](vec2(0.0, -0.5),vec2(0.5, 0.5),vec2(-0.5, 0.5)
);void main() {gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
}
main方法是每個(gè)頂點(diǎn)都涉及的,內(nèi)置的gl_VertexIndex變量包含了當(dāng)前頂點(diǎn)的索引。這通常是頂點(diǎn)緩沖的索引,但是我們這兒它就是硬編碼數(shù)組的頂點(diǎn)數(shù)據(jù)的索引。每個(gè)頂點(diǎn)的位置通過著色器的連續(xù)數(shù)組獲取,且和虛擬z和w部分一起組成一個(gè)裁剪坐標(biāo)的位置。內(nèi)置變量gl_Position作為輸出。
2.2?片段著色器
由來自頂點(diǎn)著色器的位置形成的三角形用片段來填充屏幕上的區(qū)域。片段著色器就在這些片段上執(zhí)行來為幀緩沖產(chǎn)生一個(gè)顏色和深度。一個(gè)簡單的為整個(gè)三角形輸出紅色的片段著色器。
#version 450
#extension GL_ARB_separate_shader_objects : enablelayout(location = 0) out vec4 outColor;void main() {outColor = vec4(1.0, 0.0, 0.0, 1.0);
}
main方法被每個(gè)片段調(diào)用,就和頂點(diǎn)著色器的main方法被每個(gè)頂點(diǎn)調(diào)用一樣。GLSL的顏色是由4部分組成的向量,就是RGB和alpha通道,范圍都是[0, 1]。不像是頂點(diǎn)著色器的gl_Position,沒有內(nèi)置變量為當(dāng)前片段輸出一個(gè)顏色。你必須為每個(gè)幀緩沖明確自己的輸出變量,布局(location = 0)修改器明確了幀緩沖的索引。這里outColor寫成紅色,和索引為0的第一個(gè)幀緩沖連接起來。
3.編譯著色器
在項(xiàng)目根目錄創(chuàng)建一個(gè)shaders目錄,存儲(chǔ)我們的著色器代碼。兩份著色器分別是shader.vert和shader.frag,GLSL沒有官方擴(kuò)展名,但是這兩個(gè)通常用于區(qū)分它們。
shader.vert如下所示:
#version 450
#extension GL_ARB_separate_shader_objects : enablelayout(location = 0) out vec3 fragColor;vec2 positions[3] = vec2[](vec2(0.0, -0.5),vec2(0.5, 0.5),vec2(-0.5, 0.5)
);vec3 colors[3] = vec3[](vec3(1.0, 0.0, 0.0),vec3(0.0, 1.0, 0.0),vec3(0.0, 0.0, 1.0)
);void main(){gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);fragColor = colors[gl_VertexIndex];
}
shader.frag如下:
#version 450
#extension GL_ARB_separate_shader_objects : enablelayout(location = 0) in vec3 fragColor;layout(location = 0) out vec4 outColor;void main() {outColor = vec4(fragColor, 1.0);
}
現(xiàn)在我們準(zhǔn)備用glslangValidator將其編譯成SPIR-V字節(jié)碼:
glslc shader.vert -o vert.spv
glslc shader.frag -o frag.spv
這兩條命令用-V標(biāo)志調(diào)用了編譯器,表明要求編譯器將GLSL源文件編譯成SPIR-V字節(jié)碼。當(dāng)你運(yùn)行編譯腳本的時(shí)候,就會(huì)發(fā)現(xiàn)兩個(gè)SPIR-V二進(jìn)制文件產(chǎn)生了,即vert.spv和frag.spv。這些名字直接來自shader類型,但是你可以進(jìn)行重命名。Vulkan SDK包含了libshaderc,也就是將你的GLSL代碼編譯成SPIR-V的東西。
4.加載著色器
當(dāng)前我們可以產(chǎn)生SPIR-V著色器了,是時(shí)候?qū)⑵浼虞d到我們的程序中了,然后在某個(gè)時(shí)刻將其插入到圖形管線中。我們先要寫一個(gè)簡單的助手方法來從文件加載二進(jìn)制數(shù)據(jù):
readFile方法會(huì)從指定文件讀取所有字節(jié),返回std::vector管理的byte數(shù)組。我們用兩個(gè)標(biāo)記打開該文件:
ate:開始讀的時(shí)候在文件末尾,就是說打開文件的時(shí)候定位到文件尾;
binary:以二進(jìn)制文件讀取文件(避免text轉(zhuǎn)換)。
現(xiàn)在我們從createGraphicsPipeline調(diào)用該方法:
void createGraphicsPipeline() {
? ? auto vertShaderCode = readFile("shaders/vert.spv");
? ? auto fragShaderCode = readFile("shaders/frag.spv");
}
準(zhǔn)備創(chuàng)建著色器模塊,在開始將代碼傳遞到管線之前,我們需要將其包裝到VkShaderModule對象中,創(chuàng)建一個(gè)createShaderModule方法