前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >UE(2):材质着色器

UE(2):材质着色器

作者头像
Peter Lu
发布于 2022-12-22 06:57:00
发布于 2022-12-22 06:57:00
1.9K00
代码可运行
举报
文章被收录于专栏:LETLET
运行总次数:0
代码可运行

有点Blue --- 《沙滩》

上一篇UE(1):材质系统整体介绍了材质的三要素UMaterialFMaterialFMaterialRenderProxy以及相互之间的逻辑关系,未涉及实现细节,比如材质和Shader之间的关联,以及其在渲染管线中的使用方式。在上篇基础上,深入了解Material和Shader之间编译(Complication)相关的内容,形成了这篇学习总结。

Shader Object

Shader三要素

UE的着色器主要有三个类:

  • FShader
    • 编译后的着色器对象
  • FShaderParameter
    • 着色器中需要绑定的参数
  • FShaderType
    • 用于序列化,编译以及缓存一个着色器对象

FShader

UE编译HLSL后会创建一个FShader对象,FShader是一个基类,主要的两个子类是:

  • FGlobalShader
    • 全局着色器,共享一个实例,用于渲染固定的几何对象和不需要材质的内容,比如后处理
  • FMaterialShader
    • 该着色器绑定了一个材质,如果该着色器也绑定了FVertexFactory(几何内容,UE的网格类型),则对应FMeshMaterialShader,继承自FMaterialShader

FShaderParameter

FShaderParameter是着色器对象中对应的参数,主要有:

  • FShaderParameter:基本的uniform变量,比如float1~4
  • FShaderResourceParameter:Shader资源,比如纹理,采样
  • FRWShaderParameter:UAV或SRV资源,具体不懂
  • FShaderUniformBufferParameter:UniformBuffer变量

FShaderParameter中的成员变量有BufferIndex,BaseIndex,NumBytes,标识了该资源在GPU中的入口(索引)和字节数,等同于OpenGL中的glGetUniformLocation或DX11中的GetResourceBindingDesc

FShaderType

FShader+ FShaderParameter建立了GPU中的着色器资源以及相关参数的索引;而一个FShader对应一个FShaderType,比如,外部通过FShaderType管理对应的FShader对象,比如FShaderType的方法ConstructCompiledType实现创建一个FShader对象。

材质中引用的着色器对象是FMeshMaterialShader,FShaderUniformBufferParameterFMeshMaterialShaderType,下面我们重点介绍这三个类的相关作用。

注:在FShader类中,变量使用了LAYOUT_FIELD宏封装,属于C++反射系统范畴。

Shader Permutation

UE中通过预处理的方式来创建着色器代码的特化,HLSL代码中会采用C风格的宏,比如#if,#define 不同的宏定义会形成不同的逻辑分支,UE会排列组合各种不同的逻辑情况(比如开启环境光或关闭,开启Fog或关闭,这样会有四种逻辑组合),每种分支对应生成一个shader变体。UE把这个过程描述为Shader Permutation。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// MaterialShaderType.h
#define IMPLEMENT_MATERIAL_SHADER_TYPE(TemplatePrefix,ShaderClass,SourceFilename,FunctionName,Frequency)

上面的宏IMPLEMENT_MATERIAL_SHADER_TYPE将一个C++类ShaderClass绑定一个HLSL文件SourceFilename,函数的入口是FunctionName,而Frequency标识着色器的类型,比如顶点SF_Vertex,片元SF_Pixel等,着色器类型对应EShaderFrequency枚举。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// BasePassRendering.cpp
#define IMPLEMENT_BASEPASS_VERTEXSHADER_TYPE(LightMapPolicyType,LightMapPolicyName) \
typedef ... ; \
IMPLEMENT_MATERIAL_SHADER_TYPE() ;

接着,对于一个给定的LightMapPolicyTypeLightMapPolicyNameIMPLEMENT_BASEPASS_VERTEXSHADER_TYPE则将不同的顶点类(Class)vertex,hulldomain绑定不同的HLSL代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// BasePassRendering.cpp
#define IMPLEMENT_BASEPASS_PIXELSHADER_TYPE

采用同样的思路,将片元类绑定对应的HLSL代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// BasePassRendering.cpp
#define IMPLEMENT_BASEPASS_LIGHTMAPPED_SHADER_TYPE \
IMPLEMENT_BASEPASS_VERTEXSHADER_TYPE \
IMPLEMENT_BASEPASS_PIXELSHADER_TYPE

