Source code: https://github.com/Zdobnov/three.js
three.js
Language: russian
Визуализация трехмерных моделей средствами
Canvas HTML5
Выполнил: Здобнов Сергей Александрович
Руководитель: Дубровина Ольга Викторовна
Цель:
Разработать модельные примеры для создания графики, отображаемой на компоненте Canvas HTML5 средствами фреймворка three.js и библиотеки webgl
Задачи:
- изучить спецификацию фреймворка three.js;
- разработать демонстрационные сцены, иллюстрирующие следующие визуальные эффекты:
- спрайты, рейкастинг,
- генерацию ландшафта,
- систему частиц,
- анимацию движения и работу с материалами;
- реализовать смешанный рендер;
- реализовать видеозахват;
- реализовать применение WebAudio API на трехмерной сцене.
- демонстрация работы с тенями и источником света;
- применение вспомогательных инструментов:
- встроенная система координат;
- координатная сетка;
- использование различных материалов:
- MeshNormalMaterial;
- MeshBasicMaterial;
- MeshPhongMaterial;
- анимация.
Реализовано:
var grid = new THREE.GridHelper(10, 5);
var sphereGeometry = new THREE.SphereGeometry(4,20,20);
var sphereMaterial = new THREE.MeshNormalMaterial({ });
var sphere = new THREE.Mesh(sphereGeometry,sphereMaterial);
sphere.position.set(20,4,2);
sphere.castShadow = true;
sphere.name="sphere";
scene.add(sphere);
Сфера
function render() {
scene.getObjectByName('cube').rotation.x += 0.02;
cameraControl.update();
stats.update();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
Свойство wireframe
var spotLight = new THREE.SpotLight( 0xffffff );
spotLight.position.set( -40, 60, -10 );
spotLight.castShadow = true;
spotLight.shadowCameraVisible = true;
spotLight.shadowCameraNear = 50;
spotLight.shadowCameraFar = 110;
scene.add(spotLight);
Финальная сцена
Реализовано:
- наложение текстурных карт;
- изменение параметров камеры;
- смешанный рендер;
- применение спрайтов,
- использование шейдеров.
function createEarthMaterial( ) {
var earthTexture = THREE.ImageUtils.loadTexture(
"../assets/textures/planets/earthmap4k.jpg
");
var earthMaterial = new THREE.MeshBasicMaterial( );
earthMaterial.map = earthTexture;
return earthMaterial;
}
Наложение текстур и совмещение сфер
var earthMaterial = new THREE.MeshPhongMaterial( );
Реакция на свет
var bgPass = new THREE.RenderPass(sceneBG, cameraBG);
var renderPass = new THREE.RenderPass(scene, camera);
renderPass.clear = false;
var effectCopy = new THREE.ShaderPass(THREE.CopyShader);
effectCopy.renderToScreen = true;
composer = new THREE.EffectComposer(renderer);
composer.addPass(bgPass);
composer.addPass(renderPass);
composer.addPass(effectCopy);
Совмещенный рендер
var normalMap = THREE.ImageUtils.loadTexture(
"planets/earth_normalmap_flat4k.jpg"
);
material.normalMap = normalMap;
material.normalScale = new THREE.Vector2(0.5, 0.7);
Карта нормалей
var specularMap = THREE.ImageUtils.loadTexture(
"planets/earthspec4k.jpg"
);
material.specularMap = specularMap;
material.specular = new THREE.Color(0x262626);
Карта отражений
vec2 uvTimeShift = vUv + vec2( –0.7, 1.5 ) * time * baseSpeed;
vec4 noiseGeneratorTimeShift = texture2D( noiseTexture, uvTimeShift );
vec2 uvNoiseTimeShift = vUv + noiseScale
* vec2( noiseGeneratorTimeShift.r, noiseGeneratorTimeShift.b );
vec4 baseColor = texture2D( baseTexture, uvNoiseTimeShift );
baseColor.a = alpha;
gl_FragColor = baseColor;
Использование шейдеров
var sunSpriteMaterial = new THREE.SpriteMaterial({
map: new THREE.ImageUtils.loadTexture( 'images/materials/glow.png' ),
color: 0xffed84,
transparent: false,
blending: THREE.AdditiveBlending
});
var sunSprite = new THREE.Sprite(sunSpriteMaterial);
sunSprite.scale.set(10000, 10000, 1.0);
sunMesh.add(sunSprite);
Спрайтовое свечение
var xLineGeometry = new THREE.Geometry();
var xVertArray = xLineGeometry.vertices;
xVertArray.push(new THREE.Vector3(0,0,0),new THREE.Vector3(10000,0,0));
xLineGeometry.computeLineDistances();
var xLineMaterial = new THREE.LineDashedMaterial({
color: 0xffffff, dashSize: 100, gapSize: 100
});
var xLine = new THREE.Line( xLineGeometry, xLineMaterial );
scene.add(xLine);
Финальная сцена
- загрузка моделей в формате json;
- изменение параметров материалов;
- адаптация шейдеров воды и неба;
- видеозахват;
- использование WebAudio API;
- управление;
- рейкастинг.
Реализовано:
waterNormals = new THREE.ImageUtils.loadTexture(
'images/materials/waternormals.jpg'
);
waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping;
water = new THREE.Water( renderer, camera, scene, {
textureWidth: 512,
textureHeight: 512,
waterNormals: waterNormals,
alpha: 1.0,
sunDirection: light.position.clone().normalize(),
sunColor: 0xffffff,
waterColor: 0x001e0f,
distortionScale: 50.0,
});
mirrorMesh = new THREE.Mesh(
new THREE.PlaneBufferGeometry(
parameters.width * 500, parameters.height * 500 ),
water.material
);
mirrorMesh.add( water );
mirrorMesh.rotation.x = - Math.PI * 0.5;
scene.add( mirrorMesh );
Моделирование окружения шейдерами
var jsonLoader = new THREE.JSONLoader();
jsonLoader.load( "objects/boat/boat-body.json", addModelToScene );
function addModelToScene( geometry, materials ) {
var material = new THREE.MeshPhongMaterial({
// material settings
});
// model settings
scene.add(boat);
render();
}
Загрузка модели в формате json
Дефрагментация модели
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 ) {
INTERSECTED = intersects[ 0 ].object;
INTERSECTED.material.color.r = Math.random();
INTERSECTED.material.color.g = Math.random();
INTERSECTED.material.color.b = Math.random();
} else {
INTERSECTED = null;
}
Рейкастинг
navigator.getUserMedia = navigator.getUserMedia
|| navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
window.URL = window.URL || window.webkitURL;
var camvideo = document.getElementById('monitor');
if (!navigator.getUserMedia)
{
document.getElementById('messageError').innerHTML =
'Sorry. <code>navigator.getUserMedia()</code> is not available.';
}
navigator.getUserMedia({video: true}, gotStream, noStream);
function gotStream(stream)
{
if (window.URL)
{ camvideo.src = window.URL.createObjectURL(stream); }
else // Opera
{ camvideo.src = stream; }
camvideo.onerror = function(e)
{ stream.stop(); };
stream.onended = noStream;
}
function noStream(e)
{
var msg = 'No camera available.';
if (e.code == 1)
{ msg = 'User denied access to use camera.'; }
document.getElementById('errorMessage').textContent = msg;
}
Видеозахват
Элементы управления
function checkAreas() {
for (var b = 0; b < buttons.length; b++) {
var blendedData = blendContext.getImageData( buttons[b].x,
buttons[b].y, buttons[b].w, buttons[b].h );
var i = 0, sum = 0, countPixels = blendedData.data.length * 0.25;
while (i < countPixels) {
sum += (blendedData.data[i*4] + blendedData.data[i*4+1]
+ blendedData.data[i*4+2]);
++i;
}
var average = Math.round(sum / (3 * countPixels));
var boat = scene.getObjectByName('boat');
var baloon = scene.getObjectByName('baloon');
if (average > 50) {
if (buttons[b].name == "cian") {
if(boat) {
baloon.material.color.r = 0;
baloon.material.color.g = 1;
baloon.material.color.b = 1;
}
}
// other buttons
}
}
}
Финальная сцена
- генерация ландшафта по карте высот;
- наложение тумана;
- управление движением в трехмерном пространстве;
- создание системы частиц;
- постэффекты.
Реализовано:
Карта высот
(function(){
var v1 = new THREE.Vector3(i * w, y[0], (-1 * j) * w);
var v2 = new THREE.Vector3((1 + i) * w, y[1], (-1 * j) * w);
var v3 = new THREE.Vector3(i * w, y[2], (-1 + (-1 * j)) * w);
geom.vertices.push(v1);
geom.vertices.push(v2);
geom.vertices.push(v3);
var face = new THREE.Face3( faceIndexBace,
faceIndexBace + 1, faceIndexBace + 2 );
face.normal = (function (){
var vx = (v1.y - v3.y) * (v2.z - v3.z) - (v1.z - v3.z) * (v2.y - v3.y);
var vy = (v1.z - v3.z) * (v2.x - v3.x) - (v1.x - v3.x) * (v2.z - v3.z);
var vz = (v1.x - v3.x) * (v2.y - v3.y) - (v1.y - v3.y) * (v2.x - v3.x);
var va = Math.sqrt( Math.pow(vx,2) +Math.pow(vy,2)+Math.pow(vz,2));
return new THREE.Vector3( vx/va, vy/va, vz/va);
})();
geom.faces.push( face );
Сгенерированный ландшафт
Добавление воды на сцену
Куб задает границы перемещения
Модель с текстурными картами
THREE.ImageUtils.loadTexture(
"images/particles/engine_smoke.png", undefined, particlesLoaded
);
function particlesLoaded(mapA) {
var cloud = new THREE.Object3D();
aircraft.add(cloud);
leftEngine = new SpriteParticleSystem({
cloud:cloud,
rate:60,
num:60,
texture:mapA,
scaleR:[0.005,0.005],
speedR:[0,0.1],
rspeedR:[-0.1,0.1],
lifespanR:[1,2],
terminalSpeed:20
});
leftEngine.addForce(new THREE.Vector3(0,0,50));
leftEngine.position.set(-0.65, -0.55, 6.75);
leftEngine.start();
}
Система частиц
scene.fog = new THREE.FogExp2( 0xcfb9a8, 0.0015 );
Использование тумана
composer = new THREE.EffectComposer( renderer );
composer.addPass( new THREE.RenderPass( scene, camera ) );
glitchPass = new THREE.GlitchPass();
glitchPass.renderToScreen = true;
composer.addPass( glitchPass );
Постэффекты
Выводы:
На основе three.js разработаны демонстрационые сцены, отображаемые средствами canvas HTML5 в браузерах. Реализованы:
- спрайты, рейкастинг,
- автоматическая генерация ландшафта,
- эффект система частиц,
- анимацию движения и наложение материалов;
- смешанный рендер;
- видеозахват;
- применение WebAudio API на трехмерной сцене.
Описанные модели могут быть использованы при изучении фреймворка three.js, а также внедрены в веб-ресурсы.
Спасибо за внимание !
Содержимое блока canvas представляет собой полностью программируемое изображение.
var example = document.getElementById("_3d_scene"),
ctx = example.getContext('2d');
ctx.strokeRect(15, 15, 266, 266);
ctx.strokeRect(18, 18, 260, 260);
ctx.fillRect(20, 20, 256, 256);
for (i = 0; i < 8; i += 2) {
for (j = 0; j < 8; j += 2) {
ctx.clearRect(20 + i * 32, 20 + j * 32, 32, 32);
ctx.clearRect(20 + (i + 1) * 32, 20 + (j + 1) * 32, 32, 32);
}
}
Скелет простейшей сцены на three.js
// поиск контейнера на странице
var container = document.getElementById("container");
// создание рендера Three.js и расположение в контейнере
var renderer = new THREE.WebGLRenderer();
renderer.setSize(container.offsetWidth, container.offsetHeight);
container.appendChild( renderer.domElement );
// создание three.js сцены
var scene = new THREE.Scene();
// создание камеры и расположение её на сцене
var camera = new THREE.PerspectiveCamera( 45,
container.offsetWidth / container.offsetHeight, 1, 4000 );
camera.position.set( 0, 0, 3.3333 );
scene.add( camera );
// создание примитивного объекта на сцене, исключительно для визуализации
var geometry = new THREE.PlaneGeometry(1, 1);
var mesh = new THREE.Mesh( geometry,
new THREE.MeshBasicMaterial( ) );
scene.add( mesh );
// Рендер сцены
renderer.render( scene, camera );
Построение моделей в WebGL
Камера и плоскости отсечения
В начале координат расположена камера, вектор X - её направление. Зеленая и красная плоскости - ближняя (вьюпорт) и дальняя плоскость отсечения. Вьюпорт
представлен экраном дисплея. Объекты между плоскостями попадают на рендер.
Куб рендера ортографической камеры. Необходим из-за особенностей ортографической камеры рендерить все в одном масштабе. С помощью ортографической камеры можно отрендерить фон сцены. Такое решение часто применялось в старых играх, вроде Baldur's gate.
Шум Перлина
При разработке сцен большое внимание уделялось производительности на мобильных устройствах, поэтому, даже на них значение fps равно 30 и больше. Для наглядного отображения производительности используется библиотека stats.js
three.js
By Serge Zdobnov
three.js
My diploma work in Belarussian State University
- 1,655