本文主要介紹Untiy5以后的GI,PBS,以及光源探頭,反射探頭的用法以及在着色器代碼中如何發揮作用,GI是如何影響渲染的,主要分成三個部分,最開始說明PBS需要的材質與相應概念,二是Unity 里相應GI的操作,三是對應着色器代碼的理解。如果沒有特殊聲明,所有操作與代碼都是針對Unity5.3.
PBS材質與概念
簡單來說,PBS的優點不同的照明下獲得一致的外觀,更容易實現,更直觀的參數。
PBS材質概念:
1.albedo 反照率
反照率貼圖定義漫反射的基本顏色,與原來的漫反射貼圖相比,不包含定向光與AO,在這我們應該由環境自己的定向光與AO來影響,Unity里,我們用GI得到相應烘培或是實時的方向光與AO。
2.Smoothness/Microsurface 表面細節 材料細節 光滑度
光滑度:描述物體微表面的一個參數,可以用來定義法線分布函數,這樣,粗糙的表面呈現寬,淡的鏡面反射,光滑呈現集中和明亮的鏡面反射。
3.Metal/reflectivity 金屬性/反射
金屬與絕緣體應該使用不同的反射設置,導體的反射率 60-90%,絕緣體0-20%,高反射光分子不容易到達內部和散射,這樣金屬的高表現出來比較淡。
如下全局設置:
對於固定的材料,反射率趨於穩定,這樣一般來說,一個模型的metal相對變化較少,如泥,水,木頭這些他們的反射率相關並不大,只有金屬與絕緣體會有相對比較大的反差,而模型表面的粗糙度應該用上面的圖光滑性來表示,這個相對變化會比較大些,如下,水和泥土有相似的金屬性(反射率),但是光滑度相差大,所以相差比較明顯。
上圖來自pbr-theory: http://www.marmoset.co/toolbag/learn/pbr-theory
Unity中事項,Metallic 貼圖,金屬性質占用R通道,光滑性占用A通道,GB被忽略。
物體的本來顏色用albedo表示,光滑性用Smoothness表示,材質特性用Metal表示,其中Unity 二種PBS標准着色,Standard其中金屬性越高,本身鏡面顏色占比越高,燈光的顏色占比越低,而高光可以設置自己的鏡面反射。
PS: pbr-practice http://www.marmoset.co/toolbag/learn/pbr-practice
BRDF 光照模型 概念
1 能量守恆:一個對象不能反射比他接收的光多。
粗糙的材料有更多比較淡的亮點,而光滑的材料有更集中,更明亮的亮點,也可以這么理解,漫反射越多,鏡面反射相對越少,鏡面反射越多,漫反射越少。
2 菲涅爾效應:邊緣的反射更亮。
更具體的可以看:http://filmicgames.com/archives/557
3 微表面模型模型:
普通的着色模型假定着色的區域是一個平滑的表面,表面有一個法線,而微表面則認為,着色區域是一個無數比入射光線覆蓋范圍更小的微小表面組成的粗糙區域,這個微小表面是光滑的鏡面反射。表面細微細節對擴散的影響,表面越粗糙,反射光越發散或模糊。
GI結合BRDF渲染
GI全局照明指的是全局照明會模擬光線在場景中的多次反射,所有的東西都是一個潛在的光源,任何可見的模型不是輻射光線,就是反射光線,其中GI(渲染間接光源,本身一般也用BRDF渲染) 配合BRDF生成帶燈光直射,環境內模型互想影響的逼真場景。
天空盒是GI的組成部分,反射天空盒可以改變場景所有模型接受反射量。
GI結合PBS,如下,攝像機的鏡頭是一樣的材質,在不同的環境下:
更詳細的可以點進這個鏈接: http://docs.unity3d.com/Manual/shader-StandardShader.html
GI操作:
全局光照的特點在於能夠捕捉間接光照,所以5以后,除開原來的direct light的效果,增加indirect light的效果,簡單來說,就是除開光源之后,然后模型本身做為光源,幅射到別的模型上,層層遞歸后的效果。現不管是預計算實時GI還是烘培GI都只是針對靜態模型。預計算Gi的實時光源與烘陪對應的烘培光源里的強度與反射強度都會影響幅射圖與方向圖的內容。需要注意,預計算Gi針對的是實時方向光,而烘培GI針對的是烘培方向光。
全局光源
Skybox :天空盒,參考材質Skybox/Cubemap,如果全局光源與全局反射探頭都選擇Skybox模式,則會把Skybox當做一個Cubemap,場景所有模型,靜態與非靜態的一部分光源從這個Cubemap反射上得到。
Sun:Skybox選擇一個方向光,以這個方向光的方向做方向,這個方向光如顏色與強度不影響全局本身,如果沒有設置方向光,選擇強度最大的那個方向光源。
Ambient Source:全局光源,如果設置天空盒,但是天空盒本身沒有設置,自動選擇下面的全局顏色設置,全局反射探頭以Ambient Source當做光源。
Ambient Intensity:全局光源強度,越高越亮,為0時,光源不起作用。
Ambient GI:當預計算GI與烘培GI二個都選擇后,這個可以選擇是用實時還是烘培。
Reflection Source:Unity默認放入的全局反射探頭,選擇Skybox會以Ambient Source里提供的光源顏色做反射,同時也可以自己提供cubemap當做反射源。
Resolution:反射探頭解析度,應該是對應RTT的cubemap六張紋理的分辨率。
Compression:是否壓縮。
Reflection Intensity:Reflection Source針對所有模型反射強度,值越大,相應的模型面上顯示越清晰的Reflection Source。
Reflection Bounces:當設置多個Reflection Probe時,互相反射對方信息的次數,如二面鏡子。
GI設置
Precomputed Realtime GI
預計算實時GI,針對實時靜態物體之間的幅射光,故相應的幅射圖與方向圖都是低分辨率下的。動態物體可以使用光照探頭來得到相應反射光源信息,注意動態模型與光探頭的距離。
Realtime Resolution:預計算實時GI,把場景分成許多格,得到每個格的幅射信息。那么這個值越高,計算量將以平方增加,最終值還將和General GI里的光源參數里的Resolution相乘。
CPU Usage:生成相應GI的數據時,在游戲運行時分配多少CPU計算能力。
Baked GI
烘培GI,因此能得到更精確的模型之間的反射光信息,但是不能運行時更改相應的光源信息,如顏色,方向,預計算實時GI沒有這個問題。
Baked Resolution:一般來說,是Realtime Resolution 10+,因為相應的幅射圖與方向圖精確度要高很多。
Baked Padding:網上說是光照貼圖中分隔的距離,還需要驗證。
Compressed:是否壓縮
Ambient Occlusion:值越高,遮擋地區得到的光比差越大。
Final Gather:用FG技術來產生烘培數據,這種技術時間會長一些。參考http://blog.sina.com.cn/s/blog_46c56d9a0100gqbv.html
Ray Count:Final Gather所用的光線追蹤光線數目。
Atlas Size:圖集里貼圖的大小,越低實際占用越精確也就是越小,但是貼圖產生越多,應該選擇一個合適的大小。
General GI
預計算實時GI還是烘培如何生成.
Direction Mode:幅射圖/帶方向光/加鏡面,具體看Shader分析,其中預計算實時GI與烘培GI在這生成的相應幅射圖,方向圖等有所不同,后面會提到。
Indirect Intensity:間接光的強度。
Default Parameters:生成相應貼圖所需要的信息。
其中全局預計算GI,烘培GI,全局反射探頭相應的改動需要重新烘培,如果選擇自動,相關改動會自動在后台烘培。
上面這些說實話,寫這么操作沒啥用,自己對着每項實踐一篇,什么都清楚了。
Light Probe:
對於GI來說,不管是預計算GI與烘培GI,都不會對非靜態模型計算間接反射,光探頭的加入,可以使非靜態模型得到周圍靜態模型的幅射光,主要技術原理使用一種球諧光照的技術,注意light probe一般不會對靜態模型有影響,你看到的影響,只是因為非靜態模型的顏色變化大造成的反差。
相關球諧光照的技術原理可以參見,本人也看不懂,只能說看了后有點印象是怎么回事:
http://www.yasrt.org/shlighting/
http://www.cppblog.com/init/archive/2012/09/19/191182.html
一種2D傅立葉級數的球形推廣,可以把光照函數展開成SH基函數的疊加,類似傅立葉變換能把任何函數展開成正弦波的疊加,對光照圖來說一般只用2 bands = 4 RGB textures,通過丟失高頻細節來壓縮存儲。
Reflection Probe:
定義一個Cubemap用來影響周圍模型的鏡面反射,給鏡面高光模型使用。
一般來說,我們想得到一個實時場景的Cubemap,只需要在一個點,用攝像機對着前后,左右,上下,各拍攝一次,形成6個面,組合成Cubemap.
Type:烘培,用戶,自動。其中,烘培就是用戶來控制生成一個當前的場景cubemap,用戶就是用戶自己提供一個cubemap,實時就是不斷更新這個cubemap以映射最新的場景。
當選擇實時,Refresh mode:On awake啟動時,每楨,用戶腳本控制,當每楨時,如下選擇。
Time slicing:一楨先生成6個面,后面8楨每楨生成一個mipmap,一共9楨。
Individual face:6面6楨,加后面8楨每個mipmap,一共14楨。
No time slicing:一楨內把mipmap與cubemap全部生成。
Importance:當多個反射探頭影響一個模型時,這個參數影響這個反射探頭的比重。
Intensity:間接光強度,強度影響鏡面反射,鏡面反射越亮。
Box Projection:從着色代碼來看,應該根據當前反射探頭的源點與AABB影響是模型原來法線。
Size:大小,范圍內的模型使用這個。
Probe Origin:原點。
Cubemap capture sttings:
Resolution:cubempa材質的大小。
HDR:高光。
Shadow Distance:陰影距離,數值越少,陰影越近。
下面就是RTT對應攝像機的屬性。
GI間接光源算法:輻射度算法
輻射度算法就是:把場景細分到很細很細的面片(如1個像素那么大的三角形),分別計算它們接受和發出的光能,然后逐次遞歸,直到每個面片的光能數據不再變化(或者到一定的閥值)為止.因此,計算量很大(要計算很多次),而且難以並行(因為遞歸),參考http://blog.sina.com.cn/s/blog_537cc4d90101iiil.html
Unity中GI選擇non-direction模式生成的輻射度,使用一張圖,儲存每個位置收到的間接光照,其中假定都只是擴散,沒有鏡面反射。
GI Directional LightMap算法:
把半球面的入射輻射度用某種方法進行采樣,保存起來在運行時根據法線圖中的法線方向來進行一次合成,由於帶方向信息,也可以支持高光計算了,可以參考:http://www.fseraph.com/?p=193
Unity中GI選擇direction模型,會在上面輻射圖添加一張圖,用來存儲接收到的光的方向選擇specular,烘培GI與預計算GI使用不同的方式,烘培GI在上面二張圖各擴大一倍,原來的保存直接光的影響,新增加的位置用來保存間接光的影響,其中預計算GI新增加一張貼圖,三張圖分別保存輻射光照,光源方向,法線,需要結合實時方向光源。
PS:
探討Unity5中全局光照(Enlighten) http://unity.jb51.net/meigongsheji/Unitymeihua/18.html
Unity 5.0新功能教學:http://tieba.baidu.com/p/3690939628
Unity 5 中的全局光照技術詳解 http://www.cocoachina.com/game/20150701/12339.html
Unity5 官網文檔GI三種模式具體區別 http://docs.unity3d.com/Manual/LightmappingDirectional.html
Unity GI BRDF Shader
主要參考對照如下Shader文件:
UnityStandardCore.cginc:前向渲染base,頂點着色入口vertForwardBase,片斷着色入口fragForwardBase,這個是着色器的主要Pass,全局方向光,GI信息合並都在這個pass中。
UnityStandardBRDF.cginc:BRDF的具體實現.
UnityGloballllumination.cginc:提取GI信息,包含烘陪GI與預計算GI。
預計算Gi針對的是實時方向光,而烘培GI針對的是烘培方向光,所以當說預計算GI的light時,指的是實時方向光,而烘培GI的light說的是烘培方向光。
相應主要代碼我都已經加上注釋,相信還是比較容易看懂的。