最后,针对不同的LightMap Policy,IMPLEMENT_BASEPASS_LIGHTMAPPED_SHADER_TYPE宏创建了三种不同的顶点类和一种片元类可能的组合对应的Pass。

Shader Permutation

上图是整体的逻辑关系,IMPLEMENT_BASEPASS_LIGHTMAPPED_SHADER_TYPE的价值就是针对不同的LightMap,不同的顶点和片元组合,创建不同的BasePass对象,每个对象通过类方法ModifyCompilationEnvironment确定该组合下,HLSL中各种环境变量值。该实现方式基于C++ Template的能力,因此做到了在材质编辑阶段执行,并缓存到Shader Map。好处是该阶段属于GPU code的编译期,host code(C++)的运行时,保证了性能;缺点是可怕的脚本编译时间,一个材质资产需要考虑各种组合的逻辑分支,增加的编译的计算量。

注:Base Pass是UE中渲染管线的概念,对应延迟渲染中的第一个Pass。编译期是将HLSL编译成bytecode,DXC(DX11)格式

这种设计的另一个缺点是C++和GPU code分离,同一个参数需要针对C++和GPU分别提供两个变量,增加了代码的复杂度,用过CUDA的人应该能够理解unified shader[1]的设计思想,如何保证host和GPU代码混写,这也是一种解决方案。

UniformBuffer

Shader中除了不同逻辑下对应的函数,另一个主角就是Uniform参数,该参数需要实现:

  • Create - 创建该UniformBuffer资源(编译中)
  • Binding - 将该资源和着色器中的参数索引绑定(编译后)
  • Update - 更新该资源(渲染阶段)

一个UniformBuffer对应一个C++的结构体:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#ifndef __UniformBuffer_Material_Definition__
#define __UniformBuffer_Material_Definition__
cbuffer Material
{
    half4 Material_VectorExpressions[2];
    half4 Material_ScalarExpressions[1];
} Material = {Material_VectorExpressions,Material_ScalarExpressions,};
#endif

UniformBuffer Struct

如上图,分别对应三步:

  • CreateBufferStruct中创建UniformBuffer结构体
  • 根据这个结构体,在ModifyCompilationEnvironment中将结构体对应的字符串插入到HLSL代码片段中
  • 最终完整的HLSL代码iniclude所有的UniformBuffer文件(ush),进入编译阶段

HLSL代码的编译是通过CompileShader方法(ShaderFormatD3D.cpp),里面主要有两个函数:

  • D3DCompileFunc:编译HLSL代码
  • D3DReflectFunc:获取着色器中Uniform变量对应的索引

上篇介绍了编译HLSL的流程,编译成功后,会执行ExtractParameterMapFromD3DShader方法,该方法会调用D3DReflectFunc,将返回的结果(索引值)保存到FShaderCompilerOutput:FShaderParameterMap结构体中,而编译后的返回值是一个FShaderCompilerOutput对象。

编译结束后,会触发FMaterialShaderMap::ProcessCompilationResultsForSingleJob,这里,会根据FShaderCompilerOutput内容创建对应的FMeshMaterialShader对象,而对象中UniformBufer的索引值BaseIndex对应该变量在着色器中的位置。

我们在编译阶段完成了Create和Binding,在渲染线程中则需要对该变量的更新,这分为两步,先在CPU内存中更新内存块,然后将内存上传到GPU对应的Uniform Buffer(DX11中的ConstantBuffer)。上篇中我们提到,在FMaterialRenderProxy::EvaluateUniformExpressions中实现了Update:

  • FUniformExpressionSet::FillUniformBuffer:将材质中的Uniform值写入到TempBuffer指针对应的CPU内存;
  • 如果GPU中还没有创建该变量,则调用RHICreateUniformBuffer,创建对应的GPU资源;
  • 然后调用RHIUpdateUniformBuffer将CPU内存拷贝到GPU上。

这样,FMaterialShaderMap::Compile编译阶段创建了UniformBuffer对应的HLSL代码片段,FMaterialShaderMap::ProcessCompilationResultsForSingleJob编译完成后创建了UniformBuffer的GPU资源,以及和着色器中的索引映射,FMaterialRenderProxy::EvaluateUniformExpressions渲染阶段则会判断该变量在host端是否变化,如果变化,则更新到GPU端。

HLSL Cross Compiler

Pixar在1988年5月发布的RenderMan接口规范3.0版中向公众介绍了'Shader'[2]这词。UE的shader是基于HLSL语法,但UE本身是跨平台的,因此,需要实现HLSL生成其他平台对应着色器的能力。

HLSL Cross Compiler

