Unity shader内置standard代码解析

最近有相关需求制作,所以这里编写一个文档,方便后续的流程查看。

下载源码

由于unity内置的shader是无法查看源码的,你需要去官网下载对应版本内置源码查看
在这里插入图片描述
在引擎下载那里,会有一个Built in Shaders,下载
在这里插入图片描述
打开以后,就是对应的shader
在这里插入图片描述
内置的shandard在DefaultResourcesExtra目录内,打开便是。

shader解析

Standard里面分了两套,一套正常的,一套精简版的,
在这里插入图片描述
这两套渲染的切换是通过设置shader的lod进行切换的。
每个shader下面由5个pass组成(简化版的不支持延迟渲染)

  1. 前向渲染主光源
  2. 前向渲染副光源
  3. 阴影渲染
  4. 延迟渲染
  5. 烘焙
    在这里插入图片描述

简化版本的渲染也不支持视差偏移,它们是通过宏去控制的,更多不同在渲染代码内部。

ForwardBase 和ForwardAdd引用的一套渲染逻辑,然后通过定义的宏和调用不同的顶点/片元着色器函数来区分到底是base还是add。

base的是这样在这里插入图片描述

add是这样
在这里插入图片描述
它们都引用的UnityStandardCoreForward渲染逻辑

在这里插入图片描述
在这个文件里面,是一些主要函数的定义,区分是否为简化的shader,如果简化的shader,则引入简化的库文件,非简化,则引入了UnityStandardCore.cginc,在这里定义了pass里面调用的顶点着色器和片元着色器函数,函数内直接调用了对应的UnityStandardCore库里的函数,这里也是standard的核心代码。
在这里插入图片描述
上面还引入了UnityStandardConfig.cginc,这个文件则是一些配置,主要定义的宏,抽几个比较重要的
下面定义了立体图贴图的曝光度以及lod层级数
在这里插入图片描述
定义brdf GGX
在这里插入图片描述

UnityStandardCore.cginc

里面代码的一些库的引用,核心库也是基于这些库实现的最终渲染
在这里插入图片描述
ForwardBase渲染主要就是调用了

VertexOutputForwardBase vertBase (VertexInput v) {
    
     return vertForwardBase(v); }
half4 fragBase (VertexOutputForwardBase i) : SV_Target {
    
     return fragForwardBaseInternal(i); }

VertexInput 就是在UnityStandardInput.cginc内实现的需要传入到顶点着色器的数据
VertexOutputForwardBase 则是从顶点传入到片元的数据
在这里插入图片描述
vertForwardBase 函数里对位置,UV,法向等做了一些处理,更复杂的还有lightmap的UV,还有视差偏移
在这里插入图片描述
重点函数,片元着色器fragForwardBaseInternal,有点代码越少越狠的节奏,后面我将一个个的函数解析
在这里插入图片描述

UNITY_APPLY_DITHER_CROSSFADE(i.pos.xy)

这个是为了实现淡入淡出的效果
在这里插入图片描述
unity_DitherMask 为unity内置生成的抖动noise贴图
unity_LODFade 为需要设置的变量,在UnityShaderVariables.cginc里面定义

float4 unity_LODFade; // x is the fade value ranging within [0,1]. y is x quantized into 16 levels

FRAGMENT_SETUP(s)

这个函数主要是生成后续使用的数据FragmentCommonData,定义为FragmentSetup函数

#define FRAGMENT_SETUP(x) FragmentCommonData x = FragmentSetup(i.tex, i.eyeVec.xyz, IN_VIEWDIR4PARALLAX(i), i.tangentToWorldAndPackedData, IN_WORLDPOS(i));

i.tex 顶点着色器计算的uv
i.eyeVec.xyz 摄像机朝向
IN_VIEWDIR4PARALLAX(i) 摄像机朝向基于视差偏移的法向值
i.tangentToWorldAndPackedData 切线坐标系转世界坐标系矩阵 [3x3:tangentToWorld | 1x3:viewDirForParallax or worldPos]
IN_WORLDPOS(i) 渲染目标世界坐标位置

FragmentCommonData 则是返回从顶点着色器拿到的数据处理后的数据,后续获取通过s变量获取。oneMinusReflectivity 为1-反射率
在这里插入图片描述
然后就是函数FragmentSetup,设置数据,截图里面我也加了注释
在这里插入图片描述
这里主要讲的是UNITY_SETUP_BRDF_INPUT函数,它可以根据工作流去设置数据,有三种 SpecularSetup RoughnessSetup MetallicSetup分别对应 高光工作流 粗糙度工作里 金属度工作流,standard.shader里面定义了金属度工作流
在这里插入图片描述
如果没有定义的话,会切换高光工作流
在这里插入图片描述
由于我这里使用的是金属度工作流,这里讲解一些金属度工作流的相关内容,MetallicSetup函数,函数内有两个函数,第一个函数去获取贴图的值,第二个函数为计算漫反射颜色,镜面反射颜色和反射率
在这里插入图片描述
MetallicGloss内返回二维向量,x为金属度,y为光滑度,光滑度还可以选择是使用的_MetallicGlossMap的a通道还是_MainTex的a通道
在这里插入图片描述
DiffuseAndSpecularFromMetallic
unity_ColorSpaceDielectricSpec的值在线性空间中默认是 half4(0.04, 0.04, 0.04, 1.0 - 0.04),这也是物理渲染中默认反射率
在这里插入图片描述
根据金属度求出反射率
在这里插入图片描述

