Pbr公式结合shader一

         原理方面的东西很难搞明白,越研究疑惑越多,所以先放弃原理,今天结合unity shader来分析下如何套用原理方面的东西很难搞明白,越研究疑惑越多,所以先放弃原理,今天结合unity shader来分析下如何套用,第一次研究,如果有错误的地方欢迎指出。

 

反射率方程

 

将这个公式拆分为两部分 

第一部分:

这是半球积分,表示多光源下光照的叠加,主要是为了环境反射

第二部分:半球积分以内的部分,主要是直射光的结果

第一部分跟第二部分结合过后的结果如下: 

输出颜色 =

 

结果还是两部分组成,漫反射部分 跟 高光部分。

 

 

        下面就开始分别给公式里的每一项来赋值了。

1.漫反射部分:

漫反射比例:Kd  它是用来根据金属度计算漫反射和镜面反射比例,当Metallic为1时,反射率接近1,函数返回的diffColor接近                          0,表示几乎不反射漫反射。下面代码的结果得到的是个float3:albedo x Kd系数 的值

inline half OneMinusReflectivityFromMetallic(half metallic)
{
    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;
}

Unity_ColorSpaceDielectricSpec为Unity的内置变量,定义了绝缘体的高光颜色和反射率,不完全为0,是一个经验值。

从第二段函数里:我们可以得到3个变量的结果:

1)specColor:有点不太好理解,只是一个由unity自带的Unity_ColorSpaceDielectricSpec内置变量,比较暗 ,跟 贴图采样              的值根据金属度来lerp,是用来做菲尼尔效果用的

                               

    metallic=0                                    metallic=1

2)oneMinusReflectivity :就是我们想要的Kd系数,及漫反射系数。

3)贴图颜色*oneMinusReflectivity

纹理颜色 

unity分为两部分来做,一个是迪斯尼漫反射,还有一个是兰伯特漫反射,兰伯特漫反射更节省一些,效果并每有太大的差异

迪斯尼漫反射:

half DisneyDiffuse(half NdotV, half NdotL, half LdotH, half perceptualRoughness)
{
    half fd90 = 0.5 + 2 * LdotH * LdotH * perceptualRoughness;
    half lightScatter   = (1 + (fd90 - 1) * Pow5(1 - NdotL));
    half viewScatter    = (1 + (fd90 - 1) * Pow5(1 - NdotV));
    return lightScatter * viewScatter;
}

那么得出的结果就是:

directDiffuse = lightScatter * viewScatter ;

这个函数计算的结果 还需要 *NdotL*atten*LightColor,而的出来的结果其实就是漫反射系数 ,比lanbert好的一点 ,是随着粗造度的变化,效果稍有变化。

directDiffuse *=  NdotL * attenColor;

环境光照的漫反射部分

 gi.indirect.diffuse:这就是环境光的漫反射部分

GI data:

                 UnityLight light;
                #ifdef LIGHTMAP_OFF
                    light.color = lightColor;
                    light.dir = lightDirection;
                    light.ndotl = LambertTerm (normalDirection, light.dir);
                #else
                    light.color = half3(0.f, 0.f, 0.f);
                    light.ndotl = 0.0f;
                    light.dir = half3(0.f, 0.f, 0.f);
                #endif
                UnityGIInput d;
                d.light = light;
                d.worldPos = i.posWorld.xyz;
                d.worldViewDir = viewDirection;
                d.atten = attenuation;
                #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
                    d.ambient = 0;
                    d.lightmapUV = i.ambientOrLightmapUV;
                #else
                    d.ambient = i.ambientOrLightmapUV;
                #endif
                #if UNITY_SPECCUBE_BLENDING || UNITY_SPECCUBE_BOX_PROJECTION
                    d.boxMin[0] = unity_SpecCube0_BoxMin;
                    d.boxMin[1] = unity_SpecCube1_BoxMin;
                #endif
                #if UNITY_SPECCUBE_BOX_PROJECTION
                    d.boxMax[0] = unity_SpecCube0_BoxMax;
                    d.boxMax[1] = unity_SpecCube1_BoxMax;
                    d.probePosition[0] = unity_SpecCube0_ProbePosition;
                    d.probePosition[1] = unity_SpecCube1_ProbePosition;
                #endif
                d.probeHDR[0] = unity_SpecCube0_HDR;
                d.probeHDR[1] = unity_SpecCube1_HDR;
                Unity_GlossyEnvironmentData ugls_en_data;
                ugls_en_data.roughness = 1.0 - gloss;
                ugls_en_data.reflUVW = viewReflectDirection;
                UnityGI gi = UnityGlobalIllumination(d, 1, normalDirection, ugls_en_data );
                lightDirection = gi.light.dir;
                lightColor = gi.light.color;

