当前位置:   article > 正文

基于物理着色(PBS)及Unity中的实现_unity createtangenttoworldpervertex

unity createtangenttoworldpervertex

 

==========================================================

首先是一些基本概念

 

立体角

是一个物体对于一个特定观察点在三维空间中的角度(观测到的大小),记作Ω,

单位为球面度(sr),即三维弧度,1球面度的立体角所对应的球面表面积为r2(r为球半径)

辐射通量 Radiant Flux

光源每秒钟发射的功率,记作Φ,单位为W


辐射亮度 Radiance

辐射源上某点在某方向上(单位投影面积在单位立体角内)的辐射通量,记作L,单位为W/(m2.sr)

即某点在某方向上的亮度,是图形学光照方程最终要计算的量

辐射强度 Radiant Intensity

辐射源在某方向上(单位立体角内)的辐射通量的积分,记作I,单位为W/sr

在图形学中用来表示光源的辐射强度分布,这个分布是方向的函数


辐射照度 Irradiance

辐射源上某点(单位投影面积)在各方向的辐射通量的积分,记作E,单位为W/m2

在图形学中用来表示表面上一个点(单位面积)接收的所有光照


微面元 Microfacet

在微观上,表面上一点是由许多(方向不同的)微面元组成,

其中每个微面元都是绝对光滑的,即光线与这些微面元的交互符合反射定律和折射定律

只有那些法线等于半矢量h(入射方向l和观察方向v的中间向量)的微面元才可能被看到


微法线分布函数 NDF

表示表面上一点的微面元的法线在各方向上分布的概率,即有多少微面元的法线等于半矢量h,

函数形式D(θh),其中θh是宏观法线n与半矢量h之间的夹角


GGX

Unity中使用的微法线分布函数,其中h为半矢量、n为宏观法线、roughness为粗糙度,

DGGX = a2 / π((a2 – 1) (n · h)2 + 1)2

a = roughness2


微面元遮挡函数

表示具有半矢量法线的微面元中,有多少是没有(在入射和反射方向上)被遮挡的

函数形式G(θl , θv),其中θl和θv分别表示入射方向l和观察方向v与宏观法线n之间的夹角


Smith-Schlick

Unity中使用的微面元遮挡函数,其中l为入射方向、v为观察方向、n为宏观法线、roughness为粗糙度,

G(l, v) = G1(l) G1(v)

G1(x) = 1 / ((n · x) (1 - K) + K)

K = roughness2 / 2

Unity5.3.7中,上述公式对应的函数为SmithGGXVisibilityTerm,而实际使用的函数为SmithJointGGXVisibilityTerm


菲涅耳公式

表示当光线由一种介质进入另一种介质(光滑表面),入射光分别被反射和折射的比率

我们主要关心其中(高光)反射的部分,而折射部分要么被吸收要么属于漫反射的范畴


Schlick菲涅耳近似

Unity中使用的菲涅耳近似等式,其中l为入射方向、h为半矢量,

RF(l, h) = F0 + (1 - F0) (1 -  l · h)5

F0 = RF(h, h) = 入射光垂直于表面时的菲涅耳反射率(高光反射率,记作Cspec)


双向反射分布函数 BRDF

表示反射方向(观察反方向、出射方向)上的辐射亮度增量与入射方向辐射照度增量的比率,

直观上讲,就是当光线沿入射方向到达表面某点时,有多少能量被反射到观察(反)方向上

函数形式Fr(wi , wr),其中wi为入射方向、wr为出射方向


次表面散射 Subsurface Scattering

也称为漫反射,是指光线从(非金属且不完全透明的)物体表面上一点(折射)进入物体内部,

由于物体内部介质不均匀,经过若干次散射,

最终,一部分被吸收,一部分重新从物体表面散射出来(可能出射点不同于入射点)

金属会吸收掉所有折射进入的光,而透明物体会被折射进入的光穿过并且再次折射出去


双向次表面散射反射分布函数 BSSRDF

用作模拟次表面散射,在BRDF的基础上增加了两个参数:入射点位置xi和出射点位置xr