MainLight

    UnityLight mainLight = MainLight(); //主光源
    UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld); //合并阴影

UnityLight 结构里面有三个值 color 光的颜色 dir 光的朝向 ndotl 法向和光的点乘值(已弃用),MainLight函数里面就是获取第一盏灯的颜色和朝向
在这里插入图片描述
UNITY_LIGHT_ATTENUATION 计算阴影遮挡。会根据光的类型调用不同的函数,一般主光源都是平衡光,这里看一下平衡光的实现,代码在AutoLight.cginc里面
在这里插入图片描述
在AutoLight.cginc中,对多种情况的处理,比如屏幕空间阴影,包含烘焙阴影
在这里插入图片描述
这里我们看最简单的使用SHADOW_ATTENUATION生成的unitySampleShadow函数,这个函数会去获取shadowmap的值来做处理

#define UNITY_SAMPLE_SCREEN_SHADOW(tex, uv) UNITY_SAMPLE_TEX2DARRAY(tex, float3((uv).x / (uv).w, (uv).y / (uv).w, (float)unity_StereoEyeIndex)).r

在这里插入图片描述
这一块解析起来确实麻烦,如果你需要阴影的话,记得直接使用UNITY_LIGHT_ATTENUATION函数。第一个值就是阴影的值。

FragmentGI

全局光照,包含了lightmap,sh球谐光照,ibl等对物体影响的内容

UnityGI gi = FragmentGI(s, occlusion, i.ambientOrLightmapUV, atten, mainLight); //全局光照

UnityGI包含全局光照有光的数据,以及间接光的漫反射和镜面反射颜色
在这里插入图片描述
FragmentGI 函数主要是设置一些所需要的值,然后调用UnityGlobalIllumination生成最终所需的UnityGI数据
在这里插入图片描述
UnityGlossyEnvironmentSetup 主要是求出了两个值 SmoothnessToPerceptualRoughness是通过光滑度求出粗糙度,也就是1-光滑度,reflUVW,根据眼睛和法向求出反射方向
在这里插入图片描述
准备好需要的全局光照计算数据以后,就要开始调用UnityGlobalIllumination计算了,分别去计算间接光漫反射,以及间接光镜面反射
在这里插入图片描述
在间接光漫反射里面,考虑光照贴图和动态光照贴图,这个我在之前我的文章里面说过,这里就不再多解释。
在这里插入图片描述
解析一下,上图比较重要的几行代码:

o_gi.light.color *= data.atten;
o_gi.indirect.diffuse = ShadeSHPerPixel(normalWorld, data.ambient, data.worldPos);
o_gi.indirect.diffuse *= occlusion;

ShadeSHPerPixel 计算间接光漫反射,相对于lightmap里面获取的,它具有动态性。球谐光照是由七个四维向量组成,
在这里插入图片描述

由引擎设置参数。unity还兼容的3d纹理方式的SHEvalLinearL0L1_SampleProbeVolume
在这里插入图片描述
计算完成间接光漫反射以后,就是计算间接光镜面反射,在unity里面是通过,实现原理就是通过立方体贴图去拾取颜色作为镜面反射的颜色
在这里插入图片描述
里面主要的方法就是Unity_GlossyEnvironment,这个去拾取引擎设置的立方体贴图,并获取颜色
在这里插入图片描述
perceptualRoughnessToMipmapLevel就是粗糙度乘以LOD级数UNITY_SPECCUBE_LOD_STEPS,粗糙度越低,表示越光滑,那么lod层级就越低,图片拾取的也最清晰。
在这里插入图片描述
最后,将全局光的灯光颜色,间接光漫反射,间接光镜面反射计算完成,交给物理渲染BRDF函数实现最后的颜色。

UNITY_BRDF_PBS

half4 c = UNITY_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect); //基于物理的渲染

看代码,standard里面内置了三套方式,

  1. BRDF1_Unity_PBS 是基于物理的BRDF(Bidirectional Reflectance Distribution Function,双向反射分布函数)
  2. BRDF2_Unity_PBS 是基于极简的微表面理论的BRDF http://www.thetenthplanet.de/archives/255
  3. BRDF3_Unity_PBS 是不是微表面的基于修正归一化的 Blinn-Phong BRDF
    在这里插入图片描述
    这里,我只介绍质量最好的第一种BRDF1_Unity_PBS,看注释,也能了解到它的模型是如何计算的
    直接光漫反射 kD / pi
    直接光镜面反射 kS * (D * V * F) / 4
    最后乘以NdotL
    BRDF里面还有两种,一种是GGX的高光,另一种是老旧的BlinnPhong的
    在这里插入图片描述
    首先函数获取到需要用的数据,粗糙度,半角向量halfDir,NdotV,NdotL,NdotH,LdotV,LdotH
    在这里插入图片描述
    然后基于数据求直接光漫反射
    // Diffuse term
    half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl;

求漫反射,还给注释迪士尼的漫反射必须除以PI,在函数外实现,貌似unity都亮了PI,所以不用除以PI了
在这里插入图片描述
然后解释了一下为什么不除以PI
在这里插入图片描述
接下来,先求出BRDF的D项和V项
在这里插入图片描述
然后在最后颜色合并的时候,求出菲涅尔项 F,怪不得之前听朋友说,unity的BRDF写的很难看,确实难看
在这里插入图片描述
最终计算出来了颜色加上自发光合并雾效,返回片元颜色
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_30100043/article/details/132900129