Shadertoy学习 vol. 3 抗锯齿,SRGB,镜面反射

result

书接上文,这次我们来实现更多的渲染功能。
本文完整代码

抗锯齿

这个内容比较简单,因为我们的渲染器是多帧合成的,所以在每一帧都对像素添加随机的位移就可以了。我们这里随机的位移范围是-0.5到+0.5

找到:

1
vec3 rayTarget =vec3((gl_FragCoord.xy/iResolution.xy) * 2.0f - 1.0f, cameraDistance);

改为:

1
2
3
vec2 jitter = vec2(RandomFloat01(rngState), RandomFloat01(rngState)) - 0.5f;

vec3 rayTarget =vec3(((gl_FragCoord.xy + jitter) / iResolution.xy) * 2.0f - 1.0f, cameraDistance);

SRGB

由于人眼对颜色的感觉不是线性的,所以我们要引入SRGB颜色空间。SRGB里的颜色并不是线性间隔的,深色的值比较多,而浅色的较少。

我们的颜色通道计算不能采用非线性值,所以我们会在最后输出像素颜色时再把颜色转换为SRGB。

这里我们可以在Shadertoy中添加一个commom选项卡,又或者在Shader Toy中新建一个frag文件,他们的作用跟头文件一样。

这里我新建了一个Test4Common.frag,并在其他两个文件的开头添加了如下代码:

1
#include "Test4Common.frag"

这样,我们的shader便可以调用别的文件里定义的函数了。我们也可以把之前的常量全部移动到这个头文件里。

以下是将线性颜色转换为SRBG颜色的代码:

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
vec3 LessThan(vec3 f, float value)
{
return vec3
(
(f.x < value) ? 1.0f : 0.0f,
(f.y < value) ? 1.0f : 0.0f,
(f.z < value) ? 1.0f : 0.0f
);
}


vec3 Linear2SRGB(vec3 rgb)
{
rgb = clamp(rgb, 0.0f, 1.0f);

return mix
(
pow(rgb, vec3(1.0f / 2.4f)) * 1.055f - 0.055f,
rgb * 12.92f,
LessThan(rgb, 0.0031308f)
);
}


vec3 SRGB2Linear(vec3 rgb)
{
return rgb;

rgb = clamp(rgb, 0.0f, 1.0f);

return mix
(
pow(((rgb + 0.055f) / 1.055f), vec3(2.4f)),
rgb / 12.92f,
LessThan(rgb, 0.04045f)
);
}

这部分的原理不必过分深究,因为它更多是人为制定的规则,这是微软提供的对颜色空间转换的解释:
formula
要记住的是最好不要在渲染时引入SRGB色彩空间,因为有可能会导致渲染出现问题。

曝光和色调映射

当我们计算光照的时候,光线的强度可以从0一直到无限,可像素的值却只能停在0到1之间。

为此,我们需要把值重新映射到0到1的区间,使暗处可以保留细节,同时也能看清楚亮处。

我们在头文件当中添加以下代码:

1
2
3
4
5
6
7
8
9
vec3 ACESFilm(vec3 x)
{
float a = 2.51f;
float b = 0.03f;
float c = 2.43f;
float d = 0.59f;
float e = 0.14f;
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0f, 1.0f);
}

Graph for this function
这个函数其实很好理解,就跟线性插值差不多。

之后再修改主函数:

1
2
color *= c_exposure;
color = ACESFilm(color);

注意我们在这里额外添加了一个c_exposure常量,用于控制曝光,我在这里设置的值为0.5. 你也可以手动修改来得到一个满意的亮度。

测试一下,看看效果!
Test1

镜面反射

现在到了大头了啊,我们要给渲染器加入镜面反射了。

GLSL是有一个reflect()函数已经被定义好的了,你只需要给出入射的光线和法线,就能返回出射光线了。

但在那之前,我们需要对SRayHitInfo的这个结构体做出一些修改。

我们先定义一个SMaterialInfo结构体,把albedo, emissive都移过来,再添加三个新的变量:specularColorpercentSpecularroughness

再把SMaterialInfo添加到SRayHitInfo里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct SMaterialInfo
{
vec3 albedo;
vec3 emissive;
vec3 specularColor;
float percentSpecular;
float roughness;
};

struct SRayHitInfo
{
float dist;
vec3 normal;
SMaterialInfo material;
};
  • specularColor描述了镜面反射后光线的颜色,它和albedo类似,不过一个物体的漫反射颜色和镜面反射颜色不一定相同。
  • percentageSpecular是一个float,取值范围在0到1之间,描述了镜面反射光线所占全部反射光线的比值,值越低,漫反射所占比重越高,反之亦然
  • roughness也是取值范围在0到1之间的float,绝对光滑物体的roughness为0。

