IBL基于图像的照明:基于环境贴图技术,在贴图的基础上通过一系列计算将环境贴图的光照信息提取出来。并将这些信息重新储存回环境贴图中。

使用IBL贴图时,贴图分辨率不要过大,一般选择512即可,

应用IBL间接光镜面反射

我们本节依然是使用CubeMap环境贴图,所以对于IBL的采样也是根据CubeMap来采样。

想要在CubeMap中提取出照明信息,只需要将它的导入设置中将Convolution Type改为Specular(Glossy Reflection)即可,然后再将Filter Mode改为Trilinear。

想要看到Unity提取出来的光照信息,需要修改环境贴图的Mipmap层级,在贴图的预览面板,我们只要滑动紫色的小方块旁边的扭就能预览光照了,一般在Mip2或Mip3就能看到效果。

想要拿到Mipmap,我们定义一个Roughness(粗糙度)属性,使用它来取得指定的Mipmap,

1
2
3
4
5
6
_Roughness("Roughness", Range(0,1)) = 0

float _Roughness;

float mip_level = _Roughness * 6.0;//一般用Mip6最高
half4 color_cubemap = texCUBElod(_CubeMap, float4(reflect_dir, mip_level));

使用texCUBElod方法来获取Mipmap,同时将reflect_dir转化为4维向量,第四个参数就是Mipmap层级。

应用粗糙度贴图

在IBL的基础上,使用粗糙度贴图能够达到更细腻的金属表面效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
_RoughnessMap("RoughnessMap", 2D) = "black" {}
_RoughnessContrast("RoughnessContrast", Range(0.01,10)) = 1
_RoughnessBrightness("RoughnessBrightness", Float) = 1
_RoughnessMin("RoughMin", Range(0,1)) = 0
_RoughnessMax("RoughMax", Range(0,1)) = 1

sampler2D _RoughnessMap;
float _RoughnessContrast;
float _RoughnessBrightness;
float _RoughnessMin;
float _RoughnessMax;

float roughness = tex2D(_RoughnessMap, i.uv);//tex2D可以返回一维到四维量
roughness = saturate(pow(roughness, _RoughnessContrast) * _RoughnessBrightness);
roughness = lerp(_RoughnessMin, _RoughnessMax, roughness);//用来控制mip_level
roughness = roughness * (1.7 - 0.7 * roughness);//从线性变化改为弧度变化,这是一个倒扣的二次函数曲线
float mip_level = roughness * 6.0;

注意,这里使用粗糙度贴图的话,删掉上面的_Roughness属性

应用ACES映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
float3 ACESFilm(float3 x)
{
float a = 2.51f;
float b = 0.03f;
float c = 2.43f;
float d = 0.59f;
float e = 0.14f;
return saturate((x*(a*x + b)) / (x*(c*x + d) + e));
}

half3 final_color_linear = pow(final_color, 2.2);
final_color = ACESFilm(final_color);
half3 final_color_gamma = pow(final_color, 1.0/2.2);

return half4(final_color_gamma, 1.0);

二次乘算Tint

教程中的提高色彩饱和度的技巧

1
half3 final_color = env_color * ao * _Tint.rgb * _Tint.rgb * _Expose;

AO贴图强度调整

想要调整AO贴图的强度,可以使用Lerp函数

1
2
3
4
5
6
_AOAdjust("AOAdjust", Range(0,1)) = 1

float _AOAdjust;

half ao = tex2D(_AOMap, i.uv).r;
ao = lerp(1.0, ao, _AOAdjust);

