Unity Shader GUI 扩展二(翻译十)
本篇摘要:
- 把自身阴影烘焙进材质
- 增加细节纹理部分
- 支持更丰富的shader变体
- 一次编辑多个材质球
遮挡区域的Self-Shading
美术能够创作非常复杂丰富的表面纹理,它只是一个视错觉。为了增强表面纹理视觉真实感,引入Self-Shading。
如何增强呢?通常我们使用了法线来增强模型表面的凹凸层次感,法线带来的视觉增强是第一步,但是法线只适用于采样直接光照下。现在开始第二步增强,给凹凸表面引入阴影:凸向凹投射阴影
Occlusion Map
使用遮挡纹理增加self-shadings,也是灰度图,凹趋近黑色0。在Properties声明和拓展gui:
1
2
3
4
[NoScaleOffset]_OcclusionMap("OcclusionMap", 2D) = "white"{}
_Occlusion("Occlusion", Range(0,1)) = 0
#pragma shader_feature _ _OCCLUSION_MAP
这两种是等效的
shader_feature _ _OCCLUSION_MAP _shader_feature _OCCLUSION_MAP
1
2
3
//但是这两种不等效!!!
#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有单个关键字时必须加_。
Occlusion GUI_Extension
1
2
3
4
5
6
7
8
9
void OcclusionShow()
{
EditorGUI.BeginChangeCheck();
MaterialProperty mp = MakerMapWithScaleShow("_OcclusionMap", "_Occlusion", false, "遮挡纹理");
if (EditorGUI.EndChangeCheck())
{
SetKeyword("_OCCLUSION_MAP", mp.textureValue);
}
}
直接光阴影
创建采样函数
1
2
3
4
5
6
float GetOcclusion(Interpolators i) {
#ifdef _OCCLUSION_MAP
return tex2D(_OcclusionMap, i.uv).g;
#endif
return 1;
}
但是由于阴影的强度有可调需求,需要动态改变。结合_OcclusionStrength做线性插值
1
2
3
4
5
6
7
float GetOcclusion(Interpolators i) {
#ifdef _OCCLUSION_MAP
float g = tex2D(_OcclusionMap, i.uv).g
return lerp(1, g, _OcclusionStrength);
#endif
return 1;
}
然后将采样得到的值作用于光照颜色内,包括直接光和间接光,这里是直接光
1
2
3
4
5
6
7
8
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;
}
上图带有遮挡纹理的凹陷阴影过渡,gif显示:
间接光阴影
直接光采样下凹陷越深阴影越重,但不是那么明显。这是因为除了直接光以外还有间接光,幸好OcclusionMap并不是针对特定光线的纹理,现在来给他增加间接光采样,然后看看效果如何。
1
2
3
4
5
6
7
8
9
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与图1.1对比,可以明显感觉到Occlusion纹理好似专门针对间接光而制作,它随着凹陷越深阴影越明显,甚至有点过头了。那么我们何不把直接光采样这步去掉,看看它的效果如何。
1
2
3
4
5
6
7
8
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;
}
把上图逐一对比,感觉最后一图的效果适中,看起来舒服。就Occlusion而言,它具有相当大真实感。虽然如此,我们通常会发现游戏里的遮挡图也被用在直接光上。Unity老旧的shader就是这样做的,虽然它不太真实,但是对灯光效果的控制提供了相当大的灵活性。
SSAO:screen-space-ambient-occlusion:它是屏幕后处理效果,使用深度缓冲来动态创建整个帧的遮挡映射。它被用来增强屏幕的深度感,因为它是后处理效果,它在所有的灯光渲染之后被使用。这意味着那个阴影即使用了间接光也使用了直接光。因此它也是不真实的。
合并纹理
我们只用了遮挡纹理的G通道,而metallic金属纹理是存储在R通道,SmoothNess纹理存储在alpha通道。这意味着我们可以把三个纹理合并为一个纹理。
优势
- 单一纹理降低了内存和存储压力;
弊端
- 这个Shader中它会采样两次;(可以手动优化为从同一纹理采样);
- 使用DXT5压缩后,纹理大小变小了但是它的质量也降低了。所幸这些纹理不要求太高的细节和精度。
细节纹理
增加细节纹理和法线,把细节纹理和法线设置为fade-out mipmap。
细节遮罩纹理
根据图2.1效果,细节纹理覆盖整个表面后,看起来的效果不是太好。最好的效果是它不覆盖金属区域部分。所以,我们可以用细节遮罩纹理来控制这部分显示,这就好像蒙版测试。不同之处在于0表示没有细节,1表示完整的细节。
Unity的Standard Shader使用了遮罩纹理的alpha通道,这张纹理四个通道存储了同样的值。
Albedo Details
调整Albedo纹理采样,必须基于detal mask纹理的采样值,在未修改和修改后的albedo之间插值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
);
}
Normal Details
对于法线,同样需要相同的调整。但是,这里的细节不符合与未修改的切线空间法向量相对应。因为原作者给的这张法线纹理不是切线空间.需要手动匹配一次。
1
2
3
4
5
6
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);
}










