[Computer Graphics] WebGL 삼각형 그리기

2023. 10. 10. 18:16Run/Computer Graphics

 

 

WebGL은 JavaScript API이다. 즉, html 페이지에 embed할 수 있다는 뜻이다.

WebGL은 GPU를 통한 hardware acceleration에 의존한다.

https://get.webgl.org/ 사이트에 접속하였을 때

 

위와 같은 화면이 나온다면 사용하는 브라우저가 JavaScript를 지원함을 의미한다. (안하는 브라우저도 있을까?)

 

 

WebGL을 이용하여 삼각형을 만들어보자.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
            <title>2D Triangle</title>
            <script type="text/javascript" src="../Common/webgl-utils.js"></script>
            <script type="text/javascript" src="../Common/initShaders.js"></script>
            <script type="text/javascript" src="../Common/MV.js"></script>
    </head>
    
    <body>
    </body>
</html>

webgl-utils.js, initShaders.js, MV.js 파일은

https://gist.github.com/Carla-de-Beer/10ad7c7309fad48d94df 에서 구할 수 있다.

 

 

width 512px, height 512px인 WebGL canvas를 생성한다. js 파일을 생성하고, html 코드에 불러온다.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
            <title>2D Triangle</title>
            <script type="text/javascript" src="../Common/webgl-utils.js"></script>
            <script type="text/javascript" src="../Common/initShaders.js"></script>
            <script type="text/javascript" src="../Common/MV.js"></script>
            <script type="text/javascript" src="drawTriangle.js"></script>
    </head>
    
    <body>
    	<canvas id="gl-canvas" width="512" height="512">
        </canvas>
    </body>
</html>

 

 

js에서 사각형과 삼각형을 그리는 함수를 생성한다.

function drawTriangle() {
    var canvas = document.getElementById("gl-canvas");
    var gl = WebGLUtils.setupWebGL(canvas);
    if (!gl) { alert("WebGL is not available") };
}

html 코드에서 id가 gl-canvas인 element를 가져왔다. 이는 우리가 사용할 canvas가 될 것이다.

webgl-utils.js에 있는 함수로 WebGL context를 생성했다.

만약 context를 생성하지 못한 경우 alert창을 띄운다.

 

 

viewport를 생성하고, 배경 색상을 설정하고, Framebuffer를 초기화한다.