计算时的原理如下:

  • 我们会随机生成一个0到1之间的小数,如果它小于percentageSpecular,则让光线进行镜面反射,否则进行漫反射。
  • roughness为1的进行漫反射。
  • roughness为0的用reflect()计算出射方向。
  • roughness在0到1之间的就先平方roughness,再线性插值漫反射和镜面反射的方向来算出反射角度,最后再归一化。
  • 对于镜面反射,我们不再乘上albedo,而是specularColor。

其实这个逻辑还是比较好理解的, 然后我们就可以开始写了,找到代码:

1
2
3
4
5
6
7
rayPos = (rayPos + rayDir * hitInfo.dist) + hitInfo.normal * c_rayPosNormalNudge;

rayDir = normalize(hitInfo.normal + RandomUnitVector(rngState));

ret += hitInfo.emissive * throughput;

throughput *= hitInfo.albedo;

更改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
rayPos = (rayPos + rayDir * hitInfo.dist) + hitInfo.normal * c_rayPosNormalNudge;

//Wether or not to do the specular reflection ray
float doSpecular = (RandomFloat01(rngState) < hitInfo.material.percentSpecular) ? 1.0f : 0.0f;

vec3 diffuseRayDir = normalize(hitInfo.normal + RandomUnitVector(rngState));

vec3 specularRayDir = reflect(rayDir, hitInfo.normal);
specularRayDir = normalize(mix(specularRayDir, diffuseRayDir, hitInfo.material.roughness * hitInfo.material.roughness));
rayDir = mix(diffuseRayDir, specularRayDir, doSpecular);

ret += hitInfo.material.emissive * throughput;

throughput *= mix(hitInfo.material.albedo, hitInfo.material.specularColor, doSpecular);