函数形式Fr(xi, wi , xr , wr)

当入射点和所有出射点的距离均小于1像素,则BSSRDF可用BRDF替代


漫反射部分的BRDF

传统的漫反射模型为Lambert模型,

首先,假设入射点和所有出射点相同(距离小于1像素);

并且,从BRDF的角度看,所有出射方向上的比率都一样:

FLambert(l, v) = Cdiffuse / π

其中Cdiffuse为物体表面的“颜色”,即对入射光的RGB各分量的漫反射(未吸收)比例


Disney Diffuse

Unity中使用的漫反射计算公式,(相比FLambert)可以更好的模拟次表面散射

Fdiffuse = (baseColor / π) (1 + (FD90 - 1) (1 – n · l)5) (1 + (FD90 - 1) (1 – n · v)5)

FD90 = 0.5 + 2 * roughness * (h · l)2

其中baseColor为物体表面的“颜色”、roughness为粗糙度、n为宏观法线、l为入射方向、v为出射方向

Unity5.3.7中,实际使用的公式,最终并没有除以π


高光反射部分的BRDF

高光反射是指光线直接从物体表面反射出去(未进入物体内部),基于微面元理论,可以得到下面的BRDF公式:

Fspecular = (RF(l, h) G(l, v, h) DGGX) / (4 (n · l) (n · v))

其中,RF为菲涅耳反射率、G为微面元遮挡函数、DGGX为微法线分布函数、4 (n · l) (n · v)为校正因子

Unity5.3.7中,实际使用的公式,并没有除以(n · l) (n · v),只除以4并且乘以π ?


最终着色结果

最终被看到的颜色(由被观察点射向摄像机的光线的亮度),为漫反射部分加上高光反射部分,即Fdiffuse+ Fspecular

金属与非金属

金属材质,没有漫反射,只有高光反射,而高光反射率RGB分量可能不同

非金属材质,漫反射率RGB分量可能不同,高光反射率RGB分量是一样的(大多数很接近<0.05, 0.05, 0.05>)

Unity的Standard.Shader中的Albedo属性就对应了这两种反射率(金属材质高光反射率、非金属材质漫反射率)

==========================================================

Unity中的实现

 

在Unity5中内置了两个基于PBR理论的Shader,Standard.Shader(金属流)和StandardSpecular.shader(反射流),

它们主要的区别是:

有一项输入属性(Property)不同,前者为金属度(Metallic),后者为高光反射度(Specular),

并且使用了不同的函数(MetallicSetup、SpecularSetup)根据各自的输入来初始化BRDF模型所需要的参数

金属流

高光反射率(F0):按金属度在unity_ColorSpaceDielectricSpec.rgb和Albedo.rgb之间插值

漫反射率:按金属度在Albedo.rgb * unity_ColorSpaceDielectricSpec.a和Albedo.rgb * 0.0之间插值

 

反射流

高光反射率(F0):就是高光反射度

漫反射率:按高光反射度(rgb中的最大值)在Albedo.rgb和<0, 0, 0>之间插值

==========================================================

Standard.Shader分析

下面以Standard.shader为例分析其中的具体实现(简单起见,只涉及一个Pass:ForwardBase)

完整代码请至官网下载,或者这里http://download.csdn.net/detail/liumazi/9863474


主要属性

_Color("Color", Color) = (1,1,1,1)  // 反射率(整体),漫反射/高光反射(非金属/金属)

_MainTex("Albedo", 2D) = "white" {}  // 反射率(纹理),漫反射/高光反射(非金属/金属)

_Cutoff("AlphaCutoff", Range(0.0, 1.0)) = 0.5  // 当RenderingMode为Cutoff时使用(alpha测试值)

_Glossiness("Smoothness", Range(0.0, 1.0)) = 0.5  // 光滑度(整体)

[Gamma]_Metallic("Metallic", Range(0.0, 1.0)) = 0.0  // 金属度(整体)

_MetallicGlossMap("Metallic", 2D) ="white" {}  // 金属度+光滑度(纹理),当指定纹理时上面2项失效

