
Python: PyGLSL v2.1 / 32. časť
V tejto časti seriálu o programovaní v jazyku Python sa poslednýkrát vrátime k aplikácii PyGLSL. Počas prípravy jej druhej verzie, v rámci ktorej sme na vykreslenie jednoduchej 3D scény využili funkcionalitu API Vulkan, sme sa zmienili, že definíciu vrcholov a farieb dvojice trojuholníkov, ktoré sú obsahom našej scény, sme zahrnuli do kódu shaderov. Navyše v spomínanej verzii aplikácie sme na pokrytie trojuholníkov použili jednoduché farby bez aplikácie textúrovania. Externú definíciu vrcholov a textúrovanie síce možno implementovať relatívne drobnými úpravami kódu, spomínaná funkcionalita je však neodmysliteľnou súčasťou moderných 3D aplikácií, a preto ju nesmieme opomenúť.
Definícia atribútov vrcholov
Objekty počítačovo generovaných 3D scén sú zväčša zložené z trojuholníkov, ktoré sú definované tromi vrcholmi. Každý z uvedených vrcholov môže mať viacero atribútov (vlastností), medzi ktoré okrem iných patrí najmä poloha, farba a textúrovacie súradnice. Poloha sa zväčša uvádza ako trojica údajov X, Y, Z, farba ako trojica údajov R, G, B a textúrovacie súradnice ako dvojica údajov U, W. Význam konkrétnych atribútov vrcholov sme v našich seriáloch o programovaní počítačovej grafiky predstavili už mnohokrát. V každom prípade pri tvorbe 3D objektov definujeme zväčša vrcholy trojuholníkov, ktorých atribúty musíme zadať buď ručne, alebo prostredníctvom nejakého automatizovaného procesu. V prípade aplikácie PyGLSL v2.1 sme definíciu atribútov vrcholov umiestnili do funkcie prepare3Dobjects(), ktorú voláme pred volaním funkcie initVulkan() a spustením hlavného programového cyklu funkciou mainLoop().
Na to, aby sme spomínané atribúty vrcholov dokázali použiť v rámci API Vulkan, musíme ich uložiť do tzv. zásobníka vrcholov (vertexBuffer, pozn.: spomeňme si na tzv. Vertex Buffer Object – VBO, ktorý sme používali v minulosti), ktorý následne pripojíme k príslušnému commandBuffer prostredníctvom funkcie vkCmdBindVertexBuffers().
Okrem už opísaných krokov musíme príslušný Vertex Shader informovať o spôsobe prepojenia jeho lokálnych premenných s externými atribútmi vrcholov. Na to sme pripravili triedu s názvom Vertex, v rámci ktorej sme definovali funkcie getVertexBindingDescription() a getVertexAttributeDescriptions(). Tie sú volané pri vytváraní GraphicsPipeline v rámci funkcie vkCreateGraphicsPipelines(), konkrétne jej štruktúry VkGraphicsPipelineCreateInfo() a atribútu pVertexInputState=PipelineVertexInputStateCreateInfo.
Obr. 1 Transformácia údajov (atribútov) vrcholov
Textúrovanie
V prípade implementácie textúrovania je situácia jemne zložitejšia. Okrem definície textúrovacích súradníc, ktoré sme zahrnuli medzi atribúty vrcholov, musíme v prvom rade vykonať všetky kroky súvisiace s nahratím obrázka textúry a následne s definíciou tzv. DescriptorSet. Oba kroky riešime v rámci funkcie initVulkan() hneď po tom, ako vytvoríme commandPool. Časť nahratia textúry spočíva vo vytvorení príslušného zásobníka (createBuffer), vytvorení obrazu (createImage), skopírovaní obsahu zásobníka do obrazu (copyBufferToImage), vytvorení ImageView (createImageView) a vytvorení príslušného Samplera (createSampler). Po vykonaní predošlých krokov získame ImageView a Sampler, ktorý je vstupom pre DescriptorSet.
DescriptorSet
Prostredníctvom DescriptorSet prepojíme ImageView a Sampler s príslušným Fragment Shaderom. Množina funkcií súvisiaca s DescriptorSet je nasledujúca:
vkCreateDescriptorSetLayout() |
Pripojenie Uniform Buffer Object (UBO) a Sampler k Fragment Shaderu: Binding=0 – UBO (nepoužité v rámci aplikácie) Binding=1 – Sampler
V rámci Fragment Shadera: layout(binding = 1) uniform sampler2D texSampler;
Výstup je použitý pri vytváraní GraphicsPipeline v rámci funkcie vkCreatePipelineLayout(), konkrétne jej štruktúry VkPipelineLayoutCreateInfo() a atribútu pSetLayouts= DescriptorSetLayoutInfo.
Navyše je výstup použitý pri vytváraní DescriptorSet v rámci funkcie vkAllocateDescriptorSets(), konkrétne jej štruktúry VkDescriptorSetAllocateInfo() a atribútu pSetLayouts=descriptorSetLayout. |
vkCreateDescriptorPool() |
Vytvorenie DescriptorPool
Výstup je použitý pri vytváraní DescriptorSet v rámci funkcie vkAllocateDescriptorSets(), konkrétne jej štruktúry VkDescriptorSetAllocateInfo() a atribútu descriptorPool= vkCreateDescriptorPool(). |
vkUpdateDescriptorSets() |
Zápis konkrétnych DescriptorSet: dstBinding=0 – UBO (nepoužité v rámci aplikácie) dstBinding=1 – Sampler
Práve v rámci štruktúry VkDescriptorImageInfo(), ktorá je vstupom pre vkUpdateDescriptorSets(), sú použité textureImageView a textureSampler, ktoré sme vytvorili v časti nahrávania a prípravy textúry. |
Posledné dva kroky, ktoré musíme vykonať na úspešnú aplikáciu textúrovania, sú nasledujúce:
- Pripojiť DescriptorSet k príslušnému commandBuffer prostredníctvom funkcie vkCmdBindDescriptorSets().
- Upraviť obsah Fragment Shader tak, aby obsahoval lokálnu uniform premennú typu sampler2D a aby sa údaje načítané z textúry použili na výpočet výslednej farby fragmentu. K tomuto výpočtu, samozrejme, musíme ako vstup doplniť príslušné textúrovacie súradnice.
Obr. 2 Grafický výstup aplikácie PyGLSL v2.1
Zobrazit Galériu