0% found this document useful (0 votes)
46 views38 pages

LabWebGL&ThreeJS 2D Affine Transform

The document discusses 2D geometry matrix transform in WebGL. It includes code for an HTML file containing a canvas and UI sliders to control translation, rotation, and scaling of a shape. Code is also provided for vertex and fragment shaders, and JavaScript functions for applying the transforms and drawing to the canvas.

Uploaded by

Điệp Cao
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
46 views38 pages

LabWebGL&ThreeJS 2D Affine Transform

The document discusses 2D geometry matrix transform in WebGL. It includes code for an HTML file containing a canvas and UI sliders to control translation, rotation, and scaling of a shape. Code is also provided for vertex and fragment shaders, and JavaScript functions for applying the transforms and drawing to the canvas.

Uploaded by

Điệp Cao
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 38

LẬP TRÌNH WEBGL

2D Geometry Matrix Transform

Họ và tên Sinh viên: Lê Anh Ngữ Mã Sinh viên:102220200

Nhóm học phần:22N13

MỤC LỤC
1. 2D Geometry Matrix Transform......................................................................1
1.1. File 2DAffineMatrixTransform.html.........................................................................1
1.2. File webgl-lessons-ui.js..............................................................................................6
1.3. File webgl-utils.js.....................................................................................................10
1.4. File webgl-tutorials.css............................................................................................30
1.5. Kết quả thực hiện.....................................................................................................33
1.6. Bài tập......................................................................................................................33
2. Tham khảo........................................................................................................33

>> Yêu cầu chụp hình ảnh là kết quả thực hành của SV. Không sử dụng lại hình ảnh của
bài lab.

1. 2D Geometry Matrix Transform


Tạo thư mục: 2D Geometry Matrix Transform có chứa thư mục con js và các file.
C:\WebGL\2DAffineMatrixTransform
| 2DAffineMatrixTransform.html
|
\---js
webgl-lessons-ui.js
webgl-tutorials.css
webgl-utils.js

1.1. File 2DAffineMatrixTransform.html


<!DOCTYPE html>
<!-- saved from url=(0088)https://webglfundamentals.org/webgl/webgl-2d-geometry-
matrix-transform-hierarchical.html -->
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0, user-


scalable=yes">
<title>WebGL - 2D Geometry Matrix Transform</title>
<link type="text/css" href="./js/webgl-tutorials.css" rel="stylesheet">
</head>
<body>
<div class="description">
Drag sliders to translate, rotate, and scale.
</div>
<canvas id="canvas" width="399" height="299"></canvas>

KhoaCNTT-Trường ĐHBK, ĐHĐN


<div id="uiContainer">
<div id="ui">
<div id="x">
<div class="gman-widget-outer">
<div class="gman-widget-label">x</div>
<div class="gman-widget-value">60</div>
<input class="gman-widget-slider" type="range" min="0" max="399"
value="60">
</div>
</div>
<div id="y">
<div class="gman-widget-outer">
<div class="gman-widget-label">y</div>
<div class="gman-widget-value">40</div>
<input class="gman-widget-slider" type="range" min="0" max="299"
value="40">
</div>
</div>
<div id="angle">
<div class="gman-widget-outer">
<div class="gman-widget-label">angle</div>
<div class="gman-widget-value">0</div>
<input class="gman-widget-slider" type="range" min="0" max="360" value="0">
</div>
</div>
<div id="scaleX">
<div class="gman-widget-outer">
<div class="gman-widget-label">scaleX</div>
<div class="gman-widget-value">0.85</div>
<input class="gman-widget-slider" type="range" min="-500" max="500"
value="85">
</div>
</div>
<div id="scaleY">
<div class="gman-widget-outer">
<div class="gman-widget-label">scaleY</div>
<div class="gman-widget-value">0.85</div>
<input class="gman-widget-slider" type="range" min="-500" max="500"
value="85">
</div>
</div>
</div>
</div>

<script id="vertex-shader-2d" type="x-shader/x-vertex">


attribute vec2 a_position;

uniform vec2 u_resolution;


uniform mat3 u_matrix;

void main() {
// Multiply the position by the matrix.
vec2 position = (u_matrix * vec3(a_position, 1)).xy;

// convert the position from pixels to 0.0 to 1.0


vec2 zeroToOne = position / u_resolution;

// convert from 0->1 to 0->2


vec2 zeroToTwo = zeroToOne * 2.0;

// convert from 0->2 to -1->+1 (clipspace)


vec2 clipSpace = zeroToTwo - 1.0;

gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);


}
</script>
<!-- fragment shader -->
<script id="fragment-shader-2d" type="x-shader/x-fragment">
precision mediump float;

KhoaCNTT-Trường ĐHBK, ĐHĐN


uniform vec4 u_color;

void main() {
gl_FragColor = u_color;
}
</script>
<!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every
sample.
See https://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="./js/webgl-utils.js"></script>
<script src="./js/webgl-lessons-ui.js"></script>
<script>
"use strict";