完整Shader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
Shader "KerryTA/Code/302IBLMap"
{
Properties
{
//_MainTex ("Texture", 2D) = "white" {}
_CubeMap ("CubeMap", Cube) = "white" {}
_Rotate("Rotate", Range(0,360)) = 0
_Tint("Tint", Color) = (1,1,1,1)
_Expose("Expose", Float) = 1
_NormalMap("NormalMap", 2D) = "bump" {}
_NormalIntensity("NormalIntensity", Range(0.0, 5.0)) = 0.5
_AOMap("AOMap", 2D) = "white" {}
_AOAdjust("AOAdjust", Range(0,1)) = 1
_RoughnessMap("RoughnessMap", 2D) = "black" {}
_RoughnessContrast("RoughnessContrast", Range(0.01,10)) = 1
_RoughnessBrightness("RoughnessBrightness", Float) = 1
_RoughnessMin("RoughMin", Range(0,1)) = 0
_RoughnessMax("RoughMax", Range(0,1)) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};

struct v2f
{
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
float3 normal_world : TEXCOORD1;
float3 pos_world : TEXCOORD2;
float3 tangent_world : TEXCOORD3;
float3 binormal_world : TEXCOORD4;
};

//sampler2D _MainTex;
//float4 _MainTex_ST;
samplerCUBE _CubeMap;
float4 _CubeMap_HDR;
sampler2D _NormalMap;
float4 _NormalMap_ST;
float _NormalIntensity;
sampler2D _AOMap;
float _AOAdjust;
float4 _Tint;
float _Expose;
float _Rotate;
sampler2D _RoughnessMap;
float _RoughnessContrast;
float _RoughnessBrightness;
float _RoughnessMin;
float _RoughnessMax;

float3 RotateYAround(float degree, float3 target)
{
float rad = degree * UNITY_PI / 100;
float2x2 m_rotate = float2x2(cos(rad), -sin(rad), sin(rad), cos(rad));
float2 dir_rotate = mul(m_rotate, target.xz);//旋转XZ坐标
target = float3(dir_rotate.x, target.y, dir_rotate.y);//重新赋值反射方向。
return target;
}

float3 ACESFilm(float3 x)
{
float a = 2.51f;
float b = 0.03f;
float c = 2.43f;
float d = 0.59f;
float e = 0.14f;
return saturate((x*(a*x + b)) / (x*(c*x + d) + e));
}

v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord * _NormalMap_ST.xy + _NormalMap_ST.zw;
o.normal_world = normalize(mul(float4(v.normal, 0.0), unity_WorldToObject).xyz);
o.tangent_world = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0.0)).xyz);
o.binormal_world = normalize(cross(o.normal_world, o.tangent_world)) * v.tangent.w;
o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}

half4 frag (v2f i) : SV_Target
{
half3 normal_dir = normalize(i.normal_world);
half3 tangent_dir = normalize(i.tangent_world);
half3 binormal_dir = normalize(i.binormal_world);
float3x3 TBN = float3x3(tangent_dir, binormal_dir, normal_dir);
half3 normaldata = UnpackNormal(tex2D(_NormalMap, i.uv));
normaldata.xy = normaldata.xy * _NormalIntensity;
normal_dir = normalize(mul(normaldata, TBN));

half ao = tex2D(_AOMap, i.uv).r;
ao = lerp(1.0, ao, _AOAdjust);
half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
half3 reflect_dir = reflect(-view_dir, normal_dir);//需要将view_dir反向,从摄像机指向顶点。

reflect_dir = RotateYAround(_Rotate, reflect_dir);

float roughness = tex2D(_RoughnessMap, i.uv);
roughness = saturate(pow(roughness, _RoughnessContrast) * _RoughnessBrightness);
roughness = lerp(_RoughnessMin, _RoughnessMax, roughness);
roughness = roughness * (1.7 - 0.7 * roughness);//从线性变化改为弧度变化
float mip_level = roughness * 6.0;
half4 color_cubemap = texCUBElod(_CubeMap, float4(reflect_dir, mip_level));
half3 env_color = DecodeHDR(color_cubemap, _CubeMap_HDR);
half3 final_color = env_color * ao * _Tint.rgb * _Tint.rgb * _Expose;

half3 final_color_linear = pow(final_color, 2.2);
final_color = ACESFilm(final_color);
half3 final_color_gamma = pow(final_color, 1.0/2.2);

return half4(final_color_gamma, 1.0);
}
ENDCG
}
}
}

使用反射探针

1
2
half4 color_cubemap = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflect_dir, mip_level);
half3 env_color = DecodeHDR(color_cubemap, unity_SpecCube0_HDR);

UNITY_SAMPLE_TEXCUBE_LOD方法发需要三个参数,我们不需要将mip_level放在四维向量里,只需要放在最后一个参数即可。

应用IBL间接光漫反射

想要应用IBL间接光漫反射,需要做到两点:

  1. 需要将环境贴图的导入设置中的Convolution Type改为Diffuse(Irradiance)
  2. 不再使用reflect_dir采样贴图,而是直接使用normal_dir,因为漫反射是固定的。
1
half4 color_cubemap = texCUBElod(_CubeMap, float4(normal_dir, mip_level));