把自身阴影烘焙进材质
增加细节纹理部分
支持更丰富的shader变体
一次编辑多个材质球
1 遮挡区域的Self-Shading
美术能够创作非常复杂丰富的表面纹理,它只是一个视错觉。为了增强表面纹理视觉真实感,引入Self-Shading。如何增强呢?通常我们使用了法线来增强模型表面的凹凸层次感,法线带来的视觉增强是第一步,但是法线只适用于采样直接光照下。现在开始第二步增强,给凹凸表面引入阴影:凸向凹投射阴影
1.1 Occlusion Map
使用遮挡纹理增加self-shadings,也是灰度图,凹趋近黑色0。在Properties声明和拓展gui:
[NoScaleOffset]_OcclusionMap("OcclusionMap", 2D) = "white"{} _Occlusion("Occlusion", Range(0,1)) = 0 #pragma shader_feature _ _OCCLUSION_MAP
注意:shader_feature _ _OCCLUSION_MAP 与 shader_feature _OCCLUSION_MAP是等效的。但是下面
//这两个不等效
#pragma shader_feature _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
总结一下shader_feature ,只有一个关键字默认生成<no keywords defined>,若有多个关键字第一个关键字会替代<no keywords defined>. 在使用手动收集ShaderVariantCollection要多加注意。而multi_compile有单个关键字时必须加_.
1.2 Occlusion GUI_Extension
void OcclusionShow() { EditorGUI.BeginChangeCheck(); MaterialProperty mp = MakerMapWithScaleShow("_OcclusionMap", "_Occlusion", false, "遮挡纹理"); if (EditorGUI.EndChangeCheck()) { SetKeyword("_OCCLUSION_MAP", mp.textureValue); } }
1.3 直接光阴影
创建采样函数
float GetOcclusion(Interpolators i) { #ifdef _OCCLUSION_MAP return tex2D(_OcclusionMap, i.uv).g; #endif return 1; }
但是由于阴影的强度有可调需求,需要动态改变。结合_OcclusionStrength做线性插值
float GetOcclusion(Interpolators i) { #ifdef _OCCLUSION_MAP float g = tex2D(_OcclusionMap, i.uv).g return lerp(1, g, _OcclusionStrength); #endif return 1; }
然后将采样得到的值作用于光照颜色内,包括直接光和间接光,这里是直接光
UnityLight CreateLight(Interpolators i) { //。。。 UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos); attenuation *= GetOcclusion(i); light.color = _LightColor0.rgb * attenuation; light.ndotl = DotClamped(i.normal, light.dir); return light; }
图1.1 without and with occlusion map
图1.1带有遮挡纹理的凹陷阴影过渡gif显示:
1.4 间接光阴影
直接光采样下凹陷越深阴影越重,但不是那么明显。这是因为除了直接光以外还有间接光,幸好OcclusionMap并不是针对特定光线的纹理,现在来给他增加间接光采样,然后看看效果如何。
UnityIndirect CreateIndirectLight(Interpolators i, float3 viewDir) { #if defined(VERTEXLIGHT_ON) //... float occlusion = GetOcclusion(i); indirectLight.diffuse *= occlusion; indirectLight.specular *= occlusion; #endif return indirectLight; }
图1.2 wihtout and with occlusion
把图1.2与图1.1对比,可以明显感觉到Occlusion纹理好似专门针对间接光而制作,它随着凹陷越深阴影越明显,甚至有点过头了。那么我们何不把直接光采样这步去掉,看看它的效果如何。
UnityLight CreateLight(Interpolators i) { //。。。 UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos);attenuation *= GetOcclusion(i);light.color = _LightColor0.rgb * attenuation; light.ndotl = DotClamped(i.normal, light.dir); return light; }
图1.3 without and with occlusion
把图1.1、1.2、1.3对比,感觉图1.3的效果适中,看起来舒服。原文翻译:就Occlusion而言,它具有相当大真实感。虽然如此,我们通常会发现游戏里的遮挡图也被用在直接光上。Unity老旧的shader就是这样做的,虽然它不太真实,但是对灯光效果的控制提供了相当大的灵活性。
SSAO:screen-space-ambient-occlusion。它是屏幕后处理效果,使用深度缓冲来动态创建整个帧的遮挡映射。它被用来增强屏幕的深度感,因为它是后处理效果,它在所有的灯光渲染之后被使用。这意味着那个阴影即使用了间接光也使用了直接光。因此它也是不真实的。
1.5 合并纹理
我们只用了遮挡纹理的G通道,而metallic金属纹理是存储在R通道,SmoothNess纹理存储在alpha通道。这意味着我们可以把三个纹理合并为一个纹理。
图1.4 合并后的纹理
优势
- 单一纹理降低了内存和存储压力;
弊端
- 这个Shader中它会采样两次;(可以手动优化为从同一纹理采样);
- 使用DXT5压缩后,纹理大小变小了但是它的质量也降低了。所幸这些纹理不要求太高的细节和精度。
2 细节纹理
增加细节纹理和法线,把细节纹理和法线设置为fade-out mipmap。
图2.1 细节纹理效果
2.1 细节遮罩纹理
根据图2.1效果,细节纹理覆盖整个表面后,看起来的效果不是太好。最好的效果是它不覆盖金属区域部分。所以,我们可以用细节遮罩纹理来控制这部分显示,这就好像蒙版测试。不同之处在于0表示没有细节,1表示完整的细节。
Unity的Standard Shader使用了遮罩纹理的alpha通道,这张纹理四个通道存储了同样的值。
2.2 Albedo Details
调整Albedo纹理采样,必须基于detal mask纹理的采样值,在未修改和修改后的albedo之间插值。
float3 GetAlbedo (Interpolators i) { float3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Tint.rgb; float3 details = tex2D(_DetailTex, i.uv.zw) * unity_ColorSpaceDouble; albedo = lerp(albedo, albedo * details, GetDetailMask(i)); return albedo; } ... float4 MyFragmentProgram (Interpolators i) : SV_TARGET { … // float3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Tint.rgb; // albedo *= tex2D(_DetailTex, i.uv.zw) * unity_ColorSpaceDouble; float3 specularTint; float oneMinusReflectivity; float3 albedo = DiffuseAndSpecularFromMetallic( GetAlbedo(i), GetMetallic(i), specularTint, oneMinusReflectivity ); … }
2.3 Normal Details
对于法线,同样需要相同的调整。但是,这里的细节不符合与未修改的切线空间法向量相对应。因为原作者给的这张法线纹理不是切线空间。需要手动匹配一次。
void InitializeFragmentNormal(inout Interpolators i) { float3 mainNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale); float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale); detailNormal = lerp(float3(0, 0, 1), detailNormal, GetDetailMask(i)); float3 tangentSpaceNormal = BlendNormals(mainNormal, detailNormal); ... }
图2.2 Mask Details