function main() {
// Get A WebGL context
/** @type {HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}

// setup GLSL program


var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader-2d",
"fragment-shader-2d"]);

// look up where the vertex data needs to go.


var positionLocation = gl.getAttribLocation(program, "a_position");

// lookup uniforms
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
var colorLocation = gl.getUniformLocation(program, "u_color");
var matrixLocation = gl.getUniformLocation(program, "u_matrix");

// Create a buffer to put positions in


var positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Put geometry data into buffer
setGeometry(gl);

var translation = [60, 40];


var angleInRadians = 0;
var scale = [0.85, 0.85];
var color = [Math.random(), Math.random(), Math.random(), 1];

drawScene();

// Setup a ui.
webglLessonsUI.setupSlider("#x", {value: translation[0], slide:
updatePosition(0), max: gl.canvas.width });
webglLessonsUI.setupSlider("#y", {value: translation[1], slide:
updatePosition(1), max: gl.canvas.height});
webglLessonsUI.setupSlider("#angle", {slide: updateAngle, max: 360});
webglLessonsUI.setupSlider("#scaleX", {value: scale[0], slide: updateScale(0),
min: -5, max: 5, step: 0.01, precision: 2});
webglLessonsUI.setupSlider("#scaleY", {value: scale[1], slide: updateScale(1),
min: -5, max: 5, step: 0.01, precision: 2});

function updatePosition(index) {
return function(event, ui) {
translation[index] = ui.value;
drawScene();

KhoaCNTT-Trường ĐHBK, ĐHĐN


};
}

function updateAngle(event, ui) {


var angleInDegrees = 360 - ui.value;
angleInRadians = angleInDegrees * Math.PI / 180;
drawScene();
}

function updateScale(index) {
return function(event, ui) {
scale[index] = ui.value;
drawScene();
};
}

// Draw the scene.


function drawScene() {
webglUtils.resizeCanvasToDisplaySize(gl.canvas);

// Tell WebGL how to convert from clip space to pixels


gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

// Clear the canvas.


gl.clear(gl.COLOR_BUFFER_BIT);

// Tell it to use our program (pair of shaders)


gl.useProgram(program);

// Turn on the attribute


gl.enableVertexAttribArray(positionLocation);

// Bind the position buffer.


gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)


var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration
to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionLocation, size, type, normalize, stride, offset);

// set the resolution


gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);

// set the color


gl.uniform4fv(colorLocation, color);

// Compute the matrices


var translationMatrix = m3.translation(translation[0], translation[1]);
var rotationMatrix = m3.rotation(angleInRadians);
var scaleMatrix = m3.scaling(scale[0], scale[1]);

// Starting Matrix.
var matrix = m3.identity();

for (var i = 0; i < 5; ++i) {


// Multiply the matrices.
matrix = m3.multiply(matrix, translationMatrix);
matrix = m3.multiply(matrix, rotationMatrix);
matrix = m3.multiply(matrix, scaleMatrix);

// Set the matrix.


gl.uniformMatrix3fv(matrixLocation, false, matrix);

// Draw the geometry.


var primitiveType = gl.TRIANGLES;

KhoaCNTT-Trường ĐHBK, ĐHĐN


var offset = 0;
var count = 18; // 6 triangles in the 'F', 3 points per triangle
gl.drawArrays(primitiveType, offset, count);
}
}
}

var m3 = {
identity: function() {
return [
1, 0, 0,
0, 1, 0,
0, 0, 1,
];
},

translation: function(tx, ty) {


return [
1, 0, 0,
0, 1, 0,
tx, ty, 1,
];
},

rotation: function(angleInRadians) {
var c = Math.cos(angleInRadians);
var s = Math.sin(angleInRadians);
return [
c,-s, 0,
s, c, 0,
0, 0, 1,
];
},

scaling: function(sx, sy) {


return [
sx, 0, 0,
0, sy, 0,
0, 0, 1,
];
},

multiply: function(a, b) {
var a00 = a[0 * 3 + 0];
var a01 = a[0 * 3 + 1];
var a02 = a[0 * 3 + 2];
var a10 = a[1 * 3 + 0];
var a11 = a[1 * 3 + 1];
var a12 = a[1 * 3 + 2];
var a20 = a[2 * 3 + 0];
var a21 = a[2 * 3 + 1];
var a22 = a[2 * 3 + 2];
var b00 = b[0 * 3 + 0];
var b01 = b[0 * 3 + 1];
var b02 = b[0 * 3 + 2];
var b10 = b[1 * 3 + 0];
var b11 = b[1 * 3 + 1];
var b12 = b[1 * 3 + 2];
var b20 = b[2 * 3 + 0];
var b21 = b[2 * 3 + 1];
var b22 = b[2 * 3 + 2];
return [
b00 * a00 + b01 * a10 + b02 * a20,
b00 * a01 + b01 * a11 + b02 * a21,
b00 * a02 + b01 * a12 + b02 * a22,
b10 * a00 + b11 * a10 + b12 * a20,
b10 * a01 + b11 * a11 + b12 * a21,
b10 * a02 + b11 * a12 + b12 * a22,
b20 * a00 + b21 * a10 + b22 * a20,
b20 * a01 + b21 * a11 + b22 * a21,

KhoaCNTT-Trường ĐHBK, ĐHĐN


b20 * a02 + b21 * a12 + b22 * a22,
];
},
};

// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl) {
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
// left column
0, 0,
30, 0,
0, 150,
0, 150,
30, 0,
30, 150,

// top rung
30, 0,
100, 0,
30, 30,
30, 30,
100, 0,
100, 30,

// middle rung
30, 60,
67, 60,
30, 90,
30, 90,
67, 60,
67, 90,
]),
gl.STATIC_DRAW);
}

main();
</script>

</body><!-- vertex shader --></html>

1.2. File webgl-lessons-ui.js

(function(root, factory) { // eslint-disable-line


if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], function() {
return factory.call(root);
});
} else {
// Browser globals
root.webglLessonsUI = factory.call(root);
}
}(this, function() {
'use strict';
const gopt = getQueryParams();

function setupSlider(selector, options) {


var parent = document.querySelector(selector);
if (!parent) {
// like jquery don't fail on a bad selector
return;
}
if (!options.name) {
options.name = selector.substring(1);
}

KhoaCNTT-Trường ĐHBK, ĐHĐN


return createSlider(parent, options); // eslint-disable-line
}

function createSlider(parent, options) {


var precision = options.precision || 0;
var min = options.min || 0;
var step = options.step || 1;
var value = options.value || 0;
var max = options.max || 1;
var fn = options.slide;
var name = gopt["ui-" + options.name] || options.name;
var uiPrecision = options.uiPrecision === undefined ? precision :
options.uiPrecision;
var uiMult = options.uiMult || 1;

min /= step;
max /= step;
value /= step;

parent.innerHTML = `
<div class="gman-widget-outer">
<div class="gman-widget-label">${name}</div>
<div class="gman-widget-value"></div>
<input class="gman-widget-slider" type="range" min="${min}" max="${max}"
value="${value}" />
</div>
`;
var valueElem = parent.querySelector(".gman-widget-value");
var sliderElem = parent.querySelector(".gman-widget-slider");

function updateValue(value) {
valueElem.textContent = (value * step * uiMult).toFixed(uiPrecision);
}

updateValue(value);

function handleChange(event) {
var value = parseInt(event.target.value);
updateValue(value);
fn(event, { value: value * step });
}

sliderElem.addEventListener('input', handleChange);
sliderElem.addEventListener('change', handleChange);

return {
elem: parent,
updateValue: (v) => {
v /= step;
sliderElem.value = v;
updateValue(v);
},
};
}

function makeSlider(options) {
const div = document.createElement("div");
return createSlider(div, options);
}

var widgetId = 0;
function getWidgetId() {
return "__widget_" + widgetId++;
}

function makeCheckbox(options) {
const div = document.createElement("div");
div.className = "gman-widget-outer";
const label = document.createElement("label");
const id = getWidgetId();

KhoaCNTT-Trường ĐHBK, ĐHĐN


label.setAttribute('for', id);
label.textContent = gopt["ui-" + options.name] || options.name;
label.className = "gman-checkbox-label";
const input = document.createElement("input");
input.type = "checkbox";
input.checked = options.value;
input.id = id;
input.className = "gman-widget-checkbox";
div.appendChild(label);
div.appendChild(input);
input.addEventListener('change', function(e) {
options.change(e, {
value: e.target.checked,
});
});

return {
elem: div,
updateValue: function(v) {
input.checked = !!v;
},
};
}

function makeOption(options) {
const div = document.createElement("div");
div.className = "gman-widget-outer";
const label = document.createElement("label");
const id = getWidgetId();
label.setAttribute('for', id);
label.textContent = gopt["ui-" + options.name] || options.name;
label.className = "gman-widget-label";
const selectElem = document.createElement("select");
options.options.forEach((name, ndx) => {
const opt = document.createElement("option");
opt.textContent = gopt["ui-" + name] || name;
opt.value = ndx;
opt.selected = ndx === options.value;
selectElem.appendChild(opt);
});
selectElem.className = "gman-widget-select";
div.appendChild(label);
div.appendChild(selectElem);
selectElem.addEventListener('change', function(e) {
options.change(e, {
value: selectElem.selectedIndex,
});
});

return {
elem: div,
updateValue: function(v) {
selectElem.selectedIndex = v;
},
};
}

function noop() {
}

function genSlider(object, ui) {


const changeFn = ui.change || noop;
ui.name = ui.name || ui.key;
ui.value = object[ui.key];
ui.slide = ui.slide || function(event, uiInfo) {
object[ui.key] = uiInfo.value;
changeFn();
};
return makeSlider(ui);
}

KhoaCNTT-Trường ĐHBK, ĐHĐN


function genCheckbox(object, ui) {
const changeFn = ui.change || noop;
ui.value = object[ui.key];
ui.name = ui.name || ui.key;
ui.change = function(event, uiInfo) {
object[ui.key] = uiInfo.value;
changeFn();
};
return makeCheckbox(ui);
}

function genOption(object, ui) {


const changeFn = ui.change || noop;
ui.value = object[ui.key];
ui.name = ui.name || ui.key;
ui.change = function(event, uiInfo) {
object[ui.key] = uiInfo.value;
changeFn();
};
return makeOption(ui);
}

const uiFuncs = {
slider: genSlider,
checkbox: genCheckbox,
option: genOption,
};

function setupUI(parent, object, uiInfos) {


const widgets = {};
uiInfos.forEach(function(ui) {
const widget = uiFuncs[ui.type](object, ui);
parent.appendChild(widget.elem);
widgets[ui.key] = widget;
});
return widgets;
}

function updateUI(widgets, data) {


Object.keys(widgets).forEach(key => {
const widget = widgets[key];
widget.updateValue(data[key]);
});
}

function getQueryParams() {
var params = {};
if (window.hackedParams) {
Object.keys(window.hackedParams).forEach(function(key) {
params[key] = window.hackedParams[key];
});
}
if (window.location.search) {
window.location.search.substring(1).split("&").forEach(function(pair) {
var keyValue = pair.split("=").map(function(kv) {
return decodeURIComponent(kv);
});
params[keyValue[0]] = keyValue[1];
});
}
return params;
}

return {
setupUI: setupUI,
updateUI: updateUI,
setupSlider: setupSlider,
makeSlider: makeSlider,
makeCheckbox: makeCheckbox,

KhoaCNTT-Trường ĐHBK, ĐHĐN


};

}));

1.3. File webgl-utils.js

(function(root, factory) { // eslint-disable-line


if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], function() {
return factory.call(root);
});
} else {
// Browser globals
root.webglUtils = factory.call(root);
}
}(this, function() {
'use strict';

const topWindow = this;

/** @module webgl-utils */

function isInIFrame(w) {
w = w || topWindow;
return w !== w.top;
}

if (!isInIFrame()) {
console.log("%c%s", 'color:blue;font-weight:bold;', 'for more about webgl-
utils.js see:'); // eslint-disable-line
console.log("%c%s", 'color:blue;font-weight:bold;',
'https://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html'); //
eslint-disable-line
}

/**
* Wrapped logging function.
* @param {string} msg The message to log.
*/
function error(msg) {
if (topWindow.console) {
if (topWindow.console.error) {
topWindow.console.error(msg);
} else if (topWindow.console.log) {
topWindow.console.log(msg);
}
}
}

/**
* Error Callback
* @callback ErrorCallback
* @param {string} msg error message.
* @memberOf module:webgl-utils
*/

/**
* Loads a shader.
* @param {WebGLRenderingContext} gl The WebGLRenderingContext to use.
* @param {string} shaderSource The shader source.
* @param {number} shaderType The type of shader.
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for
errors.
* @return {WebGLShader} The created shader.

KhoaCNTT-Trường ĐHBK, ĐHĐN


*/
function loadShader(gl, shaderSource, shaderType, opt_errorCallback) {
const errFn = opt_errorCallback || error;
// Create the shader object
const shader = gl.createShader(shaderType);

// Load the shader source


gl.shaderSource(shader, shaderSource);

// Compile the shader


gl.compileShader(shader);

// Check the compile status


const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!compiled) {
// Something went wrong during compilation; get the error
const lastError = gl.getShaderInfoLog(shader);
errFn('*** Error compiling shader \'' + shader + '\':' + lastError + `\n` +
shaderSource.split('\n').map((l,i) => `${i + 1}: ${l}`).join('\n'));
gl.deleteShader(shader);
return null;
}

return shader;
}

/**
* Creates a program, attaches shaders, binds attrib locations, links the
* program and calls useProgram.
* @param {WebGLShader[]} shaders The shaders to attach
* @param {string[]} [opt_attribs] An array of attribs names. Locations will be
assigned by index if not passed in
* @param {number[]} [opt_locations] The locations for the. A parallel array to
opt_attribs letting you assign locations.
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for
errors. By default it just prints an error to the console
* on error. If you want something else pass an callback. It's passed an
error message.
* @memberOf module:webgl-utils
*/
function createProgram(
gl, shaders, opt_attribs, opt_locations, opt_errorCallback) {
const errFn = opt_errorCallback || error;
const program = gl.createProgram();
shaders.forEach(function(shader) {
gl.attachShader(program, shader);
});
if (opt_attribs) {
opt_attribs.forEach(function(attrib, ndx) {
gl.bindAttribLocation(
program,
opt_locations ? opt_locations[ndx] : ndx,
attrib);
});
}
gl.linkProgram(program);

// Check the link status


const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
// something went wrong with the link
const lastError = gl.getProgramInfoLog(program);
errFn('Error in program linking:' + lastError);

gl.deleteProgram(program);
return null;
}
return program;
}