_BumpScale("Scale", Float) = 1.0

_BumpMap("NormalMap", 2D) ="bump" {}  // 法线纹理
 

顶点着色器参数

  1. struct VertexInput
  2. {
  3. float4 vertex: POSITION; // 顶点位置
  4. half3 normal : NORMAL; // 顶点法线
  5. float2 uv0 : TEXCOORD0;// 纹理坐标
  6. float2 uv1 : TEXCOORD1;// 纹理坐标2
  7. #ifdef _TANGENT_TO_WORLD // 存在法线贴图时 = 1
  8. half4 tangent: TANGENT; // 顶点切线
  9. #endif
  10. };

顶点着色器返回值/片元着色器参数

  1. struct VertexOutputForwardBase
  2. {
  3. float4 pos: SV_POSITION; // 顶点位置(裁剪空间)
  4. float4 tex: TEXCOORD0; // 纹理坐标(2套)
  5. half3 eyeVec: TEXCOORD1; // 观察方向
  6. half4 tangentToWorldAndParallax[3]: TEXCOORD2; // TBN矩阵(用于转换切空间法线)或法线(存于[2])
  7. half4 ambientOrLightmapUV: TEXCOORD5; // SH or Lightmap UV
  8. SHADOW_COORDS(6) // 阴影相关,忽略
  9. UNITY_FOG_COORDS(7) // 雾相关,忽略
  10. // next ones would not fit into SM2.0 limits, but theyare always for SM3.0+
  11. #if UNITY_SPECCUBE_BOX_PROJECTION
  12. float3 posWorld: TEXCOORD8; // 顶点位置(世界空间)
  13. #endif
  14. #if UNITY_OPTIMIZE_TEXCUBELOD
  15. #if UNITY_SPECCUBE_BOX_PROJECTION
  16. half3 reflUVW: TEXCOORD9;
  17. #else
  18. half3 reflUVW: TEXCOORD8;
  19. #endif
  20. #endif
  21. };

顶点着色器

  1. VertexOutputForwardBase vertForwardBase(VertexInput v)
  2. {
  3. VertexOutputForwardBase o;
  4. UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o); // 初始化(清零)
  5. float4 posWorld = mul(_Object2World, v.vertex); // 坐标->世界空间
  6. #if UNITY_SPECCUBE_BOX_PROJECTION
  7. o.posWorld = posWorld.xyz;
  8. #endif
  9. o.pos = mul(UNITY_MATRIX_MVP, v.vertex); // 坐标->裁剪空间
  10. o.tex = TexCoords(v); // 纹理坐标变换,通过TRANSFORM_TEX
  11. o.eyeVec = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos); // 观察方向(相机到顶点)
  12. float3 normalWorld = UnityObjectToWorldNormal(v.normal); // 法线->世界坐标
  13. #ifdef _TANGENT_TO_WORLD // 存在法线贴图时 = 1
  14. // 切线->世界坐标(注意v.tangent.w用来修正副法线binormal方向)
  15. float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
  16. // 根据切线和法线计算副法线,从而得到TBN矩阵
  17. float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w);
  18. o.tangentToWorldAndParallax[0].xyz = tangentToWorld[0];
  19. o.tangentToWorldAndParallax[1].xyz = tangentToWorld[1];
  20. o.tangentToWorldAndParallax[2].xyz = tangentToWorld[2];
  21. #else
  22. o.tangentToWorldAndParallax[0].xyz = 0;
  23. o.tangentToWorldAndParallax[1].xyz = 0;
  24. o.tangentToWorldAndParallax[2].xyz = normalWorld;
  25. #endif
  26. TRANSFER_SHADOW(o); // 阴影相关,忽略
  27. o.ambientOrLightmapUV = VertexGIForward(v,posWorld, normalWorld); // GI相关,忽略
  28. #ifdef _PARALLAXMAP // 不存在高度图,忽略
  29. TANGENT_SPACE_ROTATION;
  30. half3 viewDirForParallax = mul(rotation,ObjSpaceViewDir(v.vertex));
  31. o.tangentToWorldAndParallax[0].w = viewDirForParallax.x;
  32. o.tangentToWorldAndParallax[1].w = viewDirForParallax.y;
  33. o.tangentToWorldAndParallax[2].w = viewDirForParallax.z;
  34. #endif
  35. #if UNITY_OPTIMIZE_TEXCUBELOD
  36. o.reflUVW = reflect(o.eyeVec, normalWorld);
  37. #endif
  38. UNITY_TRANSFER_FOG(o, o.pos); // 雾相关,忽略
  39. return o;
  40. }

