本文主要介绍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的,通过上面的着色器代码,我们可以完全理解下面这个链接里所说的。