shadertoy.html 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. <!DOCTYPE html><html lang="ko"><head>
  2. <meta charset="utf-8">
  3. <title>쉐이더토이(Shadertoy) 활용하기</title>
  4. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  5. <meta name="twitter:card" content="summary_large_image">
  6. <meta name="twitter:site" content="@threejs">
  7. <meta name="twitter:title" content="Three.js – 쉐이더토이(Shadertoy) 활용하기">
  8. <meta property="og:image" content="https://threejs.org/files/share.png">
  9. <link rel="shortcut icon" href="../../files/favicon_white.ico" media="(prefers-color-scheme: dark)">
  10. <link rel="shortcut icon" href="../../files/favicon.ico" media="(prefers-color-scheme: light)">
  11. <link rel="stylesheet" href="../resources/lesson.css">
  12. <link rel="stylesheet" href="../resources/lang.css">
  13. <script type="importmap">
  14. {
  15. "imports": {
  16. "three": "../../build/three.module.js"
  17. }
  18. }
  19. </script>
  20. <link rel="stylesheet" href="/manual/ko/lang.css">
  21. </head>
  22. <body>
  23. <div class="container">
  24. <div class="lesson-title">
  25. <h1>쉐이더토이(Shadertoy) 활용하기</h1>
  26. </div>
  27. <div class="lesson">
  28. <div class="lesson-main">
  29. <p><a href="https://shadertoy.com">쉐이더토이(Shadertoy)</a>는 다양한 쉐이더를 제공하는 유명한 사이트입니다. 시리즈를 진행하다보니 쉐이더토이에서 받은 쉐이더를 Three.js에 적용하는 법을 물어보시는 분들이 꽤 있더군요.</p>
  30. <p>하지만 쉐이더<strong>토이</strong>라고 불리는 데는 이유가 있습니다. 쉐이더토이에 올라온 쉐이더는 정석대로 만들어진 쉐이더가 아닙니다. <a href="https://dwitter.net">드위터(dwitter)</a>(140자 내로 코드를 작성하는 사이트)나 <a href="https://js13kgames.com">js13kGames</a>(13kb 이하의 게임을 만드는 사이트)처럼 여러 사람이 쉐이더-챌린지를 진행하는 곳이죠.</p>
  31. <p>쉐이더토이의 미션은 <em>주어진 픽셀 위치값으로 무언가 재밌는 것을 렌더링하는 함수를 만드는 것</em>입니다. 재밌는 미션이고 많은 결과물을 보면 대단하다는 소리가 절로 나옵니다. 하지만 초보자가 보고 배우기에 좋은 예제들은 아니죠.</p>
  32. <p>아래의 <a href="https://www.shadertoy.com/view/XtsSWs">쉐이더로 도시 전체를 렌더링한 쉐이더토이 예제</a>를 한 번 봅시다.</p>
  33. <div class="threejs_center"><img src="../resources/images/shadertoy-skyline.png"></div>
  34. <p>제 컴퓨터에서 FHD 해상도를 기준으로 약 5 프레임 내외가 나옵니다. 이를 <a href="https://store.steampowered.com/app/255710/Cities_Skylines/">시티즈: 스카이라인(Cities: Skylines)</a> 같은 게임과 비교해보면</p>
  35. <div class="threejs_center"><img src="../resources/images/cities-skylines.jpg" style="width: 600px;"></div>
  36. <p>같은 컴퓨터에서 30-60 프레임이 나옵니다. 이 게임이 텍스처를 입힌 삼각형을 렌더링하는 등 좀 더 일반적인 기법을 사용했기 때문이죠.</p>
  37. <p>뭐 그렇다고 해도 쉐이더토이의 쉐이더를 Three.js에 한 번 불러와보는 건 나쁘지 않을 겁니다.</p>
  38. <p>아래는 쉐이더토이에서 <a href="https://www.shadertoy.com/new">"New"를 클릭했을 때</a> 나오는 기본 쉐이더입니다(2019년 1월 기준).</p>
  39. <pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">// By iq: https://www.shadertoy.com/user/iq
  40. // license: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
  41. void mainImage( out vec4 fragColor, in vec2 fragCoord )
  42. {
  43. // Normalized pixel coordinates (from 0 to 1)
  44. vec2 uv = fragCoord/iResolution.xy;
  45. // Time varying pixel color
  46. vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
  47. // Output to screen
  48. fragColor = vec4(col,1.0);
  49. }
  50. </pre>
  51. <p>여기서 중요한 건 쉐이더는 GLSL(Graphic Library Shading Language)로 작성한다는 겁니다. GLSL은 3D 수학을 위해 C 언어 기반으로 고안된 상위 언어로, GLSL만의 고유한 타입이 있습니다. 위 코드의 <code class="notranslate" translate="no">vec4</code>, <code class="notranslate" translate="no">vec2</code>, <code class="notranslate" translate="no">vec3</code>이 그런 고유 타입들이죠. <code class="notranslate" translate="no">vec2</code>는 값이 2개, <code class="notranslate" translate="no">vec3</code>는 3개, <code class="notranslate" translate="no">vec4</code>는 4개인데, 이들은 각각 다양한 값으로 사용되지만 <code class="notranslate" translate="no">x</code>, <code class="notranslate" translate="no">y</code>, <code class="notranslate" translate="no">z</code> 그리고 <code class="notranslate" translate="no">w</code>로 사용하는 게 보통입니다.</p>
  52. <pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">vec4 v1 = vec4(1.0, 2.0, 3.0, 4.0);
  53. float v2 = v1.x + v1.y; // adds 1.0 + 2.0
  54. </pre>
  55. <p>GLSL은 자바스크립트와 달리 C나 C++처럼 변수를 선언할 때 해당 타입을 사용해야 합니다. 예를 들어 실수(float)를 변수에 담을 때 자바스크립트는 <code class="notranslate" translate="no">var v = 1.2;</code>와 같이 쓰지만, GLSL에서는 <code class="notranslate" translate="no">float v = 1.2;</code>와 같이 씁니다.</p>
  56. <p>이 글에서 GLSL을 자세히 설명하는 건 주제에 벗어나니 <a href="https://webglfundamentals.org/webgl/lessons/ko/webgl-shaders-and-glsl.html">이 글</a>을 읽어보거나 <a href="https://thebookofshaders.com/?lan=kr">이 시리즈</a>를 정주행해보기 바랍니다.</p>
  57. <p>또한 2019년 1월 기준으로 <a href="https://shadertoy.com">쉐이더토이</a>는 <em>fragment shaders</em>만 지원합니다. fragment shader는 아까 말한 미션처럼 픽셀의 좌표를 받아 해당 픽셀에 특정 색을 지정하는 역할을 하죠.</p>
  58. <p>위 코드를 보면 <code class="notranslate" translate="no">mainImage</code> 함수에 <code class="notranslate" translate="no">fragColor</code>라는 이름의 <code class="notranslate" translate="no">out</code> 매개변수가 보일 겁니다. <code class="notranslate" translate="no">out</code>은 <code class="notranslate" translate="no">output</code>의 줄임말로, 바로 이 매개변수가 색상값을 지정할 변수 합니다.</p>
  59. <p><code class="notranslate" translate="no">fragCoord</code>, <code class="notranslate" translate="no">in</code>(<code class="notranslate" translate="no">input</code>의 줄임말) 매개변수는 <code class="notranslate" translate="no">out</code>의 색상값을 사용할 픽셀의 좌표입니다. 이 픽셀 좌표로 새로운 색상값을 만들 수 있죠. 만약 400x300짜리 캔버스에 이 쉐이더를 적용한다면 이 함수는 400x300번, 그러니까 총 120,000번 호출되는 셈입니다.</p>
  60. <p>코드에 선언부가 없긴 하지만 사용할 수 있는 변수가 2개 더 있습니다. 하나는 캔버스의 해상도를 설정하는 <code class="notranslate" translate="no">iResolution</code>으로, 캔버스를 400x300으로 만들려면 <code class="notranslate" translate="no">iResolution</code>을 <code class="notranslate" translate="no">400, 300</code>으로 설정해야 합니다. 그리고 <code class="notranslate" translate="no">iResolution</code>에 의해 픽셀 좌표가 바뀌면 <code class="notranslate" translate="no">uv</code> 변수는 텍스처 크기의 0.0에서 1.0만큼 위, 옆으로 갑니다. 이렇듯 어떤 값을 <em>정규화(normalize)</em>하는 것이 작업을 간단히 하는 데 도움이 되기에 쉐이더토이의 주 함수는 대개 저런 식으로 시작합니다.</p>
  61. <p>다른 하나는 <code class="notranslate" translate="no">iTime</code>으로, 이는 페이지가 로드된 이후의 초 단위 시간값입니다.</p>
  62. <p>쉐이더의 세계에서 이 전역 변수들은 <em>균등(uniform)</em> 변수라고 불립니다. <em>균등</em>이라고 불리는 이유는 쉐이더 한 루프 안에서는 전혀 변하지 않는 변수이기 때문이죠. 다만 위에서 언급한 변수들은 GLSL의 <em>표준</em> 변수가 아니라 쉐이더토이에서 자체적으로 제공하는 변수입니다.</p>
  63. <p><a href="https://www.shadertoy.com/howto">쉐이더토이 공식 문서에는 몇 가지 변수를 더 언급</a>해 놓았지만, 일단은 위 두 변수를 활용해 예제를 하나 만들어보겠습니다.</p>
  64. <p>먼저 캔버스 전체를 채울 평면을 하나 만듭니다. <a href="backgrounds.html">배경과 하늘상자 추가하기</a>에서 정육면체를 뺀 예제를 가져오겠습니다. 코드가 길지 않으니 아래에 전부 적도록 하죠.</p>
  65. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function main() {
  66. const canvas = document.querySelector('#c');
  67. const renderer = new THREE.WebGLRenderer({ canvas });
  68. renderer.autoClearColor = false;
  69. const camera = new THREE.OrthographicCamera(
  70. -1, // left
  71. 1, // right
  72. 1, // top
  73. -1, // bottom
  74. -1, // near,
  75. 1, // far
  76. );
  77. const scene = new THREE.Scene();
  78. const plane = new THREE.PlaneGeometry(2, 2);
  79. const material = new THREE.MeshBasicMaterial({
  80. color: 'red',
  81. });
  82. scene.add(new THREE.Mesh(plane, material));
  83. function resizeRendererToDisplaySize(renderer) {
  84. const canvas = renderer.domElement;
  85. const width = canvas.clientWidth;
  86. const height = canvas.clientHeight;
  87. const needResize = canvas.width !== width || canvas.height !== height;
  88. if (needResize) {
  89. renderer.setSize(width, height, false);
  90. }
  91. return needResize;
  92. }
  93. function render() {
  94. resizeRendererToDisplaySize(renderer);
  95. renderer.render(scene, camera);
  96. requestAnimationFrame(render);
  97. }
  98. requestAnimationFrame(render);
  99. }
  100. main();
  101. </pre>
  102. <p><a href="backgrounds.html">배경과 하늘상자 추가하기</a>에서도 설명했지만 위와 같이 <a href="/docs/#api/ko/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>를 설정하면 2칸짜리 평면이 캔버스 전체를 채우게 됩니다. 당장은 평면이 빨간 <a href="/docs/#api/ko/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a>을 사용했기에 캔버스 전체가 빨갛게 보입니다.</p>
  103. <p></p><div translate="no" class="threejs_example_container notranslate">
  104. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/shadertoy-prep.html"></iframe></div>
  105. <a class="threejs_center" href="/manual/examples/shadertoy-prep.html" target="_blank">새 탭에서 보기</a>
  106. </div>
  107. <p></p>
  108. <p>이제 쉐이더토이에서 쉐이더를 가져와 적용해봅시다.</p>
  109. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fragmentShader = `
  110. #include &lt;common&gt;
  111. uniform vec3 iResolution;
  112. uniform float iTime;
  113. // By iq: https://www.shadertoy.com/user/iq
  114. // license: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
  115. void mainImage( out vec4 fragColor, in vec2 fragCoord )
  116. {
  117. // Normalized pixel coordinates (from 0 to 1)
  118. vec2 uv = fragCoord/iResolution.xy;
  119. // Time varying pixel color
  120. vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
  121. // Output to screen
  122. fragColor = vec4(col,1.0);
  123. }
  124. void main() {
  125. mainImage(gl_FragColor, gl_FragCoord.xy);
  126. }
  127. `;
  128. </pre>
  129. <p>위 코드에서는 아까 설명했던 균등 변수 2개를 선언했습니다. 그런 다음 쉐이더토이에서 만든 GLSL 코드를 복사해 넣었죠. 그리고 하단에서 <code class="notranslate" translate="no">mainImage</code>에 <code class="notranslate" translate="no">gl_FragColor</code>와 <code class="notranslate" translate="no">gl_FragCoord.xy</code>를 넘겨 호출했습니다. 여기서 사용한 <code class="notranslate" translate="no">gl_FragColor</code>는 WebGL의 공식 전역 변수로, 쉐이더는 여기에 해당 픽셀의 색상값을 지정해야 합니다. <code class="notranslate" translate="no">gl_FragCoord</code> 또한 WebGL 공식 전역 변수로, 현재 색상을 적용해야 하는 픽셀의 좌표값을 나타내죠.</p>
  130. <p>다음으로 쉐이더에 데이터를 전달할 전달해야 하니 Three.js 균등 변수를 생성합니다.</p>
  131. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const uniforms = {
  132. iTime: { value: 0 },
  133. iResolution: { value: new THREE.Vector3() },
  134. };
  135. </pre>
  136. <p>Three.js의 균등 변수에는 <code class="notranslate" translate="no">value</code> 속성을 넣어야 합니다. 물론 값으로 들어가는 데이터도 균등 변수의 타입과 맞아야 하죠.</p>
  137. <p>fragment 쉐이더와 균등 변수를 <a href="/docs/#api/ko/materials/ShaderMaterial"><code class="notranslate" translate="no">ShaderMaterial</code></a>에 넘겨줍니다.</p>
  138. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const material = new THREE.MeshBasicMaterial({
  139. - color: 'red',
  140. -});
  141. +const material = new THREE.ShaderMaterial({
  142. + fragmentShader,
  143. + uniforms,
  144. +});
  145. </pre>
  146. <p>또한 매 프레임마다 균등 변수의 값을 변경하도록 합니다.</p>
  147. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-function render() {
  148. +function render(time) {
  149. + time *= 0.001; // 초 단위로 변환
  150. resizeRendererToDisplaySize(renderer);
  151. + const canvas = renderer.domElement;
  152. + uniforms.iResolution.value.set(canvas.width, canvas.height, 1);
  153. + uniforms.iTime.value = time;
  154. renderer.render(scene, camera);
  155. requestAnimationFrame(render);
  156. }
  157. </pre>
  158. <blockquote>
  159. <p>참고: <a href="https://www.shadertoy.com/howto">쉐이더토이 공식 문서</a>를 뒤져봤지만 <code class="notranslate" translate="no">iResolution</code>가 왜 <code class="notranslate" translate="no">vec3</code>여야 하는지, 3번째 값은 어디에 쓰이는 건지 알아내지 못했습니다. 일단 예제에서는 쓰지 않는 값이니 1로 설정하고 넘어가야겠네요. ¯\_(ツ)_/¯</p>
  160. </blockquote>
  161. <p></p><div translate="no" class="threejs_example_container notranslate">
  162. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/shadertoy-basic.html"></iframe></div>
  163. <a class="threejs_center" href="/manual/examples/shadertoy-basic.html" target="_blank">새 탭에서 보기</a>
  164. </div>
  165. <p></p>
  166. <p><a href="https://www.shadertoy.com/new">쉐이더토이에서 "New"를 클릭했을 때 나왔던 결과</a>와 똑같네요. 물론 2019년 1월 기준으로 말이죠😉. 이 쉐이더는 어떻게 이런 결과를 만들어낸 걸까요?</p>
  167. <ul>
  168. <li><code class="notranslate" translate="no">uv</code>가 시간에 따라 서서히 0에서 1로 바뀝니다.</li>
  169. <li><code class="notranslate" translate="no">cos(uv.xyx)</code>는 입력한 값에 코사인 함수를 적용해 <code class="notranslate" translate="no">vec3</code> 형식으로 반환합니다. 하나는 <code class="notranslate" translate="no">uv.x</code>에, 다른 하나는 <code class="notranslate" translate="no">uv.y</code>에, 마지막은 다시 <code class="notranslate" translate="no">uv.x</code>에 코사인 함수를 실행한 결과값이죠.</li>
  170. <li>이전에 <code class="notranslate" translate="no">cos(iTime+uv.xyx)</code> 이렇게 시간값을 더해 애니메이션을 구현합니다.</li>
  171. <li>이전에 <code class="notranslate" translate="no">vec3(0,2,4)</code>를 더해 <code class="notranslate" translate="no">cos(iTime+uv.xyx+vec3(0,2,4))</code>와 같이 하면 코사인 파도가 생깁니다.</li>
  172. <li><code class="notranslate" translate="no">cos</code>의 결과값은 -1부터 1까지이므로, <code class="notranslate" translate="no">0.5 * 0.5 + cos(...)</code>을 적용하면 -1 &lt;-&gt; 1, 0.0 &lt;-&gt; 1.0 이런 식으로 바뀝니다.</li>
  173. <li>이 결과값을 현재 픽셀에 대한 RGB 값으로 씁니다.</li>
  174. </ul>
  175. <p>코드를 조금 수정하면 코사인 파도가 더 잘 보일 겁니다. 지금은 <code class="notranslate" translate="no">uv</code>의 값이 0부터 1까지죠. 코사인의 주기는 2π이니 <code class="notranslate" translate="no">uv</code>에 40.0을 곱해 <code class="notranslate" translate="no">uv</code>값이 0부터 40까지 되도록 해보겠습니다. 이러면 화면에 파도가 약 6.3번 반복될 거예요.</p>
  176. <pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">-vec3 col = 0.5 + 0.5 * cos(iTime + uv.xyx + vec3(0,2,4));
  177. +vec3 col = 0.5 + 0.5 * cos(iTime + uv.xyx * 40.0 + vec3(0,2,4));
  178. </pre>
  179. <p>아래 예제를 보니 파도가 약 6개 하고 1/3 정도 보입니다. 파란선 사이에 빨간선이 보이는 건 파란색의 위치를 <code class="notranslate" translate="no">+ vec3(0,2,4)</code>로 4만큼 옮겼기 때문이죠. 이렇게 하지 않았다면 빨강과 파랑이 완전히 겹쳐 자주색으로 보였을 겁니다.</p>
  180. <p></p><div translate="no" class="threejs_example_container notranslate">
  181. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/shadertoy-basic-x40.html"></iframe></div>
  182. <a class="threejs_center" href="/manual/examples/shadertoy-basic-x40.html" target="_blank">새 탭에서 보기</a>
  183. </div>
  184. <p></p>
  185. <p>주어지는 값이 이렇게 단순한데 이걸로 <a href="https://www.shadertoy.com/view/MdXGW2">도심 운하</a>, <a href="https://www.shadertoy.com/view/4ttSWf">숲</a>, <a href="https://www.shadertoy.com/view/ld3Gz2">달팽이</a>, <a href="https://www.shadertoy.com/view/4tBXR1">버섯</a> 등을 구현하다니 정말 놀랍네요. 다만 삼각형으로 장면을 구성하는 일반적인 방법에 비해 왜 이 방법이 안 좋은지 분명히 하지 않은 게 아쉽습니다. 한 픽셀 한 픽셀 정성들여 픽셀의 색상을 연산하니 그만큼 한 프레임을 만드는 데 시간이 많이 걸릴 수밖에 없죠.</p>
  186. <p>쉐이더토이 쉐이더 중에는 <a href="https://www.shadertoy.com/view/MsXSzM">텍스처를 받아서 사용하는 경우</a>도 있습니다.</p>
  187. <pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">// By Daedelus: https://www.shadertoy.com/user/Daedelus
  188. // license: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
  189. #define TIMESCALE 0.25
  190. #define TILES 8
  191. #define COLOR 0.7, 1.6, 2.8
  192. void mainImage( out vec4 fragColor, in vec2 fragCoord )
  193. {
  194. vec2 uv = fragCoord.xy / iResolution.xy;
  195. uv.x *= iResolution.x / iResolution.y;
  196. vec4 noise = texture2D(iChannel0, floor(uv * float(TILES)) / float(TILES));
  197. float p = 1.0 - mod(noise.r + noise.g + noise.b + iTime * float(TIMESCALE), 1.0);
  198. p = min(max(p * 3.0 - 1.8, 0.1), 2.0);
  199. vec2 r = mod(uv * float(TILES), 1.0);
  200. r = vec2(pow(r.x - 0.5, 2.0), pow(r.y - 0.5, 2.0));
  201. p *= 1.0 - pow(min(1.0, 12.0 * dot(r, r)), 2.0);
  202. fragColor = vec4(COLOR, 1.0) * p;
  203. }
  204. </pre>
  205. <p>쉐이더에 텍스처를 넘겨주는 건 <a href="textures.html">재질(material)에 텍스처를 넘겨주는 것</a>과 비슷하나, 대신 텍스처를 균등 변수에 지정해야 합니다.</p>
  206. <p>먼저 쉐이더에 균등 변수를 추가합니다. 텍스처는 GLSL에서 <code class="notranslate" translate="no">sampler2D</code>라고 불립니다.</p>
  207. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const fragmentShader = `
  208. #include &lt;common&gt;
  209. uniform vec3 iResolution;
  210. uniform float iTime;
  211. +uniform sampler2D iChannel0;
  212. ...
  213. </pre>
  214. <p>다음으로 <a href="textures.html">이 글</a>에서 했던 것처럼 텍스처를 불러와 균등 변수에 지정합니다.</p>
  215. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const loader = new THREE.TextureLoader();
  216. +const texture = loader.load('resources/images/bayer.png');
  217. +texture.minFilter = THREE.NearestFilter;
  218. +texture.magFilter = THREE.NearestFilter;
  219. +texture.wrapS = THREE.RepeatWrapping;
  220. +texture.wrapT = THREE.RepeatWrapping;
  221. const uniforms = {
  222. iTime: { value: 0 },
  223. iResolution: { value: new THREE.Vector3() },
  224. + iChannel0: { value: texture },
  225. };
  226. </pre>
  227. <p></p><div translate="no" class="threejs_example_container notranslate">
  228. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/shadertoy-bleepy-blocks.html"></iframe></div>
  229. <a class="threejs_center" href="/manual/examples/shadertoy-bleepy-blocks.html" target="_blank">새 탭에서 보기</a>
  230. </div>
  231. <p></p>
  232. <p>여태까지는 <a href="https://shadertoy.com">쉐이더토이 사이트</a>에 나와있는 대로 캔버스 전체에 쉐이더를 구현했습니다. 하지만 쉐이더를 사용할 때 꼭 예제 형식에 얽매일 필요는 없겠죠. 쉐이더토이의 작가들은 대부분 <code class="notranslate" translate="no">fragCoord</code>와 <code class="notranslate" translate="no">iResolution</code>을 사용한다는 것만 기억하면 됩니다. <code class="notranslate" translate="no">fragCoord</code>가 꼭 픽셀의 좌표여야할 이유는 없다는 말입니다. 이를 텍스처 좌표로 바꿔 쉐이더를 텍스처처럼 사용할 수도 있죠. 이렇게 쉐이더 함수로 텍스처를 만드는 기법을 <a href="https://www.google.com/search?q=procedural+texture"><em>절차적 텍스처(procedural texture)</em></a>라고 합니다.</p>
  233. <p>예제에 이 기법을 적용해봅시다. Three.js에서 텍스처 좌표를 받아 여기에 <code class="notranslate" translate="no">iResolution</code>을 곱해 <code class="notranslate" translate="no">fragCoord</code>에 넘기는 게 제일 간단할 듯하네요.</p>
  234. <p>먼저 fragment 쉐이더에서 쓸 <em>varying</em>을 추가합니다. varying은 vertex(정점) 쉐이더에서 fragment 쉐이더에 넘겨주는 값으로, 각 정점 사이를 보간한(점진적으로 채운(varied)) 값입니다. Three.js가 텍스처 좌표를 <code class="notranslate" translate="no">uv</code> 앞에 <em>varying</em>을 이니셜인 <code class="notranslate" translate="no">v</code>를 붙여 표시하니 그 이름을 그대로 사용하겠습니다.</p>
  235. <pre class="prettyprint showlinemods notranslate lang-glsl" translate="no">...
  236. +varying vec2 vUv;
  237. void main() {
  238. - mainImage(gl_FragColor, gl_FragCoord.xy);
  239. + mainImage(gl_FragColor, vUv * iResolution.xy);
  240. }
  241. </pre>
  242. <p>추가로 vertex 쉐이더를 만들어야 합니다. 아래는 가장 간단한 형태의 Three.js vertex 쉐이더로, <code class="notranslate" translate="no">uv</code>, <code class="notranslate" translate="no">projectionMatrix</code>, <code class="notranslate" translate="no">modelViewMatrix</code>, <code class="notranslate" translate="no">position</code> 등의 변수는 Three.js가 선언해줄 겁니다.</p>
  243. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const vertexShader = `
  244. varying vec2 vUv;
  245. void main() {
  246. vUv = uv;
  247. gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
  248. }
  249. `;
  250. </pre>
  251. <p>vertex 쉐이더도 같이 <a href="/docs/#api/ko/materials/ShaderMaterial"><code class="notranslate" translate="no">ShaderMaterial</code></a>에 넘겨줍니다.</p>
  252. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const material = new THREE.ShaderMaterial({
  253. vertexShader,
  254. fragmentShader,
  255. uniforms,
  256. });
  257. </pre>
  258. <p>텍스처의 크기는 바뀔 일이 없으니 <code class="notranslate" translate="no">iResolution</code> 균등 변수의 초기값을 미리 설정합니다.</p>
  259. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const uniforms = {
  260. iTime: { value: 0 },
  261. - iResolution: { value: new THREE.Vector3() },
  262. + iResolution: { value: new THREE.Vector3(1, 1, 1) },
  263. iChannel0: { value: texture },
  264. };
  265. </pre>
  266. <p><code class="notranslate" translate="no">render</code> 함수 안에 있던 코드도 삭제합니다.</p>
  267. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const canvas = renderer.domElement;
  268. -uniforms.iResolution.value.set(canvas.width, canvas.height, 1);
  269. uniforms.iTime.value = time;
  270. </pre>
  271. <p>여기에 <a href="responsive.html">반응형 디자인에 관한 글</a>에서 카메라와 회전하는 정육면체 3개를 가져왔습니다. 이제 한 번 실행해보죠.</p>
  272. <p></p><div translate="no" class="threejs_example_container notranslate">
  273. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/shadertoy-as-texture.html"></iframe></div>
  274. <a class="threejs_center" href="/manual/examples/shadertoy-as-texture.html" target="_blank">새 탭에서 보기</a>
  275. </div>
  276. <p></p>
  277. <p>이 글이 Three.js에서 쉐이더토이의 쉐이더를 활용하는 데 도움이 되었으면 합니다. 누차 말하지만 쉐이더토이의 쉐이더는 실제 사용하기 위해 제작되었다기보다-함수 하나로 모든 요소를 만드는-연습용 챌린지에 가깝습니다. 하지만 그래도 쉐이더토이에 올라온 쉐이더들은 여전히 인상 깊고, 놀랍습니다. 배울 점도 굉장히 많죠.</p>
  278. </div>
  279. </div>
  280. </div>
  281. <script src="../resources/prettify.js"></script>
  282. <script src="../resources/lesson.js"></script>
  283. </body></html>