본문 바로가기
WebGL

WebGL - (4) 삼각형 그려보기

by 똑똑한 영장류 2013. 5. 22.

WebGL 은 기본적으로 3D를 위해서 존재합니다.


가상의 공간에 물체를 놓고, 어느 한 시점에서 바라볼 때 눈에 보이는 장면을 2D 로 그려내는 것입니다.


3D Graphic 분야에서 사용되는, 익숙하지 않은 많은 용어들이 있습니다. 한꺼번에 다 설명할 수도 없고, 진행해 나가면서 필요할 때 하나씩 설명을 하겠습니다. 사실 저도 3D 전문가가 아니라, 잘 모릅니다. 단지 제가 이해하는 만큼 설명이 가능할 것입니다.



1. HTML 기본 태그를 작성합시다.

-----

<!DOCTYPE html>
<html>
<head>
<title>WebGL</title>
<meta charset="UTF-8">

<script type="text/javascript">

function webgl_start() {

} // webgl_start()

</script>

</head>

<body onload="webgl_start()">

<canvas id="webgl-screen" width='500' height='500'></canvas>

</body>

</html>

-----

가로/세로 500짜리 canvas 를 준비해두고, webgl_start() 함수내에서 작업해봅시다.


먼저 WebGLRenderingContext 를 가져옵시다.

전역변수로 gl 을 만들어 두고, canvas 의 id 를 인자로 받아서 해당 context 를 리턴하는 함수를 작성해서 사용하도록 합시다.

아래의 getGL() 함수를 script  태그 내에 작성을 합시다.


-----

function getGL(name) {
    var canvas = document.getElementById(name);
        var ctx = null;
       
        if (canvas == null){
            alert('there is no canvas on this page');
            return null;
        }
        else {
            c_width = canvas.width;
            c_height = canvas.height;
        }
               
        var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
   
        for (var i = 0; i < names.length; ++i) {
        try {
            ctx = canvas.getContext(names[i]);
        }
        catch(e) {}
            if (ctx) {
                break;
            }
        }
        if (ctx == null) {
            alert("Could not initialise WebGL");
            return null;
        }
        else {
            return ctx;
        }
}

-----


<script type="text/javascript"> 태그 안에 아래와 같이 변수를 선언해 둡시다.

var gl;

var c_width; // 캔버스 가로

var c_height; // 캔버스 세로



webgl_start() 안에 아래 줄을 작성합시다.


gl = getGL('webgl-screen');


webgl-screen canvas에서 context 를 가져옵니다. 이때, 캔버스의 가로/세로 크기도 저장을 해 놓습니다.

webGL 을 사용할 준비는 됐습니다.



Vertex Buffer / Index Buffer 준비


그릴 대상을 설정해주어야겠지요?


(-1, -1, 0) , (1,-1,0), (0,1,0) 를 세꼭지점으로 하는 삼각형을 그려봅시다.


vertex 좌표를 모아놓는 vertex buffer 와 삼각형을 지정할 index 를 모아놓는 index buffer 을 준비해줘야합니다.


-----

    var vertices = [ -1.0, -1.0, 0,
                            1.0, -1.0, 0,
                               0,  1.0, 0
                        ];
    var indices = [ 0, 1, 2 ];

    var vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW );

   
    var indexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW );

    gl.bindBuffer(gl.ARRAY_BUFFER, null);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

-----


배열변수 vertice 에 알아보기 쉽게 3개의 꼭지점 좌표를 선언해줍니다.

순서대로 0, 1, 2 라는 번호를 가지게 됩니다.


그럼, indices 는 삼각형을 이루게 될 세 꼭지점을 꼭지점 번호로 지정을 해 줍니다.

0,1,2 3개의 vertex 가 삼각형을 이루게 되겠죠.


이렇게 선언한 자바스크립트 배열을 webGL 이 써먹을 수 있게 손을 좀 봐 줍시다.


Vertex Buffer 와 Index Buffer


vertex 를 모아놓을 버퍼를 생성하는 방법입니다.

var vertexBuffer = gl.createBuffer();

먼저 버퍼를 준비하고,

gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

 그 버퍼를 ARRAY_BUFFER 라고 gl에 연결시킵니다.

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW );

그 리고나서 그 버퍼에 데이터를 저장합니다. 이때 저장될 데이터는 float32 형태의 배열로 되어있으니, vertices 변수에서 가져와라...라고 알려줍니다. STATIC_DRAW 는 이 버퍼의 내용은 변하지 않는 STATIC 한 형태라고 알려줍니다.


