이번에는 WebGL에서 Geometry에 Texture를 입혀보는 예제를 소개하도록 하겠다. Local System에서 작동되는 OpenGL 프로그램에서의 Texture로딩은 Local Disk에 저장되어 있는 Image 파일을 읽어들여 사용함을 잘 알고 있을 것이다. 그렇다면, Web상에서 작동하는 WebGL은 어떻게 Texture를 로딩할 것인가? 답은 Local System에서의 방법과 큰 차이가 없다는 것이다. Local System에서, Image 파일을 읽기 위해서는 그 파일의 경로(Local Disk상에서의 경로)를 알고 있어야 한다. 비슷하게 WebGL에서는 Image 파일의 Web상의 경로(절대경로 혹은 상대경로)를 알고 있으면 된다. 그리고 JavaScript의 Image객체를 사용하여 Image Loading을 수행하면 된다. 아래의 예제를 보도록 하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//Texture객체 정의
Texture = function(src){
_texture = null;
var img= new Image();
img.onload= function(){
_texture= gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, _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, img);
}
img.src = src;
}
Texture.prototype.getTexture = function(){
return _texture;
}
위 코드는 Texture
객체를 정의한 코드이다. new Image();를 이용하여, Image 객체의 인스턴스를 생성하고, loading이 성공했을 때의 이벤트 핸들러를 설정하며, img.src=”…“로 이미지의 경로 설정 및 로딩을 시작한다.
만약 경로 src
에 이미지가 존재하고 로딩이 성공하면, onload 함수가 호출되고 텍스쳐 생성을 시작한다. 반대로 이미지 로딩에 실패하면 아무런 동작을 하지 않는다. 간단한 몇 줄의 코드로 이미지 로딩 및 텍스쳐 생성이 완료된다.
아래의 코드는 텍스쳐 좌표(aTexCoordinate
)를 넘겨 받는 VertexShader 이다. VertexShader는 vTexCoordinate
변수를 통해 FragmentShader로 텍스쳐 좌표를 전달한다. FragmentShader는 VertexShader로 부터 넘겨 받은 vTexCoordinate
변수를 통해 텍스쳐 좌표에 접근한다.
1. 텍스쳐
와 텍스쳐 좌표
를 GLSL로 넘기기 위한 VertexShader & FragmentShader 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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>
2. GLSL에 텍스쳐
를 전달
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Texture Coordinate를 넘기기 위한 핸들을 얻어옴
g_hVertTex= gl.getAttribLocation(shaderProgram, "aTexCoordinate");
if( g_hVertTex==-1 ){
alert("Unable to find Vertex Color Attribute Location.");
return false;
}
gl.enableVertexAttribArray(g_hVertTex);
// Texture를 넘기기 위한 Uniform 핸들 얻어옴
g_hSampler1 = gl.getUniformLocation(shaderProgram, "gSampler1");
if( g_hSampler1 == -1 ){
alert("Unable to find 'gSampler1' uniform location.");
return false;
}
3. 텍스쳐 좌표 데이터를 위한 버퍼 생성
1
2
3
4
5
6
7
8
9
var texAry = [
0.0, 0.0,
1.0, 0.0,
0.0, 1.0
];
var glBufTex = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, glBufTex);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texAry), gl.STATIC_DRAW);
4. GLSL에 텍스쳐 좌표 데이터 버퍼를 넘김
1
2
gl.bindBuffer(gl.ARRAY_BUFFER, glBufTex);
gl.vertexAttribPointer(g_hVertTex, 2, gl.FLOAT, false, 0, 0);
5. Texture인스턴스 생성 및, GLSL에 텍스쳐 넘김
1
2
3
4
5
6
g_Texture = new Texture("webgl.png");
gl.enable(gl.TEXTURE_2D);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, g_Texture.getTexture());
gl.uniform1i(g_hSampler1, 0);
이전 예제를 바탕으로, 위의 다섯 단계를 추가한 코드를 작성하면, 아래와 비슷한 코드 형태가 될 것이다.
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
<html>
<head>
<title>WebGL - Prac08 - Texture</title>
<script src="glMath.js" type="text/javascript"></script>
<script src="glTexture.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;
var g_hSampler1;
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 texAry = [
0.0, 0.0,
1.0, 0.0,
0.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 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_Texture = new Texture("WebGL.png");
gl.enable(gl.TEXTURE_2D);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, g_Texture.getTexture());
gl.uniform1i(g_hSampler1, 0);
g_mtxView = new Matrix();
//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.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_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 <canvas> element.
</canvas>
</body>
</html>
위 코드의 결과는 아래와 같다.
사용한 텍스쳐는 아래와 같다.