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

    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.
    Поделиться публикацией

    Похожие публикации

    Комментарии 11
      +1
      Зачем рисовать background с помощью CubeGeometry? Разве в three.js нет более экономного способа вывести картинку? У вас уходит 36 ushort на куб и 144 на background.
        +1
        Спасибо за комментарий. Конечно можно использовать PlaneGeometry для оптимизации. В любом случае хочу предупредить — не стоит использовать код из моего демо в продакшене, он жутко не оптимален и написал лишь для того чтоб проиллюстрировать содержание статьи.
        0
        Каждый раз, когда смотрю на код three.js я удивляюсь "… она позволяет быстро и просто..."
          0
          я пробовал напрямую с webGL работать. На фоне этого — быстро и просто.
          А вообще — на самом деле очень не хватает цикла статей про шейдеры больше всего, нигде даже нагуглить ничего приличного не могу из азов.
            0
            Вот, кстати, да. Поддержу!

            Сейчас «по долгу службы» приходится работать напрямую с шейдерами и OpenGLES, в сравнение с этим three.js просто волшебная библиотека. Как глоток свежего воздуха, ей-Богу.
          0
          А какое железо надо, чтобы получить нормальный fps? Сейчас у меня 9.
            0
            точно не могу ответить, на моем трехлетнем ноуте с процессором I3 и интегрированной видео картой — около 55-60fps
              0
              У меня Athlon 64 X2 4200+ и GeForce GT 630
                0
                Значит это проблемы браузера (вероятнее всего ускорения). на i3-2330 c HD3000, fps стабильные 60, браузер хромиум Version 32.0.1700.107 (248368)
                  0
                  В Chrome 9 fps, в Firefox 75 fps, в Chrome почему-то не включается аппаратное ускорение, хотя галка стоит
            +1
            Спасибо за статью!

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое