[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

相关文章

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

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

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

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

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

图神经网络模型介绍(1)

我们将图神经网络分为基于谱域的模型和基于空域的模型,并按照发展顺序详解每个类别中的重要模型。 1.1基于谱域的图神经网络         谱域上的图卷积在图学习迈向深度学习的发展历程中起到了关键的作用。本节主要介绍三个具有代表性的谱域图神经网络:谱图卷积网络、切比雪夫网络和图卷积网络。 (1)谱图卷积网络 卷积定理:函数卷积的傅里叶变换是函数傅里叶变换的乘积,即F{f*g}

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL