목차

WebGLFundamentals.org

Fix, Fork, Contribute

WebGL 2개 이상의 텍스처 사용

이 글은 WebGL 이미지 처리에서 이어집니다. 아직 읽지 않았다면 거기부터 시작하는 게 좋습니다.

"어떻게 2개 이상의 텍스처를 사용할 수 있나요?"라는 질문에 답하기 좋은 시점인 것 같군요.

꽤 간단합니다. 하나의 이미지를 그리고 2개의 이미지로 업데이트하는 첫 번째 셰이더에 대한 몇 가지 수업으로 돌아가 봅시다.

먼저 해야할 일은 2개의 이미지를 로딩할 수 있도록 코드를 수정하는 겁니다. 이건 실제로 WebGL이 아니라, HTML5 자바스크립트가 할 일이지만, 우리가 다룰 수 있습니다. 이미지는 비동기적으로 로드되기 때문에 익숙해지는 데에 약간의 시간이 걸릴 수 있습니다.

기본적으로 처리할 수 있는 2가지 방법이 있습니다. 텍스처 없이 실행되고 텍스처가 로드되면 프로그램이 업데이트하도록 코드를 구조화할 수 있는데요. 이후의 글을 위해 해당 메서드를 저장해두겠습니다.

이 경우 그리기 전에 모든 이미지가 로드되는 걸 기다릴 겁니다.

먼저 이미지를 로드하는 코드를 함수로 수정해봅시다. 이는 매우 간단합니다. 새로운 Image 객체를 생성하고, 로드할 URL을 설정한 다음, 이미지 로딩이 끝났을 때 호출할 콜백을 설정합니다.

function loadImage(url, callback) {
  var image = new Image();
  image.src = url;
  image.onload = callback;
  return image;
}

이제 URL 배열을 로드하고 이미지 배열을 생성하는 함수를 만들어 보겠습니다. 먼저 imagesToLoad를 로드할 이미지 개수로 설정합니다. 그런 다음 loadImage에 전달하는 콜백에서 imagesToLoad를 감소시킵니다. imagesToLoad가 0이 되면 모든 이미지가 로드되었고 이미지 배열을 콜백에 전달합니다.

function loadImages(urls, callback) {
  var images = [];
  var imagesToLoad = urls.length;

  // 이미지 로딩이 끝날 때마다 호출
  var onImageLoad = function() {
    --imagesToLoad;
    // 모든 이미지가 로드되면 콜백 호출
    if (imagesToLoad == 0) {
      callback(images);
    }
  };

  for (var ii = 0; ii < imagesToLoad; ++ii) {
    var image = loadImage(urls[ii], onImageLoad);
    images.push(image);
  }
}

이제 이렇게 loadImages를 호출합니다.

function main() {
  loadImages([
    "resources/leaves.jpg",
    "resources/star.jpg",
  ], render);
}

다음으로 2개의 텍스처를 사용하도록 셰이더를 수정합니다. 이 경우 하나의 텍스처에 다른 텍스처를 곱할 겁니다.

<script id="fragment-shader-2d" type="x-shader/x-fragment">
precision mediump float;

// 텍스처
uniform sampler2D u_image0;
uniform sampler2D u_image1;

// 정점 셰이더에서 전달된 텍스처 좌표
varying vec2 v_texCoord;

void main() {
   vec4 color0 = texture2D(u_image0, v_texCoord);
   vec4 color1 = texture2D(u_image1, v_texCoord);
   gl_FragColor = color0 * color1;
}
</script>

2개의 WebGL 텍스처 객체를 생성해야 합니다.

  // 2개의 텍스처 생성
  var textures = [];
  for (var ii = 0; ii < 2; ++ii) {
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);

    // 어떤 크기의 이미지도 렌더할 수 있도록 매개변수 설정
    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.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

    // 텍스처에 이미지 업로드
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, images[ii]);

    // 텍스처 배열에 텍스처 추가
    textures.push(texture);
  }

WebGL에는 "텍스처 유닛"이 있습니다. 이는 텍스처에 대한 레퍼런스 배열이라 생각할 수 있습니다. 각 샘플러에 대해 사용할 텍스처 유닛을 셰이더에 알려줍니다.

  // 샘플러 위치 탐색
  var u_image0Location = gl.getUniformLocation(program, "u_image0");
  var u_image1Location = gl.getUniformLocation(program, "u_image1");

  ...

  // 함께 렌더링할 텍스처 유닛 설정
  gl.uniform1i(u_image0Location, 0);  // 텍스처 유닛 0
  gl.uniform1i(u_image1Location, 1);  // 텍스처 유닛 1

그런 다음 텍스처를 각각의 텍스처 유닛에 할당해야 합니다.

  // 특정 텍스처를 사용하도록 각각의 텍스처 유닛 설정
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, textures[0]);
  gl.activeTexture(gl.TEXTURE1);
  gl.bindTexture(gl.TEXTURE_2D, textures[1]);

로딩중인 2개의 이미지는 다음과 같습니다.

그리고 여기 WebGL을 사용하여 곱한 결과입니다.

몇 가지 살펴봐야 할 것이 있습니다.

텍스처 유닛을 생각하는 간단한 방법: 모든 텍스처 함수는 "활성 텍스처 유닛"에서 작동한다. "활성 텍스처 유닛"은 작업하려는 텍스처 유닛의 전역 변수입니다. 각 텍스처 유닛은 2가지 대상을 가지는데요. TEXTURE_2D 대상과 TEXTURE_CUBE_MAP 대상입니다. 모든 텍스처 함수는 현재 활성 텍스처 유닛에서 지정된 대상과 함께 작동합니다. 자바스크립트로 WebGL을 구현한다면 다음과 같을 겁니다.

var getContext = function() {
  var textureUnits = [
    { TEXTURE_2D: ??, TEXTURE_CUBE_MAP: ?? },
    { TEXTURE_2D: ??, TEXTURE_CUBE_MAP: ?? },
    { TEXTURE_2D: ??, TEXTURE_CUBE_MAP: ?? },
    { TEXTURE_2D: ??, TEXTURE_CUBE_MAP: ?? },
    { TEXTURE_2D: ??, TEXTURE_CUBE_MAP: ?? },
    ...
  ];
  var activeTextureUnit = 0;

  var activeTexture = function(unit) {
    // "unit enum"을 인덱스로 변환
    var index = unit - gl.TEXTURE0;
    // 활성 텍스처 유닛 설정
    activeTextureUnit = index;
  };

  var bindTexture = function(target, texture) {
    // 활성 텍스처 유닛의 대상에 대한 텍스처 설정
    textureUnits[activeTextureUnit][target] = texture;
  };

  var texImage2D = function(target, ... args ...) {
    // 활성 텍스처 유닛의 현재 텍스처에서 texImage2D 호출
    var texture = textureUnits[activeTextureUnit][target];
    texture.image2D(...args...);
  };

  // WebGL API 반환
  return {
    activeTexture: activeTexture,
    bindTexture: bindTexture,
    texImage2D: texImage2D,
  }
};

셰이더는 인덱스를 텍스처 유닛으로 가져옵니다. 이 두 줄이 더 명료하게 만들어주길 바랍니다.

  gl.uniform1i(u_image0Location, 0);  // 텍스처 유닛 0
  gl.uniform1i(u_image1Location, 1);  // 텍스처 유닛 1

한 가지 유의해야할 점은, 유니폼을 사용할 때 텍스처 유닛에 대한 인덱스를 사용하지만, gl.activeTexture를 호출할 때는 특수 상수 gl.TEXTURE0, gl.TEXTURE1 등을 전달해야 한다는 겁니다. 다행히 상수는 연속적이므로 이렇게 대체할 수 있습니다.

  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, textures[0]);
  gl.activeTexture(gl.TEXTURE1);
  gl.bindTexture(gl.TEXTURE_2D, textures[1]);

이렇게 할 수도 있고,

  gl.activeTexture(gl.TEXTURE0 + 0);
  gl.bindTexture(gl.TEXTURE_2D, textures[0]);
  gl.activeTexture(gl.TEXTURE0 + 1);
  gl.bindTexture(gl.TEXTURE_2D, textures[1]);

혹은 이렇게,

  for (var ii = 0; ii < 2; ++ii) {
    gl.activeTexture(gl.TEXTURE0 + ii);
    gl.bindTexture(gl.TEXTURE_2D, textures[ii]);
  }

이 글이 WebGL의 단일 그리기 호출에서 여러 텍스처를 사용하는 방법을 설명하는 데에 도움이 되길 바랍니다.

이슈/버그는? Github에 이슈를 만들어주세요.
코드 블록은 <pre><code>여기에 코드 입력</code></pre>를 사용해주세요
comments powered by Disqus