漫談CG Kit中的材質系統

通用材質系統介紹

材質系統是一個實時渲染引擎很是重要的部分,它使得開發者可以很是便捷地設計出具備真實感的場景和角色。一個好的材質系統能夠提升引擎的易用性,並能夠方便的擴展渲染效果,來提高渲染質量和效率。java

材質系統需求

圖形引擎一般須要支持不一樣的渲染效果,一個優秀的材質系統一般要支持多Pass渲染管線和自定義Shader模板,因爲渲染效果的複雜多樣性會致使Shader數量大幅增長,這樣會形成Shader文件冗餘,所以材質系統要提供一套Shader複用的機制。同時,市面上各硬件廠商對圖形API的支持程度不一樣,受限於硬件水平的差別,材質系統也要兼容中低高端硬件。綜上所述,通用材質系統須要知足如下需求:
多Technique:材質中包含多個實現方案,這樣在進行高中低端機適配或實現不一樣材質效果時,咱們能夠方便進行材質更替。
多Pass:對於複雜繪製效果,單次繪製沒法實現,經常包含多個Pass的渲染。
自定義Shader:減小Shader數量,提供Shader複用機制。
模板 + 實例:材質是一個模板,經過對某一個材質進行實例化,指定不一樣的數據和貼圖,就可讓物體表現出不一樣的顯示效果。git

材質數據

材質描述了場景中物體與光照進行交互的過程,本質上它是指可以描述一個物體顯示外觀的一系列數據,它包括幾個方面:github

  • 着色模型(Shading Model):着色器的組合,決定了材質的參數與光照參數如何被處理,從而合成最終的顏色。好比最基本的着色模式爲:Surface Color =  Diffuse + Specular + Emissive + Ambient。
  • 渲染狀態:好比剔除模式(正面、背面、不剔除),混合模式(開啓,關閉),混合因子,深度測試,模板測試等。
  • 混合模式(Blend Mode):決定了幾何對象渲染完成後如何與場景中的其餘物體進行疊加,混合模式會影響對象的繪製順序,混合模式的渲染次序從先到後是:不透明(Opaque) > 蒙版(Masked) > 半透明(Translucent) > 疊加(Additive) > 相乘(Modulate)。
  • 參數: Shader中使用到的Uniform參數,好比紋理貼圖,採樣器,顏色因子,相機參數,光源參數,Pass間的混合參數等。

材質模板

材質文件就是將上述的材質數據進行合理的組織,方便應用開發者使用,一般材質文件被劃分爲三個重要的模塊:算法

  • Defines:宏列表,定義Shader宏有什麼值。
  • Properties:定義Shader中參數的值。
  • Technique:Pass列表,定義渲染用的狀態和Shader文件。

總結下來,一個材質模板文件應該是相似這樣的一個結構:json

Material 「ForwardPbr」 {
Properties {
	Color(「Color」,Color)=(1,1,1,1)
	SpecularColor(「SpecularColor」,Color)=(1,1,1,1)
	Gloss(「Gloss」,Range(8.0,256))=20
}
     Technique {
         Pass {
Blend One One
CullMode None
SkinningEnable true
Shader 「ForwardPbr.vert」
Shader 「ForwardPbr.frag」
}
         Pass{}
     }
     Technique {
          Pass{}
          Pass{}
     }
}

CGKit的材質系統

圖形引擎中提到的材質貼圖和物體顏色,高光計算,ALPHA混合、紋理過濾、裁剪模式等,在Vulkan中大多數由渲染管線控制。緩存

Vulkan圖形渲染管線介紹

Vulkan中的圖形管線決定了頂點數據如何被程序加工與處理,以及幾何對象的渲染順序,提交到設備的狀態控制值,着色器模型,是圖形引擎的核心模塊,Vulkan的圖形管線包括如下幾個部分(其中Vulkan經過Pipeline State Object進行狀態管理):數據結構

在圖形引擎或遊戲引擎中,咱們一般用材質文件來描述上述PSO狀態數據,Shader數據和貼圖數據,經過各類變換操做,最終將網格使用到的頂點數據轉化爲屏幕空間像素。材質決定了應用最後的展現效果和圖像質量。架構

CGKit材質系統介紹

CGKit的材質系統一樣也要知足通用材質系統的需求,支持多Technique、多Pass和自定義Shader。數據結構和算法

CGKit材質系統架構圖和類ide

