개발/cg

[WebGL] WebGL과 Shader

seungho-dev 2024. 11. 17. 16:39

 

최근 성냥퍼즐 프로젝트에서 사용한 Konva.js와 예전에 사용했던 Three.js 모두

webGL을 쉽게 다룰 수 있도록 만들어진 라이브러리인데

오늘은 웹에서 그래픽을 다루기 위해 설계된 WebGL에 대해서 공부해 보았습니다.

 

개인적인 공부를 위해 작성하는 블로그입니다. 혹시라도 잘못되거나 부족한 부분이 있다면 댓글로 알려주시면 감사하겠습니다.

 

💁🏻 WebGL이란 사실 래스터화 엔진이다.

래스터화(rasterize)란 백터형태의 데이터를 2d 이미지(2차원 배열)로 변환하여 픽셀에 색상을 할당하는 과정을 말한다.

우리가 보는 모니터화면은 GPU내부 메모리, framebuffer에 있는 2차원 배열의 픽셀정보를
가져와 보여주게 되는데 이때 래스터화에 대한 계산이 GPU를 통해 병렬적으로 처리된다.

 

이때 shader래스터화 연산을 GPU에게 병렬처리 시키기 위한 함수이다.

 

래스터화를 위한 함수인 shader에는 두가지 shader가 필요한데 우선 정점의 최종위치를 계산할 정점(vertex) shader와 픽셀의 최종 색상을 계산할 fragment shader가 있어야 한다. GPU에서 실행되는 셰이더는 C/C++처럼 엄격한 타입을 가지는 GLSL로 작성되어져야 하는데 다음과 같이 정의해 사용할 수 있다.

 

코드로 어떻게 구현되는지 알아보자

    // Vertex shader
    const vertexShaderSource = `
        // 속성은 버퍼에서 데이터를 받습니다.
      attribute vec2 a_position;
            // 모든 셰이더는 main 함수를 가집니다.
      void main() {
            // gl_Position은 정점 셰이더가 설정을 담당하는 특수 변수
        gl_Position = vec4(a_position, 0, 1.0);
      }
    `;

    // Fragment shader
    const fragmentShaderSource = `
        // 프래그먼트 셰이더는 기본 정밀도를 가지고 있지 않으므로 하나를 선택해야 합니다.
        // mediump은 좋은 기본값으로 "중간 정밀도"를 의미합니다.
      precision mediump float;

      void main() {
        // gl_FragColor는 프래그먼트 셰이더가 설정을 담당하는 특수 변수
                gl_FragColor = vec4(1, 0, 0.5, 1); // 자주색 반환
      }
    `;

위에서 이렇게 문자열로 정의된 셰이더를 컴파일해서 GPU에 할당하게 된다.

// 셰이더를 생성하고 컴파일하는 함수
function createShader(gl, type, source) {
  const shader = gl.createShader(type); // 셰이더 객체 생성
  gl.shaderSource(shader, source); // 셰이더 소스 코드 할당
  gl.compileShader(shader); // 셰이더 컴파일

  // 셰이더 컴파일 상태 확인
  const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (success) {
    return shader; // 컴파일 성공 시 셰이더 반환
  }

  console.log(gl.getShaderInfoLog(shader)); // 컴파일 실패 시 에러 출력
  gl.deleteShader(shader); // 컴파일 실패 시 셰이더 삭제
}

이제 두 개의 셰이더를 만들고 프로그램으로 연결해줘야 한다.

// 버텍스 및 프래그먼트 셰이더 컴파일
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

// 셰이더 프로그램을 생성하고 연결하는 함수
function createProgram(gl, vertexShader, fragmentShader) {
  const program = gl.createProgram(); // 프로그램 객체 생성
  gl.attachShader(program, vertexShader); // 버텍스 셰이더 연결
  gl.attachShader(program, fragmentShader); // 프래그먼트 셰이더 연결
  gl.linkProgram(program); // 프로그램 링크

    // 셰이더 프로그램 생성 및 링크
  const success = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) {
    return program; // 링크 성공 시 프로그램 반환
  }

  console.log(gl.getProgramInfoLog(program)); // 링크 실패 시 에러 출력
  gl.deleteProgram(program); // 실패 시 프로그램 삭제
}

const program = createProgram(gl, vertexShader, fragmentShader);

이렇게 만들어진 WebGL 프로그램을 사용하려면 canvas의 getContext('webgl')로 가져온 다음

// WebGL 초기화
const canvas = document.querySelector("#glCanvas");
const gl = canvas.getContext("webgl");

// WebGL이 지원되지 않는 경우 에러 처리
if (!gl) {
  console.error("WebGL을 지원하지 않는 브라우저입니다.");
}

// WebGL 프로그램 사용
gl.useProgram(program);

정점을 버퍼에 담아 연결해주면 화면에 그려지게 된다.

// 정점 데이터를 WebGL 버퍼에 전달
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

// 2D 삼각형 좌표 정의
const positions = [
  0, 0,
  0, 0.5,
  0.7, 0
];

// 버퍼에 정점 데이터 로드
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

// 속성 위치 찾기
const positionLocation = gl.getAttribLocation(program, "a_position");

// 속성을 활성화하고 버퍼 데이터 연결
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

// 캔버스 초기화
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 1); // 배경색을 검은색으로 설정
gl.clear(gl.COLOR_BUFFER_BIT); // 캔버스를 초기화

// 삼각형을 그리기
gl.drawArrays(gl.TRIANGLES, 0, 3);

 

참조링크
https://webglfundamentals.org/webgl/lessons/ko/webgl-fundamentals.html

 

WebGL 기초

기초부터 시작하는 WebGL 첫 강의

webglfundamentals.org

 

읽어주셔서 감사합니다. 주말 잘 보내세요~