-
-
Notifications
You must be signed in to change notification settings - Fork 35.7k
/
Copy pathshadertoy.html
412 lines (372 loc) · 22.6 KB
/
shadertoy.html
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
<!DOCTYPE html><html lang="en"><head>
<meta charset="utf-8">
<title>Three.js and Shadertoy</title>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@threejs">
<meta name="twitter:title" content="Three.js – and Shadertoy">
<meta property="og:image" content="https://threejs.org/files/share.png">
<link rel="shortcut icon" href="../../files/favicon_white.ico" media="(prefers-color-scheme: dark)">
<link rel="shortcut icon" href="../../files/favicon.ico" media="(prefers-color-scheme: light)">
<link rel="stylesheet" href="../resources/lesson.css">
<link rel="stylesheet" href="../resources/lang.css">
<script type="importmap">
{
"imports": {
"three": "../../build/three.module.js"
}
}
</script>
</head>
<body>
<div class="container">
<div class="lesson-title">
<h1>Three.js and Shadertoy</h1>
</div>
<div class="lesson">
<div class="lesson-main">
<p><a href="https://shadertoy.com">Shadertoy</a> is a famous website hosting amazing shader
experiments. People often ask how they can use those shaders with Three.js.</p>
<p>It's important to recognize it's called Shader<strong>TOY</strong> for a reason. In general
shadertoy shaders are not about best practices. Rather they are a fun challenge
similar to say <a href="https://dwitter.net">dwitter</a> (write code in 140 characters) or
<a href="https://js13kgames.com">js13kGames</a> (make a game in 13k or less).</p>
<p>In the case of Shadertoy the puzzle is, <em>write a function that for a given pixel
location outputs a color that draws something interesting</em>. It's a fun challenge
and many of the result are amazing. But, it is not best practice.</p>
<p>Compare <a href="https://www.shadertoy.com/view/XtsSWs">this amazing shadertoy shader that draws an entire city</a></p>
<div class="threejs_center"><img src="../resources/images/shadertoy-skyline.png"></div>
<p>Fullscreen on my GPU it runs at about 5 frames a second. Contrast that to
<a href="https://store.steampowered.com/app/255710/Cities_Skylines/">a game like Cities: Skylines</a></p>
<div class="threejs_center"><img src="../resources/images/cities-skylines.jpg" style="width: 600px;"></div>
<p>This game runs 30-60 frames a second on the same machine because it uses more
traditional techniques, drawing buildings made from triangles with textures on
them, etc...</p>
<p>Still, let's go over using a Shadertoy shader with three.js.</p>
<p>This is the default shadertoy shader if you <a href="https://www.shadertoy.com/new">pick "New" on shadertoy.com</a>, at least as of January 2019.</p>
<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">// By iq: https://www.shadertoy.com/user/iq
// license: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
// Time varying pixel color
vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
// Output to screen
fragColor = vec4(col,1.0);
}
</pre>
<p>One thing important to understand about shaders is they are written in a
language called GLSL (Graphics Library Shading Language) designed for 3D math
which includes special types. Above we see <code class="notranslate" translate="no">vec4</code>, <code class="notranslate" translate="no">vec2</code>, <code class="notranslate" translate="no">vec3</code> as 3 such
special types. A <code class="notranslate" translate="no">vec2</code> has 2 values, a <code class="notranslate" translate="no">vec3</code> 3, a <code class="notranslate" translate="no">vec4</code> 4 values. They can be
addressed in a bunch of ways. The most common ways are with <code class="notranslate" translate="no">x</code>, <code class="notranslate" translate="no">y</code>, <code class="notranslate" translate="no">z</code>, and
<code class="notranslate" translate="no">w</code> as in</p>
<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">vec4 v1 = vec4(1.0, 2.0, 3.0, 4.0);
float v2 = v1.x + v1.y; // adds 1.0 + 2.0
</pre>
<p>Unlike JavaScript, GLSL is more like C/C++ where variables have to have their
type declared so instead of <code class="notranslate" translate="no">var v = 1.2;</code> it's <code class="notranslate" translate="no">float v = 1.2;</code> declaring <code class="notranslate" translate="no">v</code>
to be a floating point number.</p>
<p>Explaining GLSL in detail is more than we can do in this article. For a quick
overview see <a href="https://webglfundamentals.org/webgl/lessons/webgl-shaders-and-glsl.html">this article</a>
and maybe follow that up with <a href="https://thebookofshaders.com/">this series</a>.</p>
<p>It should be noted that, at least as of January 2019,
<a href="https://shadertoy.com">shadertoy.com</a> only concerns itself with <em>fragment
shaders</em>. A fragment shader's responsibility is, given a pixel location output
a color for that pixel.</p>
<p>Looking at the function above we can see the shader has an <code class="notranslate" translate="no">out</code> parameter
called <code class="notranslate" translate="no">fragColor</code>. <code class="notranslate" translate="no">out</code> stands for <code class="notranslate" translate="no">output</code>. It's a parameter the function is
expected to provide a value for. We need to set this to some color.</p>
<p>It also has an <code class="notranslate" translate="no">in</code> (for input) parameter called <code class="notranslate" translate="no">fragCoord</code>. This is the pixel
coordinate that is about to be drawn. We can use that coordinate to decide on a
color. If the canvas we're drawing to is 400x300 pixels then the function will
be called 400x300 times or 120,000 times. Each time <code class="notranslate" translate="no">fragCoord</code> will be a
different pixel coordinate.</p>
<p>There are 2 more variables being used that are not defined in the code. One is
<code class="notranslate" translate="no">iResolution</code>. This is set to the resolution of the canvas. If the canvas is
400x300 then <code class="notranslate" translate="no">iResolution</code> would be 400,300 so as the pixel coordinates change
that makes <code class="notranslate" translate="no">uv</code> go from 0.0 to 1.0 across and up the texture. Working with
<em>normalized</em> values often makes things easier and so the majority of shadertoy
shaders start with something like this.</p>
<p>The other undefined variable in the shader is <code class="notranslate" translate="no">iTime</code>. This is the time since
the page loaded in seconds.</p>
<p>In shader jargon these global variables are called <em>uniform</em> variables. They are
called <em>uniform</em> because they don't change, they stay uniform from one iteration
of the shader to the next. It's important to note all of them are specific to
shadertoy. They not <em>official</em> GLSL variables. They are variables the makers of
shadertoy made up.</p>
<p>The <a href="https://www.shadertoy.com/howto">Shadertoy docs define several more</a>. For
now let's write something that handles the two being used in the shader above.</p>
<p>The first thing to do is let's make a single plane that fills the canvas. If you
haven't read it yet we did this in <a href="backgrounds.html">the article on backgrounds</a>
so let's grab that example but remove the cubes. It's pretty short so here's the
entire thing</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
renderer.autoClearColor = false;
const camera = new THREE.OrthographicCamera(
-1, // left
1, // right
1, // top
-1, // bottom
-1, // near,
1, // far
);
const scene = new THREE.Scene();
const plane = new THREE.PlaneGeometry(2, 2);
const material = new THREE.MeshBasicMaterial({
color: 'red',
});
scene.add(new THREE.Mesh(plane, material));
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
resizeRendererToDisplaySize(renderer);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</pre>
<p>As <a href="backgrounds.html">explained in the backgrounds article</a> an
<a href="/docs/#api/en/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a> with these parameters and a 2 unit plane will fill the
canvas. For now all we'll get is a red canvas as our plane is using a red
<a href="/docs/#api/en/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a>.</p>
<p></p><div translate="no" class="threejs_example_container notranslate">
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/shadertoy-prep.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/shadertoy-prep.html" target="_blank">click here to open in a separate window</a>
</div>
<p></p>
<p>Now that we have something working let's add the shadertoy shader. </p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fragmentShader = `
#include <common>
uniform vec3 iResolution;
uniform float iTime;
// By iq: https://www.shadertoy.com/user/iq
// license: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
// Time varying pixel color
vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
// Output to screen
fragColor = vec4(col,1.0);
}
void main() {
mainImage(gl_FragColor, gl_FragCoord.xy);
}
`;
</pre>
<p>Above we declared the 2 uniform variables we talked about. Then we inserted the
shader GLSL code from shadertoy. Finally we called <code class="notranslate" translate="no">mainImage</code> passing it
<code class="notranslate" translate="no">gl_FragColor</code> and <code class="notranslate" translate="no">gl_FragCoord.xy</code>. <code class="notranslate" translate="no">gl_FragColor</code> is an official WebGL
global variable the shader is responsible for setting to whatever color it wants
the current pixel to be. <code class="notranslate" translate="no">gl_FragCoord</code> is another official WebGL global
variable that tells us the coordinate of the pixel we're currently choosing a
color for.</p>
<p>We then need to setup three.js uniforms so we can supply values to the shader.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const uniforms = {
iTime: { value: 0 },
iResolution: { value: new THREE.Vector3() },
};
</pre>
<p>Each uniform in THREE.js has <code class="notranslate" translate="no">value</code> parameter. That value has to match the type
of the uniform.</p>
<p>Then we pass both the fragment shader and uniforms to a <a href="/docs/#api/en/materials/ShaderMaterial"><code class="notranslate" translate="no">ShaderMaterial</code></a>.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const material = new THREE.MeshBasicMaterial({
- color: 'red',
-});
+const material = new THREE.ShaderMaterial({
+ fragmentShader,
+ uniforms,
+});
</pre>
<p>and before rendering we need to set the values of the uniforms</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-function render() {
+function render(time) {
+ time *= 0.001; // convert to seconds
resizeRendererToDisplaySize(renderer);
+ const canvas = renderer.domElement;
+ uniforms.iResolution.value.set(canvas.width, canvas.height, 1);
+ uniforms.iTime.value = time;
renderer.render(scene, camera);
requestAnimationFrame(render);
}
</pre>
<blockquote>
<p>Note: I have no idea why <code class="notranslate" translate="no">iResolution</code> is a <code class="notranslate" translate="no">vec3</code> and what's in the 3rd value
<a href="https://www.shadertoy.com/howto">is not documented on shadertoy.com</a>. It's
not used above so just setting it to 1 for now. ¯\_(ツ)_/¯</p>
</blockquote>
<p></p><div translate="no" class="threejs_example_container notranslate">
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/shadertoy-basic.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/shadertoy-basic.html" target="_blank">click here to open in a separate window</a>
</div>
<p></p>
<p>This <a href="https://www.shadertoy.com/new">matches what we see on Shadertoy for a new shader</a>,
at least as of January 2019 😉. What's the shader above doing? </p>
<ul>
<li><code class="notranslate" translate="no">uv</code> goes from 0 to 1. </li>
<li><code class="notranslate" translate="no">cos(uv.xyx)</code> gives us 3 cosine values as a <code class="notranslate" translate="no">vec3</code>. One for <code class="notranslate" translate="no">uv.x</code>, another for <code class="notranslate" translate="no">uv.y</code> and another for <code class="notranslate" translate="no">uv.x</code> again.</li>
<li>Adding in the time, <code class="notranslate" translate="no">cos(iTime+uv.xyx)</code> makes them animate.</li>
<li>Adding in <code class="notranslate" translate="no">vec3(0,2,4)</code> as in <code class="notranslate" translate="no">cos(iTime+uv.xyx+vec3(0,2,4))</code> offsets the cosine waves</li>
<li><code class="notranslate" translate="no">cos</code> goes from -1 to 1 so the <code class="notranslate" translate="no">0.5 * 0.5 + cos(...)</code> converts from -1 <-> 1 to 0.0 <-> 1.0</li>
<li>the results are then used as the RGB color for the current pixel</li>
</ul>
<p>A minor change will make it easier to see the cosine waves. Right now <code class="notranslate" translate="no">uv</code> only
goes from 0 to 1. A cosine repeats at 2π so let's make it go from 0 to 40 by
multiplying by 40.0. That should make it repeat about 6.3 times.</p>
<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">-vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
+vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx*40.0+vec3(0,2,4));
</pre>
<p>Counting below I see about 6.3 repeats. We can see the blue between the red
since it's offset by 4 via the <code class="notranslate" translate="no">+vec3(0,2,4)</code>. Without that the blue and red
would overlap perfectly making purple.</p>
<p></p><div translate="no" class="threejs_example_container notranslate">
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/shadertoy-basic-x40.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/shadertoy-basic-x40.html" target="_blank">click here to open in a separate window</a>
</div>
<p></p>
<p>Knowing how simple the inputs are and then seeing results like
<a href="https://www.shadertoy.com/view/MdXGW2">a city canal</a>,
<a href="https://www.shadertoy.com/view/4ttSWf">a forest</a>,
<a href="https://www.shadertoy.com/view/ld3Gz2">a snail</a>,
<a href="https://www.shadertoy.com/view/4tBXR1">a mushroom</a>
make the challenge all that much more impressive. Hopefully they also make it
clear why it's not generally the right approach vs the more traditional ways of
making scenes from triangles. The fact that so much math has to be put into
computing the color of every pixel means those examples run very slow.</p>
<p>Some shadertoy shaders take textures as inputs like
<a href="https://www.shadertoy.com/view/MsXSzM">this one</a>. </p>
<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">// By Daedelus: https://www.shadertoy.com/user/Daedelus
// license: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
#define TIMESCALE 0.25
#define TILES 8
#define COLOR 0.7, 1.6, 2.8
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord.xy / iResolution.xy;
uv.x *= iResolution.x / iResolution.y;
vec4 noise = texture2D(iChannel0, floor(uv * float(TILES)) / float(TILES));
float p = 1.0 - mod(noise.r + noise.g + noise.b + iTime * float(TIMESCALE), 1.0);
p = min(max(p * 3.0 - 1.8, 0.1), 2.0);
vec2 r = mod(uv * float(TILES), 1.0);
r = vec2(pow(r.x - 0.5, 2.0), pow(r.y - 0.5, 2.0));
p *= 1.0 - pow(min(1.0, 12.0 * dot(r, r)), 2.0);
fragColor = vec4(COLOR, 1.0) * p;
}
</pre>
<p>Passing a texture into a shader is similar to
<a href="textures.html">passing one into a normal material</a> but we need to set
up the texture on the uniforms.</p>
<p>First we'll add the uniform for the texture to the shader. They're referred to
as <code class="notranslate" translate="no">sampler2D</code> in GLSL.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fragmentShader = `
#include <common>
uniform vec3 iResolution;
uniform float iTime;
+uniform sampler2D iChannel0;
...
</pre>
<p>Then we can load a texture like we covered <a href="textures.html">here</a> and assign the uniform's value.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const loader = new THREE.TextureLoader();
+const texture = loader.load('resources/images/bayer.png');
+texture.minFilter = THREE.NearestFilter;
+texture.magFilter = THREE.NearestFilter;
+texture.wrapS = THREE.RepeatWrapping;
+texture.wrapT = THREE.RepeatWrapping;
const uniforms = {
iTime: { value: 0 },
iResolution: { value: new THREE.Vector3() },
+ iChannel0: { value: texture },
};
</pre>
<p></p><div translate="no" class="threejs_example_container notranslate">
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/shadertoy-bleepy-blocks.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/shadertoy-bleepy-blocks.html" target="_blank">click here to open in a separate window</a>
</div>
<p></p>
<p>So far we've been using Shadertoy shaders as they are used on
<a href="https://shadertoy.com">Shadertoy.com</a>, namely drawing to cover the canvas.
There's no reason we need to limit it to just that use case though. The
important part to remember is the functions people write on shadertoy generally
just take a <code class="notranslate" translate="no">fragCoord</code> input and a <code class="notranslate" translate="no">iResolution</code>. <code class="notranslate" translate="no">fragCoord</code> does not have to
come from pixel coordinates, we could use something else like texture
coordinates instead and could then use them kind of like other textures. This
technique of using a function to generate textures is often called a
<a href="https://www.google.com/search?q=procedural+texture"><em>procedural texture</em></a>.</p>
<p>Let's change the shader above to do this. The simplest thing to do might be to
take the texture coordinates that three.js normally supplies, multiply them by
<code class="notranslate" translate="no">iResolution</code> and pass that in for <code class="notranslate" translate="no">fragCoords</code>. </p>
<p>To do that we add in a <em>varying</em>. A varying is a value passed from the vertex
shader to the fragment shader that gets interpolated (or varied) between
vertices. To use it in our fragment shader we declare it. Three.js refers to its
texture coordinates as <code class="notranslate" translate="no">uv</code> with the <code class="notranslate" translate="no">v</code> in front meaning <em>varying</em>.</p>
<pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">...
+varying vec2 vUv;
void main() {
- mainImage(gl_FragColor, gl_FragCoord.xy);
+ mainImage(gl_FragColor, vUv * iResolution.xy);
}
</pre>
<p>Then we need to also provide our own vertex shader. Here is a fairly common
minimal three.js vertex shader. Three.js declares and will provide values for
<code class="notranslate" translate="no">uv</code>, <code class="notranslate" translate="no">projectionMatrix</code>, <code class="notranslate" translate="no">modelViewMatrix</code>, and <code class="notranslate" translate="no">position</code>.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`;
</pre>
<p>We need to pass the vertex shader to the <a href="/docs/#api/en/materials/ShaderMaterial"><code class="notranslate" translate="no">ShaderMaterial</code></a></p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms,
});
</pre>
<p>We can set the <code class="notranslate" translate="no">iResolution</code> uniform value at init time since it will no longer change.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const uniforms = {
iTime: { value: 0 },
- iResolution: { value: new THREE.Vector3() },
+ iResolution: { value: new THREE.Vector3(1, 1, 1) },
iChannel0: { value: texture },
};
</pre>
<p>and we no longer need to set it at render time</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const canvas = renderer.domElement;
-uniforms.iResolution.value.set(canvas.width, canvas.height, 1);
uniforms.iTime.value = time;
</pre>
<p>Otherwise I copied back in the original camera and code that sets up 3 rotating
cubes from <a href="responsive.html">the article on responsiveness</a>. The result:</p>
<p></p><div translate="no" class="threejs_example_container notranslate">
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/shadertoy-as-texture.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/shadertoy-as-texture.html" target="_blank">click here to open in a separate window</a>
</div>
<p></p>
<p>I hope this at least gets you started on how to use a shadertoy shader with
three.js. Again, it's important to remember that most shadertoy shaders are an
interesting challenge (draw everything with a single function) rather than the
recommended way to actually display things in a performant way. Still, they are
amazing, impressive, beautiful, and you can learn a ton by seeing how they work.</p>
</div>
</div>
</div>
<script src="../resources/prettify.js"></script>
<script src="../resources/lesson.js"></script>
</body></html>