片元通用结构

  1. struct FragmentCommonData
  2. {
  3. half3 diffColor, specColor; // 漫反射率、高光反射率(F0)
  4. // Note: oneMinusRoughness & oneMinusReflectivity foroptimization purposes, mostly for DX9 SM2.0 level.
  5. // Most of the math is being done on these (1-x) values,and that saves a few precious ALU slots.
  6. half oneMinusReflectivity, oneMinusRoughness; // 漫反射总比率、光滑度
  7. half3 normalWorld, eyeVec, posWorld; // 法线、观察方向、位置(均处于世界空间)
  8. half alpha; // 透明度
  9. #ifUNITY_OPTIMIZE_TEXCUBELOD || UNITY_STANDARD_SIMPLE
  10. half3 reflUVW;
  11. #endif
  12. };

 

金属度与两种反射率的关系

  1. inline half3 DiffuseAndSpecularFromMetallic(half3 albedo, half metallic,
  2. out half3 specColor, out half oneMinusReflectivity)
  3. {
  4. // 当材质为金属(metallic = 1.0),高光反射率(F0)= albedo
  5. // 当材质为非金属(metallic = 0.0),高光反射率(F0)= unity_ColorSpaceDielectricSpec(电介质的F0)
  6. // 当材质介于两者之间,按金属度 metallic插值
  7. specColor = lerp(unity_ColorSpaceDielectricSpec.rgb, albedo, metallic); // 高光反射率(F0)
  8. // 当材质为金属,漫反射总比率 = 0.0(没有漫反射)
  9. // 当材质为非金属,漫反射总比率 = unity_ColorSpaceDielectricSpec.a = 1-reflectivity(接近1.0)
  10. // 当材质介于两者之间,按金属度 metallic插值
  11. oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic); // 漫反射总比率
  12. return albedo * oneMinusReflectivity; // 漫反射率
  13. }

 

金属流输入处理

  1. inline FragmentCommonData MetallicSetup(float4 i_tex)
  2. {
  3. // 获取金属度、光滑度(从_MetallicGlossMap中采样或直接使用_Metallic和_Glossiness的值)
  4. half2 metallicGloss = MetallicGloss(i_tex.xy);
  5. half metallic = metallicGloss.x;
  6. half oneMinusRoughness = metallicGloss.y; // this is 1 minusthe square root of real roughness m.
  7. half oneMinusReflectivity;
  8. half3 specColor;
  9. half3 diffColor = DiffuseAndSpecularFromMetallic(
  10. Albedo(i_tex), // _Color.rgb * _MainTex纹理采样值
  11. metallic,
  12. /*out*/specColor,
  13. /*out*/oneMinusReflectivity);
  14. FragmentCommonData o = (FragmentCommonData)0;
  15. o.diffColor = diffColor; // 漫反射率
  16. o.specColor = specColor; // 高光反射率(F0)
  17. o.oneMinusReflectivity = oneMinusReflectivity;
  18. o.oneMinusRoughness = oneMinusRoughness;
  19. return o;
  20. }

 

