WebGL을 이용하여, Text Rendering을 하는 것은 JavaScript의 지식을 비교적 많이 필요로 한다. 그 외에는 WebGL의 사각형에, JavaScript로 렌더링된 Text 이미지를 텍스쳐링하는 것만 수행하면 된다. Windows platform에서 OpenGL의 wgl을 이용하여 text를 렌더링해본 경험이 있다면, 혹은 glut를 이용하여 text를 렌더링해본 경험이 있다면, WebGL에서의 방법은 비교적 복잡한 코드 구조를 만들어 내는 것에 거부감을 느낄지도 모르겠다. 하지만, 이 방법은 OpenGL에서 동적 text를 렌더링 하는 유일한 방법으로(효율성을 제외) 생각되므로 소개하도록 하겠다. 기본 원리는 OpenGL/wgl의 wglUseFontBitmaps(…);를 이용한 방법과 다를 바 없다.(차이점은 앞서 소개한 바와 같이 WebGL이 OpenGL ES를 기반으로 하므로 반드시 Shader를 사용해야 한다는 점 등이 있을 수 있음)
텍스트 렌더링의 전체적 수행 과정은 아래와 같다.
- JavaScript를 이용하여 Text를 렌더링 한다.
- WebGL의 사각형에
1
의 결과를 텍스쳐링하여 렌더링 한다.
첫 번째 단계의 세부 수행 과정은 아래와 같다.
a. canvas
객체를 생성한다. ex) var aaa= document.createElement(“canvas”);
b. a
단계의 결과로 부터 2d 컨텍스트를 얻어온다. ex) var bbb= aaa.getContext(“2d”);
c. b
단계의 결과에 텍스트를 렌더링한다. ex) bbb.fillText(“텍스트”, 100, 100);
d. 마지막으로 a
를 이용하여 WebGL에 사용할 텍스쳐릉 생성한다.
a
단계에서 canvas 객체를 생성한다고 했을 때 var aaa= document.createElement(“canvas”); 와 같이 해도 되지만 body 본문 내에
1
<canvas id="text" style="border: none" width="128" height="128"></canvas>
와 같이 HTML을 작성한 후 var aaa= document.getElementById(“text”);와 같이 canvas 엘리먼트를 얻어오는 JavaScript를 사용해도 된다. JavaScript에 의해 렌더링된 텍스트를 직접확인하기 위해 우선 두번째 방법을 사용해 보도록 하겠다.
길게 설명하는 것 보다 아래의 소스코드 몇 줄이 더 잘 전달 될 것이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function createTexture(){
var txtImg = document.getElementById("text");
var ctx = txtImg.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.save();
ctx.font = "bold 20px Arial";
var str = "www.jrr.kr";
ctx.fillStyle = "rgb(255, 150, 00)";
ctx.fillText(str, (ctx.canvas.width-ctx.measureText(str).width)/2, 72);
ctx.restore();
g_Texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, g_Texture);
gl.texImage2D(gl.TEXTURE_2D, 0, txtImg, true);
gl.generateMipmap(gl.TEXTURE_2D);
}
위의 코드는
1
<canvas id="text" style="border: none" width="128" height="128"></canvas>
라는 HTML코드가 본문에 존재했을 때 제대로 동작할 것이다. WebGL에 이렇게 생성된 텍스쳐(변수명 g_Texture)를 사각형에 붙이기 전에 JavaScript로 렌더링된 Text를 확인해 보면 결과는 아래와 같다.
전체 코드는 아래와 같다.
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
<html>
<head>
<title>WebGL - Prac08 - Text Rendering</title>
<script src="glMath.js" type="text/javascript"></script>
<script type="text/javascript">
var gl;
var g_hVertPos;
var g_hVertTex;
var g_hWorld;
var g_transPosX = 0;
var g_mtxView;
var g_Texture = null;
var g_hSampler1;
function start() {
if (!initWebGL()) {
return;
}
if (!initShader()) {
return;
}
var verAry = [
0.5, 0.0, 0.0,
0.5, 0.5, 0.0,
0.0, 0.0, 0.0,
0.0, 0.5, 0.0
];
var texAry = [
1.0, 1.0,
1.0, 0.0,
0.0, 1.0,
0.0, 0.0
];
var glBufVert = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, glBufVert);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verAry), gl.STATIC_DRAW);
var glBufTex = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, glBufTex);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texAry), 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, glBufTex);
gl.vertexAttribPointer(g_hVertTex, 2, gl.FLOAT, false, 0, 0);
g_mtxView = new Matrix();
createTexture();
//0.05초 마다 drawScene 호출
setInterval(drawScene, 30);
}
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.TRIANGLE_STRIP, 0, 4);
}
function createTexture() {
var txtImg = document.getElementById("text");
var ctx = txtImg.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.save();
ctx.font = "bold 20px Arial";
var str = "www.jrr.kr";
ctx.fillStyle = "rgb(255, 150, 00)";
ctx.fillText(str, (ctx.canvas.width - ctx.measureText(str).width) / 2, 72);
ctx.restore();
g_Texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, g_Texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, txtImg);
}
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_hVertTex = gl.getAttribLocation(shaderProgram, "aTexCoordinate");
if (g_hVertTex == -1) {
alert("Unable to find Vertex Color Attribute Location.");
return false;
}
gl.enableVertexAttribArray(g_hVertTex);
g_hWorld = gl.getUniformLocation(shaderProgram, "gWorld");
if (g_hWorld == -1) {
alert("Unable to find 'gView' uniform location.");
return false;
}
g_hSampler1 = gl.getUniformLocation(shaderProgram, "gSampler1");
if (g_hSampler1 == -1) {
alert("Unable to find 'gSampler1' 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 vec2 aTexCoordinate;
uniform mat4 gWorld;
varying vec2 vTexCoordinate;
void main(){
gl_Position= gWorld * vec4(aPos, 1.0);
vTexCoordinate= aTexCoordinate;
}
</script>
<script id="FragmentShader" type="x-shader/x-vertex">
precision mediump float;
varying vec2 vTexCoordinate;
uniform sampler2D gSampler1;
void main(){
gl_FragColor= texture2D(gSampler1, vTexCoordinate);
}
</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>
<canvas id="text" style="border: none" width="128" height="128"></canvas>
</body>
</html>
위 코드의 결과는 아래와 같다.