1 ---------UnityStandardCore 2 //頂點着色器入口 3 VertexOutputForwardBase vertForwardBase (VertexInput v) 4 { 5 VertexOutputForwardBase o; 6 UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o); 7 //世界坐標下位置 8 float4 posWorld = mul(_Object2World, v.vertex); 9 #if UNITY_SPECCUBE_BOX_PROJECTION 10 o.posWorld = posWorld.xyz; 11 #endif 12 //屏幕空間位置 13 o.pos = mul(UNITY_MATRIX_MVP, v.vertex); 14 o.tex = TexCoords(v); 15 o.eyeVec = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos); 16 float3 normalWorld = UnityObjectToWorldNormal(v.normal); 17 #ifdef _TANGENT_TO_WORLD 18 float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w); 19 20 float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w); 21 o.tangentToWorldAndParallax[0].xyz = tangentToWorld[0]; 22 o.tangentToWorldAndParallax[1].xyz = tangentToWorld[1]; 23 o.tangentToWorldAndParallax[2].xyz = tangentToWorld[2]; 24 #else 25 o.tangentToWorldAndParallax[0].xyz = 0; 26 o.tangentToWorldAndParallax[1].xyz = 0; 27 o.tangentToWorldAndParallax[2].xyz = normalWorld; 28 #endif 29 //We need this for shadow receving 30 TRANSFER_SHADOW(o); 31 32 // Static lightmaps 33 #ifndef LIGHTMAP_OFF 34 //開啟烘培GI后 35 o.ambientOrLightmapUV.xy = v.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw; 36 o.ambientOrLightmapUV.zw = 0; 37 // Sample light probe for Dynamic objects only (no static or dynamic lightmaps) 38 //光源探頭對動態模型的影響,rgb(顏色)根據SH系數還原光源探頭與不重要的點光源上的顏色信息 39 #elif UNITY_SHOULD_SAMPLE_SH 40 #if UNITY_SAMPLE_FULL_SH_PER_PIXEL 41 o.ambientOrLightmapUV.rgb = 0; 42 #elif (SHADER_TARGET < 30) 43 o.ambientOrLightmapUV.rgb = ShadeSH9(half4(normalWorld, 1.0)); 44 #else 45 // Optimization: L2 per-vertex, L0..L1 per-pixel 46 o.ambientOrLightmapUV.rgb = ShadeSH3Order(half4(normalWorld, 1.0)); 47 #endif 48 // Add approximated illumination from non-important point lights 49 #ifdef VERTEXLIGHT_ON 50 o.ambientOrLightmapUV.rgb += Shade4PointLights ( 51 unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, 52 unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb, 53 unity_4LightAtten0, posWorld, normalWorld); 54 #endif 55 #endif 56 //開啟預計算GI后 57 #ifdef DYNAMICLIGHTMAP_ON 58 o.ambientOrLightmapUV.zw = v.uv2.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw; 59 #endif 60 61 #ifdef _PARALLAXMAP 62 TANGENT_SPACE_ROTATION; 63 half3 viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex)); 64 o.tangentToWorldAndParallax[0].w = viewDirForParallax.x; 65 o.tangentToWorldAndParallax[1].w = viewDirForParallax.y; 66 o.tangentToWorldAndParallax[2].w = viewDirForParallax.z; 67 #endif 68 69 UNITY_TRANSFER_FOG(o,o.pos); 70 return o; 71 } 72 //片斷着色器入口 73 half4 fragForwardBase (VertexOutputForwardBase i) : SV_Target 74 { 75 FRAGMENT_SETUP(s) 76 UnityLight mainLight = MainLight (s.normalWorld); 77 half atten = SHADOW_ATTENUATION(i); 78 79 half occlusion = Occlusion(i.tex.xy); 80 //提取GI里的信息到UnityGI中。 81 UnityGI gi = FragmentGI ( 82 s.posWorld, occlusion, i.ambientOrLightmapUV, atten, s.oneMinusRoughness, s.normalWorld, s.eyeVec, mainLight); 83 // 如果是預計算GI或動態模型,gi.light表示主光源,焙烘取 84 half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect); 85 // GI生成類型是spceular,才會計算 86 c.rgb += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi); 87 c.rgb += Emission(i.tex.xy); 88 89 UNITY_APPLY_FOG(i.fogCoord, c.rgb); 90 return OutputForward (c, s.alpha); 91 } 92 93 //填充UnityGIInput,用來得到UnityGI信息。 94 inline UnityGI FragmentGI ( 95 float3 posWorld, 96 half occlusion, half4 i_ambientOrLightmapUV, half atten, half oneMinusRoughness, half3 normalWorld, half3 eyeVec, 97 UnityLight light) 98 { 99 UnityGIInput d; 100 d.light = light; 101 d.worldPos = posWorld; 102 d.worldViewDir = -eyeVec; 103 d.atten = atten; 104 //如果有GI信息。 105 #if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON) 106 d.ambient = 0; 107 d.lightmapUV = i_ambientOrLightmapUV; 108 #else 109 //一般來說,非靜態模型,得到環境光 110 d.ambient = i_ambientOrLightmapUV.rgb; 111 d.lightmapUV = 0; 112 #endif 113 //全局反射探頭的AABB 114 d.boxMax[0] = unity_SpecCube0_BoxMax; 115 d.boxMin[0] = unity_SpecCube0_BoxMin; 116 //位置 117 d.probePosition[0] = unity_SpecCube0_ProbePosition; 118 d.probeHDR[0] = unity_SpecCube0_HDR; 119 120 //用戶定義的反射探頭AABB 121 d.boxMax[1] = unity_SpecCube1_BoxMax; 122 d.boxMin[1] = unity_SpecCube1_BoxMin; 123 d.probePosition[1] = unity_SpecCube1_ProbePosition; 124 d.probeHDR[1] = unity_SpecCube1_HDR; 125 126 return UnityGlobalIllumination ( 127 d, occlusion, oneMinusRoughness, normalWorld); 128 } 129 130 --------UnityGloballllumination.cginc 131 //FragmentGI 跳轉到這 132 inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half oneMinusRoughness, half3 normalWorld, bool reflections) 133 { 134 UnityGI o_gi; 135 UNITY_INITIALIZE_OUTPUT(UnityGI, o_gi); 136 137 // Explicitly reset all members of UnityGI 138 ResetUnityGI(o_gi); 139 140 //動態模型使用 SH得到的漫反射信息。 141 #if UNITY_SHOULD_SAMPLE_SH 142 #if UNITY_SAMPLE_FULL_SH_PER_PIXEL 143 half3 sh = ShadeSH9(half4(normalWorld, 1.0)); 144 #elif (SHADER_TARGET >= 30) 145 half3 sh = data.ambient + ShadeSH12Order(half4(normalWorld, 1.0)); 146 #else 147 half3 sh = data.ambient; 148 #endif 149 150 o_gi.indirect.diffuse += sh; 151 #endif 152 153 //如果沒有烘培GI,需要當前全局方向光源的信息 154 #if !defined(LIGHTMAP_ON) 155 o_gi.light = data.light; 156 //atten陰影信息,值越小陰影越重 157 o_gi.light.color *= data.atten; 158 //烘培GI 159 #else 160 // Baked lightmaps 161 fixed4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy); 162 half3 bakedColor = DecodeLightmap(bakedColorTex); 163 //沒有方向貼圖 164 #ifdef DIRLIGHTMAP_OFF 165 //設置漫反射 166 o_gi.indirect.diffuse = bakedColor; 167 168 #ifdef SHADOWS_SCREEN 169 o_gi.indirect.diffuse = MixLightmapWithRealtimeAttenuation (o_gi.indirect.diffuse, data.atten, bakedColorTex); 170 #endif // SHADOWS_SCREEN 171 //方向與漫反射 172 #elif DIRLIGHTMAP_COMBINED 173 fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER (unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy); 174 //更精准的漫反射 調整過后的half Lambert 175 o_gi.indirect.diffuse = DecodeDirectionalLightmap (bakedColor, bakedDirTex, normalWorld); 176 177 #ifdef SHADOWS_SCREEN 178 o_gi.indirect.diffuse = MixLightmapWithRealtimeAttenuation (o_gi.indirect.diffuse, data.atten, bakedColorTex); 179 #endif // SHADOWS_SCREEN 180 //漫反射,方向,高光 181 #elif DIRLIGHTMAP_SEPARATE 182 // Left halves of both intensity and direction lightmaps store direct light; right halves - indirect. 183 184 // Direct 調整o_gi.light 185 fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy); 186 o_gi.indirect.diffuse += DecodeDirectionalSpecularLightmap (bakedColor, bakedDirTex, normalWorld, false, 0, o_gi.light); 187 188 // Indirect 漫反射,鏡面都是保存在unity_Lightmap中,豎直中間分開 189 //調整o_gi.light2 190 half2 uvIndirect = data.lightmapUV.xy + half2(0.5, 0); 191 bakedColor = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, uvIndirect)); 192 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_LightmapInd, unity_Lightmap, uvIndirect); 193 o_gi.indirect.diffuse += DecodeDirectionalSpecularLightmap (bakedColor, bakedDirTex, normalWorld, false, 0, o_gi.light2); 194 #endif 195 #endif 196 197 //預計算GI 198 #ifdef DYNAMICLIGHTMAP_ON 199 // Dynamic lightmaps unity_DynamicLightmap unity_DynamicDirectionality unity_DynamicNormal 200 fixed4 realtimeColorTex = UNITY_SAMPLE_TEX2D(unity_DynamicLightmap, data.lightmapUV.zw); 201 //間接漫反射 202 half3 realtimeColor = DecodeRealtimeLightmap (realtimeColorTex); 203 204 #ifdef DIRLIGHTMAP_OFF 205 o_gi.indirect.diffuse += realtimeColor; 206 207 #elif DIRLIGHTMAP_COMBINED 208 half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw); 209 //調整漫反射 210 o_gi.indirect.diffuse += DecodeDirectionalLightmap (realtimeColor, realtimeDirTex, normalWorld); 211 212 #elif DIRLIGHTMAP_SEPARATE 213 half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw); 214 half4 realtimeNormalTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicNormal, unity_DynamicLightmap, data.lightmapUV.zw); 215 //調整o_gi.light3 216 o_gi.indirect.diffuse += DecodeDirectionalSpecularLightmap (realtimeColor, realtimeDirTex, normalWorld, true, realtimeNormalTex, o_gi.light3); 217 #endif 218 #endif 219 //gi里的信息全放入indirect的diffuse中 220 o_gi.indirect.diffuse *= occlusion; 221 //有反射探頭,設置鏡面光源信息。 222 if (reflections) 223 { 224 half3 worldNormal = reflect(-data.worldViewDir, normalWorld); 225 226 #if UNITY_SPECCUBE_BOX_PROJECTION 227 half3 worldNormal0 = BoxProjectedCubemapDirection (worldNormal, data.worldPos, data.probePosition[0], data.boxMin[0], data.boxMax[0]); 228 #else 229 half3 worldNormal0 = worldNormal; 230 #endif 231 232 half3 env0 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE(unity_SpecCube0), data.probeHDR[0], worldNormal0, 1-oneMinusRoughness); 233 #if UNITY_SPECCUBE_BLENDING 234 const float kBlendFactor = 0.99999; 235 float blendLerp = data.boxMin[0].w; 236 UNITY_BRANCH 237 if (blendLerp < kBlendFactor) 238 { 239 #if UNITY_SPECCUBE_BOX_PROJECTION 240 half3 worldNormal1 = BoxProjectedCubemapDirection (worldNormal, data.worldPos, data.probePosition[1], data.boxMin[1], data.boxMax[1]); 241 #else 242 half3 worldNormal1 = worldNormal; 243 #endif 244 245 half3 env1 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE(unity_SpecCube1), data.probeHDR[1], worldNormal1, 1-oneMinusRoughness); 246 o_gi.indirect.specular = lerp(env1, env0, blendLerp); 247 } 248 else 249 { 250 o_gi.indirect.specular = env0; 251 } 252 #else 253 o_gi.indirect.specular = env0; 254 #endif 255 } 256 //反射探頭的信息存入到gi的indirect鏡面中 257 o_gi.indirect.specular *= occlusion; 258 259 return o_gi; 260 } 261 262 --------UnityStandardBRDF.cginc 263 // Main Physically Based BRDF 264 // Derived from Disney work and based on Torrance-Sparrow micro-facet model 265 // 266 // BRDF = kD / pi + kS * (D * V * F) / 4 267 // I = BRDF * NdotL 268 // 269 // * NDF (depending on UNITY_BRDF_GGX): 270 // a) Normalized BlinnPhong 271 // b) GGX 272 // * Smith for Visiblity term 273 // * Schlick approximation for Fresnel 274 half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness, 275 half3 normal, half3 viewDir, 276 UnityLight light, UnityIndirect gi) 277 { 278 half roughness = 1-oneMinusRoughness; 279 //nh,能射入眼睛的光線的角度 也叫半線 280 half3 halfDir = Unity_SafeNormalize (light.dir + viewDir); 281 half nl = light.ndotl; 282 //nh,半線與法線夾角,夾角越少,射入眼睛的光越大 283 half nh = BlinnTerm (normal, halfDir); 284 //射線與法線夾角 285 half nv = DotClamped (normal, viewDir); 286 //光線與法線夾角 287 half lv = DotClamped (light.dir, viewDir); 288 //半線與光線夾角 289 half lh = DotClamped (light.dir, halfDir); 290 291 #if UNITY_BRDF_GGX 292 //遮擋函數 293 half V = SmithGGXVisibilityTerm (nl, nv, roughness); 294 //法線分布函數 1/Pi 295 half D = GGXTerm (nh, roughness); 296 #else 297 //遮擋函數 298 half V = SmithBeckmannVisibilityTerm (nl, nv, roughness); 299 //法線分布函數 1/Pi 300 half D = NDFBlinnPhongNormalizedTerm (nh, RoughnessToSpecPower (roughness)); 301 #endif 302 half nlPow5 = Pow5 (1-nl); 303 half nvPow5 = Pow5 (1-nv); 304 half Fd90 = 0.5 + 2 * lh * lh * roughness; 305 //disney Diffuse 菲涅爾 邊角有更亮的光 306 half disneyDiffuse = (1 + (Fd90-1) * nlPow5) * (1 + (Fd90-1) * nvPow5); 307 // HACK: theoretically we should divide by Pi diffuseTerm and not multiply specularTerm! 308 // BUT 1) that will make shader look significantly darker than Legacy ones 309 // and 2) on engine side "Non-important" lights have to be divided by Pi to in cases when they are injected into ambient SH 310 // NOTE: multiplication by Pi is part of single constant together with 1/4 now 311 //鏡面系數 312 half specularTerm = max(0, (V * D * nl) * unity_LightGammaCorrectionConsts_PIDiv4);// Torrance-Sparrow model, Fresnel is applied later (for optimization reasons) 313 half diffuseTerm = disneyDiffuse * nl; 314 //Gi鏡面,可以看到oneMinusReflectivity越高,grazingTerm越低,specColor越高(本身鏡面顏色) 315 half grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity)); 316 //GI non-direction與direction的BRDF如下情況 317 // 烘培GI 預計算GI 動態模型 318 //light.color 空 實時方向光直接信息 實時方向光直接信息 319 //gi.diffuse 直接與間接光源信息 間接光源信息 SH(光源探頭)間接光源信息 320 //gi.specular 反射探頭 反射探頭 反射探頭 321 half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm) 322 + specularTerm * light.color * FresnelTerm (specColor, lh) 323 + gi.specular * FresnelLerp (specColor, grazingTerm, nv); 324 return half4(color, 1); 325 }
整個代碼並不多,但是對於前面所說所有東東,在這都有一個完整的解釋,這份代碼來告訴我們,那些金屬性,光滑度,GI中幅射圖,方向圖,還有光源探頭,反射探頭所起的作用。
BRDF 光照模型:
如下是現有光照模型沒有考慮的問題:
光照現象,漫反射並不是各個方面平均發散. ----微表面模型(NDF).
菲涅爾定理(Fresnel) ----光源在邊角處有更明亮的反光。
能量守恆,反射的光不能超過入射的光. ----遮擋因素,越光滑鏡面越集中越亮
普通的着色模型假定着色的區域是一個平滑的表面,表面有一個法線,而微表面則認為,着色區域是一個無數比入射光線覆蓋范圍更小的微小表面組成的粗糙區域,這個微小表面是光滑的鏡面反射,因為着色區域並不能一個法向量來表示表面的方向,轉面代替用一概率分布函數(NDF)來表示。一般來說,分別用如下字母表示:
D 用來表示法線分布。
F 用來菲涅爾影響,光源在邊角處有更明亮的反光。
G/V 用來表示凹凸表面間的遮擋因素(Unity用V來表示)
如下是Unity相對應BRDF的處理
先要說明每個引擎對BRDF處理各不同,在這只介紹Untiy的實現:
D 采用GGX與BlinnPhong二種法線分布函數,BlinnPhong比較簡單,效率高。
F 采用簡化的Disney Fresnel方式求得菲涅爾影響。
G/V 采用GGX與Beckmann二種技術,可以看到,光滑度是個關鍵參數。
針對Unity5.3簡化過的Disney Fresnel,簡單分析下.
Nl:法線與燈光的夾角,夾角越大,這個值越小。
Nv:法線與視線的夾角,夾角越大,這個值越小。
Lh:燈光與視線的半線與法線的夾角,其中燈光與視線的半線就是燈光與視線的平均線,簡單來說,這個線與法線重合,這條由燈光發出來的射線才能進入我們的眼鏡。
假定 fd90不變,nl與nl的角度越大,那么nlPow5與nvPow5的值越大,最終結果越大,這也是菲涅爾想表達的,光源在邊角處有更明亮的反光。
在Unity中,可以看到,D與V影響鏡面反射,F影響漫反射,特別說明,只有Unity是這樣處理。
其中,可以看到反射率(也就是金屬性)影響的是GI的鏡面反射,也就是反射探頭。
其余的部分挑的說明下:
頂點着色器中,填充VertexOutputForwardBase 信息,其中ambientOrLightmapUV(half4) 如果包含烘培GI,xy填充相應烘培GI的UV坐標,如果包含預計算GI,zw填充為預計算GI的UV坐標。如果是非靜態模型,不包含GI信息,相應light probe提供的光源信息放入rgb中。
片斷着色器中,每個像素要得到對應像素上的Unity GI信息,相應的,Unity GI中的屬性light並不是表示光源,而是當前像素受如主光源,幅射,鏡面對當前像素的影響,每個像素對應的light都有差別,千萬不要看到寫的是個light,就把它當做光照,這樣所有理解都不對了。
其中如果只有烘培GI,當前像素的Unity GI中參數light不提供信息,indirect里的漫反射包含烘培光源的光照。而預計算GI中當前像素的light本身就是全局光源,indirect只包含物體之間的漫反射信息,而非靜態模型中當前像素只有實時光源等直接光照信息,其周圍的靜態模型的反射光只有通過光探頭得到SH信息。
FragmentGI里常見結構:
1 UnityLight:
包含當前像素中光源顏色,方向,法線與光源方向點積
2 UnityIndirect:
包含當前像素中diffuse漫反射,specular鏡面信息
3 UnityGI:特別注意,里面的light是UnityLight類型,並不表示光源,而是用來表示當前像素受光源影響的量。
Gi.light 如果烘陪GI信息,則使用當前主光源填充UnityLight,如果有烘陪GI信息,則填充為空。
Gi.light2 當烘培GI啟用高光后,才會調用。
Gi.light3 預計算GI啟用高光后,才會調用。
UnityGI在根據函數UnityGlobalIllumination被填充,我們可以分析得到動態模型,使用SH得到漫反射信息。如果沒有烘培GI,我們需要實時全局光源,故gi.light是像素所受全局方向光。烘培GI,GI光照信息保存在gl.light2中,gi漫反射直接取光照圖里的diffuse.預計算GI,GI光照信息保存在gl.light3中,gi漫反射添加光照圖里的diffuse.而反射探頭用來添加反射的信息到GI里的鏡面信息中了,其中Occlusion 控制gi的diffuse與specular系數。(n*=occlusion)
其中UNITY_SHOULD_SAMPLE_SH 當前渲染的動態模型,使用SH得到間接的漫反射信息,其中UNITY_SHOULD_SAMPLE_SH如下定義。#define UNITY_SHOULD_SAMPLE_SH ( defined (LIGHTMAP_OFF) && defined(DYNAMICLIGHTMAP_OFF) )
預計算GI與烘培GI
烘培GI如上代碼中分析得到,選擇non-direction,只有一張圖,保存直接與間接光照所有信息,選擇direction后,會保存方向,選擇specular后,上面二張圖,長度擴大一倍,保存鏡面有關信息。
其中預計算GI,選擇non-direction,只有一張圖,不保存直接光照,只保存間接光照,選擇direction,保存方向,選擇specular后,不同於烘培GI,會新增一張紋理保存鏡面相關信息(從代碼來上看,可能是組織過的法線)
反射探頭:
unity_SpecCube0_ 全局反射探頭 unity_SpecCube1_當前模型受影響的反射探頭。
FragmnetGI:得到全局反射探頭與用戶定義的反射探頭位置,AABB,IsHDR.
UnityGlobalIllumination:片斷像素中得到反射探頭影響鏡面值,可以看到,SpecCube0與SpecCube1通過深度影響gi間接光源上的鏡面顏色。
BoxProjectedCubemapDirection:影響worldNormal(反視線進過法線后的反射)
Unity_GlossyEnvironment:可以看到 ,光滑值影響LOD值,越光滑越清晰。 UNITY_SAMPLE_TEXCUBE 從cubemap取值。
光源探頭:
vertForwardBase:VertexOutputForwardBase的ambientOrLightmapUV設置顏色,從這可以看到,光源探頭實際不影響靜態的物體,你如果看到有影響,只是因為周圍的非靜態模型顏色反差造成給你的影響。
UnityGlobalIllumination:燈光探頭的值賦到gi間接光源上的漫反射上。
PS:
深入理解Unity5中的Standard Shader(三)
Unity3d 基於物理渲染Physically-Based Rendering之specular BRDF
2016/2/1:今天看UE4中的環境反射文檔,我去,Unity里的做法完全是參照UE4的,通過上面的着色器代碼,我們可以完全理解下面這個鏈接里所說的。