片元预处理

  1. inline FragmentCommonData FragmentSetup(
  2. float4 i_tex, // 纹理坐标
  3. half3 i_eyeVec, // 观察方向
  4. half3 i_viewDirForParallax, // 无高度纹理时为<0,0,0>
  5. half4 tangentToWorld[3], // TBN矩阵(用于转换切空间法线)或法线(存于[2])
  6. half3 i_posWorld) // 世界坐标位置
  7. {
  8. i_tex = Parallax(i_tex,i_viewDirForParallax); // 无高度纹理时直接返回i_tex
  9. half alpha = Alpha(i_tex.xy); // tex2D(_MainTex,uv).a * _Color.a;
  10. #if defined(_ALPHATEST_ON)
  11. clip(alpha - _Cutoff); // Alpha测试
  12. #endif
  13. // 调用MetallicSetup,以初始化o的部分内容:diffColor、specColor、oneMinusReflectivity、oneMinusRoughness
  14. FragmentCommonData o = UNITY_SETUP_BRDF_INPUT(i_tex);
  15. o.normalWorld = PerPixelWorldNormal(i_tex,tangentToWorld); // 取世界空间法线(来自法线纹理或顶点法线)
  16. o.eyeVec = NormalizePerPixelNormal(i_eyeVec); // 观察方向(归一化)
  17. o.posWorld = i_posWorld; // 世界空间坐标
  18. // NOTE: shader relies on pre-multiply alpha-blend(_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
  19. o.diffColor = PreMultiplyAlpha(o.diffColor,alpha, o.oneMinusReflectivity,/*out*/o.alpha); // 预乘
  20. return o;
  21. }

 

