[PBS真的可以为所欲为] Unreal材质模型源码分析

2023-10-17 19:50

本文主要是介绍[PBS真的可以为所欲为] Unreal材质模型源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

http://blog.sina.com.cn/s/blog_76d02ce90102xr4x.html


unreal应该算是现在最实用的开源渲染引擎之一了


基于物理的渲染(PBS)用起来真的是为所欲为

这里决定花点时间研究下unreal低层的渲染代码,这里假设读者具有基本的渲染知识,至少知道BRDF是什么

引擎中的着色器代码都在Engine/Shaders中,不同版本之间会有些差别,不过不会差太多就是了

这里使用的版本是4.17

打开Engine/Shaders/Private文件夹,可以看见大量的.usf和.ush文件,这些就是unreal渲染的核心



两者的语法都类似GLSL或HLSL,区别是ush没有入口函数,只能被其他ush或usf来引用,而usf有入口函数,不可被应用。

整个Engine/Shaders/Private中有近300个文件,一行一行解释显然不现实,这篇决定优先分析引擎中material的源码

这部分源码主要在ShadingModels.ush中,会引用其他的BRDF.ush,下面来慢慢分析

首先是下面两个函数:
NormalText Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//  @param  DiffSpecMask  .r:  diffuse,  .g:specular  e.g.  float2(1,1)  for  both,  float2(1,0)  for  diffuse  only
float3  SurfaceShading(  FGBufferData  GBuffer,  float3  LobeRoughness,  float3  LobeEnergy,  float3  L,  float3  V,  half3  N,  uint2  Random  )
{
        switch(  GBuffer.ShadingModelID  )
        {
                case  SHADINGMODELID_UNLIT:
                case  SHADINGMODELID_DEFAULT_LIT:
                case  SHADINGMODELID_SUBSURFACE:
                case  SHADINGMODELID_PREINTEGRATED_SKIN:
                case  SHADINGMODELID_SUBSURFACE_PROFILE:
                case  SHADINGMODELID_TWOSIDED_FOLIAGE:
                        return  StandardShading(  GBuffer.DiffuseColor,  GBuffer.SpecularColor,  LobeRoughness,  LobeEnergy,  L,  V,  );
                case  SHADINGMODELID_CLEAR_COAT:
                        return  ClearCoatShading(  GBuffer,  LobeRoughness,  LobeEnergy,  L,  V,  );
                case  SHADINGMODELID_CLOTH:
                        return  ClothShading(  GBuffer,  LobeRoughness,  LobeEnergy,  L,  V,  );
                case  SHADINGMODELID_EYE:
                        return  EyeShading(  GBuffer,  LobeRoughness,  LobeEnergy,  L,  V,  );
                default:
                        return  0;
        }
}

float3  SubsurfaceShading(  FGBufferData  GBuffer,  float3  L,  float3  V,  half3  N,  float  Shadow,  uint2  Random  )
{
        float3  SubsurfaceColor  ExtractSubsurfaceColor(GBuffer);

        switch(  GBuffer.ShadingModelID  )
        {
                case  SHADINGMODELID_SUBSURFACE:
                        return  SubsurfaceShadingSubsurf ace(  GBuffer,  L,  V,  );
                case  SHADINGMODELID_PREINTEGRATED_SKIN:
                        return  SubsurfaceShadingPreinte gratedSkin(  GBuffer,  L,  V,  );
                case  SHADINGMODELID_TWOSIDED_FOLIAGE:
                        return  SubsurfaceShadingTwoSide d(  SubsurfaceColor,  L,  V,  );
                case  SHADINGMODELID_HAIR:
                        return  HairShading(  GBuffer,  L,  V,  N,  Shadow,  1,  0,  Random  );
                case  SHADINGMODELID_EYE:
                        return  EyeSubsurfaceShading(  GBuffer,  L,  V,  );
                default:
                        return  0;
        }
}

与引擎中的shading model完全一致



从函数中的switch中不难看出用途,这两个函数就定义了不同shading model使用的基础算法和subsurface算法

