|
| 1 | +Title: WebGL 최소 프로그램 |
| 2 | +Description: 테스트를 위한 최소한의 코드 |
| 3 | +TOC: 최소 프로그램 |
| 4 | + |
| 5 | +이 글은 [기초](webgl-fundamentals.html)로 시작하여 여러 다른 글을 읽었다고 가정합니다. |
| 6 | +아직 읽지 않았다면 거기부터 시작해주세요. |
| 7 | + |
| 8 | +두 가지 목적이 있어서 이 글을 어떻게 정리해야 할지 잘 모르겠습니다. |
| 9 | + |
| 10 | +1. 최소한의 WebGL 프로그램을 보여 드립니다. |
| 11 | + |
| 12 | + 이러한 기술은 무언가를 테스트하거나 [Stack Overflow를 위한 MCVE](https://meta.stackoverflow.com/a/349790/128511)를 만들 때 혹은 버그의 범위를 좁히려고 할 때 굉장히 유용합니다. |
| 13 | + |
| 14 | +2. 틀 밖에서 생각하는 방법 배웁니다. |
| 15 | + |
| 16 | + 일반적인 패턴 뿐 아니라 더 큰 그림을 볼 때 도움이 되도록 이에 대한 더 많은 글을 작성하려 합니다. |
| 17 | + 하나는 [여기](webgl-drawing-without-data.html)에 있습니다. |
| 18 | + |
| 19 | +## 그냥 지우기 |
| 20 | + |
| 21 | +다음은 무언가를 하는 최소 WebGL 프로그램입니다. |
| 22 | + |
| 23 | +```js |
| 24 | +const gl = document.querySelector('canvas').getContext('webgl'); |
| 25 | +gl.clearColor(1, 0, 0, 1); // 빨간색 |
| 26 | +gl.clear(gl.COLOR_BUFFER_BIT); |
| 27 | +``` |
| 28 | + |
| 29 | +이 프로그램이 하는 것은 캔버스를 빨간색으로 지우는 게 전부지만 실제로는 무언가 했습니다. |
| 30 | + |
| 31 | +이것만으로도 실제로 몇 가지 테스트를 할 수 있는데요. |
| 32 | +[텍스처에 렌더링](webgl-render-to-texture.html)하는데 작동하지 않는다고 가정해봅시다. |
| 33 | +그리고 [해당 글](webgl-render-to-texture.html)의 예제와 같다고 가정해봅시다. |
| 34 | +하나 이상의 3D 물체를 텍스처에 렌더링한 다음 그 결과를 큐브에 렌더링합니다. |
| 35 | + |
| 36 | +아무것도 안 보이는데요. |
| 37 | +간단한 테스트로서 알려진 색상으로 텍스처를 지우는 셰이더로 텍스처에 렌더링하는 것을 멈춰봅시다. |
| 38 | + |
| 39 | +```js |
| 40 | +gl.bindFramebuffer(gl.FRAMEBUFFER, framebufferWithTexture) |
| 41 | +gl.clearColor(1, 0, 1, 1); // 자홍색 |
| 42 | +gl.clear(gl.COLOR_BUFFER_BIT); |
| 43 | +``` |
| 44 | + |
| 45 | +이제 프레임버퍼의 텍스처로 렌더링합니다. |
| 46 | +큐브가 자홍색으로 돌아갔나요? |
| 47 | +아니라면 텍스처에 렌더링하는 부분이 아니라 다른 문제입니다. |
| 48 | + |
| 49 | +## `SCISSOR_TEST` 그리고 `gl.clear` 사용 |
| 50 | + |
| 51 | +`SCISSOR_TEST`는 그리기와 지우기 모두 캔버스(혹은 현재 프레임버퍼)의 하위 사각형으로 자릅니다. |
| 52 | +다음과 같이 작성하여 scissor test를 활성화합니다. |
| 53 | + |
| 54 | +```js |
| 55 | +gl.enable(gl.SCISSOR_TEST); |
| 56 | +``` |
| 57 | + |
| 58 | +그런 다음 왼쪽 하단 모서리를 기준으로 scissor rectangle을 픽셀 단위로 설정합니다. |
| 59 | +이는 `gl.viewport`와 같은 매개 변수를 사용합니다. |
| 60 | + |
| 61 | +```js |
| 62 | +gl.scissor(x, y, width, height); |
| 63 | +``` |
| 64 | + |
| 65 | +이렇게 하면 `SCISSOR_TEST`와 `gl.clear`를 사용하여 사각형을 그릴 수 있습니다. |
| 66 | + |
| 67 | +예시: |
| 68 | + |
| 69 | +```js |
| 70 | +const gl = document.querySelector('#c').getContext('webgl'); |
| 71 | + |
| 72 | +gl.enable(gl.SCISSOR_TEST); |
| 73 | + |
| 74 | +function drawRect(x, y, width, height, color) { |
| 75 | + gl.scissor(x, y, width, height); |
| 76 | + gl.clearColor(...color); |
| 77 | + gl.clear(gl.COLOR_BUFFER_BIT); |
| 78 | +} |
| 79 | + |
| 80 | +for (let i = 0; i < 100; ++i) { |
| 81 | + const x = rand(0, 300); |
| 82 | + const y = rand(0, 150); |
| 83 | + const width = rand(0, 300 - x); |
| 84 | + const height = rand(0, 150 - y); |
| 85 | + drawRect(x, y, width, height, [rand(1), rand(1), rand(1), 1]); |
| 86 | +} |
| 87 | + |
| 88 | + |
| 89 | +function rand(min, max) { |
| 90 | + if (max === undefined) { |
| 91 | + max = min; |
| 92 | + min = 0; |
| 93 | + } |
| 94 | + return Math.random() * (max - min) + min; |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +{{{example url="../webgl-simple-scissor.html"}}} |
| 99 | + |
| 100 | +Not saying that particular one is all that useful but still it's good to know. |
| 101 | + |
| 102 | +## 큰 `gl.POINTS` 하나 사용 |
| 103 | + |
| 104 | +대부분의 예제에서 볼 수 있듯이, WebGL에서 가장 일반적인 수행하는 작업은 버퍼 생성입니다. |
| 105 | +이러한 버퍼에 정점 데이터를 넣습니다. |
| 106 | +속성이 있는 셰이더를 만듭니다. |
| 107 | +버퍼에서 데이터를 가져오도록 속성을 설정합니다. |
| 108 | +그런 다음 셰이더에서도 사용되는 uniform과 텍스처를 사용하여 그립니다. |
| 109 | + |
| 110 | +하지만 간혹 테스트하고 싶을 때가 있습니다. |
| 111 | +무언가 그리는 걸 보고 싶다고 가정해봅시다. |
| 112 | + |
| 113 | +이런 셰이더 세트는 어떨까요? |
| 114 | + |
| 115 | +```glsl |
| 116 | +// vertex shader |
| 117 | +void main() { |
| 118 | + gl_Position = vec4(0, 0, 0, 1); // 중앙 |
| 119 | + gl_PointSize = 120.0; |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +```glsl |
| 124 | +// fragment shader |
| 125 | +precision mediump float; |
| 126 | +
|
| 127 | +void main() { |
| 128 | + gl_FragColor = vec4(1, 0, 0, 1); // 빨간색 |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +그리고 이를 사용하는 코드는 다음과 같습니다. |
| 133 | + |
| 134 | +```js |
| 135 | +// GLSL 프로그램 설정 |
| 136 | +const program = webglUtils.createProgramFromSources(gl, [vs, fs]); |
| 137 | + |
| 138 | +gl.useProgram(program); |
| 139 | + |
| 140 | +const offset = 0; |
| 141 | +const count = 1; |
| 142 | +gl.drawArrays(gl.POINTS, offset, count); |
| 143 | +``` |
| 144 | + |
| 145 | +생성하는 버퍼도 없고, 설정하는 uniform도 없으며, 캔버스 중앙에 하나의 점만 있습니다. |
| 146 | + |
| 147 | +{{{example url="../webgl-simple-point.html"}}} |
| 148 | + |
| 149 | +> NOTE: Safari pre 15는 이 기능에 대한 [WebGL 적합성 테스트](https://www.khronos.org/registry/webgl/sdk/tests/conformance/rendering/point-no-attributes.html?webglVersion=1&quiet=0)를 통과하지 못 했습니다. |
| 150 | +
|
| 151 | +`gl.POINTS` 정보: `gl.POINTS`를 `gl.drawArrays`로 전달할 때 픽셀 단위의 크기로 정점 셰이더의 `gl_PointSize`도 설정해야 합니다. |
| 152 | +GPU/드라이버마다 사용할 수 있는 최대 점 크기가 다르다는 점에 주의하세요. |
| 153 | +최대 크기는 다음과 같이 쿼리할 수 있습니다. |
| 154 | + |
| 155 | +``` |
| 156 | +const [minSize, maxSize] = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE); |
| 157 | +``` |
| 158 | + |
| 159 | +WebGL 스펙은 오직 최대 크기 1.0을 필요로 합니다. |
| 160 | +다행히도 [모든 GPU와 드라이버는 아니지만 대부분이 더 큰 크기를 지원](https://webglstats.com/webgl/parameter/ALIASED_POINT_SIZE_RANGE)합니다. |
| 161 | + |
| 162 | +`gl_PointSize`를 설정한 다음 정점 셰이더가 종료될 때, `gl_Position`에 설정한 모든 값은 픽셀 단위의 화면/캔버스 공간으로 변환되며, 네 방향 모두에 +/- gl_PointSize / 2인 위치에 정사각형이 생성됩니다. |
| 163 | + |
| 164 | +누가 점 하나 그리는 걸 원하겠다고 생각하실 수 있습니다. |
| 165 | + |
| 166 | +점은 자동적으로 비어있는 [텍스처 좌표](webgl-3d-textures.html)를 가집니다. |
| 167 | +Fragment shader의 특수 변수 `gl_PointCoord`로 사용할 수 있는데요. |
| 168 | +해당 점의 텍스처를 그려봅시다. |
| 169 | + |
| 170 | +먼저 fragment shader를 변경합니다. |
| 171 | + |
| 172 | +```glsl |
| 173 | +// fragment shader |
| 174 | +precision mediump float; |
| 175 | +
|
| 176 | ++uniform sampler2D tex; |
| 177 | +
|
| 178 | +void main() { |
| 179 | +- gl_FragColor = vec4(1, 0, 0, 1); // 빨간색 |
| 180 | ++ gl_FragColor = texture2D(tex, gl_PointCoord.xy); |
| 181 | +} |
| 182 | +``` |
| 183 | + |
| 184 | +이제 이를 단순하게 유지하기 위해 [데이터 텍스처에 대한 글](webgl-data-textures.html)에서 다룬 것처럼 원본 데이터로 만들어 보겠습니다. |
| 185 | + |
| 186 | +```js |
| 187 | +// 2x2 pixel data |
| 188 | +const pixels = new Uint8Array([ |
| 189 | + 0xFF, 0x00, 0x00, 0xFF, // 빨간색 |
| 190 | + 0x00, 0xFF, 0x00, 0xFF, // 초록색 |
| 191 | + 0x00, 0x00, 0xFF, 0xFF, // 파란색 |
| 192 | + 0xFF, 0x00, 0xFF, 0xFF, // 자홍색 |
| 193 | +]); |
| 194 | +const tex = gl.createTexture(); |
| 195 | +gl.bindTexture(gl.TEXTURE_2D, tex); |
| 196 | +gl.texImage2D( |
| 197 | + gl.TEXTURE_2D, |
| 198 | + 0, // 레벨 |
| 199 | + gl.RGBA, // 내부 포맷 |
| 200 | + 2, // 너비 |
| 201 | + 2, // 높이 |
| 202 | + 0, // 테두리 |
| 203 | + gl.RGBA, // 포맷 |
| 204 | + gl.UNSIGNED_BYTE, // 타입 |
| 205 | + pixels, // 데이터 |
| 206 | +); |
| 207 | +gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); |
| 208 | +gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); |
| 209 | +``` |
| 210 | + |
| 211 | +WebGL은 기본적으로 texture unit 0을 사용하고 uniform은 기본적으로 0이므로 따로 설정할 것은 없습니다. |
| 212 | + |
| 213 | +{{{example url="../webgl-simple-point-w-texture.html"}}} |
| 214 | + |
| 215 | +이건 텍스처 관련 문제를 테스트하는 좋은 방법이 될 수 있습니다. |
| 216 | +버퍼도 안 쓰고, 속성도 없으며, uniform을 찾아 설정하지 않아도 됩니다. |
| 217 | +예를 들어 이미지를 로드한다면 표시되지 않는데요. |
| 218 | +위 셰이더를 사용하면 점에 이미지가 표시될까요? |
| 219 | +텍스처에 렌더링한 다음 텍스처를 보려고 합니다. |
| 220 | +일반적으로 버퍼와 속성을 통해 geometry를 설정하지만 이 단일 점에 표시하는 것만으로 텍스처를 렌더링할 수 있습니다. |
| 221 | + |
| 222 | +## 여러 단일 `POINTS` 사용 |
| 223 | + |
| 224 | +위 예제를 기반으로 한 간단한 변경입니다. |
| 225 | +정점 셰이더를 이렇게 바꿀 수 있는데요. |
| 226 | + |
| 227 | +```glsl |
| 228 | +// vertex shader |
| 229 | +
|
| 230 | ++attribute vec4 position; |
| 231 | +
|
| 232 | +void main() { |
| 233 | +- gl_Position = vec4(0, 0, 0, 1); |
| 234 | ++ gl_Position = position; |
| 235 | + gl_PointSize = 120.0; |
| 236 | +} |
| 237 | +``` |
| 238 | + |
| 239 | +속성은 기본값으로 `0, 0, 0, 1`을 가지므로 위 예제로 변경해도 여전히 작동합니다. |
| 240 | +하지만 이제 우리가 원하는 위치를 설정할 수 있는 능력을 얻었습니다. |
| 241 | + |
| 242 | +```js |
| 243 | ++const program = webglUtils.createProgramFromSources(gl, [vs, fs]); |
| 244 | +const positionLoc = gl.getAttribLocation(program, 'position'); |
| 245 | + |
| 246 | +... |
| 247 | + |
| 248 | ++const numPoints = 5; |
| 249 | ++for (let i = 0; i < numPoints; ++i) { |
| 250 | ++ const u = i / (numPoints - 1); // 0 ~ 1 |
| 251 | ++ const clipspace = u * 1.6 - 0.8; // -0.8 ~ +0.8 |
| 252 | ++ gl.vertexAttrib2f(positionLoc, clipspace, clipspace); |
| 253 | + |
| 254 | +* const offset = 0; |
| 255 | +* const count = 1; |
| 256 | +* gl.drawArrays(gl.POINTS, offset, count); |
| 257 | ++} |
| 258 | +``` |
| 259 | + |
| 260 | +실행하기 전에 점을 더 작게 만듭시다. |
| 261 | + |
| 262 | +```glsl |
| 263 | +// vertex shader |
| 264 | +
|
| 265 | +attribute vec4 position; |
| 266 | ++uniform float size; |
| 267 | +
|
| 268 | +void main() { |
| 269 | + gl_Position = position; |
| 270 | +- gl_PointSize = 120.0; |
| 271 | ++ gl_PointSize = 20.0; |
| 272 | +} |
| 273 | +``` |
| 274 | + |
| 275 | +그리고 점의 색상을 설정할 수 있도록 만들어 보겠습니다. |
| 276 | +(참고: 텍스처가 없는 코드로 다시 전환했습니다) |
| 277 | + |
| 278 | +```glsl |
| 279 | +precision mediump float; |
| 280 | +
|
| 281 | ++uniform vec4 color; |
| 282 | +
|
| 283 | +void main() { |
| 284 | +- gl_FragColor = vec4(1, 0, 0, 1); // 빨간색 |
| 285 | ++ gl_FragColor = color; |
| 286 | +} |
| 287 | +``` |
| 288 | + |
| 289 | +그리고 color location을 찾아야 합니다. |
| 290 | + |
| 291 | +```js |
| 292 | +// GLSL 프로그램 설정 |
| 293 | +const program = webglUtils.createProgramFromSources(gl, [vs, fs]); |
| 294 | +const positionLoc = gl.getAttribLocation(program, 'position'); |
| 295 | ++const colorLoc = gl.getUniformLocation(program, 'color'); |
| 296 | +``` |
| 297 | + |
| 298 | +그런 다음 그것들을 사용합니다. |
| 299 | + |
| 300 | +```js |
| 301 | +gl.useProgram(program); |
| 302 | + |
| 303 | +const numPoints = 5; |
| 304 | +for (let i = 0; i < numPoints; ++i) { |
| 305 | + const u = i / (numPoints - 1); // 0 ~ 1 |
| 306 | + const clipspace = u * 1.6 - 0.8; // -0.8 ~ +0.8 |
| 307 | + gl.vertexAttrib2f(positionLoc, clipspace, clipspace); |
| 308 | + |
| 309 | ++ gl.uniform4f(colorLoc, u, 0, 1 - u, 1); |
| 310 | + |
| 311 | + const offset = 0; |
| 312 | + const count = 1; |
| 313 | + gl.drawArrays(gl.POINTS, offset, count); |
| 314 | +} |
| 315 | +``` |
| 316 | + |
| 317 | +이제 5개의 색상을 가진 5개의 점이 있으며 여전히 어떤 버퍼나 속성도 설정하지 않았습니다. |
| 318 | + |
| 319 | +{{{example url="../webgl-simple-points.html"}}} |
| 320 | + |
| 321 | +물론 이건 WebGL에서 많은 양의 점을 그려야 하는 방식이 아닙니다. |
| 322 | +많은 점을 그리려면 각 점의 위치와 색상으로 속성을 설정하고 단일 그리기 호출에서 모든 점을 그려야 합니다. |
| 323 | + |
| 324 | +하지만! 테스트, 디버깅, [MCVE](https://meta.stackoverflow.com/a/349790/128511)를 만들기 위해 코드를 최소화하기 좋은 방법입니다. |
| 325 | +또 다른 예로 후처리 효과를 위해 텍스처에 그리는 중이고 이를 시각화하고 싶다고 가정해보겠습니다. |
| 326 | +이 예제와 텍스처를 사용한 이전 예제의 조합을 사용하여 각각 하나의 큰 점을 그릴 수 있습니다. |
| 327 | +복잡한 버퍼와 속성 과정이 필요하지 않아 디버깅에 적합합니다. |
| 328 | + |
0 commit comments