我们的目标:UnityStandard
我一直作Unity开发,所以环境也就选择了Unity。目标也就是尽可能接近UnityStandard的效果,不过主要为了学习PBR,所以就只实现基本的PBR和法线。也就是使用Albedo,Matellic,Normal三个贴图。遮蔽,自发光,反射和ImageBasedLighting这种就先不管了
PBR原理
PBR的原理务必要看Trace的这篇【PBR】基于物理渲染的基础理论1.0版
简单的解释
1. 分开处理反射面的绝缘体特性和金属特性,最后光照应该是Diffuse+Specular
2. 纯金属没有Diffuse,非金属主要是Diffuse,有一点反射
3. 反光部分的主要有三个东西影响:微表面的法线分布(NDF),微表面的入射和反射遮挡(Geometry Function),反射率和入射角的关系(Fresnel反射)
猜测Unity的方案
我没去找Standard的源码,照着WORKING WITH PHYSICALLY-BASED SHADING: A PRACTICAL APPROACH猜测一下他的方案。
Unity Metallic方式
1. Albedo:基础色,和以前的Diffuse图不一样,不能画出光的效
2. Metallic:Metallic图的r通道,描述金属性质。应该是描述反射率的参数,用这个参数将Diffuse部分和Specular部分分开
3. Smoothness:光滑度,Metallic图的a通道。用来描述光滑程度(废话),或者说,描述微表面理论中微表面法线和整体法线的一致程度(NDF),以及微表面遮挡的程度(Geometry Function)
4. Normal:法线,没啥说的
所以看起来差不多应该是这样的意思:
Diffuse*(1-Metallic)+f(l,v)*Metallic
f(l,v)就是PBR的核心内容,BRDF公式:
// D(h) F(v,h) G(l,v,h) //f(l,v) = --------------------------- // 4(n·l)(n·v)
BRDF公式的选择与说明
公式这东西谁看谁晕,所以先强推这个文章
如何看懂这些"该死的"图形学公式
我能继续搞下去全靠这个了。
到这里问题就很简单了,选择合适的公式就对了。翻找了一下SIGGRAPH的文章,全是干货真是好地方!照着人家的干货做了如下选择:
1. D(h):GGX
// alpha^2 //D(m) = ----------------------------------- // pi*((n·m)^2 *(alpha^2-1)+1)^2
Q:alpha是什么
A:alpha = roughness * roughness,roughness是粗糙度,roughness= 1-smoothness
Q:m是什么?h是什么?
A:m是微表面的法线, h是入射光和反射光的半角向量,根据微表面原理,只有法线和半角向量一致的微表面参与反射,所以m和h是相等的
2. G(l,v,h):Smith-Schlick,在Smith近似下G(l,v,h) = g(l)*g(v),讲道理入射和反射的遮蔽应该是相同的。
// n·v //g(v) = ----------------- // (n·v) *(1-k) +k
Q:k是什么?
A:k是Schlick公式的参数,具体的要去看Schlick的论文
Q:k应该是多少?
A:我看了几个地方,k的选择都不太一样,这里我们本着找NB的抄的态度,用了UE4的 k =(a^2 +1) * (a^2 +1)/8;
3. F(v,h):UE4对Schlick的一个近似。
//Schlick //F(v,h) = F0 +(1-F0)*(1-(v·h))^5 // //UE4 approximation // //F(v,h) = F0+(1-F0)2^((-5.55473(v·h)-6.98316)*v·h)
Q:F0是什么?
A:F0,入射角0时的反射率。这个数值我用了Metallic的值,应该还有跟roughness相关的计算,谁清除求告知
然后合成一个!
// alpha^2 * (F0+(1-F0)*pow(2,(-5.55473(v·h)-6.98316)*v·h)) //f(l,v) = --------------------------------------------------------------------------- // 4*pi*((n·h)^2 *(alpha^2-1)+1)^2*((n·v) *(1-k) +k)*((n·l) *(1-k) +k)
写Shader
这就没啥说的了,就是把公式写上去。
效果图:右边是Standard,左边是Custom,有一点点不同,反射更强烈一点
Shader "Stein/CustomPBR" { Properties { _Matel("Matel",2D) = "white"{} _Albedo("Albedo", 2D) = "white" {} _Normal ("Normal", 2D) = "bump"{} } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { Name "FORWARD" Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #pragma multi_compile_fwdbase_fullshadows #include "UnityCG.cginc" #include "AutoLight.cginc" #define PI 3.14159265359 struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal:NORMAL; float4 tangent : TANGENT; }; struct VertexOutput { float4 pos : SV_POSITION; float2 uv0 : TEXCOORD0; float4 posWorld : TEXCOORD1; float3 normalDir : TEXCOORD2; float3 tangentDir : TEXCOORD3; float3 bitangentDir : TEXCOORD4; LIGHTING_COORDS(5,6) UNITY_FOG_COORDS(7) }; uniform float4 _LightColor0; sampler2D _Albedo; float4 _Albedo_ST; sampler2D _Matel; float4 _Matel_ST; uniform sampler2D _Normal; uniform float4 _Normal_ST; VertexOutput vert (appdata v) { VertexOutput o = (VertexOutput)0; o.pos = mul(UNITY_MATRIX_MVP, v.vertex ); o.uv0 = v.uv; o.posWorld = mul(_Object2World, v.vertex); //世界坐标下的几个向量值,参考ShaderForge o.normalDir = UnityObjectToWorldNormal(v.normal); o.tangentDir = normalize( mul( _Object2World, float4( v.tangent.xyz, 0.0 ) ).xyz ); o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w); UNITY_TRANSFER_FOG(o,o.pos); TRANSFER_VERTEX_TO_FRAGMENT(o) return o; } fixed4 frag (VertexOutput i) : SV_Target { i.normalDir = normalize(i.normalDir); float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz); float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz); //法线左边转换 float3x3 tangentTransform = float3x3( i.tangentDir, i.bitangentDir, i.normalDir);//法线的TBN旋转矩阵 float4 _Normal_var = tex2D(_Normal,TRANSFORM_TEX(i.uv0, _Normal)); float3 normalLocal =_Normal_var.rgb*2-1;//之前的问题是没有Unpack,整个坐标是偏了的,参考UnityCG.cginc float3 normalDirection = normalize(mul( normalLocal, tangentTransform )); // 最终的法线 //从matellic图上取数据 fixed4 matelTex = tex2D(_Matel,TRANSFORM_TEX(i.uv0,_Matel)); float matellic = matelTex.r;//unity matellic 值,是一个grayscale value ,存在 r 通道 float roughness = 1-matelTex.a;//unity 用的是smoothness,在matellic map的alpha 通道,这里转换一下 float f0 = matelTex.r;//HACK 这个就是先这样用…… //预先计算一些常量 float3 h =normalize( lightDirection+viewDirection);//h,l和v的半角向量 float a = roughness*roughness;//alpha float a2 = a*a;//alpha^2 float NoL =saturate( dot(normalDirection,lightDirection)); float NoV =saturate(dot(normalDirection,viewDirection)); float NoH =saturate(dot(normalDirection,h)); float VoH =saturate(dot(viewDirection,h)); //light & light color float3 attenColor = LIGHT_ATTENUATION(i) * _LightColor0.xyz; // sample the _Albedo texture fixed4 albedo = tex2D(_Albedo, i.uv0); //diffuse part float3 directDiffuse =dot( normalDirection, lightDirection ) * attenColor; float3 indirectDiffuse = float3(0,0,0); indirectDiffuse += UNITY_LIGHTMODEL_AMBIENT.rgb; // Ambient Light float3 diffuse = (directDiffuse + indirectDiffuse) * albedo*(1-matellic); //specular part //微表面BRDF公式 // D(h) F(v,h) G(l,v,h) //f(l,v) = --------------------------- // 4(n·l)(n·v) //这个是GGX // alpha^2 //D(m) = ----------------------------------- // pi*((n·m)^2 *(alpha^2-1)+1)^2 //简化 D(h)*PI/4 float sqrtD = rcp(NoH*NoH*(a2-1)+1); // float D = a2*sqrtD*sqrtD/rcp(PI*4); float D = a2*sqrtD*sqrtD/4;//在 direct specular时,BRDF好像要乘PI,这里就直接约去。Naty Hoffman的那个文没太看懂 //in smith model G(l,v,h) = g(l)*g(v),这个公式是Schlick的趋近公式,参数各有不同 // n·v //G(v) = ----------------- // (n·v) *(1-k) +k // float k = a2*sqrt(2/PI); //Schlick-Beckmann // float k = a2/2; //Schlick-GGX float k =(a2+1)*(a2+1)/8; //UE4,咱们就挑NB的抄 //简化G(l,v,h)/(n·l)(n·v) float GV=(NoV *(1-k) +k); float GL =(NoL *(1-k) +k); //F(v,h) float f = f0 +(1-f0)*pow(2,(-5.55473*VoH-6.98316)*VoH);//参数是从UE4那里抄来的,应该是Schlick公式的趋近 fixed3 specularTerm = D*f *rcp(GV*GL); fixed3 specular = albedo*attenColor*(1/PI+ specularTerm)*NoL*matellic;//albedo/PI是BRDF公式的diffuse部分,没有就会偏黑 fixed4 finalcolor = (fixed4)0; finalcolor.rgb =diffuse +specular; finalcolor.a = albedo.a; // apply fog UNITY_APPLY_FOG(i.fogCoord, finalcolor); return finalcolor; } ENDCG } Pass { Name "FORWARD_DELTA" Tags { "LightMode"="ForwardAdd" } Blend One One CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #pragma multi_compile_fwdbase_fullshadows #include "UnityCG.cginc" #include "AutoLight.cginc" #define PI 3.14159265359 struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal:NORMAL; float4 tangent : TANGENT; }; struct VertexOutput { float4 pos : SV_POSITION; float2 uv0 : TEXCOORD0; float4 posWorld : TEXCOORD1; float3 normalDir : TEXCOORD2; float3 tangentDir : TEXCOORD3; float3 bitangentDir : TEXCOORD4; LIGHTING_COORDS(5,6) UNITY_FOG_COORDS(7) }; uniform float4 _LightColor0; sampler2D _Albedo; float4 _Albedo_ST; sampler2D _Matel; float4 _Matel_ST; uniform sampler2D _Normal; uniform float4 _Normal_ST; VertexOutput vert (appdata v) { VertexOutput o = (VertexOutput)0; o.pos = mul(UNITY_MATRIX_MVP, v.vertex ); o.uv0 = v.uv; o.posWorld = mul(_Object2World, v.vertex); //世界坐标下的几个向量值,参考ShaderForge o.normalDir = UnityObjectToWorldNormal(v.normal); o.tangentDir = normalize( mul( _Object2World, float4( v.tangent.xyz, 0.0 ) ).xyz ); o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w); UNITY_TRANSFER_FOG(o,o.pos); TRANSFER_VERTEX_TO_FRAGMENT(o) return o; } fixed4 frag (VertexOutput i) : SV_Target { i.normalDir = normalize(i.normalDir); //light dir & light color float3 lightDirection =(float3)0; float3 attenColor = (float3)0; if(_WorldSpaceLightPos0.w==0) { lightDirection = normalize(_WorldSpaceLightPos0.xyz); attenColor = LIGHT_ATTENUATION(i) * _LightColor0.xyz; } else { lightDirection =_WorldSpaceLightPos0.xyz- i.posWorld; attenColor =_LightColor0.xyz /(1+length(lightDirection)); lightDirection = normalize(lightDirection); } // float3 attenColor = LIGHT_ATTENUATION(i) * _LightColor0.xyz; float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz); //法线左边转换 float3x3 tangentTransform = float3x3( i.tangentDir, i.bitangentDir, i.normalDir);//法线的TBN旋转矩阵 float4 _Normal_var = tex2D(_Normal,TRANSFORM_TEX(i.uv0, _Normal)); float3 normalLocal =_Normal_var.rgb*2-1;//之前的问题是没有Unpack,整个坐标是偏了的,参考UnityCG.cginc float3 normalDirection = normalize(mul( normalLocal, tangentTransform )); // 最终的法线 //从matellic图上取数据 fixed4 matelTex = tex2D(_Matel,TRANSFORM_TEX(i.uv0,_Matel)); float matellic = matelTex.r;//unity matellic 值,是一个grayscale value ,存在 r 通道 float roughness = 1-matelTex.a;//unity 用的是smoothness,在matellic map的alpha 通道,这里转换一下 float f0 = matelTex.r;//HACK 这个就是先这样用…… //预先计算一些常量 float3 h =normalize( lightDirection+viewDirection);//h,l和v的半角向量 float a = roughness*roughness;//alpha float a2 = a*a;//alpha^2 float NoL =saturate( dot(normalDirection,lightDirection)); float NoV =saturate(dot(normalDirection,viewDirection)); float NoH =saturate(dot(normalDirection,h)); float VoH =saturate(dot(viewDirection,h)); // sample the _Albedo texture fixed4 albedo = tex2D(_Albedo, i.uv0); //diffuse part float3 directDiffuse =dot( normalDirection, lightDirection ) * attenColor; float3 indirectDiffuse = float3(0,0,0); indirectDiffuse += UNITY_LIGHTMODEL_AMBIENT.rgb; // Ambient Light float3 diffuse = (directDiffuse + indirectDiffuse) * albedo*(1-matellic); //specular part //微表面BRDF公式 // D(h) F(v,h) G(l,v,h) //f(l,v) = --------------------------- // 4(n·l)(n·v) //这个是GGX // alpha^2 //D(m) = ----------------------------------- // pi*((n·m)^2 *(alpha^2-1)+1)^2 //简化 D(h)*PI/4 float sqrtD = rcp(NoH*NoH*(a2-1)+1); // float D = a2*sqrtD*sqrtD/rcp(PI*4); float D = a2*sqrtD*sqrtD/4;//在 direct specular时,BRDF好像要乘PI,这里就直接约去。Naty Hoffman的那个文没太看懂 //in smith model G(l,v,h) = g(l)*g(v),这个公式是Schlick的趋近公式,参数各有不同 // n·v //G(v) = ----------------- // (n·v) *(1-k) +k // float k = a2*sqrt(2/PI); //Schlick-Beckmann // float k = a2/2; //Schlick-GGX float k =(a2+1)*(a2+1)/8; //UE4,咱们就挑NB的抄 //简化G(l,v,h)/(n·l)(n·v) float GV=(NoV *(1-k) +k); float GL =(NoL *(1-k) +k); //F(v,h) float f = f0 +(1-f0)*pow(2,(-5.55473*VoH-6.98316)*VoH);//参数是从UE4那里抄来的,应该是Schlick公式的趋近 fixed3 specularTerm = D*f *rcp(GV*GL); fixed3 specular = albedo* attenColor*(1/PI+ specularTerm)*NoL*matellic;//albedo/PI是BRDF公式的diffuse部分,没有就会偏黑 fixed4 finalcolor = (fixed4)0; finalcolor.rgb =diffuse +specular; finalcolor.a = 0; // apply fog UNITY_APPLY_FOG(i.fogCoord, finalcolor); return finalcolor; } ENDCG } } }