直接光照:光源直接照射到物体上,并反射到眼中的光照。
间接光照:光源先照射到其它物体上,并经过一次或多次弹射,最终抵达到观察物体,反射到眼中的光照。
漫反射:照射到物体表面的光线在各个方向均匀地反射出来,反射强度跟入射角相关,跟观察角度无关。
镜面反射:当入射角和反射角越接近时光照越强,因此不同观察角度反射强度不同,传统的Lightmap无法计算。
环境反射:无法被光线直接照射到的区域,通过光线在物体之间的弹射而被照亮。在不使用全局光照进行计算时,通常简单地指定一个纯色。
光照图(Lightmap):将光照的反射信息预先计算好并存储在纹理上,渲染时直接采样这张纹理模拟实时光照。
带方向的光照图(Directional Lightmap):类似于传统Lightmap,但是额外生成了一张光照图用来保存光源的照射方向以及这个方向的光照强度贡献比例,以便能在使用光照图的同时计算Specular。
示例场景(全静态光照投影)
光照图
带方向的光照图
和传统光照图类似,但只在光照图上存储间接光照的信息,而不存储直接的光照信息。
只有间接光照的渲染效果
间接光照图
由于无法对场景中的人物等动态物体像静态物件一样烘焙光照图,计算间接光照。如果场景中仅有静态物体存在间接光照效果,动态物体会显得十分突兀。而Light Probe则是用来为动态物体计算近似的间接光照效果,一般成组地批量使用,分布在场景中。
图中木桶等杂物没有计算间接光照
Light Probe使用球型和谐函数编码记录一定空间区域内的光照信息,占用的数据存储空间很小,只有27个float,并且在Shader中解码的计算开销也很小。但由于没有存储物体表面逐像素的光照数据,因此无法像光照图一样表现出物体的光照细节。
具体的算法可参考文档:http://gdcvault.com/play/1015312/Light-Probe-Interpolation-Using-Tetrahedral
Light Probe Group和红黄色两盏点光,尚未烘焙。
烘焙Light Probes后的动态物体光照效果
通过额外Drawcall绘制物体在光源空间的深度图来实现投影效果,质量较高,但性能开销大。
基于ShadowMap实时计算投影效果
光源空间的深度图
预先计算光线到物体表面的遮挡关系,并存储到纹理中。渲染时通过采样此纹理计算光照投影区域。
ShadowMask图为一张ARGB 32位图,每个通道分别用来记录一个光源是否能直接照射到物体表面。当使用ShadowMask时最多能同时记录同区域内4盏光源的投影信息,如果超过这个值则不会生成ShadowMask,而是将光源作为静态光照直接烘焙到Lightmap上。
ShadowMaskMap
通常情况下,如果要实现全局光照的效果,为了性能考虑游戏只会实时计算物体的直接光照,而间接光照则会被预计算并烘焙到光照图中。而在开启预计算实时全局光照时,Unity会预计算静态物体之间的光线弹射传播路径,并使用这些信息在运行时生成低分辨率的间接光照图。这样可以在改变光源位置、方向、颜色时,也能实时计算物体的间接光照效果。
但这样需要在LightingData Asset中保存额外的光照传播路径数据,还需要在运行时生成光照图,产生额外的内存开销和渲染计算量。并且由于用于保存这些信息的光照图分辨率较低,如果直接复用普通光照图的纹理坐标进行采样,会出现很严重的瑕疵,因此必须为其生成单独的纹理坐标。
有关Unity预计算全局光照算法的更详细的说明可参考文章:
https://blogs.unity3d.com/2015/11/05/awesome-realtime-gi-on-desktops-and-consoles/
模型原始的纹理坐标有可能把不同的面映射到相同的纹理区域,而在采样光照图时,由于不同面的光照结果不同,所以必须要求模型的每一个面都映射到单独的光照图区域。为了解决这个问题,我们可以在建模工具中预先生成好展开的纹理坐标,保存到UV1通道中。也可以使用Unity自带的纹理坐标展开(Unwrapping)算法自动为模型生成光照图纹理坐标。
在生成光照图纹理坐标时,每一块不连续的模型几何面映射区域叫做一个纹理坐标图块(UV Chart)。在采样光照图时,为了避免图块之间因为采样的过滤插值而造成溢色,我们需要在图块之间保持一定的间距。但是这么做会造成光照图空间的浪费。为了避免生成的光照图纹理坐标独立图块数量过多,Unity的纹理坐标生成算法可以自动地将相邻面片的图块拼合在一起。
未合并的纹理图块
根据面片相邻关系合并的纹理图块
而在生成预计算实时全局光照信息的光照图时,系统会自动将纹理坐标的采样边缘对齐到半个像素的位置,因此即便不同的纹理坐标图块之间不保留空白像素,也不会产生溢色问题。
半像素对齐的光照图纹理坐标
但是Enlighten实现的预计算实时全局光照的算法要求光照图的每个图块最小也要有2x2像素的采样Block。如果分割的纹理图块过多,浪费的纹理空间依然会很大。因此Unity还为预计算全局光照图提供了额外的纹理坐标简化算法,可以将指定间距、夹角容差范围内的面片对应的纹理坐标图块进一步合并到一起。
但当开启纹理坐标图块简化时,Enlighten要求拼合后的每个图块的边缘保存分离的光照方向信息,而光照方向信息又是以2x2像素的Block为单位保存的,所以最小纹理坐标图块的尺寸则变成了4x4像素。
在Unity5.6.0以上版本中,官方已放弃了原先的DirectionalLightmap with Specular,如果将灯光设置为Baked的纯静态光照烘焙模式则无法实现任何Specular的光照效果。而新加入的Mixed的混合光照模式则得到了大幅改进。当使用Mixed光照模式时,引擎提供了四种新的光照混合选项,分别为:Baked Indirect、Distance Shadowmask、Shadowmask和Subtractive,这四种选项在混合实时动态光照、静态烘焙光照,实时投影及烘焙投影时采用了不同的处理方法。
每个光源使用的光照模式通过在Inspector编辑页面中修改光源组件的Mode属性值指定。
每个光源可以设置不同的光照模式。场景中可以同时存在纯实时光照(Realtime)、纯静态烘焙光照(Baked)和动静混合光照(Mixed)。如果同时有多个光源都指定为Baked或Mixed,那么它们需要烘焙的直接或间接光照信息会被混合起来保存在同一张光照图上。
当光源被指定为Mixed模式时,还需要在全局光照设置选项中为所有Mixed光源统一指定具体的混合模式。详细说明可参见章节——混合光照模式。
Intensity用来调整光源的整体强度。
Indirect Multiplier用来单独指定间接光照的强度。
Skybox Material: 指定一个用于渲染天空盒的材质。应使用采样Cube Map的特殊Shader。
Sun Source: 指定作为太阳的主光源。将使用该光源的方向来计算光晕特效的位置。
Environment Lighting
环境光源选项
Source:
Ambient Mode:仅当Realtime Global Illumination开启时可选,否则强制烘焙环境光。
Environment Reflections
环境反射光源
Source:
Resolution:
环境反射CubeMap的分辨率(覆盖原始天空盒材质纹理尺寸)
Compression:
Intensity Multiplier:
反射强度控制系数,取值范围从0到1。默认为1,即真实的反射强度。
Bounces:
当场景中存在类似镜子的反射物时,场景可以被反射的最大次数。如设置为1,则环境反射贴图中的镜面则不会被绘制,显示为黑色。
Realtime Global Illumination:
是否开启预计算实时全局光照。
如开启则会烘焙间接光照传递信息贴图,占用额外的内存,并影响渲染性能。
如关闭则将全局光照结果烘焙到光照图中。
Baked Global Illumination:
控制是否烘焙全局光照。如关闭,则Mixed和Baked光源均不会生成光照贴图。
Subtractive
光照:
等同于Unity5.6版之前Non-Directional光照烘焙模式,将直接光照和间接光照都烘焙到光照图上,但不会有镜面反射的高光效果。
投影:
如光源为Mixed模式,在ShadowDistance距离内,动态物体可使用ShadowMap在动态物体上产生投影,但动态物体只在静态物体上产生主光源造成的投影。并且必须通过Realtime Shadow Color指定一个强制的投影遮挡区域间接光照颜色。在ShadowDistance距离外动态物体不产生投影。
静态物体在静态物体上的投影使用Lightmap计算,在动态物体上的投影使用LightProbe计算,均不受ShadowDistance影响。
Baked Indirect
光照:
直接光照实时计算,间接光照烘焙到光照图。
投影:
无论对动态物体还是静态物体均使用基于ShadowMap的实时投影。但限定在ShadowDistance可视距离内,该距离外无投影效果。
Shadowmask
光照:
直接光照实时计算,间接光照烘焙到光照图。
投影:
所有静态物体投射到静态物体的投影都使用预烘焙的ShadowMask图计算,不考虑ShadowDistance距离。
所有静态物体投射到动态物体的投影使用LightProbe计算,不考虑ShadowDistance距离。
所有动态物体投射的投影在ShadowDistance距离内都使用ShadowMap计算,在ShadowDistance外无投影。
Distance Shadowmask
光照:
直接光照实时计算,间接光照烘焙到光照图。
投影:
在ShadowDistance距离内,全部使用基于ShadowMap的实时投影。
在ShadowDistance距离外,动态物体无投影。静态物体在动态物体上的投影使用Light Probes,静态物体在静态物体上的投影使用ShadowMask。
Lightmapper:
Indirect Resolution:
间接光照计算分辨率,影响光照图上间接光照的表现细节和精度,会极大地影响光照图烘焙时间。在测试期间建议将其设置为0.00001之类的极小值。
Lightmap Resolution:
光照图分辨率。是纹理像素和场景模型世界空间的表面积尺寸之间的比值。此值越大对于相同的模型生成的光照图越大,细节也越多。
Lightmap Padding:
对于模型表面在光照图上不同的UV展开区域,之间间隔的像素单位尺寸大小。用于避免当两块不相邻的面片在光照图上被映射到同一区域时,由于纹理采样插值会导致互相溢色。通常纹理分辨率越高此值也应该指定得越大。但过大会降低光照图的空间利用率。
Lightmap Size:
单张光照图的尺寸。注意该值是在计算光照图时输出的最大尺寸。实际的光照图尺寸还受到光照图导入纹理格式设置尺寸的影响。
Compress Lightmap:
是否压缩光照图。
Ambient Occlusion:
烘焙光照图时是否计算环境遮挡。如开启则在模型的沟槽和边角处会产生自然的黑暗区域。
Final Gather:
是否在计算全局光照弹射的最后一步使用与光照图相同的分辨率。如不开启在某些情况下光照图会产生错误的黑块,强烈建议开启。
Directional Mode:
Non-Directional –传统的单张光照图,无法产生镜面反射高光效果。
Directional –生成两张光照图,一张用于存储直接和间接光照,另一张用于存储主要光照贡献方向和比例,以便计算镜面反射效果。但是必须指定为非Subtractive的光照模式。
Indirect Intensity:
间接光照强度。取值范围0到5。可用于手动减弱或加强间接光照效果。
Albedo Boost:
材质反照率增强系数。取值范围1到10。可用于加亮材质本身的颜色。
Lightmap Parameters:
指定一个光照图计算参数配置表,内含更具体的光照烘焙计算参数。通常情况下只需在预制的配置方案中选择即可。
如果一个物体想要烘焙静态光照图或ShadowMask,则它必须在编辑器中被标记为Static静态物体。动态物体只能使用ShadowMap计算实时投影,使用LightProbe接受投影。
所有继承于Renderer的渲染器组件都有Lighting属性组进行光照相关的设置:
Light Probes:
Cast Shadows:
Receive Shadows:
其它遮挡物是否能在此物体上投影
Lightmap Static:
是否为该物体烘焙光照图(或ShadowMask图)
UV Charting Control:
预计算实时全局光照图纹理坐标优化控制参数
Lightmap Settings:
Unity引擎原始的ShadowMask投影遮罩区域图强制使用16位RGBA格式纹理保存,为了实现投影区域渐变的软边过渡,因此对于每个光源必须使用4位数据记录投影遮挡灰度信息。每个通道用来记录一个光源的投影遮挡信息,最大同时记录光照图渲染区域内的4个光源。
但对于游戏场景来说,大多数情况只需要有一个方向光作为主光源产生投影,并不会占用全部4个通道。对于地形之类的物件,必须要求使用一张整体的光照图,否则在不同的纹理坐标图块之间必然会出现接缝问题。而在一些低端的移动设备上,并不支持2048尺寸以上的单张贴图。这会造成投影精度完全达不到美术的要求。即便不考虑地形,ShadowMask图所占用的内存空间也太大,其中3/4的空间都是浪费掉的。
为了提高投影图分辨率,节省内存占用,我修改了Unity引擎烘焙及采样ShadowMask图的代码。并在光源属性设置页面增加了一个选项Use Multi-Channel Shadow Mask。
如果勾选了这个选项,且要烘焙的光照图中仅有这一个光源需要烘焙投影,则会把ShadowMask图的实际烘焙计算尺寸设置为光照图的2倍,即4倍的面积。然后在输出ShadowMask图时,将这张4倍分辨率的ShadowMask图等分为4份,分别存储到原始尺寸ShadowMask图的4个通道中。
在实时渲染时,引擎同样会检测这张ShadowMask图是否只对应唯一的一个烘焙投影的光源。如果是,则会使用单独实现的Shader变体来根据纹理坐标决定采样哪个通道的投影遮罩信息。
多通道存储的ShadowMask图
多通道ShadowMask采样Shader核心修改代码
fixed4 GetMultiChannelShadowMaskSelectorAndUV(inoutfloat2 lightmapUV) { // Sample the shadow mask from all 4 rgba channels by remapping uv. lightmapUV *= 2; fixed4 multiChannelOcclusionSelector; if (lightmapUV.x >1.0) { lightmapUV.x -= 1; multiChannelOcclusionSelector = fixed4(0,1,0,1); } else { multiChannelOcclusionSelector = fixed4(1,0,1,0); } if (lightmapUV.y >1.0) { lightmapUV.y -= 1; multiChannelOcclusionSelector *= fixed4(0,0,1,1); } else { multiChannelOcclusionSelector *= fixed4(1,1,0,0); } return multiChannelOcclusionSelector; } inline fixed UnitySampleBakedOcclusion (float2 lightmapUV, float3 worldPos) { #if defined(USE_MULTI_CHANNEL_SHADOWMASK) fixed4 multiChannelOcclusionSelector = GetMultiChannelShadowMaskSelectorAndUV(lightmapUV); #endif fixed4 rawOcclusionMask = UNITY_SAMPLE_TEX2D_SAMPLER(unity_ShadowMask, unity_Lightmap, lightmapUV.xy); #if defined(USE_MULTI_CHANNEL_SHADOWMASK) return saturate(dot(rawOcclusionMask, multiChannelOcclusionSelector)); #else return saturate(dot(rawOcclusionMask, unity_OcclusionMaskSelector)); #endif }
原创声明,本文系作者授权云+社区发表,未经许可,不得转载。
如有侵权,请联系 [email protected] 删除。
编辑于 2017-11-17