Unity5 GI与PBS渲染从用法到着色代码
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity5 GI与PBS渲染从用法到着色代码相关的知识,希望对你有一定的参考价值。
本文主要介绍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操作:
全局光源
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设置
全局光照的特点在于能够捕捉间接光照,所以5以后,除开原来的direct light的效果,增加indirect light的效果,简单来说,就是除开光源之后,然后模型本身做为光源,反射到别的模型上,层层递归后的效果。现不管是预计算实时GI还是烘培GI都只是针对静态模型。预计算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
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) ----光源在边角处有更明亮的反光。
能量守恒,反射的
以上是关于Unity5 GI与PBS渲染从用法到着色代码的主要内容,如果未能解决你的问题,请参考以下文章