Pull to refresh

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

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



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

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

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

Update: Спасибо за то что добавил anaglyph в демо, теперь можно попробовать его на любом мониторе в красно синих очках.
//инициализация вспомогательных текстур и сцены для их отрисовки
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 текстуры и высоту кадра в пикселях экрана
вершинный шейдер вычисляет позицию точки и передает ее в пиксельный шейдер
там мы вычисляем четная ли у нас линия по вертикали или нет, для четных линий берем одну текстуру — для нечетных другую (в этой статье я писал как такой подход позволяет формировать 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 мониторов могут посмотреть демо (на обычном мониторе это конечно не так красиво). Код можно найти на github

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

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

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

Update: Спасибо KOLANICH, что добавил anagyph эффект к этому демо — теперь можно попробовать посмотреть его на любом мониторе в красно-синих очках. У меня нет таких, поэтому проверить не могу, но если кто проверит и найдет баги — приму pull-requests.
Tags:
Hubs:
+14
Comments11

Articles