CGKit的材質系統主要由如下類組成,咱們簡短介紹下它們的功能:
Material:包含了建立一個材質須要的全部資源,包括屬性定義,Shader文件定義,紋理貼圖,渲染狀態設置,由多個Technique組成。
PipelineState:Vulkan的PSO的封裝,包括了管線中的全部狀態。
ShaderProgram:表示渲染一個模型用到的全部Shader,負責把glsl編譯成SPIRV,並反射出全部的ShaderResource。
ShaderResource:經過Shader反射系統獲取的Shader資源,能夠獲取到資源的名字,位置等信息。
DescriptorSetLayout:定義了Shader中的資源與DescriptorSet的映射。
PipelineLayout:管理一組描述符集合佈局。
DescriptorSet:管理一組Shader資源。
RenderPipeline:用於渲染的Vulkan管線。
MaterialInstance:根據Material文件建立材質實例,會根據Material文件建立DescriptorSetLayout,DescriptorSet和RenderPipeline。
它對應的架構圖以下所示:

CGKit材質模板

CGKit使用json文件格式定義材質模板,材質文件描述僞代碼以下:

「Material」 : {
「basePath」 : 「material/ ForwardPbr.cgmat」,
「Properties」 : [{
「name」 : 「Color」,
「type」 : 「vec4」,
「value」 : 「1,1,1,1」
},
{
「name」 : 「albedo」,
「type」 : 「texture」,
「value」 : 「models/chip/chip_albedo.png」
},
{
「name」 : 「normal」,
「type」 : 「texture」,
「value」 : 「models/chip/chip_normal.png」
}]
     「Techniques」 : [{
     	「Pass」:[{
"rasterizationState": {
"cullMode": " NONE"
},
"depthStencilState": {
   	"depthTestEnable": true,
     "depthWriteEnable": true
},
「SkinningEnable」 : true,
"shader": [{
"type": "SHADER_STAGE_TYPE_FRAGMENT"
          "uri": "shaders/basic_pbr.frag",
     }],
}]
    }]
}

CGKit自定義Shader

材質系統中最重要的一塊就是Shader文件的配置,實現Shader的自定義須要完成如下功能:

  • Shader編譯;
  • Shader代碼複用;
  • Shader拼接;
  • Shader反射;
  • Shader參數更新;

Shader編譯

Shader只是一段可執行的彙編代碼,咱們不管是使用GLSL、HLSL、CG,或者使用Unity的Unity Shader,最終提交給GPU時,都須要將這些高層實現語言編譯成二進制的彙編語言。
CGKit的圖形API是Vulkan,而Vulkan使用的是SPIRV格式的Shader,咱們經過KhronosGroup提供的Glslang能夠將GLSL、HLSL編寫的Shader代碼編譯成SPIRV中間代碼。CGKit使用Glslang將GLSL轉換稱爲SPIRV:

External/`uname -s`/bin/GlslangValidator -H -o Asset/Shaders/Vulkan/pbr_ps.spv Asset/Shaders/Vulkan/pbr.frag

Shader代碼複用

不一樣的渲染效果須要不一樣的Shader實現,每一個Shader徹底獨立輸入的方式會形成Shader文件大量的冗餘,CGKit提供了一套Shader代碼複用的機制,經過將Shader進行模塊劃分並增長預處理宏來減小Shader數量。
鑑於Shader存在大量通用的數據結構及函數,經過對Shader進行合理模塊劃分,能夠達到Shader代碼複用的功能。好比咱們對不一樣的材質效果進行整理,找出它們數據結構之間的共性,抽取通用部分放在獨立的glsl文件裏,而後將剩餘獨有的部分保留在各自的文件裏。
一般咱們會將一些常量數據,如燈光,MVP矩陣,相機參數,材質貼圖(如陰影圖,PBR材質模型貼圖)放在cbuffer.glsl文件中。一樣的會將一些通用算法,如求交函數,僞隨機函數,插值函數,光照陰影計算,PBR中的各類GGX計算函數放在一個functions.glsl文件中。
爲了複用Shader的數據結構和算法,CGKit在Shader中定義了預處理宏,經過在材質文件中開啓或關閉這些宏來動態啓用或關閉Shader代碼,達到了減小Shader文件數量的目的。例如咱們能夠動態開啓和關閉一些渲染效果,如光照,陰影,霧效等等。
由於要動態開啓和關閉宏,CGKit經過Glslang對Shader實時編譯,爲避免實時編譯增長Shader的加載時間,CGKit同時也提供了Shader緩存機制。

