WebGPU를 이용한 삼각형 그리기

WebGPU는 GPU를 이용해 그래픽을 렌더링하거나 범용적인 연산을 실행할 수 있는 웹 API입니다. 이 글은 WebGPU를 이용해 간단한 삼각형을 렌더링하는 코드를 살펴봅니다.

WebGL과 마찬가지로 그래픽을 출력할 Canvas가 필요합니다. HTML 파일에 Canvas 요소를 추가해야 합니다. 추가했다고 치고…

WebGPU의 초기화가 필요한데, WebGPU의 초기화는 비동기적으로 실행되므로 별도의 비동기 함수로 처리합니다.

async function main() {
  // 앞으로 코드는 모두 여기에 추가됨
}

await main();

먼저 GPU 디바이스 객체를 얻어옵니다.

  const adapter = await navigator.gpu.requestAdapter();
  const device = await adapter.requestDevice();

이제 이 device 객체를 통해 그래픽을 렌더링할텐데, 아래의 코드를 통해 렌더링 결과가 출력된 캔버스와 연결해야 합니다.

  const canvas = document.querySelector('canvas');
  const context = canvas.getContext('webgpu');
  const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
  context.configure({
    device,
    format: presentationFormat,
  });

WebGL과 마찬가지로 WebGPU 역시 Shader 코드가 필요합니다. 하지만 쉐이더 언어가 다르며 WebGL은 GLSL이고 WebGPU는 WGSL(위그실)입니다. WebGL에서는 Vertex Shader와 Fragment Shader가 필요했던 것처럼 WebGPU 역시도 마찬가지입니다. 해당 코드는 다음과 같습니다.

  const module = device.createShaderModule({
    label: 'our hardcoded red triangle shaders',
    code: /* wgsl */ `
      @vertex fn vs(
        @builtin(vertex_index) vertexIndex : u32
      ) -> @builtin(position) vec4f {
        let pos = array(
          vec3f(   0,  .5, 0), 
          vec3f( -.5, -.5, 0), 
          vec3f(  .5, -.5, 0)  
        );
 
        return vec4f(pos[vertexIndex], 1.0);
      }
 
      @fragment fn fs() -> @location(0) vec4f {
        return vec4f(1.0, 0.0, 0.0, 1.0);
      }
    `,
  });

이제 쉐이더 코드를 실행할 렌더 파이프 라인 객체를 생성합니다.

  const pipeline = device.createRenderPipeline({
    label: 'our hardcoded red triangle pipeline',
    layout: 'auto',
    vertex: {
      module,
      entryPoint: 'vs',
    },
    fragment: {
      module,
      entryPoint: 'fs',
      targets: [{ format: presentationFormat }],
    },
  });

다음은 실제 렌더링을 위해 필요한 설정값(Canvas의 텍스쳐뷰, 배경색상 등)을 갖는 디스크립터 객체를 생성합니다.

  const renderPassDescriptor = {
    label: 'our basic canvas renderPass',
    colorAttachments: [
      {
        view: context.getCurrentTexture().createView(),
        clearValue: [0.3, 0.3, 0.3, 1],
        loadOp: 'clear',
        storeOp: 'store',
      },
    ],
  };  

이제 실제 렌더링을 위한 구체적인 명령을 인코딩하기 위한 객체를 생성하고 렌더링 명령을 지정합니다.

  const encoder = device.createCommandEncoder({ label: 'our encoder' });

  const pass = encoder.beginRenderPass(renderPassDescriptor);
  pass.setPipeline(pipeline);
  pass.draw(3);  // 정점 셰이더를 3번 호출
  pass.end();

위의 코드는 명령을 지정만 했을 뿐이고 실제 실행을 위한 코드는 다음과 같습니다.

  const commandBuffer = encoder.finish();
  device.queue.submit([commandBuffer]);

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다