- PVSM.RU - https://www.pvsm.ru -

Реализуем 3D картинку в браузере

HTML 3D LOGO В этой статье я хочу продолжить рассказ о моих экспериментах с 3D монитором.В первой статье [1] было описано как выводить стерео изображение из видео потока (в VLC виде плеере) сейчас я расскажу как получить стерео картинку прямо в вашем браузере. Для демо я взял замечательную библиотеку Three.js [2] об ней уже много писали на Хабре, она позволяет быстро и просто создавать красивые web приложения на WebGL. Ниже я покажу как сделать так чтобы пользователь увидел глубокую 3D картинку а не плоскую проекцию.

В качестве исходных данных возьмем самый простой пример из Three.js [2]
— это будет вращающийся кубик [3]. Чтобы 3D эффект был ярче — я добавил к вращению еще поступательное движение по направлению к наблюдателю.

Для того чтобы получить 2 ракурса для нашей трех-мерной картинки — делаем такой трюк
в цикле отрисовки каждого фрейма:
— отрисовываем сцену с камеры не на экран а в текстуру
— сдвигаем камеру (как бы в позицию второго глаза)
— отрисовываем сцену в другую текстуру
— теперь у нас есть картинки для левого и правого глаза — нам остается их правильно смешать чтобы левый глаз увидел левую картинку а правый — правую на 3D мониторе.

теперь опишем это в коде
(базовый код примера webgl_geometry_cube приводить нет смысла, опишу лишь то что я добавил)

//инициализация вспомогательных текстур и сцены для их отрисовки
function initORTscene() {
    //projection to screen
    rtTexture = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBFormat });
    ltTexture = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBFormat });

    //текстуры будут растеризовываться при помощи шейдера - я его описывал в предыдущей статье
    materialScreen = new THREE.ShaderMaterial({
        uniforms: { lRacurs: { type: "t", value: ltTexture }, rRacurs: { type: "t", value: rtTexture }, height: { type: "f", value: window.innerHeight } },
        vertexShader: document.getElementById('vertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader').textContent,
        depthWrite: false
    });

    //рисовать будем на прямоугольнике который занимает все окно
    var plane = new THREE.PlaneGeometry(window.innerWidth, window.innerHeight);
    var offscreenMesh = new THREE.Mesh(plane, materialScreen);
    offscreenMesh.position.z = -1; //a little behind
    sceneORT = new THREE.Scene();
    //перспектива нам здесь не нужна, поэтому тут будет камера которая возьмет ортографическую проекцию 
    cameraORT = new THREE.OrthographicCamera(window.innerWidth / -2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / -2, -10000, 10000);
    sceneORT.add(offscreenMesh);
}

шейдеры
в них мы передаем 2 текстуры и высоту кадра в пикселях экрана
вершинный шейдер вычисляет позицию точки и передает ее в пиксельный шейдер
там мы вычисляем четная ли у нас линия по вертикали или нет, для четных линий берем одну текстуру — для нечетных другую (в этой статье [1] я писал как такой подход позволяет формировать 3D картинку)

<script id="fragmentShader" type="x-shader/x-fragment">
    varying vec2 vUv;
    uniform sampler2D rRacurs;
    uniform sampler2D lRacurs;
    uniform float height;
    void main() {
	    //odd from left racurs, even from right
	    float d = mod((floor(height*(vUv.y+1.0))),2.0); //odd or even, height - is new uniform to get viewport height
	    if(d > 0.1) {
	    	gl_FragColor = texture2D( rRacurs, vUv );
	    } else {
	    	gl_FragColor = texture2D( lRacurs, vUv );
	    }
    }
</script>

<script id="vertexShader" type="x-shader/x-vertex">
    varying vec2 vUv;
    void main() {
    	vUv = uv;
    	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }
</script>

теперь отрисуем нашу сцену

   var x = camera.position.x;
    var faceWidth = 5; //расстояние между глаз

    //отрисовка правой текстуры
    camera.position.x = x + faceWidth / 2;
    renderer.render(scene, camera, rtTexture, true);

    //отрисовка левой текстуры
    camera.position.x = x - faceWidth / 2;
    renderer.render(scene, camera, ltTexture, true);

    camera.position.x = x;

    // а теперь отрисуем прямоугольник с двумя текстурами прямо на экран
    renderer.render(sceneORT, cameraORT);

Вот и все.
Счастливые владельцы пассивных 3D мониторов могут посмотреть демо [4] (на обычном мониторе это конечно не так красиво). Код можно найти на github [5]

Хочу заметить, тут конечно не 30 строк кода, но не больше 70 — и это все что нужно чтоб реализовать 3D картинку.

Параметр faceWidth можно менять — чем он больше — тем сильней 3D, но значительны геометрические искажения.

Данный код можно использовать для любой сцены, написанной на three.js(WebGL), чтоб добавить к ней настоящий 3D, например вот ссылка на игру [6], которую я написал в рамках изучения javascript, — в 3D выглядит вполне неплохо.

Автор: mangelov

Источник [7]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/news/54816

Ссылки в тексте:

[1] В первой статье: http://habrahabr.ru/post/211468/

[2] Three.js : http://threejs.org

[3] вращающийся кубик: http://threejs.org/examples/#webgl_geometry_cube

[4] демо: http://mikhail-angelov.github.io/html5-3D-demo/index.html

[5] github: https://github.com/mikhail-angelov/html5-3D-demo

[6] ссылка на игру: http://mikhail-angelov.github.io/shooter/

[7] Источник: http://habrahabr.ru/post/212297/