漫反射最终:

float3 diffuse = (directDiffuse/ UNITY_PI + gi.indirect.diffuse) * Kd * NdotL * attenColor;

在unity里面 并没有/ UNITY_PI  可能是考虑效果吧 ,漫反射会有点暗

 

高光部分

 

首先看高光 的鏡面反射比例,在unity shader里面 并没有看到这个数值,看到别人的文章,发现,F就是这个系数....

先看这部分吧

镜面高光D:

inline half GGXTerm (half NdotH, half roughness)
{
    half a2 = roughness * roughness;
    half d = (NdotH * a2 - NdotH) * NdotH + 1.0f;
    return UNITY_INV_PI * a2 / (d * d + 1e-7f);
}

这里的 roughness 也就是公式种的a:表示 粗造度的平方 ,所以a2 就是粗造度的4次方

d 的结果就是 它的平方也就是d*d,  UNITY_INV_PI  应该就是1/UNITY_PI 

d*d 后面还要加个很小的值  是防止分母为0  

这里官方有了个提示:这个函数不打算在手机上使用,如果用在手机上,应该是需要优化的

几何遮罩G:

这里unity里面的代码

inline half SmithVisibilityTerm (half NdotL, half NdotV, half k)
{
    half gL = NdotL * (1-k) + k;
    half gV = NdotV * (1-k) + k;
    return 1.0 / (gL * gV + 1e-5f); 
}


inline half SmithBeckmannVisibilityTerm (half NdotL, half NdotV, half roughness)
{
    half c = 0.797884560802865h; 
    half k = roughness * c;
    return SmithVisibilityTerm (NdotL, NdotV, k) * 0.25f;
}

第一个函数: 

gL 部分就是

gv 部分就是

所以第一个函数的出来得是这个公式的    1/分母部分  在第二个函数里  乘上它的分子部分就可以了

第二个函数:

k的值跟公式里的不一样,c的值是sqrt(2 / Pi),2/Pi的开平方  而结果的地方并没有乘以分子部分 而是直接乘以0.25f 

是因为分子部分跟总的公式的分母部分对消掉了       而0.25f 也就是总公式分母部分的4

其实正真的G的结果应该是

float4 G = NdotL*NdotV/ (gL * gV + 1e-5f)

 

菲尼尔部分 F :

inline half3 FresnelTerm (half3 F0, half cosA)
{
    half t = Pow5 (1 - cosA);   // ala Schlick interpoliation
    return F0 + (1-F0) * t;
}
inline half3 FresnelLerp (half3 F0, half3 F90, half cosA)
{
    half t = Pow5 (1 - cosA);   // ala Schlick interpoliation
    return lerp (F0, F90, t);
}

inline half3 FresnelLerpFast (half3 F0, half3 F90, half cosA)
{
    half t = Pow4 (1 - cosA);
    return lerp (F0, F90, t);
}

unity 用了3种方式写了菲尼尔

第一种:F0 就是我们上面的specularColor,不记得可以往上翻,漫反射系数的地方。cosA 就是LdotH

LdotH 的由来:

float3 halfDirection = normalize(viewDirection+lightDirection);
float LdotH = saturate(dot(lightDirection, halfDirection));

在unitypbr第一档中用的是第一种

第二种:F0 依然是specularColor ,F90 = saturate(gloss + (1-oneMinusReflectivity)),t=NdotV;