KhoaCNTT-Trường ĐHBK, ĐHĐN


/**
* Loads a shader from a script tag.
* @param {WebGLRenderingContext} gl The WebGLRenderingContext to use.
* @param {string} scriptId The id of the script tag.
* @param {number} opt_shaderType The type of shader. If not passed in it will
* be derived from the type of the script tag.
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for
errors.
* @return {WebGLShader} The created shader.
*/
function createShaderFromScript(
gl, scriptId, opt_shaderType, opt_errorCallback) {
let shaderSource = '';
let shaderType;
const shaderScript = document.getElementById(scriptId);
if (!shaderScript) {
throw ('*** Error: unknown script element' + scriptId);
}
shaderSource = shaderScript.text;

if (!opt_shaderType) {
if (shaderScript.type === 'x-shader/x-vertex') {
shaderType = gl.VERTEX_SHADER;
} else if (shaderScript.type === 'x-shader/x-fragment') {
shaderType = gl.FRAGMENT_SHADER;
} else if (shaderType !== gl.VERTEX_SHADER && shaderType !==
gl.FRAGMENT_SHADER) {
throw ('*** Error: unknown shader type');
}
}

return loadShader(
gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType,
opt_errorCallback);
}

const defaultShaderType = [
'VERTEX_SHADER',
'FRAGMENT_SHADER',
];

/**
* Creates a program from 2 script tags.
*
* @param {WebGLRenderingContext} gl The WebGLRenderingContext
* to use.
* @param {string[]} shaderScriptIds Array of ids of the script
* tags for the shaders. The first is assumed to be the
* vertex shader, the second the fragment shader.
* @param {string[]} [opt_attribs] An array of attribs names. Locations will be
assigned by index if not passed in
* @param {number[]} [opt_locations] The locations for the. A parallel array to
opt_attribs letting you assign locations.
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for
errors. By default it just prints an error to the console
* on error. If you want something else pass an callback. It's passed an
error message.
* @return {WebGLProgram} The created program.
* @memberOf module:webgl-utils
*/
function createProgramFromScripts(
gl, shaderScriptIds, opt_attribs, opt_locations, opt_errorCallback) {
const shaders = [];
for (let ii = 0; ii < shaderScriptIds.length; ++ii) {
shaders.push(createShaderFromScript(
gl, shaderScriptIds[ii], gl[defaultShaderType[ii]], opt_errorCallback));
}
return createProgram(gl, shaders, opt_attribs, opt_locations,
opt_errorCallback);
}

KhoaCNTT-Trường ĐHBK, ĐHĐN


/**
* Creates a program from 2 sources.
*
* @param {WebGLRenderingContext} gl The WebGLRenderingContext
* to use.
* @param {string[]} shaderSourcess Array of sources for the
* shaders. The first is assumed to be the vertex shader,
* the second the fragment shader.
* @param {string[]} [opt_attribs] An array of attribs names. Locations will be
assigned by index if not passed in
* @param {number[]} [opt_locations] The locations for the. A parallel array to
opt_attribs letting you assign locations.
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for
errors. By default it just prints an error to the console
* on error. If you want something else pass an callback. It's passed an
error message.
* @return {WebGLProgram} The created program.
* @memberOf module:webgl-utils
*/
function createProgramFromSources(
gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) {
const shaders = [];
for (let ii = 0; ii < shaderSources.length; ++ii) {
shaders.push(loadShader(
gl, shaderSources[ii], gl[defaultShaderType[ii]], opt_errorCallback));
}
return createProgram(gl, shaders, opt_attribs, opt_locations,
opt_errorCallback);
}

/**
* Returns the corresponding bind point for a given sampler type
*/
function getBindPointForSamplerType(gl, type) {
if (type === gl.SAMPLER_2D) return gl.TEXTURE_2D; // eslint-disable-
line
if (type === gl.SAMPLER_CUBE) return gl.TEXTURE_CUBE_MAP; // eslint-disable-
line
return undefined;
}

/**
* @typedef {Object.<string, function>} Setters
*/