index buffer 의 경우도 매우 유사합니다. 단지 ELEMENT_ARRAY_BUFFER 라는 인자가 사용되고, 저장될 데이터가 Uint16 형태라는 차이가 있을 뿐입니다.


마 지막 두 줄은 데이터를 채워넣으려고 붙잡아뒀던 buffer를 놓아주는 것입니다. gl 은 뭔가 작업을 할때, 연결된 버퍼를 찾아서 작업을 하게 됩니다. 필요할 때 붙여서 작업을 하고, 다 했으면 다시 떼어놓고.. 붙였다, 뗏다 하면서 진행됩니다.



Shader program


3D 를 하다보면 render 라고하는, 이미지화하는 작업을 의미하는 동사를 많이 만나게 됩니다. renderder 라고 하면, 데이터로부터 이미지를 만들어내는 녀석을 의미하죠. renderer 는 어떻게 이미지로 만들까요? 그래픽 카드에서 뭔가 사삭하고 돌아가면서 색색이 화면을 만들어 낼 텐데, 이때, 그 작업을 하는 프로그램이 있습니다.  그녀석을 shader 라고 부릅니다. 실제 컴파일 링크되서 실행코드가 되어서 webgl 옆에서 열심히 실행되는 녀석입니다. 색깔을 어찌어찌하고 모양은 어찌어찌하도록 처리를 해라..라는 것들을 프로그래밍 해 줄 수 있습니다.

렌더링을 시키기 전에 제대로된 지시사항을 내려주는 거죠.



우리도 여기서 삼각형을 어떤 색상으로 그려라...를 shader 를 통해 지정을 해 줍시다.

shader 는 vertex shader, fragment shader 두가지가 있습니다. 점을 어떻게 처리해라..라는 것을 지정하는 것이 vertex shader, 점들로 이루어진 면을 어떻게 처리해라...라는 것을 지정하는 것이 fragment shader 입니다.


------

<!-- Fragment Shader //-->
<script id='shader-fs' type='x-shader/x-fragment'>
    #ifdef GL_ES
    precision highp float;
    #endif

    void main(void) {
        gl_FragColor = vec4(0.5, 0.9, 0.2, 1.0); //Green
    }
</script>

<!-- Vertex Shader //-->
<script id='shader-vs' type='x-shader/x-vertex'>
    attribute vec3 aVertexPosition;

    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;

    void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
        gl_PointSize = 3.0;
    }
</script>

-----

위와 같이 script 태그를 이용해서 두 가지 shader를 프로그래밍할 수 있습니다. C언어와 매우 유사합니다. main() 함수가 있는 각각이 하나의 프로그램이되죠.


쉐이더 프로그래밍 분야도 심오합니다. 위의 내용은 단지, 색상을 녹색으로 설정한다는 정도로 알고 우선 넘어갑니다. ㅜㅜ


저 코드들을 컴파일하고 링크해서 WebGL에게 전해줘야합니다.

우선 프로그램 코드부분을 추출해 내야 컴파일/링크를 할 수 있겠죠?

그래서 아래의 코드가 추가로 필요합니다. script 태그 내에 포함된 프로그램 코드를 가져와서 컴파일까지 하는 자바스크립트 입니다.

-----

function getShader(gl, id) {
    var script = document.getElementById(id);
    if (!script) {
        return null;
    }

    var str = "";
    var k = script.firstChild;
    while (k) {
        if (k.nodeType == 3) {
            str += k.textContent;
        }
        k = k.nextSibling;
    }

    var shader;
    if (script.type == "x-shader/x-fragment") {
        shader = gl.createShader(gl.FRAGMENT_SHADER);
    } else if (script.type == "x-shader/x-vertex") {
        shader = gl.createShader(gl.VERTEX_SHADER);
    } else {
        return null;
    }

    gl.shaderSource(shader, str);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        alert(gl.getShaderInfoLog(shader));
        return null;
    }
    return shader;
}

-----

이 함수는 getGL() 함수 선언 뒤에 추가해 줍시다.


이제 shader 코드를 가져와서 컴파일까지 할 수 있으니, 이제 WebGL에 전해줘야겠네요.

webgl-start() 함수의 아까 마지막 작업했던

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); 다음 줄에 아래 코드를 계속 이어갑시다.