这种用在了环境高光的系数上,光滑度越大,环境高光就越亮

第三种跟第二种差不多 只是将第二种的5次方变成了4次方

最后得出的直射高光部分:

SpecularTerm = (G*D*F) * UNITY_PI;  看起来跟公式不太一样

分母的4放在了G上面 也就是最后面乘的0.25,这部分跟G的分子抵消掉了,多了个UNITY_PI,说明unity的公式跟我们的公式不太一样,或者说pbr的公式不是唯一的。

然后在这个基础上还要乘以  也就是这个部分

SpecularTerm*=NdotL;

SpecularTerm*=attenColor;

环境光高光部分:

                half surfaceReduction;
                #ifdef UNITY_COLORSPACE_GAMMA
                surfaceReduction = 1.0-0.28*roughness*perceptualRoughness;
                #else
                surfaceReduction = 1.0/(roughness*roughness + 1.0);
                #endif
                half grazingTerm = saturate( gloss + oneMinusReflectivity);
				float3 indirectSpecular =  (gi.indirect.specular);
                indirectSpecular *= FresnelLerp (specularColor, grazingTerm, NdotV);
                indirectSpecular *= surfaceReduction;

高光部分最终的输出:

float3 specular = (SpecularTerm + indirectSpecular);

 

总的公式最终输出:

float3 finalColor = diffuse+ specular;

感觉写得比较乱,不知道大家能不能看懂,看不懂也不要紧,下面给出代码,直接套用,就可以了,只是用在手机端需要慎重,需要各种优化才可以,下面的代码是只有一个pass 独立灯光的。

代码如下:

 



