본문 바로가기

WebGL

WebGL Fundamentals > WebGL Textures

https://webglfundamentals.org/webgl/lessons/webgl-3d-textures.html

 

WebGL Textures

How textures work in WebGL

webglfundamentals.org

https://github.com/Myoungmin/WebGL_Fundamentals

 

GitHub - Myoungmin/WebGL_Fundamentals: WebGL 학습 프로젝트

WebGL 학습 프로젝트. Contribute to Myoungmin/WebGL_Fundamentals development by creating an account on GitHub.

github.com

 

 

WebGL Textures

텍스처 좌표를 프래그먼트 셰이더에 전달하기

attribute로 받고, varying으로 넘겨준다.

 

attribute vec4 a_position;
attribute vec2 a_texcoord;
 
uniform mat4 u_matrix;
 
varying vec2 v_texcoord;
 
void main() {
  // 위치에 행렬 곱하기
  gl_Position = u_matrix * a_position;
 
  // 텍스처 좌표를 프래그먼트 셰이더로 전달
  v_texcoord = a_texcoord;
}

프래그먼트 셰이더에서 텍스처를 참조하게 해주는 uniform sampler2D를 선언, 정점 셰이더에서 전달된 텍스처 좌표를 사용, 해당 텍스처에서 색상을 찾기 위해 texture2D를 호출

precision mediump float;
 
// 정점 셰이더에서 전달됩니다.
varying vec2 v_texcoord;
 
// 텍스처
uniform sampler2D u_texture;
 
void main() {
   gl_FragColor = texture2D(u_texture, v_texcoord);
}

자바스크립트에서 텍스처 좌표를 설정

정점셰이더의 attribute vec2 a_texcoord; 로 설정한 값이 넘어간다.

 

// 정점 데이터가 어디로 가야하는지 탐색
var positionLocation = gl.getAttribLocation(program, "a_position");
var texcoordLocation = gl.getAttribLocation(program, "a_texcoord");
 
//...
 
// 텍스처 좌표에 대한 버퍼 생성
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(texcoordLocation);
 
// 텍스처 좌표는 부동 소수점으로 제공
gl.vertexAttribPointer(texcoordLocation, 2, gl.FLOAT, false, 0, 0);
 
// 텍스처 좌표 설정
setTexcoords(gl);

// 전체 텍스처를 'F'의 각 사각형에 매핑하여 사용하는 좌표 설정
// F에 대한 텍스처 좌표로 버퍼 채우기
function setTexcoords(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
        // 왼쪽 열 앞면
        0, 0,
        0, 1,
        1, 0,
        0, 1,
        1, 1,
        1, 0,
 
        // 상단 획 앞면
        0, 0,
        0, 1,
        1, 0,
        0, 1,
        1, 1,
        1, 0,
        ...
       ]),
       gl.STATIC_DRAW);

텍스처 이미지 로드하기

브라우저가 다운로드하는데 시간이 걸리기 때문에 이미지를 로드하는 것은 비동기적으로 발생한다.

일반적으로 이에 대한 2가지 해결책이 있다.

  1. 텍스처가 다운로드될 때까지 기다리는 코드를 만들고 다운로드 후 그리기 시작
  2. 이미지가 다운로드될 때까지 사용할 텍스처를 만드는 것

두 번째 방법을 사용하면 다운로드를 기다리지 않고 바로 렌더링을 시작할 수 있다.

 

그리고 이미지가 다운로드 되면 이미지를 텍스처에 복사한다.

gl.texImage2D를 사용하여 이미지를 텍스처에 복사한다.

 

// 텍스처 생성
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])
);
 
// 비동기적으로 이미지 로드
var image = new Image();
image.src = "resources/f-texture.png";
image.addEventListener('load', function() {
  // 이제 이미지가 로드되었기 때문에 텍스처로 복사
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,gl.UNSIGNED_BYTE, image);
  gl.generateMipmap(gl.TEXTURE_2D);
});

 

WebGL은 높이와 너비 모두 2의 거듭제곱이 아닌 텍스처에 대해 엄격한 제한을 가진다.

텍스처의 가로 세로가 2의 거듭제곱이 아니면 텍스처 표시에 실패한다.

셰이더에서 texture2D가 호출되고 참조된 텍스처가 제대로 설정되지 않은 경우 WebGL은 검은색(0, 0, 0, 1)을 사용한다.

 

자바스크립트 콘솔이나 웹 콘솔을 열어보면, 브라우저에 따라 이렇게 문제를 지적하는 오류가 나타난다.

WebGL: INVALID_OPERATION: generateMipmap: level 0 not power of 2 or not all the same size.
WebGL: drawArrays: texture bound to texture unit 0 is not renderable.   It maybe non-power-of-2 and have incompatible texture filtering or is not 'texture complete'.


이것을 해결하기 위해서는 wrap mode를 CLAMP_TO_EDGE로 설정하고 필터링을 LINEAR나 NEAREST로 설정하여 밉 매핑을 꺼야한다.

 