function drawTriangle() {
    var canvas = document.getElementById("gl-canvas");
    var gl = WebGLUtils.setupWebGL(canvas);
    if (!gl) { alert("WebGL is not available") };
    
    gl.viewport(0, 0, 512, 512);
    gl.clearColor(1.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
}

(0, 0)은 canvas의 bottom-left corner의 좌표가 된다. (512, 512)는 각각 width와 height 값이 된다.

canvas의 좌표의 범위는 (0, 0) ~ (512, 512)이고, clipspace의 좌표의 범위는 (-1, -1) ~ (1, 1)이다.

범위는 자동으로 매핑된다. (x, y) → (2x / 512 - 1, 2y / 512 - 1)

 

clearColor의 인자는 순서대로 red, green, blue, alpha(opacity)이다.

red, green, blue의 경우 각각 범위가 0~1이고, 이 값은 색상의 intensity를 의미한다.

위의 코드에서는 red가 1, green, blue가 0이므로 배경 색상은 빨간색이 되고, opacity가 1이므로 완전히 불투명하다.

 

COLOR_BUFFER_BIT는 color buffer를 초기화하는 WebGL 상수이다.

 

 

다시 html 코드로 돌아와서 브라우저가 drawTriangle() 함수를 실행하도록 한다.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
            <title>2D Triangle</title>
            <script type="text/javascript" src="../Common/webgl-utils.js"></script>
            <script type="text/javascript" src="../Common/initShaders.js"></script>
            <script type="text/javascript" src="../Common/MV.js"></script>
            <script type="text/javascript" src="drawTriangle.js"></script>
    </head>
    
    <body onload="drawTriangle()">
    	<canvas id="gl-canvas" width="512" height="512">
        </canvas>
    </body>
</html>

 

 

브라우저로 확인하면 다음과 같다.

 

이제 빨간색 캔버스 위에 삼각형을 그려보자. 삼각형은 세 개의 점을 사용하여 생성한다.

MV.js에는 vector을 생성할 수 있는 함수를 가지고 있다. vec2(), vec3()

다시 js 코드로 돌아가서 세 개의 점을 생성하고, 이들을 담은 배열을 생성한다.

function drawTriangle() {
    var canvas = document.getElementById("gl-canvas");
    var gl = WebGLUtils.setupWebGL(canvas);
    if (!gl) { alert("WebGL is not available") };
    
    gl.viewport(0, 0, 512, 512);
    gl.clearColor(1.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    
    var p0 = vec2(0, 0);
    var p1 = vec2(0, 1);
    var p2 = vec2(1, 0);
    
    var arrayOfPointsForTriangle = [p0, p1, p2];
}

 

 

buffer object를 생성하고, 이를 array buffer과 연결하고, 세 개의 점에 대한 데이터를 buffer에 옮긴다.

function drawTriangle() {
    var canvas = document.getElementById("gl-canvas");
    var gl = WebGLUtils.setupWebGL(canvas);
    if (!gl) { alert("WebGL is not available") };
    
    gl.viewport(0, 0, 512, 512);
    gl.clearColor(1.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    
    var p0 = vec2(0, 0);
    var p1 = vec2(0, 1);
    var p2 = vec2(1, 0);
    
    var arrayOfPointsForTriangle = [p0, p1, p2];
    
    var bufferId = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(arrayOfPointsForTriangle), gl.STATIC_DRAW);
}

bindBuffer를 사용하면 GPU에게 '앞으로 내가 bufferId에 작업을 하면, GPU의 array buffer를 확인해라' 라고 말하는 것과 같다.

bufferData를 사용하면 array buffer에 arrayOfPointsForTriangle의 data를 전달하는 것과 같다.

flatten() 함수는 MV.js에 있는 함수로, vec2를 buffer에 적합한 데이터 타입으로 변경하는 역할을 한다.

STATIC_DRAW는 data가 buffer에 한 번 쓰이고, 많이 사용됨을 의미한다.

STREAM_DRAW는 data가 buffer에 한 번 쓰이고, 적게 사용됨을 의미한다.

DYNAMIC_DRAW는 data가 buffer에 여러 번 쓰이고, 많이 사용됨을 의미한다.

 

 

html 코드로 이동해서 shader를 생성한다.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
            <title>2D Triangle</title>
            <script type="text/javascript" src="../Common/webgl-utils.js"></script>
            <script type="text/javascript" src="../Common/initShaders.js"></script>
            <script type="text/javascript" src="../Common/MV.js"></script>
            <script type="text/javascript" src="drawTriangle.js"></script>
            
            <script id="vertex-shader" type="x-shader/x-vertex"></script>
            <script id="fragment-shader" type="x-shader/x-fragment"></script>
    </head>
    
    <body onload="drawTriangle()">
    	<canvas id="gl-canvas" width="512" height="512">
        </canvas>
    </body>
</html>

 

 

vertex shader를 다뤄보자.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
            <title>2D Triangle</title>
            <script type="text/javascript" src="../Common/webgl-utils.js"></script>
            <script type="text/javascript" src="../Common/initShaders.js"></script>
            <script type="text/javascript" src="../Common/MV.js"></script>
            <script type="text/javascript" src="drawTriangle.js"></script>
            
            <script id="vertex-shader" type="x-shader/x-vertex">
                attribute vec4 myPosition;
                void main() {
                    gl_PointSize = 1.0;
                    gl_Position = myPosition;
                }
            </script>
            <script id="fragment-shader" type="x-shader/x-fragment"></script>
    </head>
    
    <body onload="drawTriangle()">
    	<canvas id="gl-canvas" width="512" height="512">
        </canvas>
    </body>
</html>

gl_PointSize와 gl_Position은 build-in 변수이다. 점의 크기를 설정하고, 점들의 위치를 myPosition으로 설정한다.

myPosition은 js 코드에서 설정할 것이다.

 

 

fragment shader를 다뤄보자.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
            <title>2D Triangle</title>
            <script type="text/javascript" src="../Common/webgl-utils.js"></script>
            <script type="text/javascript" src="../Common/initShaders.js"></script>
            <script type="text/javascript" src="../Common/MV.js"></script>
            <script type="text/javascript" src="drawTriangle.js"></script>
            
            <script id="vertex-shader" type="x-shader/x-vertex">
                attribute vec4 myPosition;
                void main() {
                    gl_PointSize = 1.0;
                    gl_Position = myPosition;
                }
            </script>
            <script id="fragment-shader" type="x-shader/x-fragment">
                void main() {
                    gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
                }
            </script>
    </head>
    
    <body onload="drawTriangle()">
    	<canvas id="gl-canvas" width="512" height="512">
        </canvas>
    </body>
</html>

이는 pixel들의 색상을 결정한다. 순서대로 red, green, blue, opacity를 나타낸다.

red, green이 1이고, blue가 0이므로 노란색이고, opacity가 1이므로 완전히 불투명하다.

 

 

이제 js 코드가 shader를 확인할 수 있도록 하자.

function drawTriangle() {
    var canvas = document.getElementById("gl-canvas");
    var gl = WebGLUtils.setupWebGL(canvas);
    if (!gl) { alert("WebGL is not available") };
    
    gl.viewport(0, 0, 512, 512);
    gl.clearColor(1.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    
    var p0 = vec2(0, 0);
    var p1 = vec2(0, 1);
    var p2 = vec2(1, 0);
    
    var arrayOfPointsForTriangle = [p0, p1, p2];
    
    var bufferId = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(arrayOfPointsForTriangle), gl.STATIC_DRAW);
    
    var myShaderProgram = initShaders(gl, "vertex-shader", "fragment-shader");
    gl.useProgram(myShaderProgram);
}

WebGL은 각 shader에서 실행 파일(executable)을 생성하고, 이를 GPU로 보낸다.

GPU는 실행 파일을 하나의 점(vertex. 또는 fragment)마다 하나씩 실행한다.

 

 

WebGL이 myPosition에 data를 제공하도록 한다.

function drawTriangle() {
    var canvas = document.getElementById("gl-canvas");
    var gl = WebGLUtils.setupWebGL(canvas);
    if (!gl) { alert("WebGL is not available") };
    
    gl.viewport(0, 0, 512, 512);
    gl.clearColor(1.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    
    var p0 = vec2(0, 0);
    var p1 = vec2(0, 1);
    var p2 = vec2(1, 0);
    
    var arrayOfPointsForTriangle = [p0, p1, p2];
    
    var bufferId = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(arrayOfPointsForTriangle), gl.STATIC_DRAW);
    
    var myShaderProgram = initShaders(gl, "vertex-shader", "fragment-shader");
    gl.useProgram(myShaderProgram);
    
    var myPosition = gl.getAttribLocation(myShaderProgram, "myPosition");
    gl.vertexAttribPointer(myPosition, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(myPosition);
}

vertexAttribPointer의 인자는 순서대로 가리킬 변수명, 사용할 요소의 개수(x, y이므로 2), data type, data를 normalize할 것인지에 대한 여부, stride, offset이다.

enableVertexAttribArray를 통해 WebGL이 myPosition을 이용하여 data에 접근하도록 한다.

 

 

마지막으로, 삼각형을 그린다.

function drawTriangle() {
    var canvas = document.getElementById("gl-canvas");
    var gl = WebGLUtils.setupWebGL(canvas);
    if (!gl) { alert("WebGL is not available") };
    
    gl.viewport(0, 0, 512, 512);
    gl.clearColor(1.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    
    var p0 = vec2(0, 0);
    var p1 = vec2(0, 1);
    var p2 = vec2(1, 0);
    
    var arrayOfPointsForTriangle = [p0, p1, p2];
    
    var bufferId = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, bufferId);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(arrayOfPointsForTriangle), gl.STATIC_DRAW);
    
    var myShaderProgram = initShaders(gl, "vertex-shader", "fragment-shader");
    gl.useProgram(myShaderProgram);
    
    var myPosition = gl.getAttribLocation(myShaderProgram, "myPosition");
    gl.vertexAttribPointer(myPosition, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(myPosition);
    
    gl.drawArrays(gl.TRIANGLES, 0, 3);
}

0은 시작점의 간격(offset)을 의미한다. 3은 점의 개수를 의미한다.

 

 

브라우저로 확인하면 다음과 같다.

 

끝!

'Run > Computer Graphics' 카테고리의 다른 글

[Computer Graphics] Texture Mapping  (0) 2023.10.10
[Computer Graphics] Lighting  (1) 2023.10.10
[Computer Graphics] Viewing  (1) 2023.10.10
[Computer Grpahics] WebGL Variables  (0) 2023.10.10
[Computer Graphics] WebGL 다각형 그리기  (0) 2023.10.10