WebGL은 웹에서 사용 가능한 래스터화 엔진입니다. 주로 3D 그래픽 처리에 사용되지만, GPGPU 등에도 사용될 수 있습니다. 단, WebGL은 그 자체로 3D API가 아닙니다. WebGL로 3D 처리를 할려면 Three.js 나 Babylon.js 등의 라이브러리를 쓰거나 직접 구현해야 합니다.
현재 WebGL의 버전으로는 WebGL1 과 WebGL2가 있습니다. 기본적으로 역호환도 됩니다. 그 외에도 GPU 관련해서 최근 지원되는 브라우저가 생기고 있는 WebGPU 도 있습니다.
WebGL은 점, 선, 삼각형을 이용해 모든 것을 그립니다.
gl.drawArrays나 gl.drawElements의 첫 번째 전달인자를 기반으로 WebGL은 점, 선, 또는 삼각형을 그립니다. 전달 가능한 주요 값들은 아래와 같습니다. 더 많은 목록은 이 글을 참고하세요.
POINTS
정점 셰이더가 출력하는 각 클립 공간 정점에 대해 해당 점의 중앙에 정사각형을 그립니다.LINES
정점 셰이더가 출력하는 두개의 클립 공간 정점에 대해 그 두 점을 연결하는 선을 그립니다.TRIANGLES
정점 셰이더가 출력하는 세개의 클립 공간 정점마다 그 점 세개로 삼각형을 그립니다.WebGL은 클립 공간의 좌표와 색상 두 가지를 다룹니다.
클립 공간에서 좌표는 X축과 Y 축 모두 캔버스 크기에 상관없이 항상 -1에서 +1의 범위를 갖습니다.
셰이더가 데이터를 전달받을 수 있는 방법은 아래와 같습니다.
셰이더는 GPU에서 실행되는 프로그램입니다. WebGL에서는 GLSL이라는 언어로 작성됩니다.
그래픽 처리에서 셰이더는 픽셀의 위치와 색상을 계산합니다.
작성시 어떤 방법으로든 자바스크립트 문자열로 나타내면 됩니다. 버전 명시를 위해 맨 앞에 반드시 #version 300 es
를 적어 주어야 합니다. 기본값은 구버전이기 때문입니다.
버텍스 셰이더(Vertex shader)는 그래픽 처리에서는 각 정점을 반환하기 위해 사용됩니다. 정점 셰이더라고 불리는 경우도 많습니다. 이후 하드웨어/소프트웨어에서 버텍스 셰이더가 반환한 정점을 바탕으로 각각의 픽셀들이 어디 찍힐지 결정됩니다.
프래그먼트 셰이더(Fragment shader)는 그래픽 처리에서 각 픽셀의 색을 설정하는데 사용됩니다. 픽셀 셰이더라고 불리는 경우도 많습니다.
WebGL2를 활용해 삼각형을 그리는 예제 코드입니다.
<!doctype html>
<html>
<head>
<title>WebGL</title>
</head>
<body>
<canvas id="out"></canvas>
<script>
// 단순히 복사하는 정점 셰이더
let vs = `#version 300 es
// attribute는 정점 셰이더에 대한 입력(in)입니다.
// 버퍼로부터 데이터를 받습니다.
in vec4 pos;
void main() {
// gl_Position은 정점 셰이더가 설정해 주어야 하는 내장 변수입니다.
gl_Position = pos;
}
`;
// 붉은색을 출력하는 프래그먼트 셰이더
let fs = `#version 300 es
// 프래그먼트 셰이더는 기본 정밀도를 가지고 있지 않으므로 선언을 해야합니다.
// highp는 기본값으로 적당합니다. "높은 정밀도(high precision)"를 의미합니다.
precision highp float;
// 프래그먼트 셰이더는 출력값을 선언해야 합니다.
out vec4 outColor;
void main() {
// 붉은색으로 출력값을 설정합니다.
outColor = vec4(1, 0, 0, 1);
}
`;
// 셰이더 컴파일 함수
function createShader(gl, type, source) {
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
// 셰이더 링크 함수
function createProgram(gl, vertexShader, fragmentShader) {
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
function main() {
// WebGL2 가져오기
var canvas = document.querySelector("#out");
var gl = canvas.getContext("webgl2");
if(!gl) {
// WebGL2 사용 불가시 처리
document.write("WebGL2 is not supported")
return -1;
}
// GLSL 셰이더 생성
var vertexShader = createShader(gl, gl.VERTEX_SHADER, vs);
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fs);
// 셰이더를 링크하여 프로그램 생성
var program = createProgram(gl, vertexShader, fragmentShader);
// 정점 데이터를 전달할 방법 설정
var positionAttributeLocation = gl.getAttribLocation(program, "pos");
// 위치 버퍼 생성 후 ARRAY_BUFFER 에 바인드
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// 위치 지정
var pos = [
0.5, 1,
0, 1,
0, 0.5,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(pos), gl.STATIC_DRAW);
// vao(vertex array object) 생성
var vao = gl.createVertexArray();
// vao 바인드
gl.bindVertexArray(vao);
// attribute 활성화
gl.enableVertexAttribArray(positionAttributeLocation);
// attribute 설정
var size = 2;
var type = gl.FLOAT;
var normalize = false;
var stride = 0;
var offset = 0;
gl.vertexAttribPointer(positionAttributeLocation, size, type, normalize, stride, offset);
// 클립 공간을 픽셀로 변환할 방법 설정
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// 캔버스 비우기
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
// --------------------
// 사용할 프로그램 지정
gl.useProgram(program);
// vao 바인드
gl.bindVertexArray(vao);
// 그리기
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = pos.length / 2;
gl.drawArrays(primitiveType, offset, count);
return 0;
}
window.onload = main;
</script>
</body>
</html>
출력은 아래와 같습니다.
GPGPU는 GPU를 그래픽 이외의 산술 연산 목적으로 사용하는 것입니다.
Transform feedback(XFB)은 vertex shader에서 varrings 출력을 하나 이상의 버퍼에 쓸 수 있는 기능입니다. Transform feedback의 출력은 1차원 형태입니다.
WebGL2를 활용해 덧셈을 수행하는 예제 코드입니다.
<!doctype html>
<html>
<head>
<title>WebGL</title>
</head>
<body>
<canvas id="out"></canvas>
<script>
// 덧셈 후 출력하는 정점 셰이더
let vs = `#version 300 es
in float a;
in float b;
out float sum;
void main() {
sum = a + b;
}
`;
// 빈 프래그먼트 셰이더
let fs = `#version 300 es
// 프래그먼트 셰이더는 기본 정밀도를 가지고 있지 않으므로 선언을 해야합니다.
// highp는 기본값으로 적당합니다. "높은 정밀도(high precision)"를 의미합니다.
precision highp float;
void main() {
}
`;
// 셰이더 컴파일 함수
function createShader(gl, type, source) {
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
// 버퍼 생성 함수
function makeBuffer(gl, sizeOrData) {
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, sizeOrData, gl.STATIC_DRAW);
return buf;
}
// 버퍼 생성 후 attribute 설정 함수
function makeBufferAndSetAttribute(gl, data, loc) {
const buf = makeBuffer(gl, data);
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(
loc,
1,
gl.FLOAT,
false,
0,
0,
);
}
function main() {
// WebGL2 가져오기
var canvas = document.querySelector("#out");
var gl = canvas.getContext("webgl2");
if(!gl) {
// WebGL2 사용 불가시 처리
document.write("WebGL2 is not supported")
return -1;
}
// GLSL 셰이더 생성
var vertexShader = createShader(gl, gl.VERTEX_SHADER, vs);
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fs);
// 셰이더를 링크하여 프로그램 생성
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.transformFeedbackVaryings(
program,
["sum"],
gl.SEPARATE_ATTRIBS,
);
gl.linkProgram(program);
if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error(gl.getProgramParameter(program));
}
const aLoc = gl.getAttribLocation(program, 'a');
const bLoc = gl.getAttribLocation(program, 'b');
// vao(vertex array object) 생성
const vao = gl.createVertexArray();
// vao 바인드
gl.bindVertexArray(vao);
// 데이터 설정
const a = [1, 2, 3, 4, 5, 6];
const b = [3, 6, 9, 12, 15, 18];
// 버퍼에 데이터 넣기
const aBuffer = makeBufferAndSetAttribute(gl, new Float32Array(a), aLoc);
const bBuffer = makeBufferAndSetAttribute(gl, new Float32Array(b), bLoc);
// Transform feedback 생성
const tf = gl.createTransformFeedback();
// Transform feedback 바인드
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
// 출력 버퍼 생성
const sumBuffer = makeBuffer(gl, a.length * 4);
// 출력 버퍼 바인드
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, sumBuffer);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
// --------------------
// 사용할 프로그램 지정
gl.useProgram(program);
// vao 바인드
gl.bindVertexArray(vao);
// 프래그먼트 셰이더 호출 비활성화
gl.enable(gl.RASTERIZER_DISCARD);
// 처리
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
gl.beginTransformFeedback(gl.POINTS);
gl.drawArrays(gl.POINTS, 0, a.length);
gl.endTransformFeedback();
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
// 프래그먼트 셰이더 호출 활성화
gl.disable(gl.RASTERIZER_DISCARD);
// 출력
const results = new Float32Array(a.length);
gl.bindBuffer(gl.ARRAY_BUFFER, sumBuffer);
gl.getBufferSubData(
gl.ARRAY_BUFFER,
0, // 오프셋
results,
);
document.write("a : ");
for(let n of a) {
document.write(n, " ");
}
document.write("<br/>");
document.write("b : ");
for(let n of b) {
document.write(n, " ");
}
document.write("<br/>");
document.write("sum : ");
for(let n of results) {
document.write(n, " ");
}
document.write("<br/>");
gl.bindBuffer(gl.ARRAY_BUFFER, null);
return 0;
}
window.onload = main;
</script>
</body>
</html>
출력은 아래와 같습니다.
Almost Native Graphics Layer Engine
ANGLE는 구글에서 만든 API로, OpenGL ES API를 하드웨어 지원 API(렌더러)로 변환해 줍니다.
Chrome, Edge, Firefox, Safari 등의 주요 브라우저에서 WebGL 구현을 위해 사용됩니다.
ANGLE의 버전/플랫폼/렌더러 지원 현황은 아래와 같습니다.
OpenGL ES 버전별 지원되는 렌더러 현황
플랫폼별 지원되는 렌더러 현황
ANGLE에서 백엔드로 사용할 그래픽 API(렌더러)를 선택하기 위해서는 크롬 실행 시 아래와 같은 형태로 명령줄 옵션을 추가해야 합니다. 참고
--use-gl=angle --use-angle=<backend>
소스코드는 Git 이나 Chromium Code Search 에서 확인할 수 있습니다.
이슈는 Crbug 에서, 최신 커밋은 Chromium Gerrit 에서 확인할 수 있습니다.
src/libGLESv2/libGLESv2_autogen.cpp
src/libGLESv2/entry_point_gles*
src/libANGLE/validationES*
src/libANGLE/Context*
src/libANGLE/renderer/*
ANGLE
https://chromium.googlesource.com/angle/angle/
ANGLE : 크롬 공격 벡터 소개
https://www.youtube.com/watch?v=7IklO4tpVGg
WebGL 기초
https://webglfundamentals.org/webgl/lessons/ko/webgl-fundamentals.html
WebGL2 기초
https://webgl2fundamentals.org/webgl/lessons/ko/webgl-fundamentals.html
Browser Hacking With ANGLE
conference.hitb.org