123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- <!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>
|