프로그래밍/JavaScript

[WebGL] Varying 과 WebGL의 작동 방식

blu3fishez 2023. 3. 16. 17:26

WebGL 의 작동 방식

GPU에서 그래픽 처리를 할 때 기본적으로 2가지 파트로 구동이 됩니다.

  1. 정점의 위치 값 등을 클립 공간으로 변환 처리
  2. 1의 부분을 기반으로 픽셀을 채움

이 과정을 호출하는 webgl 함수가 gl.drawArray(type, offset, count) 입니다.

우리가 설정한 버텍스 셰이더(이하 vsh) 에서 사전정의된 gl_Position 이라는 변수에 값을 넣는다면,
해당 값을 기준으로 실제 캔버스의 클립공간의 위치에 정의시킵니다.

gl.drayArray 에서 우리는 type에 따라 GPU는 채울 픽셀의 경계선을 정합니다.

예를들어, gl.TRIANGLES 의 경우 삼각형임을 나타내므로, 정점 3개를 생성할 때마다 이 3 개의 정점으로 삼각형이 되는지 확인한 다음,

각 부분을 픽셀로 그립니다. 이 픽셀로 그리는 과정을 래스터화라고 합니다.

프래그먼트 셰이더(이하 fsh)는 채워지는 각 픽셀별로 호출되어 어떤 색으로 채울지 정합니다. 실제로 우리가 채워지는 색상 값은 glsl에서 사전 정의된 gl_FragColor 라는 변수에 대입함으로써 그 색상이 정의됩니다.

Varying

그렇다면 모든 pixel이 정의가 얼만큼 될줄 알고 우리가 해당 색상 값을 어떻게 정할 수 있을까요?

네 같은 삼각형이여도 단순히 단색으로 채울 수도 있지만, 기본적으로는 우리가 vsh 로부터 값을 fsh에 보내줄 수 있습니다. 이를 우리는 Varying 이라고 정해진 타입으로 변수를 만들어 넣어주면 fsh에서 동일하게 정의하여 색상값을 받아올 수 있습니다.

Varying은 fsh에서 래스터화 시, 주어진 각 정점별로 정의된 각각의 값들을 보간하여 각 픽셀에 값을 변환하여 불러옵니다.

Varying 값이 각 정점의 정보를 기반으로 각 픽셀 당 색상 값을 보간하여 래스터화한 모습

예를들어보면 아래와 같은 경우 캔버스에 정의된 위치값에 따라 각 픽셀의 모든 색상이 변화될 것입니다.

정점 개수만큼 정의된 varying 값이 모든 픽셀 값에 대해서 값이 정의가 되었지만, 실제 값들은 수많은 픽셀 별로 대응이 됩니다. 그래서 "varying"이라고 정한 겁니다.

varying vec4 v_color;

void main() {
    // ...
    v_color = gl_Position * 0.5 + 0.5;
}
varying vec4 v_color;

void main() {
    //...
    gl_FragColor = v_color;
}

위 예제에서 varying은 포지션 값 을 기반으로 픽셀의 색을 정했다면, 정점을 기준으로 한 아예 새로운 값을 varying에 넣어줄 수도 있습니다.

버퍼를 통해 새로운 속성 값을 추가해주고, 이 해당 값을 varying에 대입하면 어떻게 될까요?

attribute vec2 a_position;
attribute vec4 a_color;

varying vec4 v_color;

void main() {
  // ...
  // 속성에서 베링으로 색상 복사
  v_color = a_color;
}

각 정점별로 정의된 3개의 색상값과 위치에 따라 색상의 강도가 보간되어 나타나는 겁니다. (위의 이미지 예시)

위에 설명된 이미지 예시는 아래 예제코드를 참고하시면 도움이 될 겁니다.

import { createProgram, createShader } from "./webglutility";

const vsh = `
attribute vec2 position;
attribute vec4 color;
uniform vec2 resolutionPosition;

varying vec4 v_color;

void main() {
    vec2 positionRatio = position / resolutionPosition;
    vec2 biasedPositionRatio = 2.0 * positionRatio;
    vec2 clipSpacePosition = biasedPositionRatio - 1.0;

    gl_Position = vec4(clipSpacePosition, 0, 1);
    v_color = color;
}
`;

const fsh = `
precision mediump float;

varying vec4 v_color;

void main(){
    gl_FragColor = v_color;
}
`;

(function main() {
    const gl = document.querySelector("#glcanvas")?.getContext("webgl");
    if (!gl) {
        alert("no canvas in page");
        return;
    }

    const program = createProgram(
        gl,
        createShader(gl, vsh, gl.VERTEX_SHADER),
        createShader(gl, fsh, gl.FRAGMENT_SHADER)
    );

    if (!program) {
        console.error("program compiling error");
        return null;
    }

    drawClipSpace(gl, program);
    console.log("finished job successfully");
    return 0;
})();

function drawClipSpace(gl, program) {
    const position = gl.getAttribLocation(program, "position");
    const color = gl.getAttribLocation(program, "color");
    const resolutionPosition = gl.getUniformLocation(program, "resolutionPosition");
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    // 캔버스 지우기
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.enableVertexAttribArray(position);
    gl.enableVertexAttribArray(color);

    const positionArr = [
        100.0, 200.0,
        400.0, 400.0,
        500.0, 100.0,
    ];
    const colorArr = [
        Math.random(), Math.random(), Math.random(), 1,
        Math.random(), Math.random(), Math.random(), 1,
        Math.random(), Math.random(), Math.random(), 1,
    ]

    const positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    gl.bufferData(
        gl.ARRAY_BUFFER,
        new Float32Array(positionArr),
        gl.STATIC_DRAW
    );

    gl.vertexAttribPointer(
        position,
        2,
        gl.FLOAT,
        0,
        0,
        0    
    );
    // set color 

    const colorBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
    gl.bufferData(
        gl.ARRAY_BUFFER,
        new Float32Array(colorArr),
        gl.STATIC_DRAW
    );
    gl.vertexAttribPointer(
        color,
        4,
        gl.FLOAT,
        0,
        0,
        0
    );


    gl.useProgram(program);
    gl.uniform2f(resolutionPosition, gl.canvas.width, gl.canvas.height);

    // draw
    var primitiveType = gl.TRIANGLES;
    var offset = 0;
    var count = 3;
    gl.drawArrays(primitiveType, offset, count);
}

유틸리티 코드는 나중에 설명할 때 올리겠습니다.