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