-----

    // shader program
    var fragmentShader = getShader(gl, "shader-fs");
    var vertexShader = getShader(gl, "shader-vs");

    var shaders = gl.createProgram();
    gl.attachShader(shaders, vertexShader);
    gl.attachShader(shaders, fragmentShader);
    gl.linkProgram(shaders);

    if (!gl.getProgramParameter(shaders, gl.LINK_STATUS)) {
      alert("Could not initialise shaders");
    }

    gl.useProgram(shaders);

    shaders.vertexPositionAttribute = gl.getAttribLocation(shaders, 'aVertexPosition');
    shaders.pMatrixUniform          = gl.getUniformLocation(shaders, 'uPMatrix');
    shaders.mvMatrixUniform         = gl.getUniformLocation(shaders, 'uMVMatrix');

-----

var fragmentShader = getShader(gl, "shader-fs");

script 중에 shader-fs 의 코드를 가져와서 컴파일 한 후, fragmentShader에 저장합니다.


var vertexShader = getShader(gl, "shader-vs");

마찬가지로, script 중에 shader-vs 의 코드를 가져와서 컴파일 한 후, vertexShader에 저장합니다.


var shaders = gl.createProgram();

쉐이더 프로그램을 담을 객체를 생성합니다.

gl.attachShader(shaders, vertexShader);
gl.attachShader(shaders, fragmentShader);

두개의 컴파일한 shader 코드를 gl에 붙이고,
gl.linkProgram(shaders);

링크해서 최종 프로그램을 만들어둡니다.


그 아래줄은 링크결과를 살펴서 오류가 있는지 확인하는 코드입니다.


gl.useProgram(shaderProgram);

오류가 없으면, gl에게 이거 쓰라고 알려줍니다.ㅎㅎ


마지막 석 줄은 설명이 다음에 설명할 수 있기를 바랍니다. ㅋ


여기까지... WebGL Context 준비하고, 그릴 대상 준비하고, 어떻게 그려라고 shader program 준비까지 했습니다.


이제 그려야겠군요.



shader 설정부분 아래에 다음 코드를 추가합시다.

-----

// draw
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.enable(gl.DEPTH_TEST);

    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.viewport(0,0,c_width, c_height);

    mat4.perspective(45, c_width / c_height, 0.1, 10000.0, pMatrix);
    mat4.identity(mvMatrix);   
    mat4.translate(mvMatrix, [0.0, 0.0, -5.0]);

    gl.uniformMatrix4fv(shaders.pMatrixUniform, false, pMatrix);
    gl.uniformMatrix4fv(shaders.mvMatrixUniform, false, mvMatrix);

    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.vertexAttribPointer(shaders.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(shaders.vertexPositionAttribute);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.drawElements(gl.LINE_LOOP, indices.length, gl.UNSIGNED_SHORT,0);

-----

그리고 gl, c_width 선언했던 곳에 아래 매트릭스도 선언해 줍시다.


var mvMatrix = mat4.create(); // The Model-View matrix
var pMatrix = mat4.create(); // The projection matrix


매트릭스가 등장했습니다.

3D 는 몽창 매트릭스 연산이더군요. 좌표가 주어지면, 그 좌표로부터 눈에 보이는 장면을 구해내기 위해서 온갖 연산을 하죠. ㅡ.ㅡ

매트릭스 관련은 따로 포스팅을 해야겠습니다. 지금은 그냥..그냥...


매트릭스 연산을 위한 라이브러리를 아래와 같이 추가해줍시다.


<script type='text/javascript' src='js/gl-matrix-min.js'></script>


gl-matrix-min.js 는 첨부했습니다.


gl-matrix-min.js



대략 과정을 보면...

화면을 지우고, viewport 를 canvas 크기만큼 설정을 해 주고, 그려줄 객체를 바라보는 방법을 matrix 를 통해서 지정을 해 줍니다.


vertex buffer 를 통해 쉐이더에게 vertex 정보를 넘깁니다. ( 이 과정도 좀 더 깊게 살펴보고 정리해야겠습니다. )


index buffer 를 바인드하고, 그것을 이용해서 드디어 화면에 그립니다. drawElements()


아래와 같이 나오나요?


전체 코드는 아래와 같습니다.

-----

<!DOCTYPE html>
<html>
<head>
<title>WebGL</title>
<meta charset="UTF-8">
<script type='text/javascript' src='js/gl-matrix-min.js'></script>

<!-- Fragment Shader //-->
<script id='shader-fs' type='x-shader/x-fragment'>
    #ifdef GL_ES
    precision highp float;
    #endif

    void main(void) {
        gl_FragColor = vec4(0.5, 0.9, 0.2, 1.0); //Green
    }
</script>

<!-- Vertex Shader //-->
<script id='shader-vs' type='x-shader/x-vertex'>
    attribute vec3 aVertexPosition;

    uniform mat4 uMVMatrix;
    uniform mat4 uPMatrix;

    void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
        gl_PointSize = 3.0;
    }
