PA9 More Complexity
Shader 与渲染算法 - 高级属性 - 复杂材质 2
1 这可是弹坑啊:Occluded Areas
在之前的凹凸方案中使用了 bump 进行法线扰动。但是 bump 产生的凹凸仅支持直射光环境下的深度错觉,没有自阴影。这种缺陷在法线贴图显示有小孔、凹痕或裂缝时最为明显,它们无法表达出足够的深度。
为了解决这一问题,这一节准备了 occlusion map。可以将其理解为一个固定的 shadow map,是材质的一部分。
Shader "Code/PA9_MoreComplexity/OccludedAreas" {
Properties {
_Tint ("Tint", Color) = (1, 1, 1, 1)
_MainTex ("Albedo", 2D) = "white" {}
[NoScaleOffset] _NormalMap ("Normals", 2D) = "bump" {}
_BumpScale ("Bump Scale", Float) = 1
[NoScaleOffset] _MetallicMap ("Metallic", 2D) = "white" {}
[Gamma] _Metallic ("Metallic", Range(0, 1)) = 0
_Smoothness ("Smoothness", Range(0, 1)) = 0.1
[NoScaleOffset] _OcclusionMap ("Occlusion", 2D) = "white" {}
_OcclusionStrength("Occlusion Strength", Range(0, 1)) = 1
[NoScaleOffset] _EmissionMap ("Emission", 2D) = "black" {}
_Emission ("Emission", Color) = (0, 0, 0)
_DetailTex ("Detail Albedo", 2D) = "gray" {}
[NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
_DetailBumpScale ("Detail Bump Scale", Float) = 1
}
CGINCLUDE
#define BINORMAL_PER_FRAGMENT
ENDCG
SubShader {
Pass {
Tags {
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma target 3.0
#pragma shader_feature _METALLIC_MAP
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
#pragma shader_feature _OCCLUSION_MAP // 仅应用于 base pase
#pragma shader_feature _EMISSION_MAP
#pragma multi_compile _ SHADOWS_SCREEN
#pragma multi_compile _ VERTEXLIGHT_ON
#pragma vertex Vert
#pragma fragment Frag
#define FORWARD_BASE_PASS
#include "OccludedAreas_Light.cginc"
ENDCG
}
...
}
CustomEditor "OccludedAreas_GUI"
}
2 Mask 技术它终于来了:Masking Details
实现一个 mask 效果,使得弹坑仅影响电路板,不会覆盖金属部分。
Unity standard shader 使用 alpha 通道存储 detail mask。
将 fragment shader 中的 albedo 提取为单独的函数,使其不仅仅将 albedo 和 detail 值简单相乘,而是将 GetDetailMask 函数返回值作为插值。
与此同时修改 InitializeFragmentNormal 函数。这里将法线向上方向(0,0,1)作为未修改的值。
Shader "Code/PA9_MoreComplexity/MaskingDetails" {
Properties {
...
[NoScaleOffset] _DetailMask ("Detail Mask", 2D) = "white" {}
_DetailTex ("Detail Albedo", 2D) = "gray" {}
[NoScaleOffset] _DetailNormalMap ("Detail Normals", 2D) = "bump" {}
_DetailBumpScale ("Detail Bump Scale", Float) = 1
}
CGINCLUDE
#define BINORMAL_PER_FRAGMENT
ENDCG
SubShader {
Pass {
Tags {
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma target 3.0
#pragma shader_feature _METALLIC_MAP
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
#pragma shader_feature _OCCLUSION_MAP
#pragma shader_feature _EMISSION_MAP
#pragma shader_feature _DETAIL_MASK
#pragma multi_compile _ SHADOWS_SCREEN
#pragma multi_compile _ VERTEXLIGHT_ON
#pragma vertex Vert
#pragma fragment Frag
#define FORWARD_BASE_PASS
#include "MaskingDetails_Light.cginc"
ENDCG
}
Pass {
Tags {
"LightMode" = "ForwardAdd"
}
Blend One One
ZWrite Off
CGPROGRAM
#pragma target 3.0
#pragma shader_feature _METALLIC_MAP
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
#pragma shader_feature _DETAIL_MASK
#pragma multi_compile_fwdadd_fullshadows
#pragma vertex Vert
#pragma fragment Frag
#include "MaskingDetails_Light.cginc"
ENDCG
}
...
}
CustomEditor "MaskingDetails_GUI"
}
3 更多操控:More Keywords
没啥好说的。
动态管理关键字(Main + GUI 代码);
Shader "Code/PA9_MoreComplexity/MoreKeywords" {
Properties {
...
}
CGINCLUDE
#define BINORMAL_PER_FRAGMENT
ENDCG
SubShader {
Pass {
Tags {
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma target 3.0
#pragma shader_feature _METALLIC_MAP
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
#pragma shader_feature _NORMAL_MAP
#pragma shader_feature _OCCLUSION_MAP
#pragma shader_feature _EMISSION_MAP
#pragma shader_feature _DETAIL_MASK
#pragma shader_feature _DETAIL_ALBEDO_MAP
#pragma shader_feature _DETAIL_NORMAL_MAP
#pragma multi_compile _ SHADOWS_SCREEN
#pragma multi_compile _ VERTEXLIGHT_ON
#pragma vertex Vert
#pragma fragment Frag
#define FORWARD_BASE_PASS
#include "MoreKeywords_Light.cginc"
ENDCG
}
Pass {
Tags {
"LightMode" = "ForwardAdd"
}
Blend One One
ZWrite Off
CGPROGRAM
#pragma target 3.0
#pragma shader_feature _METALLIC_MAP
#pragma shader_feature _ _SMOOTHNESS_ALBEDO _SMOOTHNESS_METALLIC
#pragma shader_feature _NORMAL_MAP
#pragma shader_feature _DETAIL_MASK
#pragma shader_feature _DETAIL_ALBEDO_MAP
#pragma shader_feature _DETAIL_NORMAL_MAP
#pragma multi_compile_fwdadd_fullshadows
#pragma vertex Vert
#pragma fragment Frag
#include "MoreKeywords_Light.cginc"
ENDCG
}
...
}
CustomEditor "MoreKeywords_GUI"
}
4 一 shader 多用:Editing Multiple Materials
在使用一个 shader 编辑多个材质时,主要考虑解决两个冲突问题。
关键字过少:“选不到” 的问题
在编辑多个材质时,如果材质 GUI 仅设置了选中材质中的第一个材质的关键字(editor.target
),其他材质的关键字可能无法正确更新。
因此在 SetKeyword 的函数了使用 foreach 循环,遍历以确保所有材质应用更新后的关键字。
关键字过多:“连坐” 问题
当选中的多个材质之间的属性不一致,可能会导致错误。比如第一个材质使用了 normal map,但第二个材质没有。如果通过 GUI 修改了 normal map 相关的属性,可能会错误地为第二个材质启用了 normal map 的关键字。
为解决这个问题,需要在修改属性之前记录贴图的当前引用值。此时仅在贴图值真正发生改变时,才更新关键字。
void DoNormals() {
MaterialProperty map = FindProperty("_NormalMap");
Texture tex = map.textureValue;
EditorGUI.BeginChangeCheck();
editor.TexturePropertySingleLine(MakeLabel(map), map, ? FindProperty("_BumpScale") : null);
if(EditorGUI.EndChangeCheck() && tex != map.textureValue) {
SetKeyword("_NORMAL_MAP", map.textureValue);
}
}
...
void SetKeyword(string keyword, bool state) {
if(state) {
foreach(Material m in editor.targets) {
m.EnableKeyword(keyword);
}
} else {
foreach(Material m in editor.targets) {
m.DisableKeyword(keyword);
}
}
}
APPENDIX
Anki
Reference
Last updated