WebGL-007: Simple animation
Post

WebGL-007: Simple animation

이번에는 WebGL을 이용한 Animation을 표현해 보도록하겠다. OpenGL이든 Direct3D이든 Geometry에 대한 연산을 행렬 형태로 표현하고 있다. 이동/회전/크기변환 등을 행렬로 표현하면 여러가지 장점이 있어 사용할 뿐 필수 사항은 아닐 것이다. 이번 장에서는 행렬을 사용하지 않고 아주 간단한 Animation을 표현할 것인데, 그 이유는 행렬을 표현함에 있어 javascript를 이용해야 하는데 프로그램 코드 작성량을 조금이라도 더 줄이기 위해서다. 본 문서는 javascript에 익숙하지 않다는 가정을 하고 있으므로 javascript로 행렬을 표현하는 방법은 차후 다루기로 하겠다.

WebGL에서 Animation을 표현하는 방법은 GLSL을 이용한 방법외에는 존재하지 않다라고 해도 무방할 것이다. 그 이유는 다음의 예를 보면 알 수 있을 것이다. 삼각형에 대한 이동을 수행하겠다고 가정하자. 가능한 방법은, 첫째 WebGL을 표현하는 Canvas를 직접 이동시키거나(이것도 animation이라고 해 줄 수 있다면), 둘째 삼각형의 각 버텍스를 javascript를 이용하여 옮기거나(버텍스의 값이 변경 될 때 마다 GLSL로 넘어갈 버퍼의 갱신이 이루어 지므로 비효율), 셋째 javascript가 GLSL에게 삼각형의 이동을 위한 수치를 넘기는 방법(가장 이상적) 들이 있다. 본 예제에서는 세번째 방법을 사용할 것이다.

이전의 예제에서 아래와 같은 Vertex Shader 코드를 사용했음을 기억할 것이다. 이 코드에서 “버텍스의 위치값”에 해당하는 변수명이 “aPos”이다. 바로 “aPos”의 값을 적당히 변경하면 삼각형의 이동이 이루어 질 것이다. 그렇다면, 어떻게 적당히 변경할 것이가?

1
2
3
4
5
6
7
8
9
10
11
<script id="VertexShader" type="x-shader/x-vertex">
    attribute vec3 aPos;
    attribute vec4 aColor;

    varying vec4 vColor;

    void main(){
        gl_Position = vec4(aPos, 1.0);
        vColor = aColor;
    }
</script>

한 예로, gl_Position = vec4(aPos, 1.0)를 gl_Position = vec4(aPos, 1.0) + vec4(0.5, 0, 0, 0)으로 변경하면, 삼각형이 오른쪽으로 0.5만큼 이동할 것이며, 그 프로그램 코드는 아래와 같을 것이다.

1
2
3
4
5
6
7
8
9
10
11
<script id="VertexShader" type="x-shader/x-vertex">
    attribute vec3 aPos;
    attribute vec4 aColor;

    varying vec4 vColor;

    void main(){
        gl_Position= vec4(aPos, 1.0) + vec4(0.5, 0, 0, 0);
        vColor= aColor;
    }
</script>

그리고 결과는 아래와 같을 것이다.

triani1

이는 삼각형이 이동만 했을 뿐 Animation이라고 부를 수 없다. Animation이라 부를 수 있으려면 적어도 움직인다라는 느낌이 들 수 있어야 하므로, 적당한 매 시간마다 이동의 값을 바꾸어가며 화면을 갱신할 필요가 있다. javascript에서는 setInterval(...)이라는 함수를 이용하여 ‘사용자가 설정한 매 시간마다 특정 함수를 호출’하는 기능을 수행할 수 있다. 이 함수에서 삼각형의 위치값을 변경하고 변경된 값을 GLSL로 넘긴 후 화면의 갱신을 수행하게 한다면 Animation 표현이 가능하게 될 것이다. 주의할 점은, Animation 표현을 위해 매 시간마다(이하 매 프레임 마다) 불려져야할 구문들과 단 한번만 불려져도 되는 구문들을 구분해야 한다는 것이다. 매 프레임마다 버퍼를 생성한다거나, Shader Script를 읽기/컴파일/링킹등을 수행하는 것과 같은 불필요한 작업은 반드시 피해야 한다.

