通用材质系统介绍

材质系统是一个实时渲染引擎非常重要的部分,它使得开发者能够非常便捷地设计出具有真实感的场景和角色。一个好的材质系统可以提高引擎的易用性,并可以方便的扩展渲染效果,来提升渲染质量和效率。

材质系统需求

图形引擎通常需要支持不同的渲染效果,一个优秀的材质系统通常要支持多Pass渲染管线和自定义Shader模板,由于渲染效果的复杂多样性会导致Shader数量大幅增加,这样会造成Shader文件冗余,因此材质系统要提供一套Shader复用的机制。同时,市面上各硬件厂商对图形API的支持程度不同,受限于硬件水平的差异,材质系统也要兼容中低高端硬件。综上所述,通用材质系统需要满足以下需求:
多Technique:材质中包含多个实现方案,这样在进行高中低端机适配或实现不同材质效果时,我们可以方便进行材质更替。
多Pass:对于复杂绘制效果,单次绘制无法实现,常常包含多个Pass的渲染。
自定义Shader:减少Shader数量,提供Shader复用机制。
模板 + 实例:材质是一个模板,通过对某一个材质进行实例化,指定不同的数据和贴图,就可以让物体表现出不同的显示效果。

材质数据

材质描述了场景中物体与光照进行交互的过程,本质上它是指能够描述一个物体显示外观的一系列数据,它包括几个方面:

  • 着色模型(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文件。

总结下来,一个材质模板文件应该是类似这样的一个结构:

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材质系统架构图和类

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;   // bindingu32 arraySize = 0;   // 对应VkDescriptorSetLayoutBinding的descriptorCountu32 offset = 0;  // for push constantsu32 size = 0;   // for push constantsu32 constantID = 0;   // for specialization constantsu32 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 creatingSHADER_RESOURCE_TYPE_SPECIALIZATION,  // for Shader stage creatingSHADER_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官方论坛
>>华为图形计算服务开源仓库地址:GitHub、Gitee

点击右上角头像右方的关注,第一时间了解华为移动服务最新技术~

漫谈图形计算中的材质系统相关推荐

  1. 漫谈图形引擎中的材质系统

    通用材质系统介绍 材质系统是一个实时渲染引擎非常重要的部分,它使得开发者能够非常便捷地设计出具有真实感的场景和角色.一个好的材质系统可以提高引擎的易用性,并可以方便的扩展渲染效果,来提升渲染质量和效率 ...

  2. 关于游戏中的材质系统

    材质,这个词有各行各业都有自己的解释. 美工把物体贴图和物体颜色,高光等统称为材质.D3D和OPENGL这样的图形接口则把物体表面贴图单独叫做纹理,而把漫反射,高光等叫做材质. 而在游戏引擎或图形引擎 ...

  3. Cocos Creator 3D 材质系统:曲面效果如何实现?

    引言 前不久发布的 Cocos Creator 1.0.2 版本中正式加入了对 OPPO 小游戏.vivo 小游戏以及华为快游戏平台的支持,在诸多 Creator 3D 制作的小游戏案例中,<猪 ...

  4. 在一个请求分页系统中,假定系统分配给一个作业的物理块数为 3,并且此作业的页面走向为 2、3、2、1、5、2、4、5、3、2、5、2。试用 FIFO和 LRU 两种算法分别计算出程序访问过程中所发生

    页面置换算法 题目: 在一个请求分页系统中,假定系统分配给一个作业的物理块数为 3,并且此作业的页面走向为 2.3.2.1.5.2.4.5.3.2.5.2.试用 FIFO和 LRU 两种算法分别计算出 ...

  5. 基于蒙特卡洛概率潮流计算 在IEEE33节点系统中,由于风光出力的不确定性,利用蒙特卡洛生成风速和光照强度得到出力

    基于蒙特卡洛概率潮流计算 在IEEE33节点系统中,由于风光出力的不确定性,利用蒙特卡洛生成风速和光照强度得到出力,可得到每个节点的电压和支路功率变化,网损和光照强度. ID:795064451977 ...

  6. 深耕图形领域,华为HMS Core图形计算服务提升图形应用表现

    自从华为HMS Core 5.0上线以来,华为持续提升图形图像开发领域的服务能力.其中,华为图形计算服务(简称"CG Kit")将计算机图形学中的前沿技术提供给开发者,并通过对计算 ...

  7. 读《环境光遮蔽技术在图形图像中若干关键技术的研究》总结-其一

    末尾附文章引用 文章架构: 开篇先写Abstract,对本文章的研究内容进行了总结性概述: Abstract怎么写? 1.点名研究内容,研究意义,提出当下需要解决的问题. 2.讲本文章解决这些问题的思 ...

  8. PBR材质系统原理简介

    一.自然界材质 要学会使用PBR首先需要了解什么是PBR,需要从真实世界的这些PBR材质特有的属性拆分开来去了解他们,这样我们就需要了解光,物体表面材质以及光是如何与材质交互的.光包括了颜色,亮度,衰 ...

  9. 漫谈边缘计算(二):各怀心事的玩家

    前一篇文章(漫谈边缘计算(一):边缘计算是大势所趋)提到我对边缘计算的理解,认为边缘计算是在一定程度上弥补传统云计算的不足,相对于传统的全集中模式的云计算中心,边缘计算节点可以在现场终端侧,也可以在终 ...

最新文章

  1. java 属性自定义配置,将自定义FXML属性设置为自定义javafx组件的参数
  2. html显示本地磁盘 图片,手把手教你为本地磁盘增添背景图片(图解)
  3. topcoder srm 500 div1
  4. pyecharts 应用6 三维曲面图
  5. 社会工程学[Social Engineering]
  6. 2021浙江高考首考成绩查询,浙江2021选考成绩什么时候出成绩?附2021浙江学考成绩查询时间...
  7. 在.NET中实现观察者模式(3种技术)
  8. js默认点击一次_JavaScript初学者,一个小小的点击案例。
  9. 在Eclipse中使用JDBC访问MySQL数据库的配置方法
  10. 什么是Java分布式?
  11. html下拉菜单几种方式,html下拉菜单的实现方式
  12. 稠密检索模型的zero-shot能力究竟如何?
  13. 新鲜出炉,2022最新的bi工具排行
  14. 【团队绩效考核方案】命劫开发
  15. bzoj 3772: 精神污染 (主席树+dfs序)
  16. 移动端布局三种视口_什么是视口?移动端浏览器中的3种视口
  17. 同期及上期数据对比显示
  18. 杰里混响调MIC增益和深度【篇】
  19. Qt删除文件和文件夹
  20. SwitchHosts——便捷切换hosts

热门文章

  1. vue 复选框默认选中_vue中,radio或者checkbox如何默认选中
  2. 福建厦门双十计算机竞赛,进国家队!福建5名学生获清华北大保送资格
  3. 《赢在中国》对80后做人做事忠告
  4. Re: cnruby筹划办ruby的电子杂志
  5. word序号10以后的字体和序号间有很大的间隙
  6. Linux 调试之SysRq
  7. 二维码的特征定位和信息识别
  8. 西瓜书_1、符号学习、迁移学习、连接主义、假设空间、版本空间
  9. 【机器学习基础】机器学习的常用数学符号
  10. windows找不到文件cmd怎么办?