这两个函数在DeferredLightngCommon.ush中被调用,比如下面
NormalText Code 
1
2
float3  Shading  SurfaceShading(  GBuffer,  LobeRoughness,  1,  L,  V,  N,  Random  NoL;
Shading  +=  SubsurfaceShading(  GBuffer,  L,  V,  N,  1,  Random  );

就是直接把结果线性相加....

SurfaceShading

先看SurfaceShading函数,这个函数表示了material除了subsurface之外的全部内容,可以看出,除了Clear Coat,Cloth , Eye有自己独有的着色算法以外,其他的shading model都用的共通的StandardShading,那么就来看看StandardShading里是什么吧

NormalText Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
float3  StandardShading(  float3  DiffuseColor,  float3  SpecularColor,  float3  LobeRoughness,  float3  LobeEnergy,  float3  L,  float3  V,  half3  )
{
        float  NoL  dot(N,  L);
        float  NoV  dot(N,  V);
        float  LoV  dot(L,  V);
        float  InvLenH  rsqrt(  LoV  );
        float  NoH  saturate(  NoL  NoV  InvLenH  );
        float  VoH  saturate(  InvLenH  InvLenH  LoV  );
        NoL  saturate(NoL);
        NoV  saturate(abs(NoV)  1e-5);

        //  Generalized  microfacet  specular
        float  D_GGX(  LobeRoughness[1],  NoH  LobeEnergy[1];
        float  Vis  Vis_SmithJointApprox(  LobeRoughness[1],  NoV,  NoL  );
        float3  F_Schlick(  SpecularColor,  VoH  );

        float3  Diffuse  Diffuse_Lambert(  DiffuseColor  );
        //float3  Diffuse  Diffuse_Burley(  DiffuseColor,  LobeRoughness[1],  NoV,  NoL,  VoH  );
        //float3  Diffuse  Diffuse_OrenNayar(  DiffuseColor,  LobeRoughness[1],  NoV,  NoL,  VoH  );

        return  Diffuse  LobeEnergy[2]  (D  Vis)  F;
}

基本遵循了epic在siggraph2013上发表的paper[1]上的算法


其中D是D_GGX,G是Vis_SmithJointApprox,F是F_Schlick。具体代码在BRDF.ush中都有。
再加上Lambert的diffuse构成了基础的shading。

Clear Coat比较复杂,有空可能会单独写一篇

Cloth其实是定义了两种不同的BRDF,在这两者间作插值

NormalText Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
float3  ClothShading(  FGBufferData  GBuffer,  float3  LobeRoughness,  float3  LobeEnergy,  float3  L,  float3  V,  half3  )
{
        const  float3  FuzzColor    saturate(GBuffer.CustomData.rgb);
        const  float    Cloth            saturate(GBuffer.CustomData.a);

        float  NoL  dot(N,  L);
        float  NoV  dot(N,  V);
        float  LoV  dot(L,  V);
        float  InvLenH  rsqrt(  LoV  );
        float  NoH  saturate(  NoL  NoV  InvLenH  );
        float  VoH  saturate(  InvLenH  InvLenH  LoV  );
        NoL  saturate(NoL);
        NoV  saturate(abs(NoV)  1e-5);

        //  Diffuse   
        float3  Diffuse  Diffuse_Lambert(  GBuffer.DiffuseColor  );
        float3  Diff  Diffuse  LobeEnergy[2];

        //  Cloth  Asperity  Scattering  Inverse  Beckmann  Layer 
        float3  F1  F_Schlick(  FuzzColor,  VoH  );
        float    D1  D_InvGGX(  LobeRoughness[1],  NoH  );
        float    V1  Vis_Cloth(  NoV,  NoL  );

        float3  Spec1  D1  V1  F1;

        //  Generalized  microfacet  specular
        float3  F2  F_Schlick(  GBuffer.SpecularColor,  VoH  );
        float    D2  D_GGX(  LobeRoughness[1],  NoH  LobeEnergy[1];
        float    V2  Vis_SmithJointApprox(  LobeRoughness[1],  NoV,  NoL  );

        float3  Spec2  D2  V2  F2;

        float3  Spec  lerp(Spec2,  Spec1,  Cloth);

        return  Diff  Spec;
}

Spec2和standShading的高光部分一样,Spec1的D和G则略有不同,用GBuffer中的custom.a做插值,得到最终的输出(Diffuse还是Lambert)

Eye的部分更加简单,就是StandShading去掉Diffuse的部分

NormalText Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
float3  EyeShading(  FGBufferData  GBuffer,  float3  LobeRoughness,  float3  LobeEnergy,  float3  L,  float3  V,  half3  )
{
        float  NoL  dot(N,  L);
        float  NoV  dot(N,  V);
        float  LoV  dot(L,  V);
        float  InvLenH  rsqrt(  LoV  );
        float  NoH  saturate(  NoL  NoV  InvLenH  );
        float  VoH  saturate(  InvLenH  InvLenH  LoV  );
        NoL  saturate(NoL);
        NoV  saturate(abs(NoV)  1e-5);

        //  Generalized  microfacet  specular
        float  D_GGX(  LobeRoughness[1],  NoH  LobeEnergy[1];
        float  Vis  Vis_SmithJointApprox(  LobeRoughness[1],  NoV,  NoL  );
        float3  F_Schlick(  GBuffer.SpecularColor,  VoH  );

        return  Vis  F;
}

不过要注意,NoH和VoH的计算方法和StandShading是不同的。

SubsurfaceShading

说完了surfaceShading,下面就来说Subsurface的部分。

首先是最普通的SubsurfaceShading:

NormalText Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
float3  SubsurfaceShadingSubsurf ace(  FGBufferData  GBuffer,  float3  L,  float3  V,  half3  )
{
        float3  SubsurfaceColor  ExtractSubsurfaceColor(GBuffer);
        float  Opacity  GBuffer.CustomData.a;

        float3  normalize(V  L);

        //  to  get  an  effect  when  you  see  through  the  material
        //  hard  coded  pow  constant
        float  InScatter  pow(saturate(dot(L,  -V)),  12)  lerp(3,  .1f,  Opacity);
        //  wrap  around  lighting,  /(PI*2)  to  be  energy  consistent  (hack  do  get  some  view  dependnt  and  light  dependent  effect)
        //  Opacity  of  gives  no  normal  dependent  lighting,  Opacity  of  gives  strong  normal  contribution
        float  NormalContribution  saturate(dot(N,  H)  Opacity  Opacity);
        float  BackScatter  GBuffer.GBufferAO  NormalContribution  (PI  2);

        //  lerp  to  never  exceed  (energy  conserving)
        return  SubsurfaceColor  lerp(BackScatter,  1,  InScatter);
}

观察NormalContribution项,显然Opacity=1时,NormalContribution=dot(N,H)。也就是说次表面的深度受到法线和半角的点积影响。

然后是皮肤的次表面

NormalText Code 
1
2
3
4
5
6
7
8
float3  SubsurfaceShadingPreinte gratedSkin(  FGBufferData  GBuffer,  float3  L,  float3  V,  half3  )
{
        float3  SubsurfaceColor  ExtractSubsurfaceColor(GBuffer);
        float  Opacity  GBuffer.CustomData.a;

        float3  PreintegratedBRDF  Texture2DSampleLevel(PreIntegratedBRDF,  PreIntegratedBRDFSampler float2(saturate(dot(N,  L)  .5  .5),  Opacity),  0).rgb;
        return  PreintegratedBRDF  SubsurfaceColor;
}

意外的简单,其实就是查找一张LUT的值。
这张图很容易找到,打开unreal4,在content browser的View Options里选择Show Engine Content


这样就可以在Engine Content/EngineMaterials/中找到这张LUT了


然后是twosided的次表面:

NormalText Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
float3  SubsurfaceShadingTwoSide d(  float3  SubsurfaceColor,  float3  L,  float3  V,  half3  )
{
        //  http://blog.stevemcauley.com/2011/12/03/energy-conserving-wrapped-diffuse/
        float  Wrap  0.5;
        float  NoL  saturate(  dot(-N,  L)  Wrap  Square(  Wrap  );

        //  GGX  scatter  distribution
        float  VoL  saturate(  dot(V,  -L)  );
        float  0.6;
        float  a2  a;
        float  VoL  a2  VoL  VoL  1;  //  mad
        float  GGX  (a2  PI)  (d  d);                //  mul,  rcp
        return  NoL  GGX  SubsurfaceColor;
}

NoL经过了Wrap来保证能量守恒,乘以GGX的NormalDistribution得到最终结果
这里注意GGX没有使用roughness,而是将a=roughness*roughness设成0.6。相当于roughness=0.77

hair的次表面特别复杂,分R、TT、TRT等很多部分,以后单独拿出来讲。eye也一样



Reference

[1] https://de45xmedrsdbp.Resources/files/2013SiggraphPresentation sNotes-26915738.pdf

这篇关于[PBS真的可以为所欲为] Unreal材质模型源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/227577

相关文章

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)

《Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)》:本文主要介绍Python基于火山引擎豆包大模型搭建QQ机器人详细的相关资料,包括开通模型、配置APIKEY鉴权和SD... 目录豆包大模型概述开通模型付费安装 SDK 环境配置 API KEY 鉴权Ark 模型接口Prompt

Spring中Bean有关NullPointerException异常的原因分析

《Spring中Bean有关NullPointerException异常的原因分析》在Spring中使用@Autowired注解注入的bean不能在静态上下文中访问,否则会导致NullPointerE... 目录Spring中Bean有关NullPointerException异常的原因问题描述解决方案总结

python中的与时间相关的模块应用场景分析

《python中的与时间相关的模块应用场景分析》本文介绍了Python中与时间相关的几个重要模块:`time`、`datetime`、`calendar`、`timeit`、`pytz`和`dateu... 目录1. time 模块2. datetime 模块3. calendar 模块4. timeit

python-nmap实现python利用nmap进行扫描分析

《python-nmap实现python利用nmap进行扫描分析》Nmap是一个非常用的网络/端口扫描工具,如果想将nmap集成进你的工具里,可以使用python-nmap这个python库,它提供了... 目录前言python-nmap的基本使用PortScanner扫描PortScannerAsync异

Oracle数据库执行计划的查看与分析技巧

《Oracle数据库执行计划的查看与分析技巧》在Oracle数据库中,执行计划能够帮助我们深入了解SQL语句在数据库内部的执行细节,进而优化查询性能、提升系统效率,执行计划是Oracle数据库优化器为... 目录一、什么是执行计划二、查看执行计划的方法(一)使用 EXPLAIN PLAN 命令(二)通过 S

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置