지금부터 변수명을 조금 수정해 보도록하겠다. GLSL에서의 변수명 aPos에 대한 핸들을 var vertexPositionAttribute; 로 선언 했었는데, 이를 g_hVertPos로 변경하고, aColor 핸들에 대한 변수명을 g_hVertColor로 변경한다. g_는 전역에 위치한 변수라는 뜻이고, h는 핸들 그 다음은 변수 고유의 이름으로 한다.

앞서 살펴보았던 Vertex Shader코드를 다음과 같이 변경한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
<script id="VertexShader" type="x-shader/x-vertex">
    attribute vec3 aPos;
    attribute vec4 aColor;

    uniform vec4 trans;

    varying vec4 vColor;

    void main(){
        gl_Position= vec4(aPos, 1.0) + trans;
        vColor= aColor;
    }
</script>

위의 코드는 GLSL의 Vertex Shader로 넘어오는 mesh의 각 Vertex의 위치에 trans값을 더해 줌을 볼 수 있다. 따라서 mesh의 모든 Vertex의 위치값에 trans를 더해 줌으로서 mesh의 이동이 수행되는 것이다. 이제, javascript에서 변수명 “trans”의 핸들을 얻을 차례이다. 이 핸들이 있어야만 GLSL에 값을 넘길 수 있기 때문이다.(==Vertex Shader에 값을 넘길 수 있기 때문이다.)

1
2
3
4
5
6
//GLSL에서 "trans"라는 변수명의 핸들을 얻음
g_hTrans = gl.getUniformLocation(shaderProgram, "trans");
if( g_hTrans == -1 ){
    alert("Unable to find 'trans' Uniform Location.");
    return false;
}

javascript의 변수명 g_hTrans가 GLSL의 trans의 핸들이다. trans가 uniform으로 선언되어 있으므로 핸들을 얻는 함수 역시 getUniformLocation이다. 이제 g_hTrans를 이용하여 GLSL에 값을 넘길 차례이다.

아래는 g_hTrans핸들을 통해 GLSL에 값을 전달하는 함수이다.

1
gl.uniform4f(g_hTrans, 0.5, 0, 0, 0);

g_hTrans핸들은 GLSL에서의 uniform vec4 trans;를 참조하고 있으므로 값을 넘길 때 uniform4f를 사용해야 한다. 여기까지 수행 하면 앞서 보았덭 것과 동일한 결과를 얻을 수 있다.

