Сегодня мы продолжаем примеры HTML5 canvas. И сегодня наш второй учебник для WebGL. Мы будем создавать анимированные скручивающиеся изображения. Также мы добавим обработчики для манипулирования изображениями с помощью мыши и клавиатуры.
Вот наш демонстрационный и загружаемый пакет:
Live Demo
скачать в упаковке
Хорошо, скачайте файлы примеров и давайте начнем кодировать!
Шаг 1. HTML
Вот html источники нашего демо. Как видите — просто пустая страница.
index.html
<!DOCTYPE html>
<html lang="en" >
<head>
<link href="css/main.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="js/glMatrix-0.9.5.min.js"></script>
<script type="text/javascript" src="js/webgl-utils.js"></script>
<script type="text/javascript" src="js/script.js"></script>
<title>Creating a Keyboard Sensitive 3D Twisted Images in WebGl | Script Tutorials</title>
</head>
<body onload="initWebGl();">
<div class="example">
<h3><a href="http://www.script-tutorials.com/twisting-images-webgl/">Creating a Keyboard Sensitive 3D Twisted Images in WebGl | Script Tutorials</a></h3>
<h3>You can use your mouse + arrow keys + page up / down</h3>
<canvas id="panel" width="500" height="333"></canvas>
<div style="clear:both;"></div>
</div>
</body>
</html>
Шаг 2. CSS
Здесь используются стили CSS.
CSS / main.css
body {
background:#eee;
font-family:Verdana, Helvetica, Arial, sans-serif;
margin:0;
padding:0
}
.example {
background:#fff;
width:500px;
font-size:80%;
border:1px #000 solid;
margin:20px auto;
padding:15px;
position:relative;
-moz-border-radius: 3px;
-webkit-border-radius:3px
}
h3 {
text-align:center;
}
Шаг 3. JS
js / webgl-utils.js и js / glMatrix-0.9.5.min.js
Эти файлы мы будем использовать в проекте для работы с WebGL. Оба файла будут в нашей упаковке.
JS / script.js
var gl; // global WebGL object
var shaderProgram;
var pics_names=['1.png', '2.png', '3.png', '4.png', '5.png', '6.png', '7.png'];
var pics_num=pics_names.length;
// diffirent initializations
function initGL(canvas) {
try {
gl = canvas.getContext('experimental-webgl');
gl.viewportWidth = canvas.width;
gl.viewportHeight = canvas.height;
} catch (e) {}
if (! gl) {
alert('Can`t initialise WebGL, not supported');
}
}
function getShader(gl, type) {
var str = '';
var shader;
if (type == 'x-fragment') {
str = "#ifdef GL_ES\n"+
"precision highp float;\n"+
"#endif\n"+
"varying vec2 vTextureCoord;\n"+
"uniform sampler2D uSampler;\n"+
"void main(void) {\n"+
" gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));\n"+
"}\n";
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (type == 'x-vertex') {
str = "attribute vec3 aVertexPosition;\n"+
"attribute vec2 aTextureCoord;\n"+
"uniform mat4 uMVMatrix;\n"+
"uniform mat4 uPMatrix;\n"+
"varying vec2 vTextureCoord;\n"+
"void main(void) {\n"+
" gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);\n"+
" vTextureCoord = aTextureCoord;\n"+
"}\n";
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}
gl.shaderSource(shader, str);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}
return shader;
}
function initShaders() {
var fragmentShader = getShader(gl, 'x-fragment');
var vertexShader = getShader(gl, 'x-vertex');
shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert('Can`t initialise shaders');
}
gl.useProgram(shaderProgram);
shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, 'aVertexPosition');
gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, 'aTextureCoord');
gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, 'uPMatrix');
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, 'uMVMatrix');
shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, 'uSampler');
}
var objVertexPositionBuffer=new Array();
var objVertexTextureCoordBuffer=new Array();
var objVertexIndexBuffer=new Array();
function initObjBuffers() {
for (var i=0;i<pics_num;i=i+1) {
objVertexPositionBuffer[i] = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, objVertexPositionBuffer[i]);
vertices = [
Math.cos(i*((2*Math.PI)/pics_num)), -0.5, Math.sin(i*((2*Math.PI)/pics_num)),
Math.cos(i*((2*Math.PI)/pics_num)), 0.5, Math.sin(i*((2*Math.PI)/pics_num)),
Math.cos((i+1)*((2*Math.PI)/pics_num)), 0.5, Math.sin((i+1)*((2*Math.PI)/pics_num)),
Math.cos((i+1)*((2*Math.PI)/pics_num)), -0.5, Math.sin((i+1)*((2*Math.PI)/pics_num)),
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
objVertexPositionBuffer[i].itemSize = 3;
objVertexPositionBuffer[i].numItems = 4;
objVertexTextureCoordBuffer[i] = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, objVertexTextureCoordBuffer[i] );
var textureCoords = [
0.0, 0.0,
0.0, 1.0,
1.0, 1.0,
1.0, 0.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
objVertexTextureCoordBuffer[i].itemSize = 2;
objVertexTextureCoordBuffer[i].numItems = 4;
objVertexIndexBuffer[i] = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, objVertexIndexBuffer[i]);
var objVertexIndices = [
0, 1, 2,
0, 2, 3,
];
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(objVertexIndices), gl.STATIC_DRAW);
objVertexIndexBuffer[i].itemSize = 1;
objVertexIndexBuffer[i].numItems = 6;
}
}
function handleLoadedTexture(texture) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.bindTexture(gl.TEXTURE_2D, null);
}
var crateTextures = Array();
function initTexture(image) {
var crateImage = new Image();
var texture = gl.createTexture();
texture.image = crateImage;
crateImage.src = image;
crateImage.onload = function () {
handleLoadedTexture(texture)
}
return texture;
}
function initTextures() {
for (var i=0; i < pics_num; i++) {
crateTextures[i]=initTexture(pics_names[i]);
}
}
var mvMatrix = mat4.create();
var mvMatrixStack = [];
var pMatrix = mat4.create();
function setMatrixUniforms() {
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
}
function degToRad(degrees) {
return degrees * Math.PI / 180;
}
// mouse and keyboard handlers
var xRot = 0;
var xSpeed = 0;
var yRot = 0;
var ySpeed = 10;
var z = -3.0;
var currentlyPressedKeys = {};
function handleKeyDown(event) {
currentlyPressedKeys[event.keyCode] = true;
}
function handleKeyUp(event) {
currentlyPressedKeys[event.keyCode] = false;
}
function handleKeys() {
if (currentlyPressedKeys[33]) { // Page Up
z -= 0.05;
}
if (currentlyPressedKeys[34]) { // Page Down
z += 0.05;
}
if (currentlyPressedKeys[37]) { // Left cursor key
ySpeed -= 1;
}
if (currentlyPressedKeys[39]) { // Right cursor key
ySpeed += 1;
}
if (currentlyPressedKeys[38]) { // Up cursor key
xSpeed -= 1;
}
if (currentlyPressedKeys[40]) { // Down cursor key
xSpeed += 1;
}
}
var mouseDown = false;
var lastMouseX = null;
var lastMouseY = null;
var RotationMatrix = mat4.create();
mat4.identity(RotationMatrix);
function handleMouseDown(event) {
mouseDown = true;
lastMouseX = event.clientX;
lastMouseY = event.clientY;
}
function handleMouseUp(event) {
mouseDown = false;
}
function handleMouseMove(event) {
if (!mouseDown) {
return;
}
var newX = event.clientX;
var newY = event.clientY;
var deltaX = newX - lastMouseX;
var newRotationMatrix = mat4.create();
mat4.identity(newRotationMatrix);
mat4.rotate(newRotationMatrix, degToRad(deltaX / 5), [0, 1, 0]);
var deltaY = newY - lastMouseY;
mat4.rotate(newRotationMatrix, degToRad(deltaY / 5), [1, 0, 0]);
mat4.multiply(newRotationMatrix, RotationMatrix, RotationMatrix);
lastMouseX = newX
lastMouseY = newY;
}
// Draw scene and initialization
var MoveMatrix = mat4.create();
mat4.identity(MoveMatrix);
function drawScene() {
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, [0.0, 0.0, z]);
mat4.rotate(mvMatrix, degToRad(xRot), [1, 0, 0]);
mat4.rotate(mvMatrix, degToRad(yRot), [0, 1, 0]);
mat4.multiply(mvMatrix, MoveMatrix);
mat4.multiply(mvMatrix, RotationMatrix);
for (var i=0;i<pics_num;i=i+1) {
gl.bindBuffer(gl.ARRAY_BUFFER, objVertexPositionBuffer[i]);
gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, objVertexPositionBuffer[i].itemSize, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, objVertexTextureCoordBuffer[i]);
gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, objVertexTextureCoordBuffer[i].itemSize, gl.FLOAT, false, 0, 0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, crateTextures[i]);
gl.uniform1i(shaderProgram.samplerUniform, 0);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, objVertexIndexBuffer[i]);
setMatrixUniforms();
gl.drawElements(gl.TRIANGLES, objVertexIndexBuffer[i].numItems, gl.UNSIGNED_SHORT, 0);
}
}
var lastTime = 0;
function animate() {
var timeNow = new Date().getTime();
if (lastTime != 0) {
var elapsed = timeNow - lastTime;
xRot += (xSpeed * elapsed) / 1000.0;
yRot += (ySpeed * elapsed) / 1000.0;
}
lastTime = timeNow;
}
function drawFrame() {
requestAnimFrame(drawFrame);
handleKeys();
drawScene();
animate();
}
function initWebGl() {
var canvas = document.getElementById('panel');
initGL(canvas);
initShaders();
initObjBuffers();
initTextures();
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.enable(gl.DEPTH_TEST);
document.onkeydown = handleKeyDown;
document.onkeyup = handleKeyUp;
canvas.onmousedown = handleMouseDown;
document.onmouseup = handleMouseUp;
document.onmousemove = handleMouseMove;
drawFrame();
}
И снова — длинный код, но самое главное. Я разделил весь код на 3 стороны: инициализации, обработчики и рисование сцены. Надеюсь, что вы уже прочитали наш предыдущий урок WebGL . В этом случае будет проще понять сегодняшний код. Просто обратите внимание, что вместо цветового буфера мы будем использовать текстурный буфер (objVertexTextureCoordBuffer). Также эта демоверсия способна работать с любым количеством использованных изображений (лучше — более 3-х).
Шаг 4. Изображения
Все эти изображения мы будем использовать для скручивания:
Live Demo
скачать в упаковке
Вывод
Надеюсь, вам понравился сегодняшний результат. Если у вас есть предложения или идеи — поделитесь ими,