片元着色器

  1. half4 fragForwardBaseInternal(VertexOutputForwardBase i)
  2. {
  3. FRAGMENT_SETUP(s) // 调用 FragmentSetup初始化 FragmentCommonData s;
  4. #ifUNITY_OPTIMIZE_TEXCUBELOD
  5. s.reflUVW = i.reflUVW;
  6. #endif
  7. UnityLight mainLight = MainLight(s.normalWorld); // 主光源信息:颜色、位置、反方向与法线的夹角余弦(点积)
  8. half atten = SHADOW_ATTENUATION(i); // 阴影相关,忽略
  9. half occlusion = Occlusion(i.tex.xy);
  10. UnityGI gi = FragmentGI(s, occlusion, i.ambientOrLightmapUV, atten, mainLight); // GI相关,忽略
  11. // 基于物理着色,分了3档,这里重点分析第1档(BRDF1_Unity_PBS)
  12. half4 c = UNITY_BRDF_PBS(s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness,
  13. s.normalWorld, -s.eyeVec, gi.light, gi.indirect); // 注意:观察方向有取反(从被观察点到摄像机)
  14. c.rgb += UNITY_BRDF_GI(s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness,
  15. s.normalWorld, -s.eyeVec, occlusion, gi); // GI相关,忽略
  16. c.rgb += Emission(i.tex.xy); // 自发光,忽略
  17. UNITY_APPLY_FOG(i.fogCoord, c.rgb); // 雾相关,忽略
  18. return OutputForward(c, s.alpha); // 修改c.a(开启混合时=s.alpha,否则等于1.0)
  19. }

 

 基于物理着色

  1. half4 BRDF1_Unity_PBS(half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness,
  2. half3 normal, half3 viewDir, UnityLight light, UnityIndirect gi)
  3. {
  4. half roughness = 1-oneMinusRoughness; // 粗糙度
  5. half3 halfDir = Unity_SafeNormalize(light.dir + viewDir); // 半矢量(注意:两个向量都是从被观察点出发)
  6. #define UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV 0
  7. #if UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV
  8. half shiftAmount = dot(normal, viewDir);
  9. normal = shiftAmount < 0.0f ? normal + viewDir * (-shiftAmount + 1e-5f) : normal;
  10. half nl = DotClamped(normal, light.dir);
  11. #else
  12. half nl = light.ndotl; // 宏观法线和光线(反)方向夹角余弦
  13. #endif
  14. half nh = BlinnTerm(normal, halfDir); // 宏观法线和半矢量夹角余弦
  15. half nv = DotClamped(normal, viewDir); // 宏观法线和观察(反)方向夹角余弦
  16. half lv = DotClamped(light.dir, viewDir); // 光线(反)方向和观察(反)方向夹角余弦
  17. half lh = DotClamped(light.dir, halfDir); // 光线(反)方向和半矢量夹角余弦
  18. #if UNITY_BRDF_GGX
  19. half V = SmithJointGGXVisibilityTerm(nl, nv, roughness); // 微面元遮挡函数G
  20. half D = GGXTerm(nh, roughness); // 微面元法线分布函数D
  21. #else
  22. half V = SmithBeckmannVisibilityTerm(nl, nv, roughness);
  23. half D = NDFBlinnPhongNormalizedTerm(nh, RoughnessToSpecPower (roughness));
  24. #endif
  25. half nlPow5 = Pow5(1-nl);
  26. half nvPow5 = Pow5(1-nv);
  27. half Fd90 = 0.5 + 2 * lh * lh * roughness;
  28. half disneyDiffuse = (1 + (Fd90-1) * nlPow5) * (1 + (Fd90-1) * nvPow5); // 漫反射项(部分,未除以Pi)
  29. // HACK: theoretically we should divide by Pi diffuseTerm and not multiply specularTerm!
  30. // BUT 1) that will make shader look significantly darker than Legacy ones
  31. // and 2) on engine side "Non-important" lights have to be divided by Pi to in cases when they are injected into ambient SH
  32. // NOTE: multiplication by Pi is part of single constant together with 1/4 now
  33. // Torrance-Sparrow model, Fresnel is applied later (for optimization reasons)
  34. half specularTerm = (V * D) * (UNITY_PI/4); // 高光反射项(部分,未加入菲涅耳反射率)
  35. if (IsGammaSpace()) // 当前处于非线性空间,则做一些转换
  36. specularTerm = sqrt(max(1e-4h, specularTerm));
  37. specularTerm = max(0, specularTerm * nl); // 高光反射强度(部分,考虑光线反方向和法线的夹角余弦)
  38. half diffuseTerm = disneyDiffuse * nl; // 漫反射强度(考虑光线反方向和法线的夹角余弦)
  39. // surfaceReduction = Int D(NdotH) * NdotH * Id(NdotL>0) dH = 1/(realRoughness^2+1)
  40. half realRoughness = roughness * roughness; // need to square perceptual roughness
  41. half surfaceReduction;
  42. if (IsGammaSpace()) // 1-0.28*x^3 as approximation for (1/(x^4+1))^(1/2.2) on the domain [0;1]
  43. surfaceReduction = 1.0 - 0.28 * realRoughness * roughness;
  44. else
  45. surfaceReduction = 1.0 / (realRoughness * realRoughness + 1.0); // fade \in [0.5;1]
  46. half grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity));
  47. half3 color =
  48. diffColor * (gi.diffuse + light.color * diffuseTerm) + // 漫反射项(其中gi.diffuse与GI相关,忽略)
  49. specularTerm * light.color * FresnelTerm(specColor/*F0*/, lh) + // 高光反射项(加入菲涅耳反射率)
  50. surfaceReduction * gi.specular * FresnelLerp(specColor, grazingTerm, nv); // GI相关,忽略
  51. return half4(color, 1);
  52. }

 

==========================================================

相关参考

全局光照技术:从离线到实时渲染     http://www.thegibook.com/

Unity Shader 入门精要     https://github.com/candycat1992/Unity_Shaders_Book

基于物理着色系列     https://zhuanlan.zhihu.com/p/20091064

基于物理的BRDF     http://www.klayge.org/wiki/index.php/%E5%9F%BA%E4%BA%8E%E7%89%A9%E7%90%86%E7%9A%84BRDF

基于物理的渲染—迪士尼的渲染模型     https://zhuanlan.zhihu.com/p/25427105

 

参考自: 

Unity3d 基于物理渲染Physically-Based Rendering之specular BRDF

Unity3d 基于物理渲染Physically-Based Rendering之实现

Unity3d 基于物理渲染Physically-Based Rendering之最终篇
 

 


 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/127765
推荐阅读
相关标签
  

闽ICP备14008679号