[WebGL] Varying 과 WebGL의 작동 방식
WebGL 의 작동 방식
GPU에서 그래픽 처리를 할 때 기본적으로 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 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);
}
유틸리티 코드는 나중에 설명할 때 올리겠습니다.