本文我將對(duì)PowerVR光線追蹤團(tuán)隊(duì)創(chuàng)建的API進(jìn)行簡(jiǎn)要介紹。該API允許開(kāi)發(fā)人員訪問(wèn)Wizard 架構(gòu)的PowerVR光線追蹤硬件。我將簡(jiǎn)單概述硬件層發(fā)生的物理變化及其相比傳統(tǒng)的GPU所變化的具體內(nèi)容。隨后,我將著重討論新創(chuàng)建的供開(kāi)發(fā)人員訪問(wèn)硬件的OpenGL ES 3.1擴(kuò)展程序。
在2016年度游戲開(kāi)發(fā)者大會(huì)(GDC)上,Imagination的 idc16開(kāi)發(fā)人員會(huì)議對(duì)API進(jìn)行了討論,點(diǎn)擊此處可獲取API討論資訊。
以下內(nèi)容的前提是默認(rèn)各位讀者已經(jīng)了解光線追蹤的概念。若不了解,可以先查閱光線追蹤的相關(guān)信息。本文不會(huì)深入探討每個(gè)功能,詳細(xì)的信息將在后續(xù)的文章中進(jìn)行討論。
前一段時(shí)間,我們介紹了啟動(dòng)光線追蹤功能的四核集群PowerVR GR6500 GPU。最近,我們又展示了一些目前在GPU上運(yùn)行的光線追蹤應(yīng)用程序。
下圖是我們添加的用于加速光線追蹤的硬件功能:
圖表展示了光線追蹤新的硬件模
光線數(shù)據(jù)管理
光線數(shù)據(jù)管理(RDM)模塊將交叉點(diǎn)歸至統(tǒng)一著色集群(USC)中,再由集群進(jìn)行光線著色
場(chǎng)景層級(jí)生成器
場(chǎng)景層級(jí)生成器(SHG)模塊即使用標(biāo)準(zhǔn)頂點(diǎn)著色器生成的世界坐標(biāo)頂點(diǎn),在光線交點(diǎn)處理器之后生成加速結(jié)構(gòu)。
相干引擎
該模塊是光線追蹤裝置(RTU)的一部分,并以相同的方式緩沖了一組遍歷場(chǎng)景層級(jí)的光線且遵循類似的執(zhí)行路徑。這樣,在內(nèi)存中獲取光線數(shù)據(jù)時(shí)便可以避免緩存缺失,并由此減少帶寬和功耗。更多信息將在后續(xù)文章中詳細(xì)介紹。同時(shí),您還可以關(guān)注2014GDC論壇獲取更多相干引擎的資訊。
光線交叉處理器
該模塊是RTU的一部分,可以測(cè)試SGH生成的位于主內(nèi)存的三角形光線及場(chǎng)景層級(jí)光線。
幀累加緩存
該硬件模塊從USC中獲取累積指令,并使用編寫的緩存來(lái)加速某些在光線追蹤著色器中可以使用的圖像原子操作。這意味著我們可以發(fā)出只編寫指令,這些指令將異步列隊(duì)并執(zhí)行。
PowerVR GR6500 GPU在PCIe卡上進(jìn)行集成,如下圖所示,使用RTU時(shí)其峰值性能為每秒3億光線。SHG的目標(biāo)是生成加速結(jié)構(gòu),使頂點(diǎn)輸出從每秒1億動(dòng)態(tài)三角形開(kāi)始加速。
The API
光線跟蹤硬件對(duì)典型的GPU設(shè)計(jì)進(jìn)行了延伸,使之不僅僅只是單一的硬件。我們決定通過(guò)添加光線追蹤功能來(lái)擴(kuò)展OpenGL ES 3.1,以充分利用這個(gè)開(kāi)發(fā)人員較為熟知的OpenGL ES API。我們選擇使用現(xiàn)代規(guī)范,如直接狀態(tài)訪問(wèn)、無(wú)綁定等,因?yàn)轭A(yù)計(jì)這將是未來(lái)的API模式。Vulkan API的潛力不容忽視,但Vulkan仍處于待開(kāi)發(fā)中,本文只介紹OpenGL ES擴(kuò)展。
● 頂點(diǎn)處理
光線追蹤器輸入與光柵輸入是不同的。光線追蹤器處理的是世界坐標(biāo)中的對(duì)象,這是因?yàn)楣饩€追蹤器需要訪問(wèn)整個(gè)場(chǎng)景。例如,其場(chǎng)景中的對(duì)象反映的攝像機(jī)獲取的某個(gè)對(duì)象,而光柵中這卻很難處理。
正因?yàn)槿绱耍琍owerVR硬件光線追蹤API處理頂點(diǎn)輸出時(shí)是在世界坐標(biāo)而非光柵的剪輯空間中。這意味著我們將要以不同的幀率運(yùn)行各層的頂點(diǎn)至光柵中——在光柵中,攝像機(jī)通常從幀到幀移動(dòng)。即便不是,大多數(shù)游戲平臺(tái)也將重新渲染場(chǎng)景,因?yàn)閭鬏斶^(guò)程中某些信息發(fā)生了改變。
這不同于光線追蹤器。我們從攝像機(jī)運(yùn)動(dòng)中解耦,在應(yīng)用程序啟動(dòng)時(shí)運(yùn)行頂點(diǎn)著色器,隨后,如果場(chǎng)景中沒(méi)有任何對(duì)象在世界坐標(biāo)中發(fā)生移動(dòng),則無(wú)需再次運(yùn)行頂點(diǎn)著色器。當(dāng)然,當(dāng)攝像機(jī)移動(dòng)時(shí),仍然需要在每一幀進(jìn)行光線遍歷,但為場(chǎng)景靜態(tài)部分生成的場(chǎng)景層級(jí)則無(wú)需如此。
預(yù)期OpenGL ES的變量及其他特性運(yùn)行如下:
layout(std140, binding=0) uniform UboPerObjectIn {
highp mat4 worldFromModel;
highp mat4 worldFromModelIT;
} UboPerObject[2];
out gl_PerVertex {
vec4 gl_Position;
float gl_PointSize;
};
out PerVertexData {
vec3 vertexNormal;
};
void main() {
gl_Position = UboPerObject[gl_BuildIDIMG].worldFromModel * vec4(inVertex, 1.0); // Output in world space
vertexNormal = (UboPerObject[gl_BuildIDIMG].worldFromModelIT * vec4(inNormal, 0.0)).xyz
(gl_BuildIDIMG explained below)
然而,由于世界坐標(biāo)中的頂點(diǎn)處理與光柵頂點(diǎn)處理不同,因此我們需要不同的API來(lái)進(jìn)行頂點(diǎn)處理……
● 場(chǎng)景構(gòu)造
對(duì)頂點(diǎn)和變量進(jìn)行處理時(shí)將生成對(duì)象的坐標(biāo)位置,使用該位置便可以生成場(chǎng)景層級(jí)加速結(jié)構(gòu)。光線遍歷階段需要使用此加速結(jié)構(gòu)。計(jì)算加速結(jié)構(gòu)工作量相對(duì)較大。這就是為何在硬件中設(shè)計(jì)了場(chǎng)景層級(jí)加速器模塊——可以有效地生成場(chǎng)景層級(jí)加速結(jié)構(gòu)。我們還設(shè)計(jì)了API,這樣開(kāi)發(fā)人員就可以將場(chǎng)景分為組件、組件集、場(chǎng)景和場(chǎng)景集。
這樣分解使得開(kāi)發(fā)人員得以更有效地使用硬件。
當(dāng)我們發(fā)出構(gòu)建命令時(shí),將在場(chǎng)景中指定的部分執(zhí)行三角形的頂點(diǎn)著色器。隨后,便使用該部分的輸出計(jì)算加速結(jié)構(gòu)。
Components
將各組件連接到頂點(diǎn)陣列對(duì)象。其與調(diào)用glDraw*()的定義類似。不過(guò),并非是立即執(zhí)行該調(diào)用,而是對(duì)其進(jìn)行記錄和延期,直至我們?cè)诎@些組件的組件集中發(fā)出一個(gè)構(gòu)建命令。
還可以對(duì)該組件設(shè)置其他屬性,以便在光線遍歷階段進(jìn)行交互:
?正面(順時(shí)針或逆時(shí)針?lè)较?
?幾何圖形是否遮擋光線
?幾何圖形是否對(duì)對(duì)某類光線可見(jiàn)
?可見(jiàn)表面(前或后)
生成組件并設(shè)置屬性的例子如下:
glGenVertexArrays(1, &vertexArray);
// ... upload index data and model space vertex data as usual
glCreateComponentsIMG(1, &componentHandle);
glComponentVertexArrayIMG(componentHandle, vertexArray);
glComponentIndexedGeometryIMG(componentHandle, GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, 0, 0);
glComponentBufferRangeIMG(componentHandle, GL_UNIFORM_BUFFER, 0, transformUBOHandle, 0, transformUBOSize);
glComponentVisibleFaceIMG(componentHandle, GL_FRONT_AND_BACK);
glComponentOccluderIMG(componentHandle, GL_TRUE);
請(qǐng)注意,GPU尚未有待執(zhí)行的對(duì)象。此刻,僅指定組件的數(shù)據(jù)。同樣要注意的是,頂點(diǎn)緩沖區(qū)需要保持激活狀態(tài),因?yàn)榻M件需要對(duì)其進(jìn)行參照——并非復(fù)制。
組件集
組件集是最小的可分割處理單元,可用于創(chuàng)建或合并命令。
glCreateComponentGroupsIMG(1, &componentGroupHandle);
componentGroupHandle = glGetComponentGroupHandleIMG(componentGroupHandle);
glComponentGroupExtentIMG(componentGroupHandle, &extentMin, &extentMax); // Set the extents
我們還可以指定最小/最大幾何圖形區(qū)段,即創(chuàng)建組件集后在先前創(chuàng)建的組件之外對(duì)其進(jìn)行構(gòu)建。這里我們可以將頂點(diǎn)著色器和光線追蹤器與組件聯(lián)系起來(lái)。在這個(gè)點(diǎn)上界定組件的材質(zhì)。構(gòu)建命令以啟動(dòng)硬件SHG部分:
componentHandle = glGetComponentProgramHandleIMG(componentHandle, vertexAndRayShaderProgram);
std::vector components = {{componentHandle}};
glBuildComponentGroupIMG(0, componentGroupHandle, components.size(), components.data());
// Create a fence sync for the scene generation so that we know when it's ready
sceneHierarchyGenerationSync = glFenceSync(GL_SYNC_SHG_COMMANDS_COMPLETE_IMG, 0);
同時(shí)還可以發(fā)出合并命令。合并命令將兩個(gè)組件集合并在一起。這樣開(kāi)發(fā)人員便可以選擇以不同的幀率重建場(chǎng)景的某些部分。構(gòu)建場(chǎng)景子集,并與已經(jīng)建立的子集合并,盡管要權(quán)衡場(chǎng)景層級(jí)的質(zhì)量,但合并相比完全重建子集更加節(jié)省功耗。
以汽車在地面移動(dòng)為例。在啟動(dòng)應(yīng)用程序時(shí)便創(chuàng)建地面組件集,且使之一直保持不變。隨后,當(dāng)汽車在地面移動(dòng)時(shí),創(chuàng)建汽車組件集,必要時(shí)每一幀畫(huà)面都可創(chuàng)建。
std::vector componentGroups = {{...}};
glMergeComponentGroupsIMG(mergedComponentGroupResultHandle, componentGroups.size(), componentGroups.data(), GL_DONT_CARE);
sceneHierarchyGenerationSync = glFenceSync(GL_SYNC_SHG_COMMANDS_COMPLETE_IMG, 0);
上述代碼中,GL fences可供硬件使用來(lái)同步訪問(wèn)內(nèi)存。它還使得多緩沖區(qū)操作變成一種可能。調(diào)用合并/構(gòu)建后,立刻等待fence直至硬件執(zhí)行完命令,且這是在啟動(dòng)應(yīng)用程序時(shí)構(gòu)建場(chǎng)景層級(jí)的一種方式。不過(guò)這樣CPU side stall在運(yùn)行時(shí)便會(huì)出現(xiàn)狀態(tài)不佳。但相反,我們可以多緩存合并/構(gòu)建命令,以便在GPU硬件讀/寫一組對(duì)象時(shí)在CPU上設(shè)置第二組對(duì)象。而為了更容易實(shí)現(xiàn)這一點(diǎn),可以將第一個(gè)參數(shù)輸入glBuildComponentGroupIMG()命令中。通過(guò)glsl builtin gl_BuildIDIMG將該指數(shù)輸入頂點(diǎn)著色器中,且通過(guò)上述示例可見(jiàn),這將有助于多緩存對(duì)象數(shù)據(jù)UBO。這意味著我們可以在世界坐標(biāo)中移動(dòng)組件集,且無(wú)需暫停CPU。
場(chǎng)景陣列
場(chǎng)景陣列包含了一個(gè)或更多在單個(gè)調(diào)度內(nèi)可遍歷的組件集。我們需要指定每個(gè)場(chǎng)景陣列的光線規(guī)格,以上可以通過(guò)glSceneArrayRayBlockSizeIMG完成。
GLuint sceneArray = glCreateSceneArrayIMG();
glSceneArrayRayBlockSizeIMG(sceneArray, 0, 0); // Primary rays
glSceneArrayRayBlockSizeIMG(sceneArray, 1, 12); // Shadow rays
glBindSceneArrayComponentGroupIMG(sceneArray, 0, componentGroupHandle);
單個(gè)場(chǎng)景陣列中的多個(gè)組件集對(duì)幾何圖形層次細(xì)節(jié) (LOD)如進(jìn)行光柵化時(shí)很用用處。例如,當(dāng)我們光線追蹤具有復(fù)雜幾何圖形的場(chǎng)景時(shí),我們可能需要場(chǎng)景的多個(gè)LOD以幫助加速交叉測(cè)試。在著色器中可以切換至不同的場(chǎng)景,這樣便可以使用距離等測(cè)試切換至更低一層的LOD場(chǎng)景中。
更簡(jiǎn)單更具邏輯性的一個(gè)例子是具有虛擬監(jiān)控和閉路電視攝像頭的場(chǎng)景。當(dāng)我們用光線追蹤監(jiān)控器時(shí),可以切換到中央電視臺(tái)現(xiàn)場(chǎng)并啟動(dòng)攝像機(jī)視角的光線。
glBindSceneArrayComponentGroupIMG(sceneArray, 0, monitorComponentGroup);
glBindSceneArrayComponentGroupIMG(sceneArray, 1, cameraViewComponentGroup);
多類場(chǎng)景需要多個(gè)場(chǎng)景陣列,例如在光線追蹤游戲視覺(jué)層次的同時(shí)又想沿著該層次光線追蹤聲音的路徑。相比視覺(jué)信息,聲音場(chǎng)景需要在光線和幾何圖形中存儲(chǔ)不同的信息。我們可以在不同的場(chǎng)景陣列中單獨(dú)區(qū)分這些細(xì)節(jié):
glBindSceneArrayIMG(sceneArrayVisuals);
glDispatchRaysIMG(0, 0, 0, frame.w, frame.h, GL_NO_WAIT_BIT_IMG);
glBindSceneArrayIMG(sceneArrayAudio);
glDispatchRaysIMG(0, 0, 0, 1, numSounds * 2, GL_NO_WAIT_BIT_IMG);
或者可以使用單個(gè)場(chǎng)景陣列和多類光線。
我們將這個(gè)綁定的場(chǎng)景陣列也稱之為場(chǎng)景,這樣在本文中涉及到場(chǎng)景時(shí)才說(shuō)的通。
無(wú)綁定
在常規(guī)OpenGL中使用紋理時(shí),我們需要首先調(diào)用glActiveTexture,然后是glBindTexture,且只有一組有限的綁定點(diǎn)。這在單一的對(duì)象層比較適用,因?yàn)閷?duì)于下一個(gè)對(duì)象,我們可以再次調(diào)用這些函數(shù)且改變對(duì)象的紋理——不需要擔(dān)心其他對(duì)象的紋理。
啟動(dòng)光線追蹤時(shí),我們需要知道所有對(duì)象的紋理和著色器。在光線遍歷時(shí)我們無(wú)法變更綁定,但又不想僅限于少量的紋理。因此需要另一種機(jī)制將這些紋理綁定到對(duì)象著色器中。這就是新的IMG_bindless_texture擴(kuò)展。可以使用以下函數(shù)替代上述的gl調(diào)用來(lái)綁定紋理:
GLuint64 textureHandle = glGetTextureHandleIMG(textureObject);
// Similar to glUniform
glProgramUniformHandleui64IMG(rayProgramHandle, samplerLocationInShader, textureHandle);
// Now in glsl we can access the texture:
layout(bindless_sampler) uniform sampler2D sTexture;
光線遍歷
光線追蹤流程
通過(guò)glDispatchRaysIMG調(diào)用進(jìn)入光線遍歷部分。我們需要定義一組glRayBounceLimitIMG光線反射數(shù)量的最大值。還需要其他一些有助于提高硬件執(zhí)行效率的輔助工具。glProgramMaxRayEmitsIMG限制了發(fā)送的光線數(shù)量。對(duì)于陰影光線,將此值設(shè)置為1,例如燈光照射得到的硬陰影。這將有助于硬件了解是否在執(zhí)行光線著色器,且最多可以發(fā)送一條陰影光線。
評(píng)論