Shader拼接

CGKit使用GLSL Shader,因爲GLSL語言不支持#include預編譯命令,咱們須要用命令行工具把不一樣模塊的Shader文件從新組合起來,造成一個完整的GLSL Shader:

cat Asset/Shaders/cbuffer.glsl Asset/Shaders/functions.glsl pbr_ps.glsl > Asset/Shaders/Vulkan/pbr.frag

Shader反射

對於Shader裏面的符號變量,如uniform buffer,texture sampler,push constant,specialization constant,CGKit須要與這些符號變量進行交互,經過材質系統設置或更新它們的值,所以,咱們須要經過一套反射機制獲取到對應變量的name,set,bind,location等信息。
SPIRV-Cross提供了一套Shader的反射機制,CGKit首先經過Glslang將指定的GLSL格式的Shader代碼編譯成SPIRV,再經過SPIRV Reflection將SPIRV code裏面的符號變量所有反射出來。

Shader參數更新
Shader中的數據流主要包括兩部分:

  • vertex、index buffer等mesh提供的數據:這部分屬於Shader固定輸入,在建立管線的時候指定好頂點格式聲明,在渲染的時候綁定相應的頂點,索引buffer便可。
  • uniform buffer,texture sampler:這部分輸入須要CGKit經過Descriptor Set進行設置和更新。經過SPIRV-Cross的Shader反射,咱們能夠獲取到對應資源的名字,位置信息。由於咱們是經過材質文件來更新這些Shader資源的,因此咱們在材質文件裏面指定了這些參數,經過嚴格按名字匹配來更新Shader資源。所以咱們建議用戶儘可能統一Shader裏面的參數名字,並定義在公共頭文件中。

CGKit材質建立

CGKit根據材質模板生成材質實例,生成材質實例的過程實際上是自動化建立Vulkan紋理貼圖,描述符集合佈局,管線佈局,描述符集合,渲染管線的過程。
CGKit加載材質的時候根據Shader反射填充好描述符集合,在更新Shader的uniform buffer,texture sampler時,會相應地更新DescriptorSet,在提交繪製命令時,只須要綁定不一樣的DescriptorSet就能切換不一樣的資源。

建立DescriptorSetLayout

建立描述符集合佈局分兩步:

1. 經過Shader反射機制獲取ShaderResource:材質文件裏面定義了一個渲染對象須要用到的全部Shader,咱們經過Shader的反射機制將Shader文件裏面的符號變量資源反射出來,做爲一個Shader資源存放在ShaderProgram類,Shader資源包含了資源的名字以及所屬的描述符集合的索引和綁定槽,相似下面的結構體:

struct ShaderResource {
    String name = 「」;
    ShaderStageFlag stageFlag = SHADER_STAGE_VERT;
    ShaderResourceType type; // 資源類型
    u32 set = 0;
    u32 binding = 0;   // binding
    u32 arraySize = 0;   // 對應VkDescriptorSetLayoutBinding的descriptorCount
    u32 offset = 0;  // for push constants
    u32 size = 0;   // for push constants
    u32 constantID = 0;   // for specialization constants
    u32 location = 0;
    u32 inputAttachmentIndex = 0;
    u32 vecSize = 0;
    u32 columns = 0;
};

其中Shader中資源的類型以下:

enum ShaderResourceType {
    SHADER_RESOURCE_TYPE_INPUT,
    SHADER_RESOURCE_TYPE_OUTPUT,
    SHADER_RESOURCE_TYPE_BUFFER_UNIFORM,
    SHADER_RESOURCE_TYPE_BUFFER_STORAGE,
    SHADER_RESOURCE_TYPE_INPUTATTACHMENT, 
    SHADER_RESOURCE_TYPE_IMAGE,
    SHADER_RESOURCE_TYPE_IMAGE_SAMPLERR,
    SHADER_RESOURCE_TYPE_IMAGE_STORAGE,
    SHADER_RESOURCE_TYPE_SAMPLER, 
    SHADER_RESOURCE_TYPE_PUSH_CONSTANT,   // for pipeline layout creating
    SHADER_RESOURCE_TYPE_SPECIALIZATION,  // for Shader stage creating
    SHADER_RESOURCE_TYPE_All
};