</script>

<script type="text/javascript">

var gl;
var c_width;
var c_height;
var mvMatrix = mat4.create(); // The Model-View matrix
var pMatrix = mat4.create(); // The projection matrix

function getGL(name) {
    var canvas = document.getElementById(name);
        var ctx = null;
       
        if (canvas == null){
            alert('there is no canvas on this page');
            return null;
        }
        else {
            c_width = canvas.width;
            c_height = canvas.height;
        }
               
        var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
   
        for (var i = 0; i < names.length; ++i) {
        try {
            ctx = canvas.getContext(names[i]);
        }
        catch(e) {}
            if (ctx) {
                break;
            }
        }
        if (ctx == null) {
            alert("Could not initialise WebGL");
            return null;
        }
        else {
            return ctx;
        }
}

function getShader(gl, id) {
    var script = document.getElementById(id);
    if (!script) {
        return null;
    }

    var str = "";
    var k = script.firstChild;
    while (k) {
        if (k.nodeType == 3) {
            str += k.textContent;
        }
        k = k.nextSibling;
    }

    var shader;
    if (script.type == "x-shader/x-fragment") {
        shader = gl.createShader(gl.FRAGMENT_SHADER);
    } else if (script.type == "x-shader/x-vertex") {
        shader = gl.createShader(gl.VERTEX_SHADER);
    } else {
        return null;
    }

    gl.shaderSource(shader, str);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        alert(gl.getShaderInfoLog(shader));
        return null;
    }
    return shader;
}

function webgl_start() {
    gl = getGL('webgl-screen');

    var vertices = [ -1.0, -1.0, 0,
                    1.0, -1.0, 0,
                    0, 1.0, 0
                        ];
    var indices = [ 0, 1, 2 ];

    var vertexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW );

   
    var indexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW );

    gl.bindBuffer(gl.ARRAY_BUFFER, null);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

    // shader program
    var fragmentShader = getShader(gl, "shader-fs");
    var vertexShader = getShader(gl, "shader-vs");

    var shaders = gl.createProgram();
    gl.attachShader(shaders, vertexShader);
    gl.attachShader(shaders, fragmentShader);
    gl.linkProgram(shaders);

    if (!gl.getProgramParameter(shaders, gl.LINK_STATUS)) {
      alert("Could not initialise shaders");
    }

    gl.useProgram(shaders);

    shaders.vertexPositionAttribute = gl.getAttribLocation(shaders, 'aVertexPosition');
    shaders.pMatrixUniform          = gl.getUniformLocation(shaders, 'uPMatrix');
    shaders.mvMatrixUniform         = gl.getUniformLocation(shaders, 'uMVMatrix');


    // draw
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.enable(gl.DEPTH_TEST);

    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.viewport(0,0,c_width, c_height);

    mat4.perspective(45, c_width / c_height, 0.1, 10000.0, pMatrix);
    mat4.identity(mvMatrix);   
    mat4.translate(mvMatrix, [0.0, 0.0, -5.0]);

    gl.uniformMatrix4fv(shaders.pMatrixUniform, false, pMatrix);
    gl.uniformMatrix4fv(shaders.mvMatrixUniform, false, mvMatrix);


    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
    gl.vertexAttribPointer(shaders.aVertexPosition, 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(shaders.vertexPositionAttribute);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
    gl.drawElements(gl.LINE_LOOP, indices.length, gl.UNSIGNED_SHORT,0);

} // webgl_start()

</script>

</head>

<body onload="webgl_start()">

<canvas id="webgl-screen" width='500' height='500'></canvas>

</body>

</html>

-----


더 찾아봐야 할 내용 많네요. ㅠㅠ











































'WebGL' 카테고리의 다른 글

WebGL - (6) 여러가지 Matrix 에 대한 설명  (0) 2013.05.25
WebGL - (5) 사각형 그리기  (0) 2013.05.23
WebGL - (3) Context 가져오는 좀 더 나은 방법  (1) 2013.05.22
WebGL - (2) 작업영역 지우기  (0) 2013.05.21
WebGL - (1)  (0) 2013.05.21

댓글