소소코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<html>
<head>
    <title>WebGL - Prac06 - Animation</title>
    <script type="text/javascript">
        var gl;
        var g_hVertPos;
        var g_hVertColor;
        var g_hTrans;

        function start() {
            if (!initWebGL()) {
                return;
            }

            if (!initShader()) {
                return;
            }

            var verAry = [
                0.0, 0.0, 0.0,
                0.5, 0.0, 0.0,
                0.0, 1.0, 0.0
            ];

            var colorAry = [
                1.0, 0.0, 0.0, 1.0,
                0.0, 1.0, 0.0, 1.0,
                0.0, 0.0, 1.0, 1.0
            ];

            var glBufVert = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, glBufVert);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verAry), gl.STATIC_DRAW);

            var glBufColor = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, glBufColor);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colorAry), gl.STATIC_DRAW);

            gl.clearColor(0.4, 0.5, 0.6, 1.0);

            gl.bindBuffer(gl.ARRAY_BUFFER, glBufVert);
            gl.vertexAttribPointer(g_hVertPos, 3, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, glBufColor);
            gl.vertexAttribPointer(g_hVertColor, 4, gl.FLOAT, false, 0, 0);

            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            //x좌표 값을 변경 시킨 후 GLSL에 넘긴다.
            gl.uniform4f(g_hTrans, 0.5, 0, 0, 0);

            gl.drawArrays(gl.TRIANGLES, 0, 3);
        }

        function initWebGL() {
            var canvas = document.getElementById("WebGL-Canvas");
            if (!canvas) {
                alert("Can't find WebGL-Canvas.");
                return false;
            }

            try {
                gl = canvas.getContext("experimental-webgl");
            } catch (e) {
            }

            if (!gl) {
                alert("Unable to initialize WebGL. Your browser may not support it.");
                return false;
            }

            return true;
        }

        function initShader() {
            var vertexShaderDesc = getShader("VertexShader");
            var vertexShader = gl.createShader(gl.VERTEX_SHADER);
            gl.shaderSource(vertexShader, vertexShaderDesc);
            gl.compileShader(vertexShader);
            if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
                alert("Error(VertexShader): " + gl.getShaderInfoLog(vertexShader));
                return false;
            }

            var fragmentShaderDesc = getShader("FragmentShader");
            var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
            gl.shaderSource(fragmentShader, fragmentShaderDesc);
            gl.compileShader(fragmentShader);
            if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
                alert("Error(FragmentShader): " + gl.getShaderInfoLog(fragmentShader));
                return false;
            }

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

            if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
                alert("Unagle to initialize the shader program.");
                return false;
            }

            gl.useProgram(shaderProgram);

            g_hVertPos = gl.getAttribLocation(shaderProgram, "aPos");
            if (g_hVertPos == -1) {
                alert("Unable to find Vertex Attribute Location.");
                return false;
            }
            gl.enableVertexAttribArray(g_hVertPos);

            g_hVertColor = gl.getAttribLocation(shaderProgram, "aColor");
            if (g_hVertColor == -1) {
                alert("Unable to find Vertex Color Attribute Location.");
                return false;
            }
            gl.enableVertexAttribArray(g_hVertColor);

            //GLSL에서 "trans"라는 변수명의 핸들을 얻음
            g_hTrans = gl.getUniformLocation(shaderProgram, "trans");
            if (g_hTrans == -1) {
                alert("Unable to find 'trans' Uniform Location.");
                return false;
            }

            return true;
        }

        function getShader(id) {
            var shaderScript = document.getElementById(id);
            if (!shaderScript) {
                alert("Unable to get shader script. id='" + id + "'");
                return null;
            }

            var shaderDesc = "";
            var currentChild = shaderScript.firstChild;
            while (currentChild) {
                if (currentChild.nodeType == 3) {
                    shaderDesc += currentChild.textContent;
                }
                currentChild = currentChild.nextSibling;
            }

            return shaderDesc;
        }
    </script>

    <script id="VertexShader" type="x-shader/x-vertex">
        attribute vec3 aPos;
        attribute vec4 aColor;

        uniform vec4 trans;

        varying vec4 vColor;

        void main(){
            gl_Position = vec4(aPos, 1.0) + trans;
            vColor= aColor;
        }

    </script>

    <script id="FragmentShader" type="x-shader/x-vertex">
        precision mediump float;
        varying vec4 vColor;
        void main(){
            gl_FragColor = vColor;
        }


    </script>

</head>

<body onload="start()">
<canvas id="WebGL-Canvas" style="border: 1px solid green" width="400" height="300">
    Your browser doesn't appear to support the HTML5 &lt;canvas&gt; element.
</canvas>
</body>
</html>

triani2

이제 javascript의 setInterval(…)함수를 이용하여, 매 프레임을 갱신하고, 삼각형의 위치를 변경시켜 보도록 하겠다. 본 예제에서는 아주 단순히 삼각형의 x좌표의 위치만 변경시켜 보도록 하겠다. 삼각형의 x좌표는 매 프레임 마다 어떤 함수에서 갱신될 것이므로 x좌표에 대한 변수는 전역으로 선언해 두고 값을 갱신해 나가야 할 것이다.

소스코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
<html>
<head>
    <title>WebGL - Prac06 - Animation</title>
    <script type="text/javascript">
        var gl;
        var g_hVertPos;
        var g_hVertColor;
        var g_hTrans;

        var g_transPosX = 0;

        function start() {
            if (!initWebGL()) {
                return;
            }

            if (!initShader()) {
                return;
            }

            var verAry = [
                0.0, 0.0, 0.0,
                0.5, 0.0, 0.0,
                0.0, 1.0, 0.0
            ];

            var colorAry = [
                1.0, 0.0, 0.0, 1.0,
                0.0, 1.0, 0.0, 1.0,
                0.0, 0.0, 1.0, 1.0
            ];

            var glBufVert = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, glBufVert);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verAry), gl.STATIC_DRAW);

            var glBufColor = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, glBufColor);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colorAry), gl.STATIC_DRAW);

            gl.clearColor(0.4, 0.5, 0.6, 1.0);

            gl.bindBuffer(gl.ARRAY_BUFFER, glBufVert);
            gl.vertexAttribPointer(g_hVertPos, 3, gl.FLOAT, false, 0, 0);

            gl.bindBuffer(gl.ARRAY_BUFFER, glBufColor);
            gl.vertexAttribPointer(g_hVertColor, 4, gl.FLOAT, false, 0, 0);

            //0.05초 마다 drawScene 호출
            setInterval(drawScene, 50);
        }

        function drawScene() {
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            //x좌표 값을 변경 시킨 후 GLSL에 넘긴다.
            g_transPosX += 0.01;
            if (g_transPosX > 0.5) g_transPosX = 0;
            gl.uniform4f(g_hTrans, g_transPosX, 0, 0, 0);

            gl.drawArrays(gl.TRIANGLES, 0, 3);
        }

        function initWebGL() {
            var canvas = document.getElementById("WebGL-Canvas");
            if (!canvas) {
                alert("Can't find WebGL-Canvas.");
                return false;
            }

            try {
                gl = canvas.getContext("experimental-webgl");
            } catch (e) {
            }

            if (!gl) {
                alert("Unable to initialize WebGL. Your browser may not support it.");
                return false;
            }

            return true;
        }

        function initShader() {
            var vertexShaderDesc = getShader("VertexShader");
            var vertexShader = gl.createShader(gl.VERTEX_SHADER);
            gl.shaderSource(vertexShader, vertexShaderDesc);
            gl.compileShader(vertexShader);
            if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
                alert("Error(VertexShader): " + gl.getShaderInfoLog(vertexShader));
                return false;
            }

            var fragmentShaderDesc = getShader("FragmentShader");
            var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
            gl.shaderSource(fragmentShader, fragmentShaderDesc);
            gl.compileShader(fragmentShader);
            if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
                alert("Error(FragmentShader): " + gl.getShaderInfoLog(fragmentShader));
                return false;
            }

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

            if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
                alert("Unagle to initialize the shader program.");
                return false;
            }

            gl.useProgram(shaderProgram);

            g_hVertPos = gl.getAttribLocation(shaderProgram, "aPos");
            if (g_hVertPos == -1) {
                alert("Unable to find Vertex Attribute Location.");
                return false;
            }
            gl.enableVertexAttribArray(g_hVertPos);

            g_hVertColor = gl.getAttribLocation(shaderProgram, "aColor");
            if (g_hVertColor == -1) {
                alert("Unable to find Vertex Color Attribute Location.");
                return false;
            }
            gl.enableVertexAttribArray(g_hVertColor);

            //GLSL에서 "trans"라는 변수명의 핸들을 얻음
            g_hTrans = gl.getUniformLocation(shaderProgram, "trans");
            if (g_hTrans == -1) {
                alert("Unable to find 'trans' Uniform Location.");
                return false;
            }

            return true;
        }

        function getShader(id) {
            var shaderScript = document.getElementById(id);
            if (!shaderScript) {
                alert("Unable to get shader script. id='" + id + "'");
                return null;
            }

            var shaderDesc = "";
            var currentChild = shaderScript.firstChild;
            while (currentChild) {
                if (currentChild.nodeType == 3) {
                    shaderDesc += currentChild.textContent;
                }
                currentChild = currentChild.nextSibling;
            }

            return shaderDesc;
        }

    </script>

    <script id="VertexShader" type="x-shader/x-vertex">
        attribute vec3 aPos;
        attribute vec4 aColor;
        uniform vec4 trans;
        varying vec4 vColor;

        void main(){
            gl_Position = vec4(aPos, 1.0) + trans;
            vColor= aColor;
        }

    </script>

    <script id="FragmentShader" type="x-shader/x-vertex">
        precision mediump float;
        varying vec4 vColor;

        void main(){
            gl_FragColor = vColor;
        }

    </script>

</head>

<body onload="start()">
<canvas id="WebGL-Canvas" style="border: 1px solid green" width="400" height="300">
    Your browser doesn't appear to support the HTML5 <code>&lt;canvas&gt;</code> element.
</canvas>
</body>
</html>