这里,主要给出了OpenGL系列和Vulkan平台对应的流程。UE主要依赖ShaderConductorHLSLccglslang。在HLSLcc中,使用了Mesa's IR解析HLSL的Abstract Syntax Tree(AST)并优化。

总结

本篇是上一篇材质的延续,涉及到材质Shader编译中的具体技术细节,包括shader permutation的设计实现,uniform buffer的实现以及对应的DX11接口,最后是UE跨平台编译HLSL的Pipeline。

突然觉得,OpenGL的时代已经过去了,为什么DX11之后可以是DX12,为什么OpenGL之后只能是Vulkan。想得却不可得,你奈人生何。So blue~

参考资料

[1]

unified shader: https://dl.acm.org/doi/10.1145/354386

[2]6

Shader wiki: https://en.wikipedia.org/wiki/Shader

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-08-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 LET 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
UE4/5 usf、ush UniformBuffer的黏合过程
第一次写UE shader大概都会疑惑,usf、ush里面的那些“View“、“BasePass”Uniform变量到底是哪来的?好像没有include怎么直接写一个字符串就可以了,或者莫名其妙的include怎么也找不到的/Engine/Generated/UniformBuffers/xxx,还有为啥c++定义了一下它们就能绑定上?这些到底是怎么魔法般运行的?
Bairuo
2023/03/13
1.7K0
UE4/5 usf、ush UniformBuffer的黏合过程
three.js 粒子效果(分别基于 CPU & GPU 实现)
前段时间做了一个基于 CPU 和 GPU 对比的粒子效果丢在学习 WebGL 的 RTX 群里,技术上没有多作讲解,有同学反馈看不太懂 GPU 版本,干脆开一篇文章,重点讲解基于 GPU 开发的版本。
万技师
2017/07/17
10.4K3
three.js 粒子效果(分别基于 CPU & GPU 实现)
Unity通用渲染管线(URP)系列(二)——Draw Calls(Shaders and Batches)
要绘制物体,CPU需要告诉GPU应该绘制什么和如何绘制。通常我们用Mesh来决定绘制什么。而如何绘制是由着色器控制的,着色器实际上就是一组GPU的指令。除了Mesh之外,着色器还需要很多其他的信息来协同完成它的工作,比如对象的transform矩阵和材质属性等。
放牛的星星
2020/12/11
6.7K0
Unity通用渲染管线(URP)系列(二)——Draw Calls(Shaders and Batches)
Threejs进阶之十五:在Thereejs 使用自定义shader
效果分析: 要实现上述效果,我们需要两张图片,作为纹理贴图,使其图案产生明暗效果;然后通过定义ShaderMaterial对象通过自定义Shader实现上述效果;后面代码中会进行详细分析; 这里我们先介绍下基础知识
九仞山
2023/10/14
2K0
Threejs进阶之十五:在Thereejs 使用自定义shader
Unity Shader基础
Unity中,我们需要配合使用材质和unity shader,它们的关系和流程是:
Zero Two
2024/07/14
1910
Unity Shader基础
【OpenGL ES】OpenGL ES 2.0 -- 制作 3D 彩色旋转三角形 - 顶点着色器 片元着色器 使用详解
最近开始关注OpenGL ES 2.0 这是真正意义上的理解的第一个3D程序 , 从零开始学习 .
韩曙亮
2023/03/27
1.6K0
【OpenGL ES】OpenGL ES 2.0 -- 制作 3D 彩色旋转三角形 - 顶点着色器  片元着色器 使用详解
OPengL ES _ 着色器_实战1
1.由于着色器编译 链接过程较为繁琐,我封装了一下,文件名为"OSShaderManager.h" 和"OSShaderManager.m" 如果你对着色器程序加载过程不熟悉请参考OpenGL ES _ 着色器 _ 程序 代码:
酷走天涯
2018/09/14
5650
OPengL ES _ 着色器_实战1
1.opengl绘制三角形
下面,你会看到一个图形渲染管线的每个阶段的抽象展示。要注意蓝色部分代表的是我们可以注入自定义的着色器的部分。
诺谦
2020/09/27
1.3K0
1.opengl绘制三角形
Unity3D学习笔记3——Unity Shader的初步使用
在上一篇文章《Unity3D学习笔记2——绘制一个带纹理的面》中介绍了如何绘制一个带纹理材质的面,并且通过调整光照,使得材质生效(变亮)。不过,上篇文章隐藏了一个很重要的细节——Unity Shader。Shader(着色器)是渲染管线中可被用户编程的阶段,依靠着色器可以控制渲染管线的细节。现代图像渲染技术,都把Shader封装成与Material(材质)相关的组件。所以这篇文章,我们就初步学习下在Unity中使用Shader。
charlee44
2021/08/06
4.3K0
Unity3D学习笔记3——Unity Shader的初步使用
three.js 着色器材质之初识着色器
说起three.js,着色器材质总是绕不过的话题,今天郭先生就说一说什么是着色器材质。着色器材质是很需要灵感和数学知识的,可以用简短的代码和绘制出十分丰富的图像,可以说着色器材质是脱离three.js的另一块知识,因此它十分难讲,我们只能在一个一个案例中逐渐掌握着色器语言的使用技巧。
郭先生的博客
2020/08/31
3.2K0
three.js 着色器材质之初识着色器
UE(1):材质系统
在写这篇文章时,我在公众号下搜关键字“材质”,总计95篇原创中有33篇提到了材质,可见,材质是一个怎么说都说不完的内容,神奇的脑回路让我忍不住想听一下这首我们的歌,于是乎,宝贵的三十分钟成为了过去式。
Peter Lu
2022/12/22
3.1K0
UE(1):材质系统
《Unity Shader入门精要》笔记:基础篇(1)
小插曲:看到具体数学冷汗直冒,细一看,嗷不是那本书呀。《具体数学》:别听《Unity Shader入门精要》里面说什么程序员的三大浪漫,真程序员就该手撕《具体数学》!
[Sugar]
2022/09/21
1.1K0
OPenGL ES _ 着色器_实战2
1.使用AVPlayer 获取视频每一帧的YUV 像素数据 2.通过CoreVideo 框架中的几个方法,将Y分量和UV 分量进行分离 3.创建着色器,对Y分量和UV 分量进行采样. 4.在着色器中,将YUV 转换为RGB 5.计算视口的位置,分别进行渲染.
酷走天涯
2018/09/14
6930
OPenGL ES _ 着色器_实战2
3.QOpenGLWidget-通过着色器来渲染渐变三角形
在上章2.通过QOpenGLWidget绘制三角形,我们学习绘制三角形还是单色的,本章将为三角形每个顶点着色.
诺谦
2020/10/26
1.1K0
3.QOpenGLWidget-通过着色器来渲染渐变三角形
OpenGLES-02 绘制基本图元(点、线、三角形)
在绘制之前,我们需要了解下面的知识: 一、渲染管线 下图中展示整个OpenGL ES 2.0可编程渲染管线 渲染管线.png 图中Vertex Shader和Fragment Shader 是可编程
清墨
2018/05/07
2.4K0
OpenGLES-02 绘制基本图元(点、线、三角形)
基础渲染系列(二)——着色器
这是渲染系列的第二篇文章,第一篇讲述的是矩阵,这次我们会写我们的第一个Shader并且导入一张纹理。
放牛的星星
2020/07/10
4.3K0
基础渲染系列(二)——着色器
Unity基础教程系列(新)(五)——计算着色器(Rendering One Million Cubes)
这是关于学习使用Unity的基础知识的系列文章中的第五篇。这次,我们将使用计算着色器显著提高图形的分辨率。
放牛的星星
2021/03/10
4.2K0
Unity基础教程系列(新)(五)——计算着色器(Rendering One Million Cubes)
OpenGL ES 着色器语言丨音视频基础
我们在音视频基础主题专栏中关于渲染的文章里介绍了 OpenGL 和 OpenGL ES 的基础理论知识和相关 API,其中涉及到了一些简单 Shader 的使用,而编写 Shader 则需要用到 OpenGL Shader Language(后面简称 GLSL)和 OpenGL ES Shading Language(后面简称 GLSL ES)。
关键帧
2023/02/14
1.7K0
OpenGL ES 着色器语言丨音视频基础
【C++】OpenGL:着色器基础与GLFW创建三角形示例
另外,在图形渲染中,要记住2D坐标和像素也是不同的,2D坐标精确表示一个点在2D空间中的位置,而2D像素是这个点的近似值,2D像素受到你的屏幕/窗口分辨率的限制。
DevFrank
2024/07/24
4100
【C++】OpenGL:着色器基础与GLFW创建三角形示例
Qt官方示例-QML创建简单的自定义材质
该示例使用Scene3D渲染将使用自定义材质的场景。场景包含一个使用自定义材质的平面模型。
Qt君
2020/05/12
1.4K0
推荐阅读
相关推荐
UE4/5 usf、ush UniformBuffer的黏合过程
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验