Статьи

Three.js Tutorial: пример с WebGL, Canvas и Webworkers

В этом уроке мы рассмотрим, как вы можете использовать three.js для рендеринга 3D-карты изображения с помощью webgl на элементе canvas. В этом примере мы растеризуем изображение (сделаем его похожим на старое 8-битное изображение) и используем это растеризованное изображение в качестве входных данных для нашей 3D-модели. Каждый растровый элемент отображается в виде куба с использованием three.js. Высота куба определяется яркостью растрового элемента. Поскольку изображение обычно лучше объясняет, к чему мы стремимся, давайте посмотрим, что мы собираемся создать:

Мы хотим этого

Эта статья использует пару примеров из предыдущих статей:

  • Он использует пул потоков веб-работника, показанный в этой статье .
  • И это растеризует изображение на основе информации отсюда .
  • И яркость рассчитывается как объяснено здесь .

На самом деле вам не нужно углубляться в эти статьи, чтобы узнать о three.js, но если вам нравится некоторая справочная информация, то эти статьи — место, на которое стоит обратить внимание. Теперь, что мы собираемся показать в этой статье.

  • Создание HTML-макета. Мы создадим очень простую галерею, в которой вы сможете выбрать изображение, которое хотите визуализировать. Нам также нужно настроить скрытый холст, который мы можем использовать для растеризации.
  • Инициализация сцены three.js: мы создаем простую сцену three.js с вращающейся камерой. К этой сцене мы добавим пару сотен кубов. Один для каждой части нашего растрового изображения.
  • Добавьте кубы в сцену. Когда изображение выбрано, мы растеризуем изображение (в ряде фоновых веб-рабочих потоков) и, основываясь на яркости, добавляем куб в определенной позиции в сцене three.js.

Создать HTML-макет

HTML очень прост. Мы только что получили пару элементов div, включили несколько библиотек javascript и стилизовали некоторые элементы. Полный HTML-код показан здесь:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title></title>
    <script type="text/javascript" src="libs/jquery-1.7.2.js"></script>
    <script type="text/javascript" src="libs/three.js"></script>
    <script type="text/javascript" src="libs/thread-pool.js"></script>
    <script type="text/javascript" src="js/rasterize.js"></script>
    <script type="text/javascript" src="js/voxel-image.js"></script>
 
    <style type="text/css">
        .thumb {
            /*float: left;*/
            margin-top: 10px;
            height: 100px;
            width: 100px;
        }
 
        #webglcontainer {
            width: 600px;
            height: 480px;
            border: solid 1px #CCC;
            -moz-box-shadow: 1px 1px 5px #ffffff;
            -webkit-box-shadow: 1px 1px 5px #ffffff;
            box-shadow: 1px 1px 5px #ffffff;
            background-color: #eeeeee;
        }
 
        body {
            background-color: #000000;
        }
    </style>
</head>
<body>
 
<div id="target" style="display: inline; visibility: hidden; height:0px; width: 0px;"></div>
 
<div style="margin-left: 130px">
<img id="f1" src="assets/sophie.jpg" class="thumb" border="0"/>
<img id="f2" src="assets/mp1.jpg" class="thumb" border="0"/>
<img id="f3" src="assets/mp2.jpg" class="thumb" border="0"/>
</div>
 
<div id="webglcontainer"></div>
 
 
<script type="text/javascript">
 
    $(document).ready(function () {
        $("#f3").load(function () {
            init();
            renderImage($(this));
 
            $(".thumb").click(function () {
                renderImage($(this));
            });
        });
    });
</script>
</body>
 
</html>

