烘焙自身阴影到材质
添加物体表面细节
支持更高效的shader 变体
一次编辑多个材质
这是渲染课程的第十节。上一节我们使用多个纹理来创建复杂的材质。本节将会添加更多复杂的细节。
本节使用unity版本5.4.3f1
1 闭塞区域
尽管我么年创建了材质使其看起来很复杂,但是那只是假象。三角形依然是平的,法线贴图可以增加深度信息,但是它只针对平行光有用,它没有自身的阴影。如果更逼真一点,那么就需要在较低的地方有点阴影。
如下面的电路板,有好多的凹槽。下面是使用法线贴图的效果:
但是没有在凹槽处阴影,显得不是那么深。
1.1 闭塞贴图
为了增加凹槽处的阴影,我们需要使用一个闭塞贴图,它是一个灰度纹理图:
声明一个属性来使用这个贴图:
[NoScaleOffset] _OccusionMap("Occlusion", 2D) = "white"{}
_OccusionStrength("Occlusion Strength", Range(0,1)) = 1
我们来给base pass增加一个变体:
#pragma shader_feature _OCCLUSION_MAP
1.2 定制属性面板
void DoOcclusion ()
{
MaterialProperty map = FindProperty("_OcclusionMap");
EditorGUI.BeginChangeCheck();
editor.TexturePropertySingleLine(
MakeLabel(map, "Occlusion (G)"), map,
map.textureValue ? FindProperty("_OcclusionStrength") : null
);
if (EditorGUI.EndChangeCheck()) {
SetKeyword("_OCCLUSION_MAP", map.textureValue);
}
}
BeginChangeCheck 和EndChangeCheck是用书改变变量的时候会调用。
1.3 增加阴影
采样:
sampler2D _OcculsionMap;
float _OclusionStrength;
定义一个使用闭塞图的方法:
float GetOcclusion(Interpolators i)
{
#if defined(_OCCULSION_MAP)
return tex2D(_OccusionMap, i.uv.xy).g;
#else
return 1;
endif
}
如果定义_OCCULSION_MAP则使用贴图,否则直接返回1。当闭塞的值为0的时候,表示不受光的影响,所以函数返回1。如果弯曲使用闭塞图,那么则完全受闭塞图的影响,所以要做个差值:
return lerp(1, tex2D(_OcculsionMap, i.uv.xy).g, _OcclusionStrength);
把采样出来的值用于光照计算:
UnityLight CreateLight(Interpolators i)
{
……
UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos);
attenuation *= GetOcclusion(i);
light.color = _LightColor0.rgb * attenuation;
……
}
1.4 利用间接光形成阴影
上面的图阴影不太明显,因为使用的是平行光,但是这个凹槽不应该和直接灯有关系,而应该是间接光中计算,所以在把上面的函数移动到间接光计算函数中:
UnityIndirect CreateIndirectLight (Interpolators i, float3 viewDir)
{
……
float occlusion = GetOcclusion(i);
indirectLight.diffuse *= occlusion;
indirectLight.specular *= occlusion;*/
……
}
效果:
在间接光中计算效果比较好。
1.5 合并贴图
闭塞贴图我们只使用了R通道信息,金属贴图我们也使用了R通道,光滑度则是存储在alpha通道,所以我们可以把这些信息合并到一个贴图中去,下面就是合并的贴图:
2 增加些细节
上面的电路板缺少细节信息,下面使用一个漫反射细节图和一个法线图:
2.1 细节遮罩
上面的图可以看到很多细节出现在了不改出现的地方,比如金属的部位也有了很多的细节纹理,我们不想让细节出现在各个地方,所以需要使用一个遮罩图。这个灰白的遮罩图,0表示没有细节,1表示该部位有细节。
细节遮罩如下:
unity标准shader使用的是细节图的alpha通道,所以这里也使用的是这个通道。
增加一个属性:
[NoScaleOffset] _DetailMask("Detail Mask", 2D)="white"{}
由于很多材质球没有细节遮罩图,所以需要增加一个变体。在基础通道和附加通道都应该加一个shader特性。
#pragma shader_feature _DETAIL_MASK
增加变量声明和采样细节贴图方法:
sampler2D _MainTex, _DetailTex, _DetailMask;
...
float GetDetailMask(Interpolators i)
{
#if defined(_DETAIL_MASK)
return tex2D(_DetailMask, i.uv.xy).a;
#else
return 1;
#endif
}
把这个属性在inspector面板画出来。
void DoDetailMask()
{
MaterialProperty mask = FindProperty("_DetailMask");
EditorGUI.BeginChangeCheck();
editor.TexturePropertySingleLine(MakeLabel(map, "Detail Mask (A)"), mask);
if(Editor.EndChangeCheck())
{
SetKeyword("_DETAIL_MASK", mask.textureValue);
}
}
2.2 漫反射细节
为了使细节有的地方有有的地方没有,我们必须调整下代码。以往我们都是使用乘法来计算,但是这次我们需要做差值。
我们同样把计算漫反射的函数提取出来:
float3 GetAlbedo(Interpolators i)
{
float3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Tint.rgb;
float3 details = tex2D(_DetailTex, i.uv.zw) * unity_ColorSpaceDouble; //颜色乘以2,翻倍
albedo = lerp(albedo, albedo * details, GetDetailMask(i));
return albedo;
}
2.3 法线细节
和漫反射细节做同样的处理:
void InitializeFramgmentNormal(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);
}
3 更多的关键字
我们可以通过判断是否有对应的贴图,来控制使用哪个变体。
比如判断是否使用漫反射细节图,可以通过下面的方法:
void DoSecondary()
{
GUILayout.Label("Secondary Maps", EditorStyles.boldLabel);
MaterialProperty detailTex = FindProperty("_DetailTex");
EditorGUI.BeginChangeCheck();
editor.TexturePropertySingleLine(MakeLabel(detailTex, "Albedo (RGB) multiplied by 2"), detailTex);
if(EditorGUI.EndChangeCheck())
{
SetKeyword("_DETAIL_ALBEDO_MAP", detailTex.textureValue);
}
……
}
其他的法线贴图和细节法线贴图也可以做类似的处理,比如是否使用法线贴图关键字:
void DoNormals () {
MaterialProperty map = FindProperty("_NormalMap");
EditorGUI.BeginChangeCheck();
editor.TexturePropertySingleLine(
MakeLabel(map), map,
map.textureValue ? FindProperty("_BumpScale") : null
);
if (EditorGUI.EndChangeCheck()) {
SetKeyword("_NORMAL_MAP", map.textureValue);
}
}
比如是否使用细节法线图:
void DoSecondaryNormals () {
MaterialProperty map = FindProperty("_DetailNormalMap");
EditorGUI.BeginChangeCheck();
editor.TexturePropertySingleLine(
MakeLabel(map), map,
map.textureValue ? FindProperty("_DetailBumpScale") : null
);
if (EditorGUI.EndChangeCheck()) {
SetKeyword("_DETAIL_NORMAL_MAP", map.textureValue);
}
}
3.1 更多的shader变体
然后再shader的basepass和addtional pass中加入关键字:
首先是基础通道+附加通道中都加入:
#pragma shader_feature _NORMAL_MAP
#pragma shader_feature _DETAIL_ALBEDO_MAP
#pragma shader_feature _DETAIL_NORMAL_MAP
3.2 使用关键字
我们必须在计算函数中判断关键字是否打开了,如计算漫反射的时候:
float3 GetAlbedo(Interpolators i)
{
floa3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Tint.rgb;
#if defined(_DETAIL_ALBEDO_MAP)
float3 details = tex2D(_DetailTex, i.uv.zw) * unity_ColorSpaceDouble;
albedo = lerp(albedo, albedo * details, GetDetailMask(i));
#endif
return albedo;
}
同样对于是否使用法线贴图以及是否使用细节法线贴图也要做类似的处理:
float3 GetTangentSpacelNormal(Interpolators i)
{
float3 normal = float3(0,0,1);
#if defined(_NORMAL_MAP)
normal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
#endif
#if defined(_DETAIL_NORMAL_MAP)
float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
detailNormal = lerp(float3(0,0,1), detailNormal, GetDetailMask(i));
normal = BlendNormals(normal, detailNormal);
#endif
return normal;
}
4 编辑多个材质