출처: webglfundamentals.org 사이트
해당 사이트의 내용과 설명이 필요한 개념들을 추가하여 재구성한 내용입니다.
GPU 에는 기본적으로 두가지 부분이 있다.
첫 번째 부분은 정점을 클립 공간의 정점으로 처리하는 부분이다.
두 번째로는 첫 번째 작업을 기반으로 픽셀을 그리는 부분이다.
var primitiveType = gl.TRIANGLES;
// gl.TRIANGLES: 삼각형을 그리기 모드
// 그림을 그리기 위해 연속적인 세 개의 정점을 사용한다.
var offset = 0;
var count = 9;
gl.drawArrays(primitiveType, offset, count);
위와 같이 호출할 때 9는 ' 정점 9개 처리'를 의미한다.
Original Vertices가 내가 제공한 데이터다.
Vertvex Shader는 GLSL로 작성하는 함수인데, 이 함수는 정점마다 한 번씩 호출된다.
몇 가지 계산 후에 현재 정점의 클립 공간으로 특수 변수 gl_Position을 설정한다.
GPU는 이 값을 가져와 내부에 저장한다.
TRIANGLES
를 그린다고 했을 때, 첫 부분에서 정점 3개를 생성할 때마다 GPU는 이걸 이용해서 삼각형을 만든다.
어떤 픽셀이 삼각형의 점 3개에 해당하는지 확인한 후에 삼각형을 픽셀로 그리기(래스터화) 한다.
각 픽셀마다 Fragment Shader를 호출해서 어떤 색상으로 만들지를 묻는다.
Fragment Shader는 특수 변수 gl_FragColor를 해당 픽셀에 원하는 색상으로 설정한다.
위의 예제에서는 픽셀마다 아주 적은 정보를 가지고 있는데 더 많은 정보를 전달하기 위해서는 다음과 같은 방법을 쓴다.
바로 Vertex Shader에서 Fragment Shader로 전달하려는 각 값에 varying을 정의하는거다.
위의 예제에 이어서 사각형을 삼각형으로 바꿔본다.
// 삼각형을 정의한 값으로 버퍼 채우기
function setGeometry(gl) {
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
0, -100,
150, 125,
-175, 100
]),
gl.STATIC_DRAW
);
}
그리고 정점 3개를 그린다.
// 장면 그리기
function drawScene() {
...
// 지오메트리 그리기
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 3;
gl.drawArrays(primitiveType, offset, count);
}
Vertex Shader 에 varying을 선언한다.
varying vec4 v_color;
...
void main() {
// 위치에 행렬 곱하기
gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
// 클립 공간에서 색상 공간으로 변환
// 클립 공간은 -1.0에서 +1.0까지
// 색상 공간은 0.0에서 1.0까지
v_color = gl_Position * 0.5 + 0.5;
}
Fragment Shader에도 동일한 varying을 선언한다.
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
WebGL은 Vertex Shader의 varying을 이름과 타입이 같은 Fragment Shader의 varying으로 연결할 것이다.
위의 코드에서 정점은 3개만 계산을 했다.
Vertex Shader는 3번만 호출되므로 3개의 색상만 계산하지만 삼각형은 여러 색상이다.
이게 varying이라고 불리는 이유다.
WebGL은 각 정점을 계산한 3개의 값을 가져오고 삼각형을 래스터화할 때 계산된 정점들 사이를 보간한다.
각 픽셀마다 해당 픽셀에 대해 보간된 값으로 Fragment Shader를 호출한다.
위의 예제에서는 3개의 정점으로 시작한다.
정점 | |
---|---|
0 | -100 |
150 | 125 |
-175 | 100 |
Vertex Shader는 translation
rotation
scale
에 행렬을 적용하고 클립 공간으로 변환한다.
translation
rotation
scale
의 기본값은
translation = 200, 150
rotation = 0
scale = 1,1
이므로 실제로는 이동만 한다.
400 x 300 인 백버퍼가 주어지면 Vertex Shader는 행렬을 적용한 뒤에 다음과 같은 3개의 클립 공간 정점을 계산한다.
gl_position | 에 작성된 값들 |
---|---|
0.000 | 0.660 |
0.750 | -0.830 |
-0.875 | -0.660 |
이걸 색상 공간으로 변환하고 선언한 varying v_color
에 작성한다.
v_color
에 작성된 3개의 값들은 보간되어 각 픽셀에 대한 Fragment Shader로 전달된다.
버퍼는 그래픽스 데이터를 임시로 저장하거나 GPU 를 전송하기 위한 메모리 공간이다.
gl.createBuffer
는 버퍼를 생성한다.
gl.bindBuffer
는 해당 버퍼를 작업할 버퍼로 설정한다.
gl.bufferData
는 데이터를 버퍼로 복사한다.
이건 보통 초기화할 때 수행된다.
버퍼에 데이터가 있으면 어떻게 데이터를 가져와 Vertex Shader의 속성에 제공할지 WebGL에 알려줘야 한다.
이를 위해서 먼저 속성을 할당한 위치를 WebGL에 물어본다.
// 정점 데이터가 어디로 가야하는지 탐색
var positionLocation = gl.getAttribLocation(program, "a_position");
var colorLocation = gl.getAttribLocation(program, "a_color");
이것도 보통 초기화할 때 사용된다.
속성의 위치를 알았으면 그리기 전에 세 가지 명령어를 실행한다.
gl.enableVertexAttribArray(location);
// 버퍼에서 데이터를 제공하길 원한다
// 라고 WebGL에 알려줌
gl.bindBuffer(gl.ARRAY_BUFFER, someBuffer);
// ARRAY_BUFFER 바인드 포인트에 버퍼를 할당
gl.vertexAttribPointer(
location,
numComponents,
typeOfData,
normalizeFlag,
strideToNextPieceOfData,
offsetIntoBuffer
);
vertexAttribPointer
는 현재 ARRAY_BUFFER 바인드 포인트에
바인딩된 버퍼에서 데이터를 가져오기 위해 정점마다 얼마나 많은 컴포넌트가 있는지,
데이터 타입은 무엇인지,
스트라이드는 한 데이터에서 다음 데이터를 가져오기 위해 몇 바이트를 건너 뛰어야하는지,
오프셋은 버퍼에서 데이터가 얼마나 멀리 있는지를 WebGL에 알려준다.
버퍼를 사용하면 데이터를 CPU 와 GPU 간에 효율적으로 전달할 수 있다. 데이터는 버퍼에 저장되어 한 번에 여러 데이터를 한꺼번에 처리하므로 성능을 향상시킬 수 있다.
그래픽 렌더링 파이프라인에서 데이터를 처리하려면, 버퍼에 저장된 데이터를 적절한 Shader로 전달하려 화면에 그래픽을 렌더링하는 과정을 거친다.
유익한 글이었습니다.