이 글은 WebGL2 시리즈에서 이어지는 글입니다. 첫 번째 글은 WebGL2 기초였고, 이전 글은 텍스처입니다.
지난 글에서 텍스처가 작동하는 방법과 이를 적용하는 방법에 대해 살펴봤습니다. 다운로드된 이미지로 텍스처를 생성했는데요, 이번 글에서는 이미지 대신 자바스크립트에서 직접 데이터를 생성할 겁니다.
자바스크립트로 텍스처로 사용할 데이터를 만드는 것은 텍스처 포맷을 보시면 대부분 명확합니다. WebGL2는 아주 많은 텍스처 포맷을 지원합니다. WebGL2에서는 WebGL1의 모든 un-sized 포맷을 지원합니다.
Format | Type | Channels | Bytes per pixel |
RGBA | UNSIGNED_BYTE | 4 | 4 |
RGB | UNSIGNED_BYTE | 3 | 3 |
RGBA | UNSIGNED_SHORT_4_4_4_4 | 4 | 2 |
RGBA | UNSIGNED_SHORT_5_5_5_1 | 4 | 2 |
RGB | UNSIGNED_SHORT_5_6_5 | 3 | 2 |
LUMINANCE_ALPHA | UNSIGNED_BYTE | 2 | 2 |
LUMINANCE | UNSIGNED_BYTE | 1 | 1 |
ALPHA | UNSIGNED_BYTE | 1 | 1 |
un-sized라고 부르는 이유는 WebGL1에서는 이들이 내부적으로 어떻게 표현될지가 정해져 있지 않기 때문입니다. WebGL2에서는 정해져 있습니다. 이러한 un-sized포맷 이외에도 다수의 sized포맷들이 있습니다.
Sized Format |
Base Format |
R bits |
G bits |
B bits |
A bits |
Shared bits |
Color renderable |
Texture filterable |
R8 | RED | 8 | ● | ● | ||||
R8_SNORM | RED | s8 | ● | |||||
RG8 | RG | 8 | 8 | ● | ● | |||
RG8_SNORM | RG | s8 | s8 | ● | ||||
RGB8 | RGB | 8 | 8 | 8 | ● | ● | ||
RGB8_SNORM | RGB | s8 | s8 | s8 | ● | |||
RGB565 | RGB | 5 | 6 | 5 | ● | ● | ||
RGBA4 | RGBA | 4 | 4 | 4 | 4 | ● | ● | |
RGB5_A1 | RGBA | 5 | 5 | 5 | 1 | ● | ● | |
RGBA8 | RGBA | 8 | 8 | 8 | 8 | ● | ● | |
RGBA8_SNORM | RGBA | s8 | s8 | s8 | s8 | ● | ||
RGB10_A2 | RGBA | 10 | 10 | 10 | 2 | ● | ● | |
RGB10_A2UI | RGBA | ui10 | ui10 | ui10 | ui2 | ● | ||
SRGB8 | RGB | 8 | 8 | 8 | ● | |||
SRGB8_ALPHA8 | RGBA | 8 | 8 | 8 | 8 | ● | ● | |
R16F | RED | f16 | ● | |||||
RG16F | RG | f16 | f16 | ● | ||||
RGB16F | RGB | f16 | f16 | f16 | ● | |||
RGBA16F | RGBA | f16 | f16 | f16 | f16 | ● | ||
R32F | RED | f32 | ||||||
RG32F | RG | f32 | f32 | |||||
RGB32F | RGB | f32 | f32 | f32 | ||||
RGBA32F | RGBA | f32 | f32 | f32 | f32 | |||
R11F_G11F_B10F | RGB | f11 | f11 | f10 | ● | |||
RGB9_E5 | RGB | 9 | 9 | 9 | 5 | ● | ||
R8I | RED | i8 | ● | |||||
R8UI | RED | ui8 | ● | |||||
R16I | RED | i16 | ● | |||||
R16UI | RED | ui16 | ● | |||||
R32I | RED | i32 | ● | |||||
R32UI | RED | ui32 | ● | |||||
RG8I | RG | i8 | i8 | ● | ||||
RG8UI | RG | ui8 | ui8 | ● | ||||
RG16I | RG | i16 | i16 | ● | ||||
RG16UI | RG | ui16 | ui16 | ● | ||||
RG32I | RG | i32 | i32 | ● | ||||
RG32UI | RG | ui32 | ui32 | ● | ||||
RGB8I | RGB | i8 | i8 | i8 | ||||
RGB8UI | RGB | ui8 | ui8 | ui8 | ||||
RGB16I | RGB | i16 | i16 | i16 | ||||
RGB16UI | RGB | ui16 | ui16 | ui16 | ||||
RGB32I | RGB | i32 | i32 | i32 | ||||
RGB32UI | RGB | ui32 | ui32 | ui32 | ||||
RGBA8I | RGBA | i8 | i8 | i8 | i8 | ● | ||
RGBA8UI | RGBA | ui8 | ui8 | ui8 | ui8 | ● | ||
RGBA16I | RGBA | i16 | i16 | i16 | i16 | ● | ||
RGBA16UI | RGBA | ui16 | ui16 | ui16 | ui16 | ● | ||
RGBA32I | RGBA | i32 | i32 | i32 | i32 | ● | ||
RGBA32UI | RGBA | ui32 | ui32 | ui32 | ui32 | ● |
아래와 같은 깊이와 스텐실을 위한 포맷도 있습니다.
Sized Format |
Base Format |
Depth bits |
Stencil bits |
DEPTH_COMPONENT16 | DEPTH_COMPONENT | 16 | |
DEPTH_COMPONENT24 | DEPTH_COMPONENT | 24 | |
DEPTH_COMPONENT32F | DEPTH_COMPONENT | f32 | |
DEPTH24_STENCIL8 | DEPTH_STENCIL | 24 | ui8 |
DEPTH32F_STENCIL8 | DEPTH_STENCIL | f32 | ui8 |
범례:
8
과 같은 숫자는 0과 1사이로 정규화되는 8비트를 의미합니다.s8
과 같이 숫자 앞에 s
가 붙으면 부호가 있는(signed) 8비트 숫자로 -1에서 1 사이로 정규화됩니다.f16
과 같이 숫자 앞에 f
가 붙으면 부동소수점 수를 의미합니다.i8
과 같이 숫자 앞에 i
가 붙으면 정수형 수를 의미합니다.ui8
과 같이 숫자 앞에 ui
가 붙으면 부호가 없는(unsigned) 정수형 수를 의미합니다.여기서 사용할 것은 아니지만 half와 float 텍스처 포맷에 하이라이트를 해 두었습니다.
WebGL1과 달리 WebGL2에서는 이러한 포맷이 항상 사용은 가능하지만 기본적으로는 color renderable이나 texture filterable이 아니라는 것을 강조하기 위함입니다.
color renderable이 아니라는 뜻은 렌더링 대상이 되지 않는다는 뜻입니다. 텍스처에 렌더링하는 법은 다른 글에 설명되어 있습니다.
texture filterable이 아니라는 것은 gl.NEAREST
옵션으로만 사용해야 한다는 뜻입니다.
이 기능들은 WebGL2에서는 선택적 확장 기능(optional extension)으로 제공됩니다.
이러한 포맷들에 대해서 여러분은 내부(internal) 포맷 (GPU 내부에서 사용할 포맷)과 포맷 그리고 데이터의 타입을 명시해 주어야 합니다. 아래 표는 내부 포맷에 대해 어떠한 포맷과 타입을 제공해야 하는지를 나타낸 표 입니다.
Internal Format |
Format | Type | Source Bytes Per Pixel |
RGBA8 RGB5_A1 RGBA4 SRGB8_ALPHA8 | RGBA | UNSIGNED_BYTE | 4 |
RGBA8_SNORM | RGBA | BYTE | 4 |
RGBA4 | RGBA | UNSIGNED_SHORT_4_4_4_4 | 2 |
RGB5_A1 | RGBA | UNSIGNED_SHORT_5_5_5_1 | 2 |
RGB10_A2 RGB5_A1 | RGBA | UNSIGNED_INT_2_10_10_10_REV | 4 |
RGBA16F | RGBA | HALF_FLOAT | 8 |
RGBA32F RGBA16F | RGBA | FLOAT | 16 |
RGBA8UI | RGBA_INTEGER | UNSIGNED_BYTE | 4 |
RGBA8I | RGBA_INTEGER | BYTE | 4 |
RGBA16UI | RGBA_INTEGER | UNSIGNED_SHORT | 8 |
RGBA16I | RGBA_INTEGER | SHORT | 8 |
RGBA32UI | RGBA_INTEGER | UNSIGNED_INT | 16 |
RGBA32I | RGBA_INTEGER | INT | 16 |
RGB10_A2UI | RGBA_INTEGER | UNSIGNED_INT_2_10_10_10_REV | 4 |
RGB8 RGB565 SRGB8 | RGB | UNSIGNED_BYTE | 3 |
RGB8_SNORM | RGB | BYTE | 3 |
RGB565 | RGB | UNSIGNED_SHORT_5_6_5 | 2 |
R11F_G11F_B10F | RGB | UNSIGNED_INT_10F_11F_11F_REV | 4 |
RGB9_E5 | RGB | UNSIGNED_INT_5_9_9_9_REV | 4 |
RGB16F R11F_G11F_B10F RGB9_E5 | RGB | HALF_FLOAT | 6 |
RGB32F RGB16F R11F_G11F_B10F RGB9_E5 | RGB | FLOAT | 12 |
RGB8UI | RGB_INTEGER | UNSIGNED_BYTE | 3 |
RGB8I | RGB_INTEGER | BYTE | 3 |
RGB16UI | RGB_INTEGER | UNSIGNED_SHORT | 6 |
RGB16I | RGB_INTEGER | SHORT | 6 |
RGB32UI | RGB_INTEGER | UNSIGNED_INT | 12 |
RGB32I | RGB_INTEGER | INT | 12 |
RG8 | RG | UNSIGNED_BYTE | 2 |
RG8_SNORM | RG | BYTE | 2 |
RG16F | RG | HALF_FLOAT | 4 |
RG32F RG16F | RG | FLOAT | 8 |
RG8UI | RG_INTEGER | UNSIGNED_BYTE | 2 |
RG8I | RG_INTEGER | BYTE | 2 |
RG16UI | RG_INTEGER | UNSIGNED_SHORT | 4 |
RG16I | RG_INTEGER | SHORT | 4 |
RG32UI | RG_INTEGER | UNSIGNED_INT | 8 |
RG32I | RG_INTEGER | INT | 8 |
R8 | RED | UNSIGNED_BYTE | 1 |
R8_SNORM | RED | BYTE | 1 |
R16F | RED | HALF_FLOAT | 2 |
R32F R16F | RED | FLOAT | 4 |
R8UI | RED_INTEGER | UNSIGNED_BYTE | 1 |
R8I | RED_INTEGER | BYTE | 1 |
R16UI | RED_INTEGER | UNSIGNED_SHORT | 2 |
R16I | RED_INTEGER | SHORT | 2 |
R32UI | RED_INTEGER | UNSIGNED_INT | 4 |
R32I | RED_INTEGER | INT | 4 |
DEPTH_COMPONENT16 | DEPTH_COMPONENT | UNSIGNED_SHORT | 2 |
DEPTH_COMPONENT24 DEPTH_COMPONENT16 | DEPTH_COMPONENT | UNSIGNED_INT | 4 |
DEPTH_COMPONENT32F | DEPTH_COMPONENT | FLOAT | 4 |
DEPTH24_STENCIL8 | DEPTH_STENCIL | UNSIGNED_INT_24_8 | 4 |
DEPTH32F_STENCIL8 | DEPTH_STENCIL | FLOAT_32_UNSIGNED_INT_24_8_REV | 8 |
RGBA | RGBA | UNSIGNED_BYTE | 4 |
RGBA | RGBA | UNSIGNED_SHORT_4_4_4_4 | 2 |
RGBA | RGBA | UNSIGNED_SHORT_5_5_5_1 | 2 |
RGB | RGB | UNSIGNED_BYTE | 3 |
RGB | RGB | UNSIGNED_SHORT_5_6_5 | 2 |
LUMINANCE_ALPHA | LUMINANCE_ALPHA | UNSIGNED_BYTE | 2 |
LUMINANCE | LUMINANCE | UNSIGNED_BYTE | 1 |
ALPHA | ALPHA | UNSIGNED_BYTE | 1 |
3x2 픽셀 크기의 R8
텍스처를 만들어 봅시다.
R8
텍스처리므로 각 픽셀마다 Red 채널에 한 개의 값만 가집니다.
지난 글에서 샘플을 가져올 겁니다. 먼저 육면체의 각 면에 전체 텍스처를 사용하기 위해 텍스처 좌표를 수정합시다.
// 육면체의 텍스처 좌표로 버퍼 채우기
function setTexcoords(gl) {
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
// 앞면
0, 0,
0, 1,
1, 0,
1, 0,
0, 1,
1, 1,
...
그런 다음 텍스처 생성 코드를 수정합니다.
// 텍스처 생성
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
-// 1x1 파란색 픽셀로 텍스처 채우기
-gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
- new Uint8Array([0, 0, 255, 255]));
// 3x2 픽셀로 텍스처 채우기
const level = 0;
const internalFormat = gl.R8;
const width = 3;
const height = 2;
const border = 0;
const format = gl.RED;
const type = gl.UNSIGNED_BYTE;
const data = new Uint8Array([
128, 64, 128,
0, 192, 0,
]);
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border,
format, type, data);
// 밉맵을 사용하지 않고 필터링을 수행하지 않도록 설정합니다.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
-// 비동기적으로 이미지 로드
-...
그리고 여기 결과입니다.
이런! 왜 작동하지 않는걸까요?!?!?
자바스크립트 콘솔을 확인하면 이런 오류가 표시됩니다.
WebGL: INVALID_OPERATION: texImage2D: ArrayBufferView not big enough for request
WebGL에는 OpenGL이 처음 만들어졌을 때의 모호한 설정들이 남아있습니다. 컴퓨터는 데이터가 특정 크기일 때 더 빠르게 동작합니다. 예를 들어 한 번에 1 바이트가 아닌 2, 4, 8 바이트를 복사하는 것이 더 빠를 수 있습니다. WebGL은 기본적으로 한 번에 4바이트를 사용하므로 각 데이터 행을 4바이트의 배수로 가정합니다. (마지막 행을 제외하고)
위의 데이터는 행마다 3 바이트로, 총 6 바이트에 불과하지만, WebGL은 첫 번째 행에 대해 4 바이트, 두 번째 행에 대해 3 바이트로, 총 7 바이트를 읽으려 시도합니다. 그래서 오류가 발생하는 것입니다.
다음과 같이 한 번에 1 바이트씩 처리하도록 WebGL에 지시할 수 있습니다.
const alignment = 1;
gl.pixelStorei(gl.UNPACK_ALIGNMENT, alignment);
alignment로 사용할 수 있는 값은 1, 2, 4, 8입니다.
WebGL에서 정렬된 데이터와 비정렬 데이터 사이의 속도 차이는 측정할 수 없을 정도로 작을 것으로 생각됩니다.
이 문제가 초보자들을 헷갈리게 만들지 않으려면 4가 아닌 1을 기본값으로 하면 좋겠지만,
OpenGL과의 호환성을 유지하기 위해 기본값은 동일해야만 했습니다.
이렇게 하면 포팅된 앱이 패딩된 행을 제공하는 경우 변경없이 작동될 겁니다.
아니면 새 앱에서 항상 alignment를 1
로 설정하고 신경쓰지 않아도 됩니다.
이렇게 설정해 주었으니 잘 동작할겁니다.
이제 텍스처에 렌더링하기로 넘어갑시다.