Shader "Shader PBR01" {
    Properties {
        _BumpMap ("Normal Map", 2D) = "bump" {}
        _Color ("Color", Color) = (0.5019608,0.5019608,0.5019608,1)
        _MainTex ("Base Color", 2D) = "white" {}
        _Metallic ("Metallic", Range(0, 1)) = 0
        _Gloss ("Gloss", Range(0, 1)) = 0.8
    }
    SubShader {
        Tags {
            "RenderType"="Opaque"
        }
        Pass {
            Name "FORWARD"
            Tags {
                "LightMode"="ForwardBase"
            }
            
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #define UNITY_PASS_FORWARDBASE
            #define SHOULD_SAMPLE_SH ( defined (LIGHTMAP_OFF) && defined(DYNAMICLIGHTMAP_OFF) )
            #define _GLOSSYENV 1
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"
            #include "UnityPBSLighting.cginc"
            #include "UnityStandardBRDF.cginc"
            #pragma multi_compile_fwdbase_fullshadows
            #pragma multi_compile LIGHTMAP_OFF LIGHTMAP_ON
            #pragma multi_compile DIRLIGHTMAP_OFF DIRLIGHTMAP_COMBINED DIRLIGHTMAP_SEPARATE
            #pragma multi_compile DYNAMICLIGHTMAP_OFF DYNAMICLIGHTMAP_ON
            #pragma multi_compile_fog
            #pragma target 3.0
             float4 _Color;
             sampler2D _MainTex; uniform float4 _MainTex_ST;
             sampler2D _BumpMap; uniform float4 _BumpMap_ST;
             float _Metallic;
             float _Gloss;
            struct VertexInput {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                float2 texcoord0 : TEXCOORD0;
                float2 texcoord1 : TEXCOORD1;
                float2 texcoord2 : TEXCOORD2;
            };
            struct VertexOutput {
                float4 pos : SV_POSITION;
                float2 uv0 : TEXCOORD0;
                float2 uv1 : TEXCOORD1;
                float2 uv2 : TEXCOORD2;
                float4 posWorld : TEXCOORD3;
                float3 normalDir : TEXCOORD4;
                float3 tangentDir : TEXCOORD5;
                float3 bitangentDir : TEXCOORD6;
                LIGHTING_COORDS(7,8)
                UNITY_FOG_COORDS(9)
                #if defined(LIGHTMAP_ON) || defined(UNITY_SHOULD_SAMPLE_SH)
                    float4 ambientOrLightmapUV : TEXCOORD10;
                #endif
            };
            VertexOutput vert (VertexInput v) {
                VertexOutput o = (VertexOutput)0;
                o.uv0 = v.texcoord0;
                o.uv1 = v.texcoord1;
                o.uv2 = v.texcoord2;
                #ifdef LIGHTMAP_ON
                    o.ambientOrLightmapUV.xy = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
                    o.ambientOrLightmapUV.zw = 0;
                #elif UNITY_SHOULD_SAMPLE_SH
                #endif
                #ifdef DYNAMICLIGHTMAP_ON
                    o.ambientOrLightmapUV.zw = v.texcoord2.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
                #endif
                o.normalDir = UnityObjectToWorldNormal(v.normal);
                o.tangentDir = normalize( mul( unity_ObjectToWorld, float4( v.tangent.xyz, 0.0 ) ).xyz );
                o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
                o.posWorld = mul(unity_ObjectToWorld, v.vertex);
                float3 lightColor = _LightColor0.rgb;
                o.pos = UnityObjectToClipPos( v.vertex );
                UNITY_TRANSFER_FOG(o,o.pos);
                TRANSFER_VERTEX_TO_FRAGMENT(o)
                return o;
            }
            float4 frag(VertexOutput i) : COLOR {
                i.normalDir = normalize(i.normalDir);
                float3x3 tangentTransform = float3x3( i.tangentDir, i.bitangentDir, i.normalDir);
                float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
                float3 _BumpMap_var = UnpackNormal(tex2D(_BumpMap,TRANSFORM_TEX(i.uv0, _BumpMap)));
                float3 normalLocal = _BumpMap_var.rgb;
                float3 normalDirection = normalize(mul( normalLocal, tangentTransform )); // Perturbed normals
                float3 viewReflectDirection = reflect( -viewDirection, normalDirection );
                float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
                float3 lightColor = _LightColor0.rgb;
                float3 halfDirection = normalize(viewDirection+lightDirection);
// Lighting:
                float attenuation = LIGHT_ATTENUATION(i);
                float3 attenColor = attenuation * _LightColor0.xyz;
                float Pi = 3.141592654;
                float InvPi = 0.31830988618;
/ Gloss:
                float gloss = _Gloss;
                float perceptualRoughness = 1.0 - _Gloss;
                float roughness = perceptualRoughness * perceptualRoughness;

/// GI Data:
                UnityLight light;
                #ifdef LIGHTMAP_OFF
                    light.color = lightColor;
                    light.dir = lightDirection;
                    light.ndotl = LambertTerm (normalDirection, light.dir);
                #else
                    light.color = half3(0.f, 0.f, 0.f);
                    light.ndotl = 0.0f;
                    light.dir = half3(0.f, 0.f, 0.f);
                #endif
                UnityGIInput d;
                d.light = light;
                d.worldPos = i.posWorld.xyz;
                d.worldViewDir = viewDirection;
                d.atten = attenuation;
                #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
                    d.ambient = 0;
                    d.lightmapUV = i.ambientOrLightmapUV;
                #else
                    d.ambient = i.ambientOrLightmapUV;
                #endif
                #if UNITY_SPECCUBE_BLENDING || UNITY_SPECCUBE_BOX_PROJECTION
                    d.boxMin[0] = unity_SpecCube0_BoxMin;
                    d.boxMin[1] = unity_SpecCube1_BoxMin;
                #endif
                #if UNITY_SPECCUBE_BOX_PROJECTION
                    d.boxMax[0] = unity_SpecCube0_BoxMax;
                    d.boxMax[1] = unity_SpecCube1_BoxMax;
                    d.probePosition[0] = unity_SpecCube0_ProbePosition;
                    d.probePosition[1] = unity_SpecCube1_ProbePosition;
                #endif
                d.probeHDR[0] = unity_SpecCube0_HDR;
                d.probeHDR[1] = unity_SpecCube1_HDR;
                Unity_GlossyEnvironmentData ugls_en_data;
                ugls_en_data.roughness = 1.0 - gloss;
                ugls_en_data.reflUVW = viewReflectDirection;
                UnityGI gi = UnityGlobalIllumination(d, 1, normalDirection, ugls_en_data );
                lightDirection = gi.light.dir;
                lightColor = gi.light.color;
// Specular:
                float NdotL = saturate(dot( normalDirection, lightDirection ));
                float LdotH = saturate(dot(lightDirection, halfDirection));
                float3 specularColor = 0;
                float oneMinusReflectivity;
                float4 _MainTex_var = tex2D(_MainTex,TRANSFORM_TEX(i.uv0, _MainTex));
				//漫反射 结合金属度的比例————————————————————————————————————————
                float3 albedo = (_MainTex_var.rgb*_Color.rgb); // Need this for specular when using metallic
				float3 Kd = DiffuseAndSpecularFromMetallic(albedo, _Metallic, specularColor, oneMinusReflectivity);
				//————————————————————————————————————————————————————————————————————————————————
				

				oneMinusReflectivity = 1.0- oneMinusReflectivity;
                float NdotV = abs(dot( normalDirection, viewDirection ));
                float NdotH = saturate(dot( normalDirection, halfDirection ));
                float VdotH = saturate(dot( viewDirection, halfDirection ));



//镜面反射部分————————————————————————————————————————————————————————————————————————————
				//G
                float G = SmithJointGGXVisibilityTerm( NdotL, NdotV, roughness );

				//D
                float D = GGXTerm(NdotH, roughness); //镜面高光

				//F
				float F = FresnelTerm(specularColor, LdotH);


                float SpecularTerm = (G*D*F) * UNITY_PI;  
//————————————————————————————————————————————————————————————————————————————————
				


                #ifdef UNITY_COLORSPACE_GAMMA
				SpecularTerm = sqrt(max(1e-4h, SpecularTerm));
                #endif
				SpecularTerm = max(0, SpecularTerm  * NdotL);
                #if defined(_SPECULARHIGHLIGHTS_OFF)
				SpecularTerm = 0.0;
                #endif
                half surfaceReduction;
                #ifdef UNITY_COLORSPACE_GAMMA
                    surfaceReduction = 1.0-0.28*roughness*perceptualRoughness;
                #else
                    surfaceReduction = 1.0/(roughness*roughness + 1.0);
                #endif
				SpecularTerm *= any(specularColor) ? 1.0 : 0.0;//如果specularColor不为0,镜面反射正常,为0的话 镜面反射为0

                 SpecularTerm *= attenColor;
                half grazingTerm = saturate( gloss + oneMinusReflectivity);
				float3 indirectSpecular =  (gi.indirect.specular);
                indirectSpecular *= FresnelLerp (specularColor, grazingTerm, NdotV);
                indirectSpecular *= surfaceReduction;
                float3 specular = (SpecularTerm + indirectSpecular);
/// Diffuse:
				//迪斯尼漫反射 x 漫反射系数 x 灯光————————————————————————————————————————
                NdotL = max(0.0,dot( normalDirection, lightDirection ));
                half fd90 = 0.5 + 2 * LdotH * LdotH * (1-gloss);
                float nlPow5 = Pow5(1-NdotL);
                float nvPow5 = Pow5(1-NdotV);
                float3 directDiffuse = (1 +(fd90 - 1)*nlPow5) * (1 + (fd90 - 1)*nvPow5) * NdotL * attenColor;

				//——————————————————————————————————————————————————————————————————————————————————————————————

				//漫反射颜色 + 环境反射颜色   然后 x 漫反射比例
                float3 diffuse = (directDiffuse + gi.indirect.diffuse) * Kd;
/// Final Color:
                float3 finalColor = diffuse+ specular;
                fixed4 finalRGBA = fixed4(finalColor,1);
                UNITY_APPLY_FOG(i.fogCoord, finalRGBA);
				return float4(diffuse, 1);
                return finalRGBA;
            }
            ENDCG
        }

    }
    FallBack "Diffuse"
   
}

亲,如果您觉得本文不错,愿意给我一些动力的话,请用手机扫描二维码即可向我打赏

                                         打赏

 

 

 

 

 

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值