1
0

shadertoy.html 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. Title: Three.js and Shadertoy
  2. Description: How to use Shadertoy shaders in THREE.js
  3. TOC: Using Shadertoy shaders
  4. [Shadertoy](https://shadertoy.com) is a famous website hosting amazing shader
  5. experiments. People often ask how they can use those shaders with Three.js.
  6. It's important to recognize it's called Shader**TOY** for a reason. In general
  7. shadertoy shaders are not about best practices. Rather they are a fun challenge
  8. similar to say [dwitter](https://dwitter.net) (write code in 140 characters) or
  9. [js13kGames](https://js13kgames.com) (make a game in 13k or less).
  10. In the case of Shadertoy the puzzle is, *write a function that for a given pixel
  11. location outputs a color that draws something interesting*. It's a fun challenge
  12. and many of the result are amazing. But, it is not best practice.
  13. Compare [this amazing shadertoy shader that draws an entire city](https://www.shadertoy.com/view/XtsSWs)
  14. <div class="threejs_center"><img src="resources/images/shadertoy-skyline.png"></div>
  15. Fullscreen on my GPU it runs at about 5 frames a second. Contrast that to
  16. [a game like Cities: Skylines](https://store.steampowered.com/app/255710/Cities_Skylines/)
  17. <div class="threejs_center"><img src="resources/images/cities-skylines.jpg" style="width: 600px;"></div>
  18. This game runs 30-60 frames a second on the same machine because it uses more
  19. traditional techniques, drawing buildings made from triangles with textures on
  20. them, etc...
  21. Still, let's go over using a Shadertoy shader with three.js.
  22. This is the default shadertoy shader if you [pick "New" on shadertoy.com](https://www.shadertoy.com/new), at least as of January 2019.
  23. ```glsl
  24. // By iq: https://www.shadertoy.com/user/iq
  25. // license: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
  26. void mainImage( out vec4 fragColor, in vec2 fragCoord )
  27. {
  28. // Normalized pixel coordinates (from 0 to 1)
  29. vec2 uv = fragCoord/iResolution.xy;
  30. // Time varying pixel color
  31. vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
  32. // Output to screen
  33. fragColor = vec4(col,1.0);
  34. }
  35. ```
  36. One thing important to understand about shaders is they are written in a
  37. language called GLSL (Graphics Library Shading Language) designed for 3D math
  38. which includes special types. Above we see `vec4`, `vec2`, `vec3` as 3 such
  39. special types. A `vec2` has 2 values, a `vec3` 3, a `vec4` 4 values. They can be
  40. addressed in a bunch of ways. The most common ways are with `x`, `y`, `z`, and
  41. `w` as in
  42. ```glsl
  43. vec4 v1 = vec4(1.0, 2.0, 3.0, 4.0);
  44. float v2 = v1.x + v1.y; // adds 1.0 + 2.0
  45. ```
  46. Unlike JavaScript, GLSL is more like C/C++ where variables have to have their
  47. type declared so instead of `var v = 1.2;` it's `float v = 1.2;` declaring `v`
  48. to be a floating point number.
  49. Explaining GLSL in detail is more than we can do in this article. For a quick
  50. overview see [this article](https://webglfundamentals.org/webgl/lessons/webgl-shaders-and-glsl.html)
  51. and maybe follow that up with [this series](https://thebookofshaders.com/).
  52. It should be noted that, at least as of January 2019,
  53. [shadertoy.com](https://shadertoy.com) only concerns itself with *fragment
  54. shaders*. A fragment shader's responsibility is, given a pixel location output
  55. a color for that pixel.
  56. Looking at the function above we can see the shader has an `out` parameter
  57. called `fragColor`. `out` stands for `output`. It's a parameter the function is
  58. expected to provide a value for. We need to set this to some color.
  59. It also has an `in` (for input) parameter called `fragCoord`. This is the pixel
  60. coordinate that is about to be drawn. We can use that coordinate to decide on a
  61. color. If the canvas we're drawing to is 400x300 pixels then the function will
  62. be called 400x300 times or 120,000 times. Each time `fragCoord` will be a
  63. different pixel coordinate.
  64. There are 2 more variables being used that are not defined in the code. One is
  65. `iResolution`. This is set to the resolution of the canvas. If the canvas is
  66. 400x300 then `iResolution` would be 400,300 so as the pixel coordinates change
  67. that makes `uv` go from 0.0 to 1.0 across and up the texture. Working with
  68. *normalized* values often makes things easier and so the majority of shadertoy
  69. shaders start with something like this.
  70. The other undefined variable in the shader is `iTime`. This is the time since
  71. the page loaded in seconds.
  72. In shader jargon these global variables are called *uniform* variables. They are
  73. called *uniform* because they don't change, they stay uniform from one iteration
  74. of the shader to the next. It's important to note all of them are specific to
  75. shadertoy. They not *official* GLSL variables. They are variables the makers of
  76. shadertoy made up.
  77. The [Shadertoy docs define several more](https://www.shadertoy.com/howto). For
  78. now let's write something that handles the two being used in the shader above.
  79. The first thing to do is let's make a single plane that fills the canvas. If you
  80. haven't read it yet we did this in [the article on backgrounds](threejs-backgrounds.html)
  81. so let's grab that example but remove the cubes. It's pretty short so here's the
  82. entire thing
  83. ```js
  84. function main() {
  85. const canvas = document.querySelector('#c');
  86. const renderer = new THREE.WebGLRenderer({canvas});
  87. renderer.autoClearColor = false;
  88. const camera = new THREE.OrthographicCamera(
  89. -1, // left
  90. 1, // right
  91. 1, // top
  92. -1, // bottom
  93. -1, // near,
  94. 1, // far
  95. );
  96. const scene = new THREE.Scene();
  97. const plane = new THREE.PlaneGeometry(2, 2);
  98. const material = new THREE.MeshBasicMaterial({
  99. color: 'red',
  100. });
  101. scene.add(new THREE.Mesh(plane, material));
  102. function resizeRendererToDisplaySize(renderer) {
  103. const canvas = renderer.domElement;
  104. const width = canvas.clientWidth;
  105. const height = canvas.clientHeight;
  106. const needResize = canvas.width !== width || canvas.height !== height;
  107. if (needResize) {
  108. renderer.setSize(width, height, false);
  109. }
  110. return needResize;
  111. }
  112. function render() {
  113. resizeRendererToDisplaySize(renderer);
  114. renderer.render(scene, camera);
  115. requestAnimationFrame(render);
  116. }
  117. requestAnimationFrame(render);
  118. }
  119. main();
  120. ```
  121. As [explained in the backgrounds article](threejs-backgrounds.html) an
  122. `OrthographicCamera` with these parameters and a 2 unit plane will fill the
  123. canvas. For now all we'll get is a red canvas as our plane is using a red
  124. `MeshBasicMaterial`.
  125. {{{example url="../threejs-shadertoy-prep.html" }}}
  126. Now that we have something working let's add the shadertoy shader.
  127. ```js
  128. const fragmentShader = `
  129. #include <common>
  130. uniform vec3 iResolution;
  131. uniform float iTime;
  132. // By iq: https://www.shadertoy.com/user/iq
  133. // license: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
  134. void mainImage( out vec4 fragColor, in vec2 fragCoord )
  135. {
  136. // Normalized pixel coordinates (from 0 to 1)
  137. vec2 uv = fragCoord/iResolution.xy;
  138. // Time varying pixel color
  139. vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
  140. // Output to screen
  141. fragColor = vec4(col,1.0);
  142. }
  143. void main() {
  144. mainImage(gl_FragColor, gl_FragCoord.xy);
  145. }
  146. `;
  147. ```
  148. Above we declared the 2 uniform variables we talked about. Then we inserted the
  149. shader GLSL code from shadertoy. Finally we called `mainImage` passing it
  150. `gl_FragColor` and `gl_FragCoord.xy`. `gl_FragColor` is an official WebGL
  151. global variable the shader is responsible for setting to whatever color it wants
  152. the current pixel to be. `gl_FragCoord` is another official WebGL global
  153. variable that tells us the coordinate of the pixel we're currently choosing a
  154. color for.
  155. We then need to setup three.js uniforms so we can supply values to the shader.
  156. ```js
  157. const uniforms = {
  158. iTime: { value: 0 },
  159. iResolution: { value: new THREE.Vector3() },
  160. };
  161. ```
  162. Each uniform in THREE.js has `value` parameter. That value has to match the type
  163. of the uniform.
  164. Then we pass both the fragment shader and uniforms to a `ShaderMaterial`.
  165. ```js
  166. -const material = new THREE.MeshBasicMaterial({
  167. - color: 'red',
  168. -});
  169. +const material = new THREE.ShaderMaterial({
  170. + fragmentShader,
  171. + uniforms,
  172. +});
  173. ```
  174. and before rendering we need to set the values of the uniforms
  175. ```js
  176. -function render() {
  177. +function render(time) {
  178. + time *= 0.001; // convert to seconds
  179. resizeRendererToDisplaySize(renderer);
  180. + const canvas = renderer.domElement;
  181. + uniforms.iResolution.value.set(canvas.width, canvas.height, 1);
  182. + uniforms.iTime.value = time;
  183. renderer.render(scene, camera);
  184. requestAnimationFrame(render);
  185. }
  186. ```
  187. > Note: I have no idea why `iResolution` is a `vec3` and what's in the 3rd value
  188. > [is not documented on shadertoy.com](https://www.shadertoy.com/howto). It's
  189. > not used above so just setting it to 1 for now. ¯\\\_(ツ)\_/¯
  190. {{{example url="../threejs-shadertoy-basic.html" }}}
  191. This [matches what we see on Shadertoy for a new shader](https://www.shadertoy.com/new),
  192. at least as of January 2019 😉. What's the shader above doing?
  193. * `uv` goes from 0 to 1.
  194. * `cos(uv.xyx)` gives us 3 cosine values as a `vec3`. One for `uv.x`, another for `uv.y` and another for `uv.x` again.
  195. * Adding in the time, `cos(iTime+uv.xyx)` makes them animate.
  196. * Adding in `vec3(0,2,4)` as in `cos(iTime+uv.xyx+vec3(0,2,4))` offsets the cosine waves
  197. * `cos` goes from -1 to 1 so the `0.5 * 0.5 + cos(...)` converts from -1 <-> 1 to 0.0 <-> 1.0
  198. * the results are then used as the RGB color for the current pixel
  199. A minor change will make it easier to see the cosine waves. Right now `uv` only
  200. goes from 0 to 1. A cosine repeats at 2π so let's make it go from 0 to 40 by
  201. multiplying by 40.0. That should make it repeat about 6.3 times.
  202. ```glsl
  203. -vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
  204. +vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx*40.0+vec3(0,2,4));
  205. ```
  206. Counting below I see about 6.3 repeats. We can see the blue between the red
  207. since it's offset by 4 via the `+vec3(0,2,4)`. Without that the blue and red
  208. would overlap perfectly making purple.
  209. {{{example url="../threejs-shadertoy-basic-x40.html" }}}
  210. Knowing how simple the inputs are and then seeing results like
  211. [a city canal](https://www.shadertoy.com/view/MdXGW2),
  212. [a forest](https://www.shadertoy.com/view/4ttSWf),
  213. [a snail](https://www.shadertoy.com/view/ld3Gz2),
  214. [a mushroom](https://www.shadertoy.com/view/4tBXR1)
  215. make the challenge all that much more impressive. Hopefully they also make it
  216. clear why it's not generally the right approach vs the more traditional ways of
  217. making scenes from triangles. The fact that so much math has to be put into
  218. computing the color of every pixel means those examples run very slow.
  219. Some shadertoy shaders take textures as inputs like
  220. [this one](https://www.shadertoy.com/view/MsXSzM).
  221. ```glsl
  222. // By Daedelus: https://www.shadertoy.com/user/Daedelus
  223. // license: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
  224. #define TIMESCALE 0.25
  225. #define TILES 8
  226. #define COLOR 0.7, 1.6, 2.8
  227. void mainImage( out vec4 fragColor, in vec2 fragCoord )
  228. {
  229. vec2 uv = fragCoord.xy / iResolution.xy;
  230. uv.x *= iResolution.x / iResolution.y;
  231. vec4 noise = texture2D(iChannel0, floor(uv * float(TILES)) / float(TILES));
  232. float p = 1.0 - mod(noise.r + noise.g + noise.b + iTime * float(TIMESCALE), 1.0);
  233. p = min(max(p * 3.0 - 1.8, 0.1), 2.0);
  234. vec2 r = mod(uv * float(TILES), 1.0);
  235. r = vec2(pow(r.x - 0.5, 2.0), pow(r.y - 0.5, 2.0));
  236. p *= 1.0 - pow(min(1.0, 12.0 * dot(r, r)), 2.0);
  237. fragColor = vec4(COLOR, 1.0) * p;
  238. }
  239. ```
  240. Passing a texture into a shader is similar to
  241. [passing one into a normal material](threejs-textures.html) but we need to set
  242. up the texture on the uniforms.
  243. First we'll add the uniform for the texture to the shader. They're referred to
  244. as `sampler2D` in GLSL.
  245. ```js
  246. const fragmentShader = `
  247. #include <common>
  248. uniform vec3 iResolution;
  249. uniform float iTime;
  250. +uniform sampler2D iChannel0;
  251. ...
  252. ```
  253. Then we can load a texture like we covered [here](threejs-textures.html) and assign the uniform's value.
  254. ```js
  255. +const loader = new THREE.TextureLoader();
  256. +const texture = loader.load('resources/images/bayer.png');
  257. +texture.minFilter = THREE.NearestFilter;
  258. +texture.magFilter = THREE.NearestFilter;
  259. +texture.wrapS = THREE.RepeatWrapping;
  260. +texture.wrapT = THREE.RepeatWrapping;
  261. const uniforms = {
  262. iTime: { value: 0 },
  263. iResolution: { value: new THREE.Vector3() },
  264. + iChannel0: { value: texture },
  265. };
  266. ```
  267. {{{example url="../threejs-shadertoy-bleepy-blocks.html" }}}
  268. So far we've been using Shadertoy shaders as they are used on
  269. [Shadertoy.com](https://shadertoy.com), namely drawing to cover the canvas.
  270. There's no reason we need to limit it to just that use case though. The
  271. important part to remember is the functions people write on shadertoy generally
  272. just take a `fragCoord` input and a `iResolution`. `fragCoord` does not have to
  273. come from pixel coordinates, we could use something else like texture
  274. coordinates instead and could then use them kind of like other textures. This
  275. technique of using a function to generate textures is often called a
  276. [*procedural texture*](https://www.google.com/search?q=procedural+texture).
  277. Let's change the shader above to do this. The simplest thing to do might be to
  278. take the texture coordinates that three.js normally supplies, multiply them by
  279. `iResolution` and pass that in for `fragCoords`.
  280. To do that we add in a *varying*. A varying is a value passed from the vertex
  281. shader to the fragment shader that gets interpolated (or varied) between
  282. vertices. To use it in our fragment shader we declare it. Three.js refers to its
  283. texture coordinates as `uv` with the `v` in front meaning *varying*.
  284. ```glsl
  285. ...
  286. +varying vec2 vUv;
  287. void main() {
  288. - mainImage(gl_FragColor, gl_FragCoord.xy);
  289. + mainImage(gl_FragColor, vUv * iResolution.xy);
  290. }
  291. ```
  292. Then we need to also provide our own vertex shader. Here is a fairly common
  293. minimal three.js vertex shader. Three.js declares and will provide values for
  294. `uv`, `projectionMatrix`, `modelViewMatrix`, and `position`.
  295. ```js
  296. const vertexShader = `
  297. varying vec2 vUv;
  298. void main() {
  299. vUv = uv;
  300. gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  301. }
  302. `;
  303. ```
  304. We need to pass the vertex shader to the `ShaderMaterial`
  305. ```js
  306. const material = new THREE.ShaderMaterial({
  307. vertexShader,
  308. fragmentShader,
  309. uniforms,
  310. });
  311. ```
  312. We can set the `iResolution` uniform value at init time since it will no longer change.
  313. ```js
  314. const uniforms = {
  315. iTime: { value: 0 },
  316. - iResolution: { value: new THREE.Vector3() },
  317. + iResolution: { value: new THREE.Vector3(1, 1, 1) },
  318. iChannel0: { value: texture },
  319. };
  320. ```
  321. and we no longer need to set it at render time
  322. ```js
  323. -const canvas = renderer.domElement;
  324. -uniforms.iResolution.value.set(canvas.width, canvas.height, 1);
  325. uniforms.iTime.value = time;
  326. ```
  327. Otherwise I copied back in the original camera and code that sets up 3 rotating
  328. cubes from [the article on responsiveness](threejs-responsive.html). The result:
  329. {{{example url="../threejs-shadertoy-as-texture.html" }}}
  330. I hope this at least gets you started on how to use a shadertoy shader with
  331. three.js. Again, it's important to remember that most shadertoy shaders are an
  332. interesting challenge (draw everything with a single function) rather than the
  333. recommended way to actually display things in a performant way. Still, they are
  334. amazing, impressive, beautiful, and you can learn a ton by seeing how they work.
粤ICP备19079148号