Особенности создания интерактивного 3D HTML5 приложения с использованием сенсора Kinect.
Задача
Продемонстрировать 3D фото и видео, отснятое в разных краях нашей большой Родины, на 3D дисплее, причем так, чтобы воспроизведение начиналось тогда, когда пользователь входил в определенную географическую зону на карте, размещенной перед экранами. В идеале стенд должен состоять из 6 3D дисплеев и 3 сенсоров Kinect, чтобы позволить максимально разнести геозоны перед экранами и дать возможность нескольким пользователям управлять своими дисплеями.
Решение
Еще во время обсуждения, мы исследовали вопрос отображения полноценного стереоскопического (неанаглифного) 3D контента. Среди возможных путей реализации была технология nVidia 3D Vision, чей драйвер и дополнения для Firefox позволяли отображать 3D.
Среди технологий 3D дисплеев мы остановились на менее дорогой, чем активное 3D (затворные очки + 100 Hz покадровая развертка), а именно на технологии пассивного поляризационного 3D с построчной технологией разделения левого и правого каналов. Поляризация была выбрана круговая как наиболее адекватная при перемещении и повороте головы относительно экрана.
Нужно отметить, что драйвер nVidia 3D Vision оказался всеядным и умел работать с любым типом дисплеев (по крайней мере с большинством из имеющихся в нашем распоряжении). Однако невнятное API, нестабильность и коммерческая политика nVidia заставили быстро усомниться в правильности выбора. Так возникла идея построить свой собственный драйвер. А доводы были такие:
- технология с построчным выводом левого и правого каналов подразумевает наличие двух изображений, стыкованных side-by-side (top-bottom или left-right),
- если перекомбинировать пикселы на FullHD side-by-side изображении в «левые/правые» строки попеременно, а также добиться строгого отображения «pixel to pixel» растра на LCD матрице, то можно достичь вменяемого отображения 3D и без специальных драйверов.
side-by-side
interlace
Так технологическая интуиция привела нас к шейдерам. Для начала мы оппробировали GLSL для WebGL на примерах из фреймворка threejs. Шейдер был записан в несколько строк: проверка четности строки, если нечетная – выводим пикселы с левой картинки, четные – с правой. Всё это хорошо завелось и работало быстро на HD – примерно 30fps (Windows 7, Chrome, nVidia GPU). FullHD видео снизило этот показатель примерно до 12 fps. Профайлер показал узкое место – перед каждым draw call нужно было передать огромный буфер с текстурой, которая менялась в каждом кадре. И это было никак не победить «на-лету». Тогда на выручку пришел Flash и небезысвестная технология Pixel Bender (2D). По существу это тот же самый GLSL синтаксис, только несколько иной спецификации. За несколько часов мы реализовали flash-компонент 3D плеера, который имел простой JS api, максимально приближенный к HTML5 <video>. Итак у нас появилось две реализации 3D плеера в JS обертке. Тесты на FullHD видео с Flash 3D плеером показали более достойные результаты ~25fps. Так мы победили сырость.
WebGL
#ifdef GL_ES
precision highp float;
#endif
uniform sampler2D Sample0;
varying vec2 vUv;
void main ()
{
vec3 colour;
float row = ceil(vUv.y * 1080.0);
vec2 pixel;
float mod = mod(row, 2.0);
if (mod == 0.0) {
pixel = vec2(vUv.x / 2.0, vUv.y);
} else {
pixel = vec2(0.5 + vUv.x / 2.0, vUv.y);
}
colour = texture2D(Sample0, pixel).xyz;
gl_FragColor.xyz = colour;
gl_FragColor.w = 1.0;
}
Pixel Bender
<languageVersion: 1.0;>
kernel interlace
< namespace : "Interlace 3D";
vendor : "NMT";
version : 2;
description : "Create interlaced video from side-by-side"; >
{
input image4 oImage;
output float4 outputColor;
parameter bool swapEyes
<
description: "swap eyes";
defaultValue: false;
>;
void
evaluatePixel()
{
float2 relativePos = outCoord();
float modulus = mod(relativePos.y, 2.0);
if (swapEyes && modulus <= 1.0 || !swapEyes && modulus >= 1.0) {
relativePos.x = 960.0 + relativePos.x / 2.0;
} else {
relativePos.x = relativePos.x / 2.0;
}
outputColor = sampleLinear( oImage, relativePos );
}
}
А дальше уже проще! Мы реализовали связку Kinect tcp-сервера с JS посредством WebSocket. Адаптировали отслеживание пользователей в области видимости Kinect под несколько зон, прикрутили эффекты, сверстали дизайн и получилось!
Итоги
Driver vs Shader
- преимущество шейдерной технологии заключается в возможности отображать 3Д контент не только заранее отренедеренный в фото или видео, но и runtime 3D сцены в стереоскопическом режиме, то есть True 3D в HTML5 / WebGL или Flash / Pixel Bender / Stage3D приложениях в браузере; однако для такого подхода важен дисплей с построчной технологией разделения левого и правого каналов.
Kinect
- пользователю важно видеть свой образ или образ рук в процессе манипуляции,
- требуются натуральные жесты с имитацией физических законов (инерционнось, коллизии),
- очень полезно разрабатывать в open space (почти в боевых) условиях «ходят тут всякие»,
- использование tcp – ограничение только для JS приложений, если вы можете себе позволить UDP (flash) – пользуйтесь, избежите проблем с утечками памяти и низким fps, хотя на момент создания UDP был доступен в Chrome Canary.
3D Display
- для получения полноценного стереоэффекта важно настроить отображение pixel-to-pixel в связке видеокарта-дисплей, это достигается путем сброса всевозможных масштабирований в драйверах видеокарты и настройках дисплея, а также точного разрешения, поддерживаемого монитором,
- важно придерживаться правил очередности глубины для объектов на экране: слой контролов и текстовой информации, слой 3D объектов, фон, перекрытие по глубине этих сущностей жестоко сказывается на восприятии и может привести к быстрой утомляемости,
- важно подобрать очки так, чтобы они хорошо закрывали оппозитные каналы (правый от левого и наоборот) – даже очки от производителя самой 3D панели пропускали оппозитный канал в виде фиолетового ghost-образа, мы нашли достаточно качественную пару 3D Screen LG 55” + Philips Glasses,
- позаботьтесь о засветках на экране – их быть не должно, очень сильно отвлекает от восприятия 3D – выглядит как дополнительный слой изображения.
Автор: xoxulin