翻译10 Unity Shader GUI 拓展2-细节纹理

把自身阴影烘焙进材质
增加细节纹理部分
支持更丰富的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_MAPshader_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;
}

image image

图1.1 without and with occlusion map

图1.1带有遮挡纹理的凹陷阴影过渡gif显示:

directLight_occlusion

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;
}

image image

图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;
}

image image

图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通道。这意味着我们可以把三个纹理合并为一个纹理。

image

图1.4 合并后的纹理

优势

  1. 单一纹理降低了内存和存储压力;

弊端

  1. 这个Shader中它会采样两次;(可以手动优化为从同一纹理采样);
  2. 使用DXT5压缩后,纹理大小变小了但是它的质量也降低了。所幸这些纹理不要求太高的细节和精度。

2 细节纹理

增加细节纹理和法线,把细节纹理和法线设置为fade-out mipmap。

image

图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);
...
}

image

图2.2 Mask Details

原文地址:https://www.cnblogs.com/baolong-chen/p/12348697.html