Как вы можете видеть из этого HTML, все, что мы здесь делаем, довольно просто. Мы определяем скрытый div, который мы используем для растеризации, div, который содержит пару изображений для нашей галереи и, наконец, div, который будет использоваться для визуализации результата. Мы также используем простые JQuery $ (document) и $ («# f3»), чтобы убедиться, что документ готов и изображение загружено, прежде чем мы начнем рендеринг. Как только первое изображение загружено, мы передаем это функции renderImage. Эта функция растеризует изображение и покажет вывод в div webglcontainer.

Инициализируйте сцену three.js

Перед тем, как изображение может быть визуализировано, нам сначала нужно правильно настроить сцену для three.js. Мы делаем это методом init.

// some global variables
var camera, scene, renderer;
var elements = [];
 
// some default values
var bulletSize = 10;
var offset = 300;
var defPos = 800;
 
// Initialize the scene and threadpool
function init() {
 
    // create a queuepool with 6 queues
    queuepool = new Pool(3);
    queuepool.init();
 
    // create a scene and a camera
    scene = new THREE.Scene();
 
    //Three.PerspectiveCamera()
    camera = new THREE.PerspectiveCamera( 55, 1,  0.1, 10000, -2000, 10000 );
    // position the camera
    camera.position.y = defPos+200;
    camera.position.z = defPos;
    camera.position.x = defPos;
 
    // and add to the scene
    scene.add(camera);
 
    // setup the renderer and attach to canvas
    renderer = new THREE.WebGLRenderer();
    renderer.setSize( 600, 600 );
 
    $("#webglcontainer").append(renderer.domElement);
 
    animate();
}

В этом методе мы сначала создаем пул очередей, этот пул очередей используется для запуска заданий. В этом примере мы используем задания для расчета наиболее доминирующего цвета определенной части изображения (дополнительную информацию можно найти в этой статье). Затем мы создаем сцену, камеру и добавляем камеру к сцене. Наконец, в этом фрагменте кода мы создаем средство визуализации для этой сцены и добавляем его на холст. Теперь, когда сцена создана, мы можем ее визуализировать. Это делается в функции анимации:

// the animation loop. This rotates the camera around the central point.
function animate() {
    var timer = Date.now() * 0.0008;
    camera.position.x = (Math.cos( timer ) * defPos);
    camera.position.z = (Math.sin( timer ) * defPos) ;
    camera.lookAt( scene.position );
 
    renderer.render( scene, camera );
    requestAnimationFrame( animate );
}

Эта функция анимации использует функциональные возможности requestAnimationFrame для получения обратного вызова, когда необходимо обновить анимацию. Мы предоставляем сам метод animate в качестве обратного вызова, поэтому анимация будет продолжаться. В этой функции анимации мы также вращаем камеру вокруг сцены. Для этого мы изменяем положение X и Z камеры, сохраняя фокус на нашей сцене. Не вдаваясь в математику за этим, если вы чередуете X-pos, используя Math.cos (t) и Z-pos, одновременно используя Math.sin (t), ваша камера будет плавно вращаться вокруг сцены.
Теперь мы можем вызвать операцию рендеринга для рендеринга сцены.

Добавьте кубики на сцену

Осталось добавить кубы на сцену. Мы делаем это в следующей функции под названием addElement

// add a cube to the grid. The cube is positioned base on the x,y values. The color
// is used to define the material, and the luminance is used for the height of the element.
function addElement(x,y, color, lumin) {
    var voxelPosition2 = voxelPosition = new THREE.Vector3();
    voxelPosition2.x = bulletSize*x -offset ;
    voxelPosition2.z = bulletSize*y -offset ;
    voxelPosition2.y = 200 + ((lumin/(255))*200)/2;
 
    var geometry = new THREE.CubeGeometry( bulletSize, (lumin/(255))*200, bulletSize );
    var mat = new THREE.MeshBasicMaterial( { color: color, shading: THREE.NoShading, wireframe: false, transparent: false })
 
    var cube = new THREE.Mesh(geometry,mat);
    cube.position=voxelPosition2;
 
    // add to elements list and to scene
    elements.push(cube);
    scene.add(cube);
}

Эта функция принимает в качестве параметров положение элемента, цвет, в котором мы должны визуализировать элемент, и яркость элемента. На основе этой информации мы определяем положение, в котором нам нужно визуализировать куб, создаем куб, высота которого основана на яркости, и создаем материал для этого куба на основе наиболее доминирующего цвета. Со всеми этими частями мы можем добавить куб в правильном положении к сцене. И поскольку мы уже запустили функцию анимации, сцена будет постоянно обновляться.

Мы упустили одну вещь, о которой мы не говорили, это то, как выглядит операция renderImage, которую мы вызываем со страницы HTML всякий раз, когда вы нажимаете на изображение. Эта функция показана здесь:

function callback(event) {
 
    var wp = event.data;
 
    // get the colors
    var colors = wp.result;
 
    var color = "0x" +
        ("0" + parseInt(colors[0][0],10).toString(16)).slice(-2) +
        ("0" + parseInt(colors[0][1],10).toString(16)).slice(-2) +
        ("0" + parseInt(colors[0][2],10).toString(16)).slice(-2);
 
 
     var lumin = colors[0][0] * .3 + colors[0][1] * .59 + colors[0][2] * .11;
 
     addElement(wp.x,wp.y, color, lumin);
}

Что мы делаем здесь, так это то, что мы очищаем очередь и останавливаем все выполняющиеся задачи (в данный момент это не идеально, поэтому вы можете увидеть несколько кубов из предыдущего изображения). Затем мы удаляем все текущие кубы со сцены и, наконец, растеризуем выбранное изображение. В этой функции растеризации мы разделяем изображение на части. Я не буду показывать детали растеризации здесь, но я покажу обратный вызов, который вызывается после определения доминирующего цвета (см. Здесь для получения дополнительной информации о растеризации).

Событие, которое мы получаем здесь, содержит информацию о том, какой доминирующий цвет определенной части изображения. Мы конвертируем этот цвет в формат, используемый Three.js, и вычисляем яркость этого цвета. Вся эта информация передается в функцию addElement, которую мы видели ранее, и добавляется в сцену.

Вот и все. Если вы запустите это, вы медленно увидите сцену, заполненную разноцветными кубиками разной высоты, например:

localhost_Dev_three.js_2_.png.png

Этот пример был протестирован с использованием последней сборки Chrome и последней бета-версии Firefox. Я заметил, что Chrome, хотя он и был быстрее, иногда зависал, но оба браузера должны быть в состоянии сделать это.