最后更改一下场景,要记得添加上新的Material属性:

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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
void TestSceneTrace(in vec3 rayPos, in vec3 rayDir, inout SRayHitInfo hitInfo)
{
// to move the scene around, since we can't move the camera yet
vec3 sceneTranslation = vec3(0.0f, 0.0f, 10.0f);
vec4 sceneTranslation4 = vec4(sceneTranslation, 0.0f);

// back wall
{
vec3 A = vec3(-12.6f, -12.6f, 25.0f) + sceneTranslation;
vec3 B = vec3( 12.6f, -12.6f, 25.0f) + sceneTranslation;
vec3 C = vec3( 12.6f, 12.6f, 25.0f) + sceneTranslation;
vec3 D = vec3(-12.6f, 12.6f, 25.0f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.7f, 0.7f, 0.7f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.0f;
hitInfo.material.roughness = 0.0f;
hitInfo.material.specularColor = vec3(0.0f, 0.0f, 0.0f);
}
}

// floor
{
vec3 A = vec3(-12.6f, -12.45f, 25.0f) + sceneTranslation;
vec3 B = vec3( 12.6f, -12.45f, 25.0f) + sceneTranslation;
vec3 C = vec3( 12.6f, -12.45f, 15.0f) + sceneTranslation;
vec3 D = vec3(-12.6f, -12.45f, 15.0f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.7f, 0.7f, 0.7f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.0f;
hitInfo.material.roughness = 0.0f;
hitInfo.material.specularColor = vec3(0.0f, 0.0f, 0.0f);
}
}

// cieling
{
vec3 A = vec3(-12.6f, 12.5f, 25.0f) + sceneTranslation;
vec3 B = vec3( 12.6f, 12.5f, 25.0f) + sceneTranslation;
vec3 C = vec3( 12.6f, 12.5f, 15.0f) + sceneTranslation;
vec3 D = vec3(-12.6f, 12.5f, 15.0f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.7f, 0.7f, 0.7f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.0f;
hitInfo.material.roughness = 0.0f;
hitInfo.material.specularColor = vec3(0.0f, 0.0f, 0.0f);
}
}

// left wall
{
vec3 A = vec3(-12.5f, -12.6f, 25.0f) + sceneTranslation;
vec3 B = vec3(-12.5f, -12.6f, 15.0f) + sceneTranslation;
vec3 C = vec3(-12.5f, 12.6f, 15.0f) + sceneTranslation;
vec3 D = vec3(-12.5f, 12.6f, 25.0f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.7f, 0.1f, 0.1f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.0f;
hitInfo.material.roughness = 0.0f;
hitInfo.material.specularColor = vec3(0.0f, 0.0f, 0.0f);
}
}

// right wall
{
vec3 A = vec3( 12.5f, -12.6f, 25.0f) + sceneTranslation;
vec3 B = vec3( 12.5f, -12.6f, 15.0f) + sceneTranslation;
vec3 C = vec3( 12.5f, 12.6f, 15.0f) + sceneTranslation;
vec3 D = vec3( 12.5f, 12.6f, 25.0f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.1f, 0.7f, 0.1f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.0f;
hitInfo.material.roughness = 0.0f;
hitInfo.material.specularColor = vec3(0.0f, 0.0f, 0.0f);
}
}

// light
{
vec3 A = vec3(-5.0f, 12.4f, 22.5f) + sceneTranslation;
vec3 B = vec3( 5.0f, 12.4f, 22.5f) + sceneTranslation;
vec3 C = vec3( 5.0f, 12.4f, 17.5f) + sceneTranslation;
vec3 D = vec3(-5.0f, 12.4f, 17.5f) + sceneTranslation;
if (TestQuadTrace(rayPos, rayDir, hitInfo, A, B, C, D))
{
hitInfo.material.albedo = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.emissive = vec3(1.0f, 0.9f, 0.7f) * 20.0f;
hitInfo.material.percentSpecular = 0.0f;
hitInfo.material.roughness = 0.0f;
hitInfo.material.specularColor = vec3(0.0f, 0.0f, 0.0f);
}
}

if (TestSphereTrace(rayPos, rayDir, hitInfo, vec4(-9.0f, -9.3f, 20.0f, 3.0f)+sceneTranslation4))
{
hitInfo.material.albedo = vec3(0.9f, 0.9f, 0.5f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.1f;
hitInfo.material.roughness = 0.2f;
hitInfo.material.specularColor = vec3(0.9f, 0.9f, 0.9f);
}

if (TestSphereTrace(rayPos, rayDir, hitInfo, vec4(0.0f, -9.3f, 20.0f, 3.0f)+sceneTranslation4))
{
hitInfo.material.albedo = vec3(0.9f, 0.5f, 0.9f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.3f;
hitInfo.material.roughness = 0.2;
hitInfo.material.specularColor = vec3(0.9f, 0.9f, 0.9f);
}

// a ball which has blue diffuse but red specular. an example of a "bad material".
// a better lighting model wouldn't let you do this sort of thing
if (TestSphereTrace(rayPos, rayDir, hitInfo, vec4(9.0f, -9.3f, 20.0f, 3.0f)+sceneTranslation4))
{
hitInfo.material.albedo = vec3(0.0f, 0.0f, 1.0f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 0.5f;
hitInfo.material.roughness = 0.4f;
hitInfo.material.specularColor = vec3(1.0f, 0.0f, 0.0f);
}

// shiny green balls of varying roughnesses
{
if (TestSphereTrace(rayPos, rayDir, hitInfo, vec4(-10.0f, 0.0f, 23.0f, 1.75f)+sceneTranslation4))
{
hitInfo.material.albedo = vec3(1.0f, 1.0f, 1.0f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 1.0f;
hitInfo.material.roughness = 0.0f;
hitInfo.material.specularColor = vec3(0.3f, 1.0f, 0.3f);
}

if (TestSphereTrace(rayPos, rayDir, hitInfo, vec4(-5.0f, 0.0f, 23.0f, 1.75f)+sceneTranslation4))
{
hitInfo.material.albedo = vec3(1.0f, 1.0f, 1.0f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 1.0f;
hitInfo.material.roughness = 0.25f;
hitInfo.material.specularColor = vec3(0.3f, 1.0f, 0.3f);
}

if (TestSphereTrace(rayPos, rayDir, hitInfo, vec4(0.0f, 0.0f, 23.0f, 1.75f)+sceneTranslation4))
{
hitInfo.material.albedo = vec3(1.0f, 1.0f, 1.0f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 1.0f;
hitInfo.material.roughness = 0.5f;
hitInfo.material.specularColor = vec3(0.3f, 1.0f, 0.3f);
}

if (TestSphereTrace(rayPos, rayDir, hitInfo, vec4(5.0f, 0.0f, 23.0f, 1.75f)+sceneTranslation4))
{
hitInfo.material.albedo = vec3(1.0f, 1.0f, 1.0f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 1.0f;
hitInfo.material.roughness = 0.75f;
hitInfo.material.specularColor = vec3(0.3f, 1.0f, 0.3f);
}

if (TestSphereTrace(rayPos, rayDir, hitInfo, vec4(10.0f, 0.0f, 23.0f, 1.75f)+sceneTranslation4))
{
hitInfo.material.albedo = vec3(1.0f, 1.0f, 1.0f);
hitInfo.material.emissive = vec3(0.0f, 0.0f, 0.0f);
hitInfo.material.percentSpecular = 1.0f;
hitInfo.material.roughness = 1.0f;
hitInfo.material.specularColor = vec3(0.3f, 1.0f, 0.3f);
}
}
}

看一下效果:

Result

真不错

写累了,来首歌放松一下:

Comments