2의 거듭제곱인지 알려줄 함수

function isPowerOf2(value) {
  return (value & (value - 1)) == 0;
}

 

2의 거듭제곱인지 확인하여 분기 생성

// 비동기적 이미지 로드
var image = new Image();
image.src = "resources/keyboard.jpg";
image.addEventListener('load', function() {
  // 이미지가 로드 후 텍스처에 복사
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,gl.UNSIGNED_BYTE, image);
 
  // 이미지의 너비와 높이 모두 2의 거듭제곱인지 확인
  if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
     // 2의 거듭제곱이면 밉 생성
     gl.generateMipmap(gl.TEXTURE_2D);
  } else {
     // 2의 거듭제곱이 아니면 밉을 끄고 가장자리에 고정되도록 래핑 설정
     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);
     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  }
}

 

 

 

 

2의 거듭제곱인 조건 이외에도 WebGL이 "texture complete" 하지 않다면 렌더링되지 않는다.

 

Texture Complete

1. 필터링을 설정했을 경우 TEXTURE_MIN_FILTER를 LINEAR나 NEAREST로 설정하는 첫 번째 밉 레벨만 사용한다.

 

2. 밉을 사용하는 경우 정확한 크기를 필요로 하며 모든 항목을 1x1 크기로 줄여서 제공해야 한다.

이걸 수행하는 가장 쉬운 방법은 gl.generateMipmap을 호출하는 것이다. 

그렇지 않고 자신의 밉을 제공하는 경우 모든 항목을 제공해야 한다.

 

3. 텍스처의 너비와 높이가 2의 거듭제곱이 아닌 경우, 위에서 언급한 것처럼 TEXTURE_MIN_FILTER를 LINEAR나 NEAREST로 설정하고, TEXTURE_WRAP_S와 TEXTURE_WRAP_T를 CLAMP_TO_EDGE로 설정해야 한다.

 

이 중 하나라도 만족하지 않다면, 텍스처의 값을 가져올 때 셰이더에서 검은색(0, 0, 0, 1)을 얻게된다.

 

 

 

 

 

큐브의 각 면에 다른 이미지를 적용하는 방법

모든 이미지들을 하나의 텍스처에 넣고, 텍스처 좌표를 사용하여 텍스처의 다른 부분을 큐브의 각 면에 매핑한다.

거의 모든 고성능 앱(게임)에서 이러한 방법을 사용한다.

 

큐브의 각 면에 다른 텍스처 좌표 세트를 사용

 // 왼쪽 상단 이미지 선택
    0   , 0  ,
    0   , 0.5,
    0.25, 0  ,
    0   , 0.5,
    0.25, 0.5,
    0.25, 0  ,
    // 중간 상단 이미지 선택
    0.25, 0  ,
    0.5 , 0  ,
    0.25, 0.5,
    0.25, 0.5,
    0.5 , 0  ,
    0.5 , 0.5,
    // 오른쪽 상단 이미지 선택
    0.5 , 0  ,
    0.5 , 0.5,
    0.75, 0  ,
    0.5 , 0.5,
    0.75, 0.5,
    0.75, 0  ,
    // 왼쪽 하단 이미지 선택
    0   , 0.5,
    0.25, 0.5,
    0   , 1  ,
    0   , 1  ,
    0.25, 0.5,
    0.25, 1  ,
    // 중간 하단 이미지 선택
    0.25, 0.5,
    0.25, 1  ,
    0.5 , 0.5,
    0.25, 1  ,
    0.5 , 1  ,
    0.5 , 0.5,
    // 오른쪽 하단 이미지 선택
    0.5 , 0.5,
    0.75, 0.5,
    0.5 , 1  ,
    0.5 , 1  ,
    0.75, 0.5,
    0.75, 1  ,

 

하나의 텍스처를 사용하여 여러 이미지를 적용하는 이 방식은 텍스처 아틀라스라고 불린다.

이 방법이 다른 이미지를 적용하기에 좋은 이유

  1. 로드하는 텍스처가 하나뿐이기 때문에 셰이더는 하나의 텍스처만을 참조하여 단순하게 유지된다. 
  2. 다른 이미지당 여러번 그리기 호출이 필요한 것이 아니라 단 한번의 렌더링 호출만 필요하다

 

 

 

 

 

 

 

https://myoungmin.github.io/WebGL_Fundamentals/

 

WebGL_Fundamentals

WebGL이란? WebGL은 Web Graphics Library의 약자로 웹상에서 2D 및 3D 그래픽을 렌더링하기 위한 로우 레벨 Javascript API. 학습내용 블로그 정리

myoungmin.github.io

 

'WebGL' 카테고리의 다른 글

WebGL Fundamentals > Using 2 or More Textures  (0) 2022.07.08
WebGL Fundamentals > Data Textures  (0) 2022.07.08
WebGL Fundamentals > Scene Graph  (0) 2022.07.06
WebGL Fundamentals > Animation  (0) 2022.07.05
WebGL Fundamentals > WebGL 3D : Cameras  (0) 2022.07.03