Статьи

Вращающиеся изображения в WebGL


Сегодня мы продолжаем примеры 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. Изображения

Все эти изображения мы будем использовать для скручивания:

    1
    2
    3
    4
    5
    6
    7


Live Demo

скачать в упаковке


Вывод

Надеюсь, вам понравился сегодняшний результат. Если у вас есть предложения или идеи — поделитесь ими,
:-)добро пожаловать, друзья!