2. 根據ShaderResource建立描述符集合佈局:經過Shader反射後ShaderProgram類最終擁有不一樣的描述符集編號,及其對應的ShaderResource。咱們根據ShaderResource生成DescriptorSetLayoutBinding,固然,要去掉四種沒有綁定槽的資源(Input,Output,PushConstant,SpecializationConstant)。而後根據DescriptorSetLayoutBinding信息生成DescriptorSetLayout。在DescriptorSetLayout類中,咱們能夠根據資源的名字獲得它的綁定槽,以及對應的描述符佈局綁定信息。

建立DescriptorSet

建立描述符集合分兩步。
1. 建立DescriptorPool:規定好每一個描述符池可以分配的最大描述符集合個數,假定爲16,從DescriptorSetLayout中獲取全部的Bindings,統計描述符的數量,用這個數與最大描述符集合個數相乘,獲得描述符池的大小,依據這個大小建立描述符池。描述符池會允許建立16個描述符集合,若是描述符集合的數量超過了16,則從新分配一個描述符池。

2.根據DescriptorSetLayout和DescriptorPool生成描述符集合:同類型的描述符集合會對應多個描述符池。

建立PipelineLayout

根據DescriptorSetLayout和Shader中的push constant資源建立管線佈局。

建立RenderPipeline 

從PipelineState中獲取管線的狀態信息和Shader信息,從mesh中拿到頂點佈局信息,建立管線。

CGKit材質應用

材質資源一旦被建立,就能夠添加到各類渲染組件中進行渲染。若是要修改材質表現效果,咱們只須要在運行時動態修改材質參數,包括渲染狀態,紋理參數,Shader參數,Shader文件,就能夠達到目的,不須要關注材質系統底層作了什麼事情。

CGKit材質系統優化

材質排序

咱們都知道,像Vulkan這樣的圖形接口每設置一次GPU狀態的時候,都會有必定的開銷。爲保證渲染流暢,咱們要儘可能減小狀態切換來下降開銷。
在CGKit中,經過對幾何對象的材質進行分組排序,將類似的材質排在一塊兒能夠減小渲染流程中的狀態切換,從而達到提升渲染效率的目的,分組的順序以下:

  • 先按混合模式分組,順序爲:不透明 > 蒙版 > 半透明 > 疊加 > 相乘;
  • 混合模式分組後,每組中的對象再按着色模型分組;
  • 着色模型分組後,每組對象再按紋理分組;
  • 紋理分組後,再按其餘參數分組。

即分組的優先級爲:混合模式 > 着色模型 > 紋理對象 > 其餘參數。

調整資源更新頻率

Shader資源在渲染時須要不斷更新,並且每一個資源的更新頻率會不同。應用須要指定每一個資源的更新頻率,按照資源的更新頻率能夠把Shader資源分爲三種類型:

  • Static:只要綁定了就不會改變的資源,例如相機屬性(包括相機位置,視圖矩陣,投影矩陣),光照屬性(光源類型,光源位置,光源方向,光源顏色,光照強度,光源衰減因子),屏幕寬高,陰影Shadowmap等全局常量。
  • Mutable:至關於材質的更新頻率,例如漫反射貼圖、法線貼圖,自發光貼圖,切換一個材質就會更新一次。
  • Dynamic:隨時均可能更新的資源,如模型的世界矩陣。

預先建立管線

Vulkan中的圖形渲染管線幾乎不可改變,若是須要更改Shader,混合,光柵化等狀態的設置,則必須從新建立管線。所以咱們能夠預先建立好全部的管線,這樣管線的操做都是提早知道的,則能夠經過驅動程序更好地優化它。

緩存機制

隨着場景複雜度的增長,材質文件數量會變多,與材質建立相關的資源會大量重複,咱們能夠將這些資源緩存起來,避免資源的重複建立並加快資源的加載和建立。與材質建立相關的資源主要有Texture,Shader,DescriptorSetLayout,PipilineLayout,DescriptorSet,enderPipiline,咱們能夠將這些資源都緩存起來,加載資源的時候,先從緩存裏面查找,找不到,再從磁盤中加載和建立。

>>訪問華爲圖形計算服務官網,瞭解更多相關內容
>>獲取華爲圖形計算服務開發指導文檔
>>華爲HMS Core官方論壇
>>華爲圖形計算服務開源倉庫地址:GitHubGitee

點擊右上角頭像右方的關注,第一時間瞭解華爲移動服務最新技術~

相關文章
相關標籤/搜索