ศึกษา Modern OpenGL ด้วย WebGL — Part 2 : ลงสีด้วย Texture

หลังจากทำสามเหลี่ยมสามสีโดยการลงสีแผ่นจุด Vertex แล้ว ในตอนก่อน มาตอนนี้จะลงสีโดยใช้ Texture

โดยใช้โค้ดเก่าเอามาแก้นิดหน่อย
เริ่มตั้งแต่การเปลี่ยน Shader จากรับค่าสีจากแต่ละ Vertex เป็นรับค่าตำแหน่ง Texture Coordinates และค่า Texture

<!DOCTYPE html>
<html>
<head>
<title>Web GL</title>
<script type="x-shader/x-vertex" id="vertex-shader">
attribute vec3 in_position;
attribute vec2 in_texcoord;
varying vec2 out_texcoord;
void main() {
gl_Position = vec4(in_position, 1.0);
out_texcoord = in_texcoord;
}
</script>
<script type="x-shader/x-fragment" id="fragment-shader">
precision mediump float;

varying vec2 out_texcoord;
uniform sampler2D u_texture;
void main() {
gl_FragColor = texture2D(u_texture, out_texcoord);;
}
</script>
<script type="text/javascript" src="js/webGL_Boilerplate.js"></script>
</head>
<body>
<canvas id="context" width="640" height="480"></canvas>
<script type="text/javascript" src="js/webGL_Project.js"></script>
</body>
</html>

จะเห็นว่าผมลบพวกตัวแปรที่เกี่ยวกับค่าสีออกหมดเลย แล้วเปลี่ยนเป็น Texture Coordinates และใน Fragment Shader มีการรับค่า Texture มาด้วย

ต่อมาก็ทยอยเปลี่ยนโค้ดโปรเจคจากที่ใช้ ตัวแปร, Buffer ของค่าสี ก็จะเปลี่ยนเป็นค่าสำหรับ Texture Coordinates

// Get A WebGL context
var gl = getContext("context");

// กำหนดค่าสีตอน Clear Screen (r, g, b, a)
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// ใช้งาน Depth Testing ตอน Clear Screen
gl.enable(gl.DEPTH_TEST);
// กำหนดการ Clear Depth ให้สิ่งที่อยู่ใกล้บังสิ่งที่อยู่ไกล
gl.depthFunc(gl.LEQUAL);
// Clear สี และ Depth
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

// setup a GLSL program
var program = createProgramFromScripts(gl, "vertex-shader", "fragment-shader");
gl.useProgram(program);

// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "in_position");
var texCoordLocation = gl.getAttribLocation(program, "in_texcoord");
gl.enableVertexAttribArray(positionLocation);
gl.enableVertexAttribArray(texCoordLocation);

สังเกตุบรรทัดที่ 19, 21 นะคือเปลี่ยนการหาตำแหน่งตัวแปรจากตอนก่อนเป็น in_color เปลี่ยนเป็น in_texcoord แล้วนะ

ต่อมาก็เป็นการสร้าง Buffer เตรียมข้อมูลให้มัน

var texCoords = new Float32Array([
0, 1,
1, 1,
0.5, 0
]);
var textCoord_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, textCoord_buffer);
gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);

สังเกตุข้อมูลของ Texture Coordinates คือตำแหน่งในรูปที่เราจะใช้เป็น Texture โดยค่าต่ำสุดของความกว้าง/สูงคือ 0 และสูงสุดคือ 1
คือ ไม่ว่าเราจะโหลดรูป กว้าง 128*128 หรือ 256*256 หรือขนาดไหนก็ตาม ค่าสูงสุดของ Texture Coordinates จะคือ 1 ถ้ามากกว่า 1 หรือน้อยกว่า 0 มันจะ Clamp ไม่ก็ Repeat ขึ้นอยู่ว่าเราตั้งค่ามันยังไง

หลังจากนั้นก็มาบอกว่า แต่ละ Vertex ใช้ข้อมูลที่กำหนดอย่างไรด้วย Function: gl.vertexAttribPointer()

gl.bindBuffer(gl.ARRAY_BUFFER, textCoord_buffer);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

ตอนนี้เราก็กำหนดค่าต่างๆ ให้กับ Buffer แล้ว แต่ยังไม่มี Texture ดังนั้นมาสร้าง Texture กันเลยด้วย Function: gl.createTexture();

var texture = gl.createTexture();

ตอนนี้ได้ Texture เปล่าๆ มาแล้ว แต่ยังไม่มีรูป เราต้องโหลดรูปมาให้มันอีกที *-*

var image = new Image();
image.src = "resources/logo.png";

แล้วก็ใส่ Event หลังจากโหลดรูปเสร็จ ให้ทำการ กำหนดรูปให้ Texture ด้วย Function: gl.texImage2D() และสร้าง Mipmap ให้มันด้วย Function: gl.generateMipmap() แล้วก็สั่งวาดเลย

image.onload = function() {
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
};

เสร็จแล้วลองทดสอบดู จะได้สามเหลี่ยมที่มี Texture คล้ายๆ แบบรูปนี้

webgl-result-2

สรุปโค้ดทั้งหมดของไฟล์ webGL_Project.js ใน Part นี้

// Get A WebGL context
var gl = getContext("context");

// กำหนดค่าสีตอน Clear Screen (r, g, b, a)
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// ใช้งาน Depth Testing ตอน Clear Screen
gl.enable(gl.DEPTH_TEST);
// กำหนดการ Clear Depth ให้สิ่งที่อยู่ใกล้บังสิ่งที่อยู่ไกล
gl.depthFunc(gl.LEQUAL);
// Clear สี และ Depth
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

// setup a GLSL program
var program = createProgramFromScripts(gl, "vertex-shader", "fragment-shader");
gl.useProgram(program);

// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "in_position");
var texCoordLocation = gl.getAttribLocation(program, "in_texcoord");
gl.enableVertexAttribArray(positionLocation);
gl.enableVertexAttribArray(texCoordLocation);

var vertices = new Float32Array([
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.0, 0.5, 0.0
]);
var position_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

var texCoords = new Float32Array([
0, 1,
1, 1,
0.5, 0
]);
var textCoord_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, textCoord_buffer);
gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);

gl.bindBuffer(gl.ARRAY_BUFFER, position_buffer);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, textCoord_buffer);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
var texture = gl.createTexture();
// Asynchronously load an image
var image = new Image();
image.src = "resources/logo.png";
image.onload = function() {
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
};