zoukankan      html  css  js  c++  java
  • 翻译4 Unity Base Lighting

    把法线从模型空间变换到世界空间.
    使用方向光.
    计算漫反射和高光反射.
    Enforce energy conservation.
    渲染金属质感的物体.
    熟悉Unity的PBS算法.

    made with unity 5.6.6f2

    1 法线Normal

    我们之所以能够看见物体,是因为人眼能察觉到部分可见电磁光谱,它被称为可见光,而其余的光就是不可见的。光谱波段从低到高:电磁波、微波、红外线、可见光、紫外线、x射线、伽马射线。

    光源发射光,其中一些光击中物体,一些光又被物体表面反射。当反射光进入人眼或摄像机,我们就能看见该物体了。


    1.1 使用Mesh法线

    创建一个shader和一个材质,把材质指定给一些物体。各物体可以有不同位置、旋转、比例大小。

    Unity内置模型都包含了顶点法线,我们可以获取它们然后直接传递给片元函数:

    struct VertexData {
         float4 position : POSITION;
         float3 normal : NORMAL;
         float2 uv : TEXCOORD0;
    };
    
    struct Interpolators {
         float4 position : SV_POSITION;
         float2 uv: TEXCOORD0;
         float3 normal : TEXCOORD1;
    };
    Interpolators MyVertexProgram(VertexData v) {
         Interpolators i;
         i.position = UnityObjectToClipPos(v.position);
         i.normal = v.normal;
         i.uv = v.uv * _MainTex_ST.xy + _MainTex_ST.zw;
         return i;
    }
    float4 MyFragmentProgram(Interpolators i) : SV_TARGET
    {
         return float4(i.normal, 1);
    }

    image

    1-1. 共面法线作为颜色

    未加工的共面法线,输出的颜色是一致的。而在球体和弧形面呈现不同的颜色,有很好的平滑插值。


    1.2 动态批处理 Dynamic Batching

    此处的作者原意是:随着变化摄像机的观察视角,某些物体的表面颜色会发生改变,这是由于动态批处理导致,需要转换法线空间。

    Dynamic Bathching:

    image

    image

    1-2. 上 未开动态批处理,下开启动态批处理

    Adreno Profiler、SnapdragonProfiler

    官方文档给出的条件如下:
    1)顶点个数:由于物体顶点太多开销会过大,因此仅适合小于900个顶点的Meshes,如果你的Shader有使用Vertex Position,Normal或是Single UV,顶点数下降到300;如果还有UV0 UV1 Tangent,那只能处理180顶点(这是参考值,会随着版本演化而改变);当前shader使用了法线所以1-2图不会合并球和圆柱体.
    2)对于缩放来说, 等比缩放和非等比缩放的物体会分开处理;非等比缩放,会自动创建一个统一缩放的Mesh再组装合并。模型需要均匀缩放.
    3)使用不同材质的实例化游戏对象,会导致批处理失败;
    4)镜像变换的游戏对象,会导致批处理失败;
    5)拥有lightmap的对象,含有额外的渲染属性,例如lightmap的索引、偏移以及缩放等系数;
    6)如果物体Shader有Multi-pass会中止批量处理,
    7)物体接收实时阴影不会纳入批处理。
    动态批处理主要是降低CPU提交dc次数,也会增加CPU计算量。


    1.3 世界空间法线

    当前显示的模型法线都位于模型空间下,现在我们需要转换法线到世界空间。因此,我们需要知道转换矩阵,以及如何转换。

    Unity分解物体的变换矩阵分解为单个变换矩阵,就像在翻译1对变换矩阵的拆解。O = T1T2T3T是独立矩阵,O是合并了的旋转、缩放、位移的矩阵。这就是object-to-world矩阵。

    定义float4x4 unity_ObjectToWorld的文件是UnityShaderVariables.cginc.由于法线是方向向量,不需要对其次坐标进行计算,把矩阵降维至3x3阶矩阵与normal相乘,或者矩阵与float4(normal.xyz, 0)计算。这两种方式的汇编代码是一样的(一模一样):

    i.normal = mul((float3x3)unity_ObjectToWorld, v.normal);//1
    i.normal = mul(unity_ObjectToWorld, float4(v.normal,0));//2
          vs_4_0
          dcl_constantbuffer CB0[4], immediateIndexed
          dcl_constantbuffer CB1[4], immediateIndexed
          dcl_constantbuffer CB2[21], immediateIndexed
          dcl_input v0.xyz//sv_position
          dcl_input v1.xyz//normal
          dcl_input v2.xy//uv
          dcl_output_siv o0.xyzw, position
          dcl_output o1.xy
          dcl_output o2.xyz
          dcl_temps 2//r0与r1两个临时变量
    //0-7计算position
       0: mul r0.xyzw, v0.yyyy, cb1[1].xyzw
       1: mad r0.xyzw, cb1[0].xyzw, v0.xxxx, r0.xyzw
       2: mad r0.xyzw, cb1[2].xyzw, v0.zzzz, r0.xyzw
       3: add r0.xyzw, r0.xyzw, cb1[3].xyzw
       4: mul r1.xyzw, r0.yyyy, cb2[18].xyzw
       5: mad r1.xyzw, cb2[17].xyzw, r0.xxxx, r1.xyzw
       6: mad r1.xyzw, cb2[19].xyzw, r0.zzzz, r1.xyzw
       7: mad o0.xyzw, cb2[20].xyzw, r0.wwww, r1.xyzw
    //计算uv
       8: mad o1.xy, v2.xyxx, cb0[3].xyxx, cb0[3].zwzz
    //9-11计算normal
       9: mul r0.xyz, v1.yyyy, cb1[1].xyzx
      10: mad r0.xyz, cb1[0].xyzx, v1.xxxx, r0.xyzx
      11: mad o2.xyz, cb1[2].xyzx, v1.zzzz, r0.xyzx
      12: ret

    在同一维空间下拉伸曲面,法线并不会以同样的方式拉伸。

    image_thumb11-3. 缩放x轴,顶点和法线变为1/2

    当发生非等比缩放时,应当翻转法线。当法线再次归一化后,它将与变形表面相匹配,同时也不会对等比缩放产生影响。

    image_thumb3

    1-4. 缩放x轴,顶点变为1/2法线变为2

    那如何去翻转缩放?

    根据O = T1T2T3矩阵描述了S缩放、R旋转、P位移,而每个T也可以拆解为SRP(见翻译1).O=S1R1P1S2R2P2S3R3P3成立,但是法线不需要改变位移,去掉T3;同时每个T也不需要位移,去掉P。最后简化为:

    O=S1R1S2R2

    目标是翻转S,所以object-to-world矩阵

    N = S-11R1S-12R2

    而Unity提供了world-to-object矩阵,它是object-to-world的逆矩阵

    N-1 = O-1=S2-1R2-1S1-1R1-1

    这个O-1同时把旋转和变换顺序也翻转了,需要对O-1进行转置消除对旋转的影响。

    (O-1T=(S2-1R2-1S1-1R1-1)T = N

    推导:
    RT = R-1 。 sin(-z) = –sin z , cos(-z) =  cosz

    image_thumb13 = image_thumb18 =image_thumb15

    O−1=R2-1S2−1R1−1S1−1=R2TS2−1R1TS1−1.

    (O−1)T=(S1−1)T(R1T)T(S21)T(R2T)T=(S1−1)TR1(S2−1)TR2.

    缩放矩阵具有单位矩阵特性,ST=S

    (O−1)T=S1−1R1S2−1R2=N.

    i.normal = mul(transpose((float3x3)unity_WorldToObject, v.normal);//这个写法行的通,只是学习一下上面的知识.汇编也很难看

    9: dp3 r0.x, cb1[4].xyzx, v1.xyzx
    10: dp3 r0.y, cb1[5].xyzx, v1.xyzx
    11: dp3 r0.z, cb1[6].xyzx, v1.xyzx
    12: dp3 r0.w, r0.xyzx, r0.xyzx
    13: rsq r0.w, r0.w
    14: mul o2.xyz, r0.wwww, r0.xyzx

    i.normal = mul(unity_ObjectToWorld, v.normal);//简化写法

    ☆Unity也提供了更好的函数:nityObjectToWorldNormal

    i.normal = UnityObjectToWorldNormal(v.normal);
    inline float3 UnityObjectToWorldNormal( in float3 norm ) {
        // Multiply by transposed inverse matrix,
        // actually using transpose() generates badly optimized code
        return normalize(
    	unity_WorldToObject[0].xyz * norm.x +
    	unity_WorldToObject[1].xyz * norm.y +
    	unity_WorldToObject[2].xyz * norm.z
        );
    }


    1.4 ReNormalizing

    在顶点函数正确计算法线并通过插值器传递给片元函数,但有问题:不同单位长度向量的线性插值不会作用于另外的单位长度向量。这可在片元函数进行纠正,但是这个问题还好。这一步优化可以在手机平台省略。


    2 漫反射着色Diffuse

    我们能够看见物体不是因为光源,而是物体反射光,这里有多种反射方式:漫反射、镜面反射、基于物理着色。这篇文章简要探讨了光照着色,现在来进行一次详细的学习。

    image_thumb47

    Diffuse = Albedo * lightColor * DotClamped(lightDir, normal)

    漫反射发生的原因是光线不会直接从物体表面反弹,而是一部分由表面的粗糙程度弥射开,另一部光穿透物体,在物体内部游走然后再次离开表面。我们不会完全遵循现实世界的物理细节。

    Lambert余弦定律:多少光从物体表面反射取决于光线射向表面时与法线的角度,当角度为0°时大多数光会反射;随着角度增加,反射光随着减少,直到90°没有光击中表面。漫射光量取决于光的方向与表面法线之间夹角的余弦值成正比我们已经知道了法线,但没有得到光的方向。

    float4 MyFragmentProgram(Interpolators i) : SV_TARGET
    {
        i.normal = normalize(i.normal);
    return dot(float3(0, 1, 0), i.normal);//先占位模拟方向光
    }

    几何定义:A⋅B=||A|| ||B|| cosθ;                           A⋅B=cosθ.
    代数定义:A⋅B=∑i=1nAiBi=A1B1+A2B2+…+AnBn. float dotProduct = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
    向量垂直投影到另一个向量,得到一个直角三角形,它的底边的长度是点积的结果。如果两个向量都是单位长度,那底边就是它们夹角的余弦。

    image_thumb20

    2-1. 点积

    2.1 约束负数Clamped Lighting

    防止物体表面被从后面来的光源照亮。当表面朝向光计算点积才是有意义的;当物体表面处于自己的阴影面是不需要接受光照的;当光的方向与法线的方向大于90°时点积的结果是负数。

    //方式1
    return max(0,dot(float3(0, 1, 0), i.normal));
    //方式2
    return saturete(dot(float3(0, 1, 0), i.normal));

    UnityStandardBRDF.cginc文件包含了一个DotClamped函数,

    inline half DotClamped (half3 a, half3 b) {
    	#if (SHADER_TARGET < 30 || defined(SHADER_API_PS3))
    		return saturate(dot(a, b));
    	#else
    		return max(0.0h, dot(a, b));
    	#endif
    }

    直接用吧。注意UnityStandardBRDF包含了UnityCG

    ...
    //#include "UnityCG.cginc"
    #include "UnityStandardBRDF.cginc"
    ...
    
    return DotClamped(float3(0, 1, 0), i.normal);

    image_thumb23

    2-2. UnityStandardBRDF引用结构

     

    2.2 光源Light Source

    在场景中新建一个方向光,参数都使用默认值。

    image_thumb272-3 Default Light

    UnityShaderVariables 定义了 float4 _WorldSpaceLightPos0,它表示当前光源的位置(或相对物体的方向),作为方向时去掉第四个分量

    float3 lightDir = _WorldSpaceLightPos0.xyz;
    return DotClamped(lightDir, i.normal);

    image_thumb36

    2-4. 为啥是黑色?


    2.3 光照模式Light Mode

    黑色的原因?在产生正确的光照结果之前,我们需要告诉Unity我们想要使用哪些光照数据。所以需要在Pass内指定LightMode标签。

    我们需要哪种光照模式取决于我们如何渲染场景。我们可以使用forward或deferred渲染路径。也有两种较老的渲染模式(legacy Deferred Lightinglegacy Vertex Lit,),但我们不会用它们。通过Camera组件image_thumb30选择渲染路径,这是5.6版本。

    使用ForwardBase关键字指定标签,这个Pass是当通过forward渲染路径渲染时的第一pass,通过它传递场景主光数据和其他数据。

    Pass {
        Tags {
            "LightMode" = "ForwardBase"
        }
        
        CGPROGRAM
        …
        ENDCG
    }

    image_thumb33

    2-5. diffuse light

     

    2.4 光照颜色Light Color

    光不全是白色,每个光都有自己的颜色。UnityLightingCommon.cginc定义了fixed4 _LightColor0变量。该变量表示光的颜色乘以光的强度:首先它是有rgba颜色值,同时光组件有一个Intensity强度属性,会改变颜色值的大小。

    float4 MyFragmentProgram (Interpolators i) : SV_TARGET {
        i.normal = normalize(i.normal);
        float3 lightDir = _WorldSpaceLightPos0.xyz;
        float3 lightColor = _LightColor0.rgb;
        float3 diffuse = lightColor * DotClamped(lightDir, i.normal);
        return float4(diffuse, 1);
    }

    image_thumb38

    2-6. 着色后的光


    2.5 反照率Albedo

    大多数物体材料会吸收一部分光,这成了它们的颜色。物体的漫反射率的颜色被称为反照率,它描述了多少红色、绿色、蓝色通道被漫反射,其余的颜色被吸收不反射。我们使用材料的纹理和色调定义它。而Albedo带有白化whiteness的含义,它作为因子控制物体由暗到亮。

    float3 diffuse = albedo * lightColor * DotClamped(lightDir, i.normal);

    image_thumb50 image_thumb51

    2-7. 漫反射:左Gama右Linear


    3 高光(镜面)反射 Specular Shading

    image_thumb49

    Specular = specularColor * lightColor * pow(DotClamped(normal, halfDir), smoothnees * n) n:0-255

    镜面反射不同于漫反射,光线以等于其击中物体表面的角度从表面反弹,这就是为什么像照镜子。表面需要极其光滑。
    观察者的位置对镜面反射很重要:仅当最终反射出的光朝向观察者是可见的,其他都看不见。
    所以,我们要知道从表面一点到观察者的方向,这就要求表面点和摄像机的世界位置。

    struct Interpolators {
    	...
    	float3 worldPos : TEXCOORD2;
    };
    //vertex计算
    i.worldPos = mul(unity_ObjectToWorld, v.position);//UnityObjectToWorldPos(v.position);
    
    //fragment计算
    float3 viewDirect = normalize(_WorldSpaceCameraPos - i.worldPos);

    float3 WorldSpaceCameraPos变量定义在UnityShaderVariables.cginc文件内


    3.1 反射光照颜色Reflecting Light

    要知道反射光线去向,可以用标准CG函数reflect(light direct, normal).它接受入射光线的方向和基于表面法线反射光线,我们要反向调整光的方向

    float3 reflectionDir = reflect(-lightDir, i.normal);
    return float4(reflectionDir * 0.5 + 0.5, 1);

    image_thumb53

    3-1. reflect specular

    Blinn公式:D – 2N(N · D)。reflect第一个参数加负号,是因为反射光线方向从表面发出,与入射光线的角度一致但方向相反而已

    假设物体表面极其光滑,我们将只能在表面角度合适的地方看见反射光,在其他地方反射光对观察者不可见并呈现黑色。但实际上物体表面是不平整的,有太多细微的凹凸,这也意味着表面法线差别很大。

    尽管我们的观察方向不完全匹配反射方向,我们仍能看见一些反射光。当我们偏离反射方向越多,我们能看见的反射光就越少,所以我们继续约束dot点积值

    return DotClamped(viewDir, reflectionDir);

    image

    image

    3-2 clamp dot specular


    3.2 光滑度Smoothness

    这中效果产生的高光大小取决于材料的粗糙度,光滑的材料聚光更好,有更小的亮点。我们能够在材质属性控制平滑度,通常定义在0-1之间。

    ...
    _Smoothness ("Smoothness", Range(0, 1)) = 0.5
    ...
    float _Smoothness;

    我们使用smoothness作为因子,通过提高点积的幂次来缩小光点。但是必须要比1大得多才具有更好的效果。再给因子提高100倍。

    return pow(
        DotClamped(viewDir, reflectionDir),
        _Smoothness * 100
    );

    image

    3-3. smoothness 幂次效果


    3.3 Bilnn-Phong

    我们上面使用的是Blinn reflection计算公式,但业界更常用Blinn-Phong reflection公式。通使用一个光照方向和视野方向的半角方向,然后再取法向量和半角向量的点积结果来决定镜面反射。

    float3 halfDirect = normalize(lightDir + viewDirect);
        return pow(
        DotClamped(i.normal, halfDirect),
        _Smoothness * 100
    );

    image

    image

    3-4. Blinn-Phong 效果

    与3-3图相比,光点面积变得更大了。但我们可以继续提高smoothness的值抵消这个问题。这两个计算都是近似模拟,但是Blinn-Phong要好一点。


    3.4 镜面颜色Specular Color-强金属

    反射颜色同漫反射原理类似,但是有不同。金属的反照率很低,但反射率很强,常带有颜色。同样增加纹理和颜色属性控制。

    float3 specular = _SpecularTint.rgb * lightColor * pow(
        DotClamped(i.normal, halfDirect),
        _Smoothness * 100
    );

    image

    3-5. Metal 效果

    3.5 Diffuse加Specular

     image image

    3-6.Diffuse+Specular :左Gamma右Linear


    4 能量守恒Energy Conservation

    直接把Diffuse加Specular是有问题的!结果可能比光源更亮,当时用全白加低smoothness值很明显。

    image

    4-1. 很亮的镜面,0.1smoothness

    实际上,当光击中物体表面,一部分会作为镜面光反弹,其余部分或穿透表面作为漫反射反弹,或被吸收。但上图4-1没有考虑到这点,导致光以最大强度被漫反射和镜面反射,最终使得光的能量加倍了。

    我们必须确保漫反射和镜面反射部分高光之和不会超过1,这保证了我们不是凭空创造光;少于1无妨,说明有一部分光被吸收了。当我们使用恒定的镜面反射色时,我们可以简单地通过将反照率乘以1减去镜面反射色调来调整反照率色度

    albedo *= 1 - _SpecularTint.rgb;

    现在,漫反射和镜面反射已绑定在一起。 镜面反射越强,漫反射部分越暗。


    4.1 单色Monochrome-非金属

    在这种色调模型下,当Specular色调是灰度图时这中方法工作良好。其他颜色就会有奇怪的结果,例如红色Specular色调只会减少漫反射的红色部分(相当于被吸收了)。为了避免这个颜色,需要采用Monochrome能量守恒,使用高光色中最强的分量来减少反照率。


    image image

    4-2.  左:红色高光,青色反照率 右:单色能量守恒

    4.2 Utility函数

    Unity也定义了相关的能量守恒函数供使用,UnityStandardUtils.cginc声明了EnergyConservationBetweenDiffuseAndSpecular

    image

    4-3. UnityShandarUtils 引用结构

    这个函数把反照率和镜面色作为输入,输出合适的反照率。它还有第三个输出变量:one-minus-reflectivity。它是1-镜面反射强度,再与反射率相乘。

    half SpecularStrength(half3 specular) {
    	#if (SHADER_TARGET < 30)
    		// SM2.0: instruction count limitation
    		// SM2.0: simplified SpecularStrength
    		// Red channel - because most metals are either monochrome
    		// or with redish/yellowish tint
    		return specular.r;
    	#else
    		return max(max(specular.r, specular.g), specular.b);
    	#endif
    }
    
    // Diffuse/Spec Energy conservation
    inline half3 EnergyConservationBetweenDiffuseAndSpecular (
    	half3 albedo, half3 specColor, out half oneMinusReflectivity
    ) {
    	oneMinusReflectivity = 1 - SpecularStrength(specColor);
    	#if !UNITY_CONSERVE_ENERGY
    		return albedo;
    	#elif UNITY_CONSERVE_ENERGY_MONOCHROME
    		return albedo * oneMinusReflectivity;
    	#else
    		return albedo * (half3(1, 1, 1) - specColor);
    	#endif
    }


    4.3 金属工作流Metallic

    镜面反射工作流:我们需要关注两种基本的材质:金属和非金属。使用更强大的镜面色specular_tint创建金属反射,使用单色specular创建非金属反射。

    金属反射工作流:金属没有反照率,使用镜面色代替反照色;而非金属没有镜面色,也就不需要高光镜面色。

    新建一个metallic变量去掉_SpecularTint,0-1的范围。两端表示要么是金属要么不是,中间值表示介于金属与非金属之间的物体。

    float3 specularTint        = albedo * _Metallic;
    float oneMinusReflectivity = 1 - _Metallic;
    //albedo = EnergyConservationBetweenDiffuseAndSpecular(
    //albedo, _SpecularTint.rgb, oneMinusReflectivity
    //);
    albedo            *= oneMinusReflectivity;
    float3 diffuse    = albedo * lightColor * DotClamped(lightDir, i.normal);
    float3 halfVector = normalize(lightDir + viewDir);
    float3 specular = specularTint * lightColor * pow(
        DotClamped(halfVector, i.normal),
        _Smoothness * 100
    );

    上述金属工作流太理想化,即使是纯非金属也有点金属反射,因此高光强度与反射值不完全匹配金属范围值。Unity也提供了函数定义在UnitStandardUtils.cginc 的DiffuseAndSpecularFromMetallic函数

    float3 specularTint; // = albedo * _Metallic;
    float oneMinusReflectivity; // = 1 - _Metallic;
    //albedo *= oneMinusReflectivity;
    albedo = DiffuseAndSpecularFromMetallic(
        albedo, _Metallic, specularTint, oneMinusReflectivity
    );
    inline half OneMinusReflectivityFromMetallic(half metallic) {
    	// We'll need oneMinusReflectivity, so
    	//   1-reflectivity = 1-lerp(dielectricSpec, 1, metallic)
    	//                  = lerp(1-dielectricSpec, 0, metallic)
    	// store (1-dielectricSpec) in unity_ColorSpaceDielectricSpec.a, then
    	//	 1-reflectivity = lerp(alpha, 0, metallic)
    	//                  = alpha + metallic*(0 - alpha)
    	//                  = alpha - metallic * alpha
    	half oneMinusDielectricSpec = unity_ColorSpaceDielectricSpec.a;
    	return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
    }
    
    inline half3 DiffuseAndSpecularFromMetallic (
    	half3 albedo, half metallic,
    	out half3 specColor, out half oneMinusReflectivity
    ) {
    	specColor = lerp(unity_ColorSpaceDielectricSpec.rgb, albedo, metallic);
    	oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic);
    	return albedo * oneMinusReflectivity;
    }

    有个细节,metallic值应该在Gamma颜色空间计算,如果Unity使用了Linear渲染,需要指定一个标签告诉Unity使用Gamma进行校正

    [Gamma] _Metallic ("Metallic", Range(0, 1)) = 0


    4.4 扣汇编:片元函数计算Diffuse、Specular

    -- Fragment shader for "d3d11":
    // Stats: 25 math, 1 textures
    Set 2D Texture "_MainTex" to slot 0
    
    Constant Buffer "$Globals" (128 bytes) on slot 0 {
      Vector4 _LightColor0 at 32
      Vector4 _Tint at 64
      Float _Metallic at 80
      Float _Smoothness at 112
    }
    Constant Buffer "UnityPerCamera" (144 bytes) on slot 1 {
      Vector3 _WorldSpaceCameraPos at 64
    }
    Constant Buffer "UnityLighting" (752 bytes) on slot 2 {
      Vector4 _WorldSpaceLightPos0 at 0
    }
    
    Shader Disassembly:
          ps_4_0
          dcl_constantbuffer CB0[8], immediateIndexed//cbuffer,0寄存器号,8元素数
          dcl_constantbuffer CB1[5], immediateIndexed
          dcl_constantbuffer CB2[1], immediateIndexed
          dcl_sampler s0, mode_default//采样寄存器
          dcl_resource_texture2d (float,float,float,float) t0
          dcl_input_ps linear v1.xy//uv
          dcl_input_ps linear v2.xyz//normal
          dcl_input_ps linear v3.xyz//worldPos
          dcl_output o0.xyzw
          dcl_temps 3
       0: add r0.xyz, -v3.xyzx, cb1[4].xyzx//得到摄像机方向
       1: dp3 r0.w, r0.xyzx, r0.xyzx//计算摄像机方向点积
       2: rsq r0.w, r0.w//开方取倒数:1/sqrt(w).
       3: mad r0.xyz, r0.xyzx, r0.wwww, cb2[0].xyzx//对摄像机方向归一化,再与_WorldSpaceLightPos0相加得到半角向量
       //4-6 对半角向量归一化 r0
       4: dp3 r0.w, r0.xyzx, r0.xyzx//计算半角向量点积
       5: rsq r0.w, r0.w//开方取倒数
       6: mul r0.xyz, r0.wwww, r0.xyzx
       //7-9 对normal归一化 r1
       7: dp3 r0.w, v2.xyzx, v2.xyzx
       8: rsq r0.w, r0.w
       9: mul r1.xyz, r0.wwww, v2.xyzx
       //10-12
      10: dp3_sat r0.x, r1.xyzx, r0.xyzx
      11: dp3_sat r0.y, cb2[0].xyzx, r1.xyzx
      12: log r0.x, r0.x
      13: mul r0.z, cb0[7].x, l(100.000000)
      14: mul r0.x, r0.x, r0.z
      15: exp r0.x, r0.x
      16: sample r1.xyzw, v1.xyxx, t0.xyzw, s0
      17: mad r2.xyz, r1.xyzx, cb0[4].xyzx, l(-0.220916, -0.220916, -0.220916, 0.000000)
      18: mul r1.xyz, r1.xyzx, cb0[4].xyzx
      19: mad r2.xyz, cb0[5].xxxx, r2.xyzx, l(0.220916, 0.220916, 0.220916, 0.000000)
      20: mul r2.xyz, r2.xyzx, cb0[2].xyzx
      21: mul r0.xzw, r0.xxxx, r2.xxyz
      22: mad r1.w, -cb0[5].x, l(0.779084), l(0.779084)
      23: mul r1.xyz, r1.wwww, r1.xyzx
      24: mul r1.xyz, r1.xyzx, cb0[2].xyzx
      25: mad o0.xyz, r1.xyzx, r0.yyyy, r0.xzwx
      26: mov o0.w, l(1.000000)
      27: ret


    5 PBR

    Blinn-Phong一直是游戏界主力选手,但是基于物理着色算法正在崛起。因为它更真实和可预测。游戏引擎和建模工具正在统一使用相同的着色算法,这使得内容创建更容易,各厂商们正慢慢向PBS实现靠拢。

    Unity的标准着色器也使用了PBS算法,Unity有多种实现,取决于不同平台、硬件、API。这个算法通过UNITY_BRDF_PBS指令访问,定义在UnityPBSLighting.cginc文件。BRDF:birdirectional reflectance distribution function,双向反射分布函数。

    image5-1. UnityPBSLighting 引用结构

    // Default BRDF to use:
    #if !defined (UNITY_BRDF_PBS)
    	// allow to explicitly override BRDF in custom shader
    	// still add safe net for low shader models,
    	// otherwise we might end up with shaders failing to compile
    	#if SHADER_TARGET < 30
    		#define UNITY_BRDF_PBS BRDF3_Unity_PBS
    	#elif UNITY_PBS_USE_BRDF3
    		#define UNITY_BRDF_PBS BRDF3_Unity_PBS
    	#elif UNITY_PBS_USE_BRDF2
    		#define UNITY_BRDF_PBS BRDF2_Unity_PBS
    	#elif UNITY_PBS_USE_BRDF1
    		#define UNITY_BRDF_PBS BRDF1_Unity_PBS
    	#elif defined(SHADER_TARGET_SURFACE_ANALYSIS)
    		// we do preprocess pass during shader analysis and we dont
    		// actually care about brdf as we need only inputs/outputs
    		#define UNITY_BRDF_PBS BRDF1_Unity_PBS
    	#else
    		#error something broke in auto-choosing BRDF
    	#endif
    #endif

    这些函数包含了大量的数学运算,这里不去探讨。它们仍将计算漫反射和镜面反射,除此外,它们还有Fresnel菲涅尔反射。这增加了在掠射角观察物体时的反射,当把场景反射纳入其中时就变得很明显。

    UNITY_BRDF_PBS(反照率,镜面色,oneMinusReflectivity,光滑度,法线,视野方向,直接光,间接光);

    前面6个参数都有,后面两个需要计算直接光和间接光


    5.1 定义光的结构体

    UnityLightingCommon.cginc定义了UnityLight结构体和UnityIndirect结构体

    struct UnityLight
    {
        half3 color;
        half3 dir;
        half  ndotl; // Deprecated: Ndotl is now calculated on the fly and is no longer stored. Do not used it.
    };
    
    struct UnityIndirect
    {
        half3 diffuse;//代表环境光
        half3 specular;//代表环境反射
    };

    只需要把计算好的值赋给结构体对应的变量即可

    //直接光
    UnityLight light;
    light.color = lightColor;
    light.dir   = lightDir;
    light.ndotl = DotClamped(lightDir, i.normal);
    //间接光
    UnityIndirect indirect;
    indirect.diffuse  = 0;
    indirect.specular = 0;


    6 原文

    给原作者点赞!

    重点是计算diffuse+specular。

  • 相关阅读:
    507.Perfect Number
    441.Arranging Coins
    344.Reverse String
    160.Intersection of Two Linked Lists
    HDU-2521 反素数
    HDU-2710 Max Factor
    HDU-2552 三足鼎立
    HDU-2549 壮志难酬
    HDU-2548 两军交锋
    HDU-2550 百步穿杨
  • 原文地址:https://www.cnblogs.com/baolong-chen/p/12173995.html
Copyright © 2011-2022 走看看