WebGL을 위한 JavaScript의 코드가 조금씩 길어짐에 따라, 소스 코드의 관리 및 가독성이 점점 떨어 지고 있다. 따라서 이번 문서에서는 WebGL을 위해 JavaScript 코드를 어떻게 사용할 것인가에 대한 내용을 살펴볼 것이다. 지금 까지는 하나의 *.html 파일에 모든 코드가 들어가 있는데, 이후 부터는 *.html파일과 *.js파일(JavaScript code file)을 구분하여 프로그램을 작성할 것이며, 자주 사용되거나 반복적으로 사용되는 JavaScript 구문들을 따로 묶어내어 코드의 Line 수를 줄임과 동시에, 유지 보수 및 가독성 향상을 용이하게 해 보도록 하겠다.
다음과 같은 방법으로 JavaScript코드와 Html코드를 분리해 낼 수 있다. 또한 각 기능에 따라 JavaScript파일을 여러개로 분리하여 관리할 수 있다.
아래와 같은 두 개의 JavaScript파일과 Html파일이 있다고 가정해보자.
1
2
3
function sayHello(){
alert("Hello");
}
1
2
3
4
<html>
<body onload="sayHello()">
</body>
</html>
위의 app.html
을 보면 웹페이지의 <body>
테그 컨텐츠의 로드가 시작 될 경우(onload
) JavaScript의 sayHello()
함수를 호출하도록 설정이 되어있다. 하지만 app.html
에는 sayHello()라는 자바스크립트 함수가 존재하지 않고 app.js
라는 파일에 정의되어 있다. 따라서 아래와 같이 app.html
을 수정한다.
1
2
3
4
5
6
7
<html>
<head>
<script src="app.js"></script>
</head>
<body onload="sayHello()">
</body>
</html>
이제 app.html
을 실행해 보면 app.js
에 정의되어 있는 sayHello()라는 함수에 의해 “Hello”라는 출력값을 갖는 MessageBox를 볼 수 있다. 이와 같은 방법으로 *.html
파일과 *.js
파일을 따로 관리하고 또 여러 *.js
파일을 이용하여 각 기능들을 분리해 낼 수 있다.
지금까지 WebGL의 예제에서는 행렬 관련 부분을 사용하지 않았는데, 그 이유는 JavaScript을 이용하여 WebGL을 위한 행렬을 표현하는 것이 이전의 예제에서 사용했던 방법(C/C++, Java)에 비해 익숙하지 않았기 때문이다. 이번 장에서 WebGL에 필요한 행렬에 관련된 기본적인 몇 가지만(Identity, Translate) 구현해 보도록하고 차차 그 기능을 늘려 보도록 하겠다. JavaScript의 자세한 기능은 따로 참고하도록 하고 여기에서는 단순히 ‘이렇게 구현하면 된다’ 정도로 끝내도록 하겠다.
아래는 JavaScript를 이용하여 Matrix 객체를 정의하고 makeIdentity함수와 translate ,rotation 함수를 구현한 결과는 아래와 같다.
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
Matrix= function(){
elements= new Float32Array(16);
}
Matrix.prototype.flattern= function(){
return elements;
}
Matrix.prototype.toString= function(){
return
elements[0]+", "+elements[1]+", "+elements[2]+", "+elements[3]+"\n" +
elements[4]+", "+elements[5]+", "+elements[6]+", "+elements[7]+"\n" +
elements[8]+", "+elements[9]+", "+elements[10]+", "+elements[11]+"\n" +
elements[12]+", "+elements[13]+", "+elements[14]+", "+elements[15];
}
Matrix.prototype.makeIdentity= function(){
elements[1]= elements[2]= elements[3]= elements[4]=
elements[6]= elements[7]= elements[8]= elements[9]=
elements[11]= elements[12]= elements[13]= elements[14]= 0;
elements[0]= elements[5]= elements[10]= elements[15]= 1;
}
Matrix.prototype.translate= function(x, y, z){
elements[1]= elements[2]= elements[3]= elements[4]=
elements[6]= elements[7]= elements[8]= elements[9]= elements[11]= 0;
elements[0]= elements[5]= elements[10]= elements[15]= 1;
elements[12]=x;
elements[13]=y;
elements[14]=z;
}
Matrix.prototype.rotateX= function(angle){
var c= Math.cos(angle);
var s= Math.sin(angle);
elements[0]=1; elements[1]= 0; elements[2]=0; elements[3]=0;
elements[4]=0; elements[5]= c; elements[6]=s; elements[7]=0;
elements[8]=0; elements[9]=-s; elements[10]=c; elements[11]=0;
elements[12]=0; elements[13]=0; elements[14]=0; elements[15]=1;
}
Matrix.prototype.rotateZ= function(angle){
var c= Math.cos(angle);
var s= Math.sin(angle);
elements[0]= c; elements[1]=s; elements[2]=0; elements[3]=0;
elements[4]=-s; elements[5]=c; elements[6]=0; elements[7]=0;
elements[8]= 0; elements[9]=0; elements[10]=0; elements[11]=0;
elements[12]=0; elements[13]=0; elements[14]=0; elements[15]=1;
}
이제 앞서 보였던 ‘삼각형의 이동’ 예제를 행렬을 이용한 형태로 바꾸어 보도록 하겠다.
아래는 이전에 사용되었던 VertexShader 이다.
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>
이를 아래와 같이 수정한다.
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 mat4 gWorld;
varying vec4 vColor;
void main(){
gl_Position= gWorld * vec4(aPos, 1.0);
vColor= aColor;
}
</script>
univorm vec4 trans; 라는 변수를 이용하여, Vertex Position의 이동을 수행했었는데, 지금은 uniform mat4 gWorld라는 변수(행렬)를 이용하여 Vertex Position의 이동을 수행함을 볼 수 있다. 따라서, 행렬의 종류(이동, 회전, 크기변환)에 따라 그에 맞는 Vertex Position의 이동이 결정될 것이다.
이제, GLSL의 변수명 “gWorld”에 대한 핸들을 얻어올 차례이다. 이전의 예제에서는 변수명 “trans”에 대한 핸들을 얻기 위해 아래와 같은 코드를 사용했다.
1
2
3
4
5
g_hTrans = gl.getUniformLocation(shaderProgram, "trans");
if( g_hTrans == -1 ){
alert("Unable to find 'trans' Uniform Location.");
return false;
}
이를 아래와 같이 수정한다.
1
2
3
4
5
g_hWorld = gl.getUniformLocation(shaderProgram, "gWorld");
if( g_hWorld == -1 ){
alert("Unable to find 'gView' uniform location.");
return false;
}
GLSL의 변수명 “gWorld”에 대한 핸들이 JavaScript의 g_hWorld
라는 이름의 변수에 할당되어 있으므로 g_hWorld
변수를 이용하여 GLSL에 행렬값을 넘겨주어야 할 것이다. 앞서 “Matrix”라는 객체를 정의 하였다. 아래와 같이 이 객체의 인스턴스를 생성하고, 적당한 행렬(본 예제에서는 앞서 보였던 예제와 같은 이동 행렬)을 만든 후 GLSL에 넘기도록 한다.
1
2
3
4
5
6
7
8
9
10
11
12
//Matrix 객체의 인스턴스 생성
g_mtxView= new Matrix();
//이전 예제와 같은 Animation을 위해 매 프레임 마다 값을 변경
g_transPosX += 0.01;
if( g_transPosX > 0.5 ) g_transPosX = 0;
//이동행렬을 만듦
g_mtxView.translate(g_transPosX, 0, 0);
//g_hWorld라는 핸들을 이용해 GLSL에 행렬 값을 넘김
gl.uniformMatrix4fv(g_hWorld, false, g_mtxView.flattern());
위 예제의 결과는 앞서 소개한 007. WebGL - Prac06 - Animation과 같은 결과를 보여 준다.
전체 소스코드는 아래와 같다.
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
191
192
<html>
<head>
<title>WebGL - Prac06 - Animation</title>
<script src="glMath.js" type="text/javascript"></script>
<script type="text/javascript">
var gl;
var g_hVertPos;
var g_hVertColor;
var g_hWorld;
var g_transPosX = 0;
var g_mtxView;
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);
g_mtxView = new Matrix();
//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;
g_mtxView.translate(g_transPosX, 0, 0);
gl.uniformMatrix4fv(g_hWorld, false, g_mtxView.flattern());
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);
g_hWorld = gl.getUniformLocation(shaderProgram, "gWorld");
if (g_hWorld == -1) {
alert("Unable to find 'gView' 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 mat4 gWorld;
varying vec4 vColor;
void main(){
gl_Position = gWorld * vec4(aPos, 1.0);
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><canvas></code> element.
</canvas>
</body>
</html>