/**
* Creates setter functions for all uniforms of a shader
* program.
*
* @see {@link module:webgl-utils.setUniforms}
*
* @param {WebGLProgram} program the program to create setters for.
* @returns {Object.<string, function>} an object with a setter by name for each
uniform
* @memberOf module:webgl-utils
*/
function createUniformSetters(gl, program) {
let textureUnit = 0;

/**
* Creates a setter for a uniform of the given program with it's
* location embedded in the setter.
* @param {WebGLProgram} program
* @param {WebGLUniformInfo} uniformInfo
* @returns {function} the created setter.
*/
function createUniformSetter(program, uniformInfo) {
const location = gl.getUniformLocation(program, uniformInfo.name);
const type = uniformInfo.type;

KhoaCNTT-Trường ĐHBK, ĐHĐN


// Check if this uniform is an array
const isArray = (uniformInfo.size > 1 && uniformInfo.name.substr(-3) ===
'[0]');
if (type === gl.FLOAT && isArray) {
return function(v) {
gl.uniform1fv(location, v);
};
}
if (type === gl.FLOAT) {
return function(v) {
gl.uniform1f(location, v);
};
}
if (type === gl.FLOAT_VEC2) {
return function(v) {
gl.uniform2fv(location, v);
};
}
if (type === gl.FLOAT_VEC3) {
return function(v) {
gl.uniform3fv(location, v);
};
}
if (type === gl.FLOAT_VEC4) {
return function(v) {
gl.uniform4fv(location, v);
};
}
if (type === gl.INT && isArray) {
return function(v) {
gl.uniform1iv(location, v);
};
}
if (type === gl.INT) {
return function(v) {
gl.uniform1i(location, v);
};
}
if (type === gl.INT_VEC2) {
return function(v) {
gl.uniform2iv(location, v);
};
}
if (type === gl.INT_VEC3) {
return function(v) {
gl.uniform3iv(location, v);
};
}
if (type === gl.INT_VEC4) {
return function(v) {
gl.uniform4iv(location, v);
};
}
if (type === gl.BOOL) {
return function(v) {
gl.uniform1iv(location, v);
};
}
if (type === gl.BOOL_VEC2) {
return function(v) {
gl.uniform2iv(location, v);
};
}
if (type === gl.BOOL_VEC3) {
return function(v) {
gl.uniform3iv(location, v);
};
}
if (type === gl.BOOL_VEC4) {
return function(v) {

KhoaCNTT-Trường ĐHBK, ĐHĐN


gl.uniform4iv(location, v);
};
}
if (type === gl.FLOAT_MAT2) {
return function(v) {
gl.uniformMatrix2fv(location, false, v);
};
}
if (type === gl.FLOAT_MAT3) {
return function(v) {
gl.uniformMatrix3fv(location, false, v);
};
}
if (type === gl.FLOAT_MAT4) {
return function(v) {
gl.uniformMatrix4fv(location, false, v);
};
}
if ((type === gl.SAMPLER_2D || type === gl.SAMPLER_CUBE) && isArray) {
const units = [];
for (let ii = 0; ii < info.size; ++ii) {
units.push(textureUnit++);
}
return function(bindPoint, units) {
return function(textures) {
gl.uniform1iv(location, units);
textures.forEach(function(texture, index) {
gl.activeTexture(gl.TEXTURE0 + units[index]);
gl.bindTexture(bindPoint, texture);
});
};
}(getBindPointForSamplerType(gl, type), units);
}
if (type === gl.SAMPLER_2D || type === gl.SAMPLER_CUBE) {
return function(bindPoint, unit) {
return function(texture) {
gl.uniform1i(location, unit);
gl.activeTexture(gl.TEXTURE0 + unit);
gl.bindTexture(bindPoint, texture);
};
}(getBindPointForSamplerType(gl, type), textureUnit++);
}
throw ('unknown type: 0x' + type.toString(16)); // we should never get here.
}

const uniformSetters = { };
const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);

for (let ii = 0; ii < numUniforms; ++ii) {


const uniformInfo = gl.getActiveUniform(program, ii);
if (!uniformInfo) {
break;
}
let name = uniformInfo.name;
// remove the array suffix.
if (name.substr(-3) === '[0]') {
name = name.substr(0, name.length - 3);
}
const setter = createUniformSetter(program, uniformInfo);
uniformSetters[name] = setter;
}
return uniformSetters;
}

/**
* Set uniforms and binds related textures.
*
* Example:
*
* let programInfo = createProgramInfo(

KhoaCNTT-Trường ĐHBK, ĐHĐN


* gl, ["some-vs", "some-fs"]);
*
* let tex1 = gl.createTexture();
* let tex2 = gl.createTexture();
*
* ... assume we setup the textures with data ...
*
* let uniforms = {
* u_someSampler: tex1,
* u_someOtherSampler: tex2,
* u_someColor: [1,0,0,1],
* u_somePosition: [0,1,1],
* u_someMatrix: [
* 1,0,0,0,
* 0,1,0,0,
* 0,0,1,0,
* 0,0,0,0,
* ],
* };
*
* gl.useProgram(program);
*
* This will automatically bind the textures AND set the
* uniforms.
*
* setUniforms(programInfo.uniformSetters, uniforms);
*
* For the example above it is equivalent to
*
* let texUnit = 0;
* gl.activeTexture(gl.TEXTURE0 + texUnit);
* gl.bindTexture(gl.TEXTURE_2D, tex1);
* gl.uniform1i(u_someSamplerLocation, texUnit++);
* gl.activeTexture(gl.TEXTURE0 + texUnit);
* gl.bindTexture(gl.TEXTURE_2D, tex2);
* gl.uniform1i(u_someSamplerLocation, texUnit++);
* gl.uniform4fv(u_someColorLocation, [1, 0, 0, 1]);
* gl.uniform3fv(u_somePositionLocation, [0, 1, 1]);
* gl.uniformMatrix4fv(u_someMatrix, false, [
* 1,0,0,0,
* 0,1,0,0,
* 0,0,1,0,
* 0,0,0,0,
* ]);
*
* Note it is perfectly reasonable to call `setUniforms` multiple times. For
example
*
* let uniforms = {
* u_someSampler: tex1,
* u_someOtherSampler: tex2,
* };
*
* let moreUniforms {
* u_someColor: [1,0,0,1],
* u_somePosition: [0,1,1],
* u_someMatrix: [
* 1,0,0,0,
* 0,1,0,0,
* 0,0,1,0,
* 0,0,0,0,
* ],
* };
*
* setUniforms(programInfo.uniformSetters, uniforms);
* setUniforms(programInfo.uniformSetters, moreUniforms);
*
* @param {Object.<string, function>|module:webgl-utils.ProgramInfo} setters the
setters returned from

KhoaCNTT-Trường ĐHBK, ĐHĐN


* `createUniformSetters` or a ProgramInfo from {@link module:webgl-
utils.createProgramInfo}.
* @param {Object.<string, value>} an object with values for the
* uniforms.
* @memberOf module:webgl-utils
*/
function setUniforms(setters, ...values) {
setters = setters.uniformSetters || setters;
for (const uniforms of values) {
Object.keys(uniforms).forEach(function(name) {
const setter = setters[name];
if (setter) {
setter(uniforms[name]);
}
});
}
}

/**
* Creates setter functions for all attributes of a shader
* program. You can pass this to {@link module:webgl-
utils.setBuffersAndAttributes} to set all your buffers and attributes.
*
* @see {@link module:webgl-utils.setAttributes} for example
* @param {WebGLProgram} program the program to create setters for.
* @return {Object.<string, function>} an object with a setter for each attribute
by name.
* @memberOf module:webgl-utils
*/
function createAttributeSetters(gl, program) {
const attribSetters = {
};

function createAttribSetter(index) {
return function(b) {
if (b.value) {
gl.disableVertexAttribArray(index);
switch (b.value.length) {
case 4:
gl.vertexAttrib4fv(index, b.value);
break;
case 3:
gl.vertexAttrib3fv(index, b.value);
break;
case 2:
gl.vertexAttrib2fv(index, b.value);
break;
case 1:
gl.vertexAttrib1fv(index, b.value);
break;
default:
throw new Error('the length of a float constant value must be
between 1 and 4!');
}
} else {
gl.bindBuffer(gl.ARRAY_BUFFER, b.buffer);
gl.enableVertexAttribArray(index);
gl.vertexAttribPointer(
index, b.numComponents || b.size, b.type || gl.FLOAT, b.normalize
|| false, b.stride || 0, b.offset || 0);
}
};
}

const numAttribs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);


for (let ii = 0; ii < numAttribs; ++ii) {
const attribInfo = gl.getActiveAttrib(program, ii);
if (!attribInfo) {
break;
}

KhoaCNTT-Trường ĐHBK, ĐHĐN


const index = gl.getAttribLocation(program, attribInfo.name);
attribSetters[attribInfo.name] = createAttribSetter(index);
}

return attribSetters;
}

/**
* Sets attributes and binds buffers (deprecated... use {@link module:webgl-
utils.setBuffersAndAttributes})
*
* Example:
*
* let program = createProgramFromScripts(
* gl, ["some-vs", "some-fs"]);
*
* let attribSetters = createAttributeSetters(program);
*
* let positionBuffer = gl.createBuffer();
* let texcoordBuffer = gl.createBuffer();
*
* let attribs = {
* a_position: {buffer: positionBuffer, numComponents: 3},
* a_texcoord: {buffer: texcoordBuffer, numComponents: 2},
* };
*
* gl.useProgram(program);
*
* This will automatically bind the buffers AND set the
* attributes.
*
* setAttributes(attribSetters, attribs);
*
* Properties of attribs. For each attrib you can add
* properties:
*
* * type: the type of data in the buffer. Default = gl.FLOAT
* * normalize: whether or not to normalize the data. Default = false
* * stride: the stride. Default = 0
* * offset: offset into the buffer. Default = 0
*
* For example if you had 3 value float positions, 2 value
* float texcoord and 4 value uint8 colors you'd setup your
* attribs like this
*
* let attribs = {
* a_position: {buffer: positionBuffer, numComponents: 3},
* a_texcoord: {buffer: texcoordBuffer, numComponents: 2},
* a_color: {
* buffer: colorBuffer,
* numComponents: 4,
* type: gl.UNSIGNED_BYTE,
* normalize: true,
* },
* };
*
* @param {Object.<string, function>|model:webgl-utils.ProgramInfo} setters
Attribute setters as returned from createAttributeSetters or a ProgramInfo as
returned {@link module:webgl-utils.createProgramInfo}
* @param {Object.<string, module:webgl-utils.AttribInfo>} attribs AttribInfos
mapped by attribute name.
* @memberOf module:webgl-utils
* @deprecated use {@link module:webgl-utils.setBuffersAndAttributes}
*/
function setAttributes(setters, attribs) {
setters = setters.attribSetters || setters;
Object.keys(attribs).forEach(function(name) {
const setter = setters[name];
if (setter) {
setter(attribs[name]);

KhoaCNTT-Trường ĐHBK, ĐHĐN


}
});
}

/**
* Creates a vertex array object and then sets the attributes
* on it
*
* @param {WebGLRenderingContext} gl The WebGLRenderingContext
* to use.
* @param {Object.<string, function>} setters Attribute setters as returned from
createAttributeSetters
* @param {Object.<string, module:webgl-utils.AttribInfo>} attribs AttribInfos
mapped by attribute name.
* @param {WebGLBuffer} [indices] an optional ELEMENT_ARRAY_BUFFER of indices
*/
function createVAOAndSetAttributes(gl, setters, attribs, indices) {
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
setAttributes(setters, attribs);
if (indices) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices);
}
// We unbind this because otherwise any change to ELEMENT_ARRAY_BUFFER
// like when creating buffers for other stuff will mess up this VAO's binding
gl.bindVertexArray(null);
return vao;
}

/**
* Creates a vertex array object and then sets the attributes
* on it
*
* @param {WebGLRenderingContext} gl The WebGLRenderingContext
* to use.
* @param {Object.<string, function>| module:webgl-utils.ProgramInfo} programInfo
as returned from createProgramInfo or Attribute setters as returned from
createAttributeSetters
* @param {module:webgl-utils:BufferInfo} bufferInfo BufferInfo as returned from
createBufferInfoFromArrays etc...
* @param {WebGLBuffer} [indices] an optional ELEMENT_ARRAY_BUFFER of indices
*/
function createVAOFromBufferInfo(gl, programInfo, bufferInfo) {
return createVAOAndSetAttributes(gl, programInfo.attribSetters || programInfo,
bufferInfo.attribs, bufferInfo.indices);
}

/**
* @typedef {Object} ProgramInfo
* @property {WebGLProgram} program A shader program
* @property {Object<string, function>} uniformSetters: object of setters as
returned from createUniformSetters,
* @property {Object<string, function>} attribSetters: object of setters as
returned from createAttribSetters,
* @memberOf module:webgl-utils
*/

/**
* Creates a ProgramInfo from 2 sources.
*
* A ProgramInfo contains
*
* programInfo = {
* program: WebGLProgram,
* uniformSetters: object of setters as returned from
createUniformSetters,
* attribSetters: object of setters as returned from createAttribSetters,
* }
*
* @param {WebGLRenderingContext} gl The WebGLRenderingContext

KhoaCNTT-Trường ĐHBK, ĐHĐN


* to use.
* @param {string[]} shaderSourcess Array of sources for the
* shaders or ids. The first is assumed to be the vertex shader,
* the second the fragment shader.
* @param {string[]} [opt_attribs] An array of attribs names. Locations will be
assigned by index if not passed in
* @param {number[]} [opt_locations] The locations for the. A parallel array to
opt_attribs letting you assign locations.
* @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for
errors. By default it just prints an error to the console
* on error. If you want something else pass an callback. It's passed an
error message.
* @return {module:webgl-utils.ProgramInfo} The created program.
* @memberOf module:webgl-utils
*/
function createProgramInfo(
gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) {
shaderSources = shaderSources.map(function(source) {
const script = document.getElementById(source);
return script ? script.text : source;
});
const program = webglUtils.createProgramFromSources(gl, shaderSources,
opt_attribs, opt_locations, opt_errorCallback);
if (!program) {
return null;
}
const uniformSetters = createUniformSetters(gl, program);
const attribSetters = createAttributeSetters(gl, program);
return {
program: program,
uniformSetters: uniformSetters,
attribSetters: attribSetters,
};
}

/**
* Sets attributes and buffers including the `ELEMENT_ARRAY_BUFFER` if
appropriate
*
* Example:
*
* let programInfo = createProgramInfo(
* gl, ["some-vs", "some-fs"]);
*
* let arrays = {
* position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10,
10, 0], },
* texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1],
},
* };
*
* let bufferInfo = createBufferInfoFromArrays(gl, arrays);
*
* gl.useProgram(programInfo.program);
*
* This will automatically bind the buffers AND set the
* attributes.
*
* setBuffersAndAttributes(programInfo.attribSetters, bufferInfo);
*
* For the example above it is equivilent to
*
* gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
* gl.enableVertexAttribArray(a_positionLocation);
* gl.vertexAttribPointer(a_positionLocation, 3, gl.FLOAT, false, 0, 0);
* gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
* gl.enableVertexAttribArray(a_texcoordLocation);
* gl.vertexAttribPointer(a_texcoordLocation, 4, gl.FLOAT, false, 0, 0);
*
* @param {WebGLRenderingContext} gl A WebGLRenderingContext.

KhoaCNTT-Trường ĐHBK, ĐHĐN


* @param {Object.<string, function>} setters Attribute setters as returned from
`createAttributeSetters`
* @param {module:webgl-utils.BufferInfo} buffers a BufferInfo as returned from
`createBufferInfoFromArrays`.
* @memberOf module:webgl-utils
*/
function setBuffersAndAttributes(gl, setters, buffers) {
setAttributes(setters, buffers.attribs);
if (buffers.indices) {
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices);
}
}

// Add your prefix here.


const browserPrefixes = [
'',
'MOZ_',
'OP_',
'WEBKIT_',
];

/**
* Given an extension name like WEBGL_compressed_texture_s3tc
* returns the supported version extension, like
* WEBKIT_WEBGL_compressed_teture_s3tc
* @param {string} name Name of extension to look for
* @return {WebGLExtension} The extension or undefined if not
* found.
* @memberOf module:webgl-utils
*/
function getExtensionWithKnownPrefixes(gl, name) {
for (let ii = 0; ii < browserPrefixes.length; ++ii) {
const prefixedName = browserPrefixes[ii] + name;
const ext = gl.getExtension(prefixedName);
if (ext) {
return ext;
}
}
return undefined;
}

/**
* Resize a canvas to match the size its displayed.
* @param {HTMLCanvasElement} canvas The canvas to resize.
* @param {number} [multiplier] amount to multiply by.
* Pass in window.devicePixelRatio for native pixels.
* @return {boolean} true if the canvas was resized.
* @memberOf module:webgl-utils
*/
function resizeCanvasToDisplaySize(canvas, multiplier) {
multiplier = multiplier || 1;
const width = canvas.clientWidth * multiplier | 0;
const height = canvas.clientHeight * multiplier | 0;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
return true;
}
return false;
}

// Add `push` to a typed array. It just keeps a 'cursor'


// and allows use to `push` values into the array so we
// don't have to manually compute offsets
function augmentTypedArray(typedArray, numComponents) {
let cursor = 0;
typedArray.push = function() {
for (let ii = 0; ii < arguments.length; ++ii) {
const value = arguments[ii];

KhoaCNTT-Trường ĐHBK, ĐHĐN


if (value instanceof Array || (value.buffer && value.buffer instanceof
ArrayBuffer)) {
for (let jj = 0; jj < value.length; ++jj) {
typedArray[cursor++] = value[jj];
}
} else {
typedArray[cursor++] = value;
}
}
};
typedArray.reset = function(opt_index) {
cursor = opt_index || 0;
};
typedArray.numComponents = numComponents;
Object.defineProperty(typedArray, 'numElements', {
get: function() {
return this.length / this.numComponents | 0;
},
});
return typedArray;
}

/**
* creates a typed array with a `push` function attached
* so that you can easily *push* values.
*
* `push` can take multiple arguments. If an argument is an array each element
* of the array will be added to the typed array.
*
* Example:
*
* let array = createAugmentedTypedArray(3, 2); // creates a Float32Array
with 6 values
* array.push(1, 2, 3);
* array.push([4, 5, 6]);
* // array now contains [1, 2, 3, 4, 5, 6]
*
* Also has `numComponents` and `numElements` properties.
*
* @param {number} numComponents number of components
* @param {number} numElements number of elements. The total size of the array
will be `numComponents * numElements`.
* @param {constructor} opt_type A constructor for the type. Default =
`Float32Array`.
* @return {ArrayBuffer} A typed array.
* @memberOf module:webgl-utils
*/
function createAugmentedTypedArray(numComponents, numElements, opt_type) {
const Type = opt_type || Float32Array;
return augmentTypedArray(new Type(numComponents * numElements), numComponents);
}

function createBufferFromTypedArray(gl, array, type, drawType) {


type = type || gl.ARRAY_BUFFER;
const buffer = gl.createBuffer();
gl.bindBuffer(type, buffer);
gl.bufferData(type, array, drawType || gl.STATIC_DRAW);
return buffer;
}

function allButIndices(name) {
return name !== 'indices';
}

function createMapping(obj) {
const mapping = {};
Object.keys(obj).filter(allButIndices).forEach(function(key) {
mapping['a_' + key] = key;
});
return mapping;

KhoaCNTT-Trường ĐHBK, ĐHĐN


}

function getGLTypeForTypedArray(gl, typedArray) {


if (typedArray instanceof Int8Array) { return gl.BYTE; } //
eslint-disable-line
if (typedArray instanceof Uint8Array) { return gl.UNSIGNED_BYTE; } //
eslint-disable-line
if (typedArray instanceof Int16Array) { return gl.SHORT; } //
eslint-disable-line
if (typedArray instanceof Uint16Array) { return gl.UNSIGNED_SHORT; } //
eslint-disable-line
if (typedArray instanceof Int32Array) { return gl.INT; } //
eslint-disable-line
if (typedArray instanceof Uint32Array) { return gl.UNSIGNED_INT; } //
eslint-disable-line
if (typedArray instanceof Float32Array) { return gl.FLOAT; } //
eslint-disable-line
throw 'unsupported typed array type';
}

// This is really just a guess. Though I can't really imagine using


// anything else? Maybe for some compression?
function getNormalizationForTypedArray(typedArray) {
if (typedArray instanceof Int8Array) { return true; } // eslint-disable-
line
if (typedArray instanceof Uint8Array) { return true; } // eslint-disable-
line
return false;
}

function isArrayBuffer(a) {
return a.buffer && a.buffer instanceof ArrayBuffer;
}

function guessNumComponentsFromName(name, length) {


let numComponents;
if (name.indexOf('coord') >= 0) {
numComponents = 2;
} else if (name.indexOf('color') >= 0) {
numComponents = 4;
} else {
numComponents = 3; // position, normals, indices ...
}

if (length % numComponents > 0) {


throw 'can not guess numComponents. You should specify it.';
}

return numComponents;
}

function makeTypedArray(array, name) {


if (isArrayBuffer(array)) {
return array;
}

if (array.data && isArrayBuffer(array.data)) {


return array.data;
}

if (Array.isArray(array)) {
array = {
data: array,
};
}

if (!array.numComponents) {
array.numComponents = guessNumComponentsFromName(name, array.length);
}

KhoaCNTT-Trường ĐHBK, ĐHĐN


let type = array.type;
if (!type) {
if (name === 'indices') {
type = Uint16Array;
}
}
const typedArray = createAugmentedTypedArray(array.numComponents,
array.data.length / array.numComponents | 0, type);
typedArray.push(array.data);
return typedArray;
}

/**
* @typedef {Object} AttribInfo
* @property {number} [numComponents] the number of components for this
attribute.
* @property {number} [size] the number of components for this attribute.
* @property {number} [type] the type of the attribute (eg. `gl.FLOAT`,
`gl.UNSIGNED_BYTE`, etc...) Default = `gl.FLOAT`
* @property {boolean} [normalized] whether or not to normalize the data. Default
= false
* @property {number} [offset] offset into buffer in bytes. Default = 0
* @property {number} [stride] the stride in bytes per element. Default = 0
* @property {WebGLBuffer} buffer the buffer that contains the data for this
attribute
* @memberOf module:webgl-utils
*/

/**
* Creates a set of attribute data and WebGLBuffers from set of arrays
*
* Given
*
* let arrays = {
* position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10,
10, 0], },
* texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1],
},
* normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
1], },
* color: { numComponents: 4, data: [255, 255, 255, 255, 255, 0, 0,
255, 0, 0, 255, 255], type: Uint8Array, },
* indices: { numComponents: 3, data: [0, 1, 2, 1, 2, 3],
},
* };
*
* returns something like
*
* let attribs = {
* a_position: { numComponents: 3, type: gl.FLOAT, normalize:
false, buffer: WebGLBuffer, },
* a_texcoord: { numComponents: 2, type: gl.FLOAT, normalize:
false, buffer: WebGLBuffer, },
* a_normal: { numComponents: 3, type: gl.FLOAT, normalize:
false, buffer: WebGLBuffer, },
* a_color: { numComponents: 4, type: gl.UNSIGNED_BYTE, normalize:
true, buffer: WebGLBuffer, },
* };
*
* @param {WebGLRenderingContext} gl The webgl rendering context.
* @param {Object.<string, array|typedarray>} arrays The arrays
* @param {Object.<string, string>} [opt_mapping] mapping from attribute name to
array name.
* if not specified defaults to "a_name" -> "name".
* @return {Object.<string, module:webgl-utils.AttribInfo>} the attribs
* @memberOf module:webgl-utils
*/
function createAttribsFromArrays(gl, arrays, opt_mapping) {
const mapping = opt_mapping || createMapping(arrays);

KhoaCNTT-Trường ĐHBK, ĐHĐN


const attribs = {};
Object.keys(mapping).forEach(function(attribName) {
const bufferName = mapping[attribName];
const origArray = arrays[bufferName];
if (origArray.value) {
attribs[attribName] = {
value: origArray.value,
};
} else {
const array = makeTypedArray(origArray, bufferName);
attribs[attribName] = {
buffer: createBufferFromTypedArray(gl, array),
numComponents: origArray.numComponents || array.numComponents ||
guessNumComponentsFromName(bufferName),
type: getGLTypeForTypedArray(gl, array),
normalize: getNormalizationForTypedArray(array),
};
}
});
return attribs;
}

function getArray(array) {
return array.length ? array : array.data;
}

const texcoordRE = /coord|texture/i;


const colorRE = /color|colour/i;

function guessNumComponentsFromName(name, length) {


let numComponents;
if (texcoordRE.test(name)) {
numComponents = 2;
} else if (colorRE.test(name)) {
numComponents = 4;
} else {
numComponents = 3; // position, normals, indices ...
}

if (length % numComponents > 0) {


throw new Error(`Can not guess numComponents for attribute '${name}'. Tried $
{numComponents} but ${length} values is not evenly divisible by ${numComponents}.
You should specify it.`);
}

return numComponents;
}

function getNumComponents(array, arrayName) {


return array.numComponents || array.size ||
guessNumComponentsFromName(arrayName, getArray(array).length);
}

/**
* tries to get the number of elements from a set of arrays.
*/
const positionKeys = ['position', 'positions', 'a_position'];
function getNumElementsFromNonIndexedArrays(arrays) {
let key;
for (const k of positionKeys) {
if (k in arrays) {
key = k;
break;
}
}
key = key || Object.keys(arrays)[0];
const array = arrays[key];
const length = getArray(array).length;
const numComponents = getNumComponents(array, key);
const numElements = length / numComponents;

KhoaCNTT-Trường ĐHBK, ĐHĐN


if (length % numComponents > 0) {
throw new Error(`numComponents ${numComponents} not correct for length $
{length}`);
}
return numElements;
}

/**
* @typedef {Object} BufferInfo
* @property {number} numElements The number of elements to pass to
`gl.drawArrays` or `gl.drawElements`.
* @property {WebGLBuffer} [indices] The indices `ELEMENT_ARRAY_BUFFER` if any
indices exist.
* @property {Object.<string, module:webgl-utils.AttribInfo>} attribs The attribs
approriate to call `setAttributes`
* @memberOf module:webgl-utils
*/

/**
* Creates a BufferInfo from an object of arrays.
*
* This can be passed to {@link module:webgl-utils.setBuffersAndAttributes} and
to
* {@link module:webgl-utils:drawBufferInfo}.
*
* Given an object like
*
* let arrays = {
* position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10,
10, 0], },
* texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1],
},
* normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
1], },
* indices: { numComponents: 3, data: [0, 1, 2, 1, 2, 3],
},
* };
*
* Creates an BufferInfo like this
*
* bufferInfo = {
* numElements: 4, // or whatever the number of elements is
* indices: WebGLBuffer, // this property will not exist if there are no
indices
* attribs: {
* a_position: { buffer: WebGLBuffer, numComponents: 3, },
* a_normal: { buffer: WebGLBuffer, numComponents: 3, },
* a_texcoord: { buffer: WebGLBuffer, numComponents: 2, },
* },
* };
*
* The properties of arrays can be JavaScript arrays in which case the number of
components
* will be guessed.
*
* let arrays = {
* position: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0],
* texcoord: [0, 0, 0, 1, 1, 0, 1, 1],
* normal: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1],
* indices: [0, 1, 2, 1, 2, 3],
* };
*
* They can also by TypedArrays
*
* let arrays = {
* position: new Float32Array([0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0]),
* texcoord: new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]),
* normal: new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]),
* indices: new Uint16Array([0, 1, 2, 1, 2, 3]),

KhoaCNTT-Trường ĐHBK, ĐHĐN


* };
*
* Or augmentedTypedArrays
*
* let positions = createAugmentedTypedArray(3, 4);
* let texcoords = createAugmentedTypedArray(2, 4);
* let normals = createAugmentedTypedArray(3, 4);
* let indices = createAugmentedTypedArray(3, 2, Uint16Array);
*
* positions.push([0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0]);
* texcoords.push([0, 0, 0, 1, 1, 0, 1, 1]);
* normals.push([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]);
* indices.push([0, 1, 2, 1, 2, 3]);
*
* let arrays = {
* position: positions,
* texcoord: texcoords,
* normal: normals,
* indices: indices,
* };
*
* For the last example it is equivalent to
*
* let bufferInfo = {
* attribs: {
* a_position: { numComponents: 3, buffer: gl.createBuffer(), },
* a_texcoods: { numComponents: 2, buffer: gl.createBuffer(), },
* a_normals: { numComponents: 3, buffer: gl.createBuffer(), },
* },
* indices: gl.createBuffer(),
* numElements: 6,
* };
*
* gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.a_position.buffer);
* gl.bufferData(gl.ARRAY_BUFFER, arrays.position, gl.STATIC_DRAW);
* gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.a_texcoord.buffer);
* gl.bufferData(gl.ARRAY_BUFFER, arrays.texcoord, gl.STATIC_DRAW);
* gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.a_normal.buffer);
* gl.bufferData(gl.ARRAY_BUFFER, arrays.normal, gl.STATIC_DRAW);
* gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferInfo.indices);
* gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, arrays.indices, gl.STATIC_DRAW);
*
* @param {WebGLRenderingContext} gl A WebGLRenderingContext
* @param {Object.<string, array|object|typedarray>} arrays Your data
* @param {Object.<string, string>} [opt_mapping] an optional mapping of
attribute to array name.
* If not passed in it's assumed the array names will be mapped to an
attribute
* of the same name with "a_" prefixed to it. An other words.
*
* let arrays = {
* position: ...,
* texcoord: ...,
* normal: ...,
* indices: ...,
* };
*
* bufferInfo = createBufferInfoFromArrays(gl, arrays);
*
* Is the same as
*
* let arrays = {
* position: ...,
* texcoord: ...,
* normal: ...,
* indices: ...,
* };
*
* let mapping = {
* a_position: "position",

KhoaCNTT-Trường ĐHBK, ĐHĐN


* a_texcoord: "texcoord",
* a_normal: "normal",
* };
*
* bufferInfo = createBufferInfoFromArrays(gl, arrays, mapping);
*
* @return {module:webgl-utils.BufferInfo} A BufferInfo
* @memberOf module:webgl-utils
*/
function createBufferInfoFromArrays(gl, arrays, opt_mapping) {
const bufferInfo = {
attribs: createAttribsFromArrays(gl, arrays, opt_mapping),
};
let indices = arrays.indices;
if (indices) {
indices = makeTypedArray(indices, 'indices');
bufferInfo.indices = createBufferFromTypedArray(gl, indices,
gl.ELEMENT_ARRAY_BUFFER);
bufferInfo.numElements = indices.length;
} else {
bufferInfo.numElements = getNumElementsFromNonIndexedArrays(arrays);
}

return bufferInfo;
}

/**
* Creates buffers from typed arrays
*
* Given something like this
*
* let arrays = {
* positions: [1, 2, 3],
* normals: [0, 0, 1],
* }
*
* returns something like
*
* buffers = {
* positions: WebGLBuffer,
* normals: WebGLBuffer,
* }
*
* If the buffer is named 'indices' it will be made an ELEMENT_ARRAY_BUFFER.
*
* @param {WebGLRenderingContext} gl A WebGLRenderingContext.
* @param {Object<string, array|typedarray>} arrays
* @return {Object<string, WebGLBuffer>} returns an object with one WebGLBuffer
per array
* @memberOf module:webgl-utils
*/
function createBuffersFromArrays(gl, arrays) {
const buffers = { };
Object.keys(arrays).forEach(function(key) {
const type = key === 'indices' ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
const array = makeTypedArray(arrays[key], name);
buffers[key] = createBufferFromTypedArray(gl, array, type);
});

// hrm
if (arrays.indices) {
buffers.numElements = arrays.indices.length;
} else if (arrays.position) {
buffers.numElements = arrays.position.length / 3;
}

return buffers;
}

/**

KhoaCNTT-Trường ĐHBK, ĐHĐN


* Calls `gl.drawElements` or `gl.drawArrays`, whichever is appropriate
*
* normally you'd call `gl.drawElements` or `gl.drawArrays` yourself
* but calling this means if you switch from indexed data to non-indexed
* data you don't have to remember to update your draw call.
*
* @param {WebGLRenderingContext} gl A WebGLRenderingContext
* @param {module:webgl-utils.BufferInfo} bufferInfo as returned from
createBufferInfoFromArrays
* @param {enum} [primitiveType] eg (gl.TRIANGLES, gl.LINES, gl.POINTS,
gl.TRIANGLE_STRIP, ...)
* @param {number} [count] An optional count. Defaults to bufferInfo.numElements
* @param {number} [offset] An optional offset. Defaults to 0.
* @memberOf module:webgl-utils
*/
function drawBufferInfo(gl, bufferInfo, primitiveType, count, offset) {
const indices = bufferInfo.indices;
primitiveType = primitiveType === undefined ? gl.TRIANGLES : primitiveType;
const numElements = count === undefined ? bufferInfo.numElements : count;
offset = offset === undefined ? 0 : offset;
if (indices) {
gl.drawElements(primitiveType, numElements, gl.UNSIGNED_SHORT, offset);
} else {
gl.drawArrays(primitiveType, offset, numElements);
}
}

/**
* @typedef {Object} DrawObject
* @property {module:webgl-utils.ProgramInfo} programInfo A ProgramInfo as
returned from createProgramInfo
* @property {module:webgl-utils.BufferInfo} bufferInfo A BufferInfo as returned
from createBufferInfoFromArrays
* @property {Object<string, ?>} uniforms The values for the uniforms
* @memberOf module:webgl-utils
*/

/**
* Draws a list of objects
* @param {WebGLRenderingContext} gl A WebGLRenderingContext
* @param {DrawObject[]} objectsToDraw an array of objects to draw.
* @memberOf module:webgl-utils
*/
function drawObjectList(gl, objectsToDraw) {
let lastUsedProgramInfo = null;
let lastUsedBufferInfo = null;

objectsToDraw.forEach(function(object) {
const programInfo = object.programInfo;
const bufferInfo = object.bufferInfo;
let bindBuffers = false;

if (programInfo !== lastUsedProgramInfo) {


lastUsedProgramInfo = programInfo;
gl.useProgram(programInfo.program);
bindBuffers = true;
}

// Setup all the needed attributes.


if (bindBuffers || bufferInfo !== lastUsedBufferInfo) {
lastUsedBufferInfo = bufferInfo;
setBuffersAndAttributes(gl, programInfo.attribSetters, bufferInfo);
}

// Set the uniforms.


setUniforms(programInfo.uniformSetters, object.uniforms);

// Draw
drawBufferInfo(gl, bufferInfo);
});

KhoaCNTT-Trường ĐHBK, ĐHĐN


}

function glEnumToString(gl, v) {
const results = [];
for (const key in gl) {
if (gl[key] === v) {
results.push(key);
}
}
return results.length
? results.join(' | ')
: `0x${v.toString(16)}`;
}

const isIE = /*@cc_on!@*/false || !!document.documentMode;


// Edge 20+
const isEdge = !isIE && !!window.StyleMedia;
if (isEdge) {
// Hack for Edge. Edge's WebGL implmentation is crap still and so they
// only respond to "experimental-webgl". I don't want to clutter the
// examples with that so his hack works around it
HTMLCanvasElement.prototype.getContext = function(origFn) {
return function() {
let args = arguments;
const type = args[0];
if (type === 'webgl') {
args = [].slice.call(arguments);
args[0] = 'experimental-webgl';
}
return origFn.apply(this, args);
};
}(HTMLCanvasElement.prototype.getContext);
}

return {
createAugmentedTypedArray: createAugmentedTypedArray,
createAttribsFromArrays: createAttribsFromArrays,
createBuffersFromArrays: createBuffersFromArrays,
createBufferInfoFromArrays: createBufferInfoFromArrays,
createAttributeSetters: createAttributeSetters,
createProgram: createProgram,
createProgramFromScripts: createProgramFromScripts,
createProgramFromSources: createProgramFromSources,
createProgramInfo: createProgramInfo,
createUniformSetters: createUniformSetters,
createVAOAndSetAttributes: createVAOAndSetAttributes,
createVAOFromBufferInfo: createVAOFromBufferInfo,
drawBufferInfo: drawBufferInfo,
drawObjectList: drawObjectList,
glEnumToString: glEnumToString,
getExtensionWithKnownPrefixes: getExtensionWithKnownPrefixes,
resizeCanvasToDisplaySize: resizeCanvasToDisplaySize,
setAttributes: setAttributes,
setBuffersAndAttributes: setBuffersAndAttributes,
setUniforms: setUniforms,
};

}));

1.4. File webgl-tutorials.css


/* Licensed under a BSD license. See ../license.html for license */

html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
user-select: none;

KhoaCNTT-Trường ĐHBK, ĐHĐN


}

body {
background-color: #aaa;
font-family: Sans-Serif;
}

canvas {
background-color: #fff;
border: 1px solid black;
/* NOTE: This size is changed if in iframe - see below '.iframe canvas' */
width: 400px;
height: 300px;
display: block;
}

#uiContainer {
position: absolute;
top: 10px;
right: 10px;
z-index: 3;
font-family: monospace;
pointer-events: none;

text-shadow:
-1px -1px 0 #FFF,
1px -1px 0 #FFF,
-1px 1px 0 #FFF,
1px 1px 0 #FFF;
}
#ui {
opacity: 0.8;
}
#ui>div {
pointer-events: none;
}
#ui input,
#ui label,
#ui select,
#ui option,
#ui canvas {
pointer-events: auto;
}

.gman-slider-label, .gman-widget-label, .gman-checkbox-label {


font-size: medium;
min-width: 10em;
text-align: right;
}
.gman-checkbox-label {
pointer-events: auto;
}
.gman-widget-value {
float: right;
font-size: medium;
order: 1;
min-width: 3em;
}

.gman-slider-upper {
height: 1.5em;
}
.gman-slider-outer, .gman-widget-outer {
float: right;
display: flex;
align-items: center;
height: 1.7em;
}
.gman-widget-slider, .gman-widget-checkbox, .gman-widget-select {
opacity: 0.5;

KhoaCNTT-Trường ĐHBK, ĐHĐN


font-size: large;
margin-left: .3em;
margin-right: .3em;
}
.gman-widget-select {
font-size: small;
}
.gman-widget-slider {
min-width: 120px;
}

/* styles to apply if in an iframe */

html.iframe {
height: 100vh;
}
body.iframe {
width: 100vw;
height: 100vh;
margin: 0;
}

.iframe>.description {
display: none;
}
.iframe .divcanvas {
width: 100vw;
height: 100vh;
}
.iframe canvas {
width: 100vw;
height: 100vh;
max-width: 100vw;
border: none;
}

.iframe>#example {
width: 100%;
height: 100%;
}
#ui #rotation>canvas {
background-color: rgba(255, 255, 255, 0.6);
}
#ui {
width: 200px;
}

@media (max-width: 390px) {


pre {
font-size: xx-small !important;
max-width: 300px !important;
}
canvas {
width: 100vw;
}
.iframe canvas {
width: 100vw;
height: 100vh;
border: none;
}
#uiContainer {
top: 120px;
}
.iframe #uiContainer {
top: 10px;
}
}

@media (prefers-color-scheme: dark) {

KhoaCNTT-Trường ĐHBK, ĐHĐN


#ui .ui-dark-support {
color: white;
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
}
}

 Kết quả thực hiện


1.5. Bài tập


 Thay hình chữ F thành các hình khác.

Chữ L:Thay hàm setGeometry(gl):


function setGeometry(gl) {
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([

KhoaCNTT-Trường ĐHBK, ĐHĐN


// Phần dọc của chữ L
0, 0,
30, 0,
0, 150,
0, 150,
30, 0,
30, 150,

// Phần ngang của chữ L


30, 120,
100, 120,
30, 150,
30, 150,
100, 120,
100, 150,
]),
gl.STATIC_DRAW
);
}

Chữ A:Thay hàm setGeometry(gl):


function setGeometry(gl) {

KhoaCNTT-Trường ĐHBK, ĐHĐN


gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
// Left side
0, 0,
0, 150,
30, 0,

0, 150,
30, 150,
30, 0,

// Right side
30, 0,
60, 150,
60, 0,

30, 0,
60, 150,
90, 0,

// Top bar
15, 75,
45, 75,
15, 90,

15, 90,
45, 75,
45, 90,
]),

KhoaCNTT-Trường ĐHBK, ĐHĐN


 Tách chương trình trên thành các phép quay, rotate, translate, scale, symmetric
Rotate:

Translate:

KhoaCNTT-Trường ĐHBK, ĐHĐN


Scale:

Symmetric:

KhoaCNTT-Trường ĐHBK, ĐHĐN


2. Tham khảo
[1]. …

(Tài liệu lưu hành nội bộ)


-----------------------------------------------

KhoaCNTT-Trường ĐHBK, ĐHĐN

You might also like