본문 바로가기

WebGL

WebGL Fundamentals > Rendering to a Texture

https://webglfundamentals.org/webgl/lessons/webgl-render-to-texture.html

 

WebGL Rendering to a Texture

How to render to a texture.

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

 

 

Rendering to a Texture

WebGL을 사용하여 텍스처에 렌더링하는 방법

먼저 특정한 크기의 텍스처를 생성한다.

// 렌더링을 위해 생성
const targetTextureWidth = 256;
const targetTextureHeight = 256;
const targetTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
 
{
  // 레벨 0의 크기와 포맷 정의
  const level = 0;
  const internalFormat = gl.RGBA;
  const border = 0;
  const format = gl.RGBA;
  const type = gl.UNSIGNED_BYTE;
  const data = null;
  gl.texImage2D(
    gl.TEXTURE_2D,
    level,
    internalFormat,
    targetTextureWidth,
    targetTextureHeight,
    border,
    format,
    type,
    data
  );
 
  // 밉이 필요하지 않도록 필터링 설정
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  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);
}

 

어떤 데이터도 제공할 필요가 없어 data를 null로 설정한다.

다음은 프레임 버퍼를 생성한다.

프레임 버퍼는 그냥 어태치먼트 모음이다.
어태치먼트는 텍스처나 렌더 버퍼이다.
렌더 버퍼는 텍스처와 매우 유사하지만 텍스처가 지원하지 않는 포맷과 옵션을 지원한다.
또한 텍스처와 달리 셰이더에 대한 입력으로 렌더 버퍼를 직접 사용할 수 없다.

프레임 버퍼를 생성하고 텍스처에 첨부한다.

// 프레임 버퍼 생성 및 바인딩
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
 
// 첫 번째 색상 어태치먼트로 텍스처 첨부
const attachmentPoint = gl.COLOR_ATTACHMENT0;
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);

 

텍스처와 버퍼처럼, 프레임 버퍼 생성 후 FRAMEBUFFER 바인드 포인트에 바인딩 해야 한다.

바인딩한 이후는 프레임 버퍼와 관련된 모든 함수들은 프레임 버퍼가 바인딩된 곳을 참조한다.

 

프레임 버퍼가 바인딩된 상태에서 gl.clear, gl.drawArrays, gl.drawElements를 호출할 때마다 WebGL은 캔버스 대신 텍스처에 렌더링한다.

텍스처에 먼저 렌더링하고 다시 캔버스에 렌더링할 수 있도록 설정한다.

// 텍스처가 캔버스와 다른 종횡비를 가지기 때문에, 투영 행렬을 계산하기 위해서는 매개변수로 aspect를 전달
function drawCube(aspect) {
  // 프로그램(셰이더 쌍) 사용 지시
  gl.useProgram(program);
 
  // 위치 속성 활성화
  gl.enableVertexAttribArray(positionLocation);
 
  // 위치 버퍼 바인딩
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
 
  // positionBuffer(ARRAY_BUFFER)에서 데이터 가져오는 방법을 위치 속성에 지시
  var size = 3;          // 반복마다 3개의 컴포넌트
  var type = gl.FLOAT;   // 데이터는 32비트 부동 소수점
  var normalize = false; // 데이터 정규화 안 함
  var stride = 0;        // 0 = 다음 위치를 가져오기 위해 반복마다 size * sizeof(type) 만큼 앞으로 이동
  var offset = 0;        // 버퍼의 처음부터 시작
  gl.vertexAttribPointer(
      positionLocation, size, type, normalize, stride, offset)
 
  // 텍스처 좌표 속성 활성화
  gl.enableVertexAttribArray(texcoordLocation);
 
  // 텍스처 좌표 버퍼 바인딩
  gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
 
  // texcoordBuffer(ARRAY_BUFFER)에서 데이터 가져오는 방법을 텍스처 좌표 속성에 지시
  var size = 2;          // 반복마다 2개의 컴포넌트
  var type = gl.FLOAT;   // 데이터는 32비트 부동 소수점
  var normalize = false; // 데이터 정규화 안 함
  var stride = 0;        // 0 = 다음 위치를 가져오기 위해 반복마다 size * sizeof(type) 만큼 앞으로 이동
  var offset = 0;        // 버퍼의 처음부터 시작
  gl.vertexAttribPointer(
      texcoordLocation, size, type, normalize, stride, offset)
 
  // 투영 행렬 계산
  var projectionMatrix =
      m4.perspective(fieldOfViewRadians, aspect, 1, 2000);
 
  var cameraPosition = [0, 0, 2];
  var up = [0, 1, 0];
  var target = [0, 0, 0];
 
  // lookAt을 사용하여 카메라 행렬 계산
  var cameraMatrix = m4.lookAt(cameraPosition, target, up);
 
  // 카메라 행렬로 뷰 행렬 만들기
  var viewMatrix = m4.inverse(cameraMatrix);
 
  var viewProjectionMatrix = m4.multiply(projectionMatrix, viewMatrix);
 
  var matrix = m4.xRotate(viewProjectionMatrix, modelXRotationRadians);
  matrix = m4.yRotate(matrix, modelYRotationRadians);
 
  // 행렬 설정
  gl.uniformMatrix4fv(matrixLocation, false, matrix);
 
  // u_texture에 대해 텍스처 유닛 0을 사용하도록 셰이더에 지시
  gl.uniform1i(textureLocation, 0);
 
  // 지오메트리 그리기
  gl.drawArrays(gl.TRIANGLES, 0, 6 * 6);
}

 

텍스처가 캔버스와 다른 종횡비를 가지기 때문에, 투영 행렬을 계산하기 위해서는 매개변수로 aspect를 전달해야 한다.

렌더링 함수를 호출

// 장면 그리기
function drawScene(time) {
 
  //...
 
  {
    // 프레임 버퍼를 바인딩하여 대상 텍스처에 렌더링
    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
 
    // 3x2 텍스처로 큐브 렌더링
    gl.bindTexture(gl.TEXTURE_2D, texture);
 
    // WebGL에 클립 공간에서 픽셀로 변환하는 방법 지시
    gl.viewport(0, 0, targetTextureWidth, targetTextureHeight);
 
    // 어태치먼트 지우기
    gl.clearColor(0, 0, 1, 1);   // 파란색으로 초기화
    gl.clear(gl.COLOR_BUFFER_BIT| gl.DEPTH_BUFFER_BIT);
 
    const aspect = targetTextureWidth / targetTextureHeight;
    drawCube(aspect)
  }
 
  {
    // 캔버스에 렌더링
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
 
    // 방금 렌더링한 텍스처로 큐브 렌더링
    gl.bindTexture(gl.TEXTURE_2D, targetTexture);
 
    // 클립 공간에서 픽셀로 변환하는 방법을 WebGL에 지시
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
 
    // 캔버스와 깊이 버퍼 지우기
    gl.clearColor(1, 1, 1, 1);   // 흰색으로 초기화
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
 
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    drawCube(aspect)
  }
 
  requestAnimationFrame(drawScene);
}

 

gl.viewport를 호출하여 렌더링하려는 대상의 크기로 설정하는 것은 매우 중요하다.

처음에는 텍스처에 렌더링하므로 텍스처를 덮도록 뷰포트를 설정한다.

그리고 그다음에는 캔버스에 렌더링 하므로 캔버스를 덮도록 뷰포트를 설정해야 한다.

 

마찬가지로 투영 행렬을 계산할 때 렌더링하려는 대상에 알맞은 종횡비를 사용해야 한다.

이 두 가지는 누락하기 쉽기 때문에 아래와 같이 두 가지를 수행하는 함수를 만들면 실수를 방지하기 좋다.

function bindFramebufferAndSetViewport(fb, width, height) {
   gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
   gl.viewport(0, 0, width, height);
}

 

이 함수만을 이용하여 렌더링 대상을 변경하면 누락할 일이 없다.

 

 

 

 

프레임 버퍼에 깊이 버퍼가 없다.

이는 프레임 버퍼에는 깊이 테스트가 없어 3D가 작동하지 않는다는 것을 의미한다.

깊이 버퍼를 추가하기 위해 하나를 생성하여 프레임 버퍼에 연결해야 한다.

// 깊이 렌더 버퍼 생성
const depthBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
 
// 대상 텍스처와 같은 크기로 깊이 버퍼 만들기
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, targetTextureWidth, targetTextureHeight);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);

 

깊이 버퍼가 프레임 버퍼에 연결되면 3D가 올바르게 작동할 수 있다.

WebGL이 3개의 어태치먼트 조합 동작만 보장한다는 사실을 기억해야 한다.

  • COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE texture
  • COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE texture + DEPTH_ATTACHMENT = DEPTH_COMPONENT16 renderbuffer
  • COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE texture + DEPTH_STENCIL_ATTACHMENT = DEPTH_STENCIL renderbuffer

 

다른 조합의 경우 사용자의 시스템/GPU/드라이버/브라우저가 해당 조합을 지원하는지 확인해야 한다.

프레임 버퍼를 만들었는지 확인하려면, 어태치먼트를 생성하고 첨부한 다음 아래와 같이 호출한다.

var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);

 

상태가 FRAMEBUFFER_COMPLETE라면 해당 어태치먼트 조합은 사용 가능한 것이다.

 

 

 

 

 

 

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

 

WebGL_Fundamentals

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

myoungmin.github.io