Javascript 를 이용하여 웹에서 사용이 가능한 그래픽 라이브러리 ( Web Graphics Library )
구현 순서
전체 코드는 여기서 확인 가능
<canvas id='glCanvas'></canvas>
const canvas = <HTMLCanvasElement>document.getElementById("glCanvas");
const gl =
canvas.getContext("webgl") || <WebGLRenderingContext>canvas.getContext("experimental-webgl");
// x, y, z
const positions = [
-1.0, -1.0, 0.0, // A
1.0, -1.0, 0.0, // B
1.0, 1.0, 0.0, // C
-1.0, 1.0, 0.0, // D
]
// r, g, b, a ( 0.0 ~ 1.0 사이의 값 )
const colors = [
1.0, 1.0, 1.0, 1.0, // A - white
1.0, 0.0, 0.0, 1.0, // B - red
0.0, 1.0, 0.0, 1.0, // C - green
0.0, 0.0, 1.0, 1.0, // D - blue
];
const indices = [
0, 1, 2, // A - B - C 삼각형
0, 2, 3 // A - C - D 삼각형
];
계산의 편의를 위해 모든 객체의 면은 polygon
( 한 평면에 존재하는 다각형 ) 이여야 한다.
ex) 평면의 법선벡터가 일정하다면 빛 계산을 한번만 해도 된다.
3개의 점은 어느 위치에 있더라도 한 평면 위에 존재한다.
따라서 polygon의 정의를 만족하기 위해선 3개의 점이 최소가 된다.
그렇기 때문에 위의 예시에서 A - B - C / A - C - D 삼각형으로 나누어 그리게 된다.
삼각형으로 나누어 그리게 되면서 A, C 정점이 중복되는 것을 볼 수 있다.
그렇기 때문에 정점의 위치는 미리 선언해 놓고 순서만 바꾸어 그리면 저장공간을 절약할 수 있다.
const vertexShaderSource = `
// attribute : vertex shader 내부에서 사용되는 변수로 js에서 해당 변수로 값 전달
// vecN : N 차원 형식의 데이터 타입
attribute vec4 aVertexPosition; // 정점의 포지션을 저장하는 변수
attribute vec4 aVertexColor; // 정점의 색깔을 저장하는 변수 ( vertex shader )
// uniform : 읽기 전용의 전역 변수 ( 쉐이더끼리 공유가 가능 )
// matN : N x N 형식의 데이터 타입
uniform mat4 uModelViewMatrix; // 모델 좌표계 - 정점의 좌표를 나타냄
uniform mat4 uProjectionMatrix; // 투영 좌표계 - 카메라의 좌표를 나타냄
// varying : vertex shader 에서 fragment shader로 넘겨주는 값
// lowp : 데이터의 정밀도를 나타냄 ( lowp -> mediump -> highp 순 )
varying lowp vec4 vColor; // 정점의 색깔을 저장하는 변수 ( fragment shader )
void main(void) {
gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; // 정점의 위치를 나타냄
vColor = aVertexColor; // 받아온 정점의 색깔을 fragment shader로 념겨줌
}
`;
좌표계
fragmentShaderSource
const fragmentShaderSource = `
varying lowp vec4 vColor; // vertex shader 에서 받아온 정점의 색깔 값
void main(void) {
gl_FragColor = vColor; // 정점의 색깔을 설정해 줌
}
`;
function createShader(gl: WebGLRenderingContext, type: number, shaderSource: string) {
const shader = gl.createShader(type); // gl context를 이용해 쉐이더 객체를 생성
gl.shaderSource(shader, shaderSource); // 해당 객체에 쉐이더 소스를 추가
gl.compileShader(shader); // glsl 쉐이더 소스를 바이너리 데이터로 컴파일
return shader;
};
...
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
function createShaderProgram(
gl: WebGLRenderingContext,
vertexShader: WebGLShader,
fragmentShader: WebGLShader
) {
const shaderProgram = gl.createProgram(); // webgl program 객체 생성
gl.attachShader(shaderProgram, vertexShader); // 해당 객체에 쉐이더를 연결
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram); // 쉐이더 완료 및 GPU 준비
return {
program: shaderProgram,
attribLocations: {
// 프로그램 내부 attribute 타입의 특정 이름을 가진 변수의 위치를 저장
// 이후 해당 위치에 값을 저장하면 GLSL 변수로 전달됨
vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
vertexColor: gl.getAttribLocation(shaderProgram, "aVertexColor"),
},
uniformLocations: {
// 프로그램 내부 uniform 타입의 특정 이름을 가진 변수의 위치를 저장
// 마찬가지로 해당 위치에 값을 저장하면 GLSL 변수로 전달
projectionMatrix: gl.getUniformLocation(shaderProgram, "uProjectionMatrix"),
modelViewMatrix: gl.getUniformLocation(shaderProgram, "uModelViewMatrix"),
},
};
}
...
const programInfo = createShaderProgram(gl, vertexShader, fragmentShader);
function createBuffer(
gl: WebGLRenderingContext,
target: number,
vertexArray: Float32Array | Uint16Array
) {
const buffer = gl.createBuffer(); // 버퍼 객체 생성
gl.bindBuffer(target, buffer); // 해당 버퍼를 target에 bind 해준다.
gl.bufferData(target, vertexArray, gl.STATIC_DRAW); // 버퍼에 데이터를 저장
return buffer;
};
...
const positionBuffer = createBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(positions));
const colorBuffer = createBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(colors));
const indexBuffer = createBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices));
requestAnimationFrame()
프레임별로 장면을 초기화하여 그림으로써 움직이는 것 처럼 보이게 만든다.function render() {
initScene(gl, programInfo, positionBuffer, colorBuffer, indexBuffer);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
interface ProgramInfo {
program: WebGLProgram;
attribLocations: {
vertexPosition: number;
vertexColor: number;
};
uniformLocations: {
projectionMatrix: WebGLUniformLocation;
modelViewMatrix: WebGLUniformLocation;
};
}
function initScene(
gl: WebGLRenderingContext,
{ program, attribLocations, uniformLocations }: ProgramInfo,
positionBuffer: WebGLBuffer,
colorBuffer: WebGLBuffer,
indexBuffer: WebGLBuffer
) {
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
const fieldOfView = (45 * Math.PI) / 180;
const aspect = gl.canvas.width / gl.canvas.height;
const zNear = 0.1;
const zFar = 100.0;
const projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
const modelViewMatrix = mat4.create();
// 모델 좌표계 변환 부분
mat4.translate(modelViewMatrix, modelViewMatrix, [-0.0, 0.0, -6.0]);
mat4.rotate(modelViewMatrix, modelViewMatrix, 0, [0, 0, 1]);
mat4.rotate(modelViewMatrix, modelViewMatrix, 0, [0, 1, 0]);
// 해당 부분을 건드려서 원하는 모델을 그릴 수 있다.
{
const numComponents = 3;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(attribLocations.vertexPosition, numComponents, type, normalize, stride, offset);
gl.enableVertexAttribArray(attribLocations.vertexPosition);
}
{
const numComponents = 4;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.vertexAttribPointer(attribLocations.vertexColor, numComponents, type, normalize, stride, offset);
gl.enableVertexAttribArray(attribLocations.vertexColor);
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.useProgram(program);
gl.uniformMatrix4fv(uniformLocations.projectionMatrix, false, projectionMatrix);
gl.uniformMatrix4fv(uniformLocations.modelViewMatrix, false, modelViewMatrix);
{
const vertexCount = 6;
const type = gl.UNSIGNED_SHORT;
const offset = 0;
gl.drawElements(gl.TRIANGLES, vertexCount, type, offset);
}
};