前段時間公司給了一個新需求就是寫一個裝修室內(nèi)3D全景效果圖,于是開始我的three.js開發(fā)之旅。
作為一個前端小白醉箕,突然接觸three.js&webgl除了懵逼還是懵逼,不過作為一個技術(shù)人對于挑戰(zhàn)也許就是軟件開發(fā)中真正的樂趣,至少不會埋頭調(diào)試一遍又一遍重復(fù)的頁面數(shù)據(jù)讥裤,上上下下左左右右BABA......簡直枯燥到極點放棒。不過three.js&webgl不得不說給我打開了新的世界,接下來我就簡單講述一下我的學(xué)習(xí)之旅坞琴。
Three.js
Three.js 是一款運行在瀏覽器中的 3D 引擎哨查,是JavaScript編寫的WebGL第三方庫,可以用它創(chuàng)建各種三維場景剧辐,包括了攝影機寒亥、光影、材質(zhì)等各種對象,three.js內(nèi)部也是webgl的封裝荧关,封裝了大量了webgl API 溉奕,讓比較繁瑣的webgl更加簡便。
WEBGL
WebGL(全寫Web Graphics Library)是一種3D繪圖協(xié)議忍啤,它讓可以讓開發(fā)進一步去了解圖形渲染加勤,Webgl是JavaScript和OpenGL ES 2.0合并出來的升級版,通過webgl可以讓前端開發(fā)者們脫離開css渲染同波,可以了解更加底層的渲染鳄梅,WebGL也可以為HTML5 Canvas提供硬件3D加速渲染,webgl是通過系統(tǒng)顯卡來在瀏覽器里更流暢地展示3D場景和模型未檩,加入shader(著色器)來對圖形渲染戴尸,學(xué)習(xí)webgl需要具備相應(yīng)的圖形學(xué)算法,屬于目前圖形渲染開發(fā)的高級技術(shù)之一冤狡。目前webgl也運用在游戲孙蒙,視頻特效,包含untiy3D也是集成webgl悲雳。
技術(shù)講解
three.js中主要由攝像機 挎峦,場景 ,渲染器 , 資源加載器合瓢,素材組成
攝像機
webgl中的所有東西都是基于攝像機去展示的坦胶,可以利用攝像頭的視角形成對3d視圖觀測視角,比如魚眼視角晴楔,從而就讓我們可以在平面圖上可以開發(fā)出真實場景的3D視圖迁央。接下來我們看看怎么用three.js創(chuàng)建一個攝像機:
//新建一個相機
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1100);
camera.target = new THREE.Vector3(0, 0, 0);
場景
攝像機有了但是為了讓景物可以更好的展現(xiàn),這時候我們就需要一個展示景物的場景滥崩,three.js也為我們封裝好了,如下所示可以創(chuàng)建一個場景:
var scene = new THREE.Scene();
scene.add(“資源”);
渲染器
渲染器是webgl的渲染啟動開關(guān)讹语,他可以調(diào)用render方式把場景渲染到攝像機钙皮。
var renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
//將場景渲染到攝像機
renderer.render(“場景”, “攝像機”);
資源加載器
three.js加載資源不同我們常見的html一樣,直接通過src屬性加載,而是通過TextureLoader.load來加載資源短条。
var textureLoader = new THREE.TextureLoader();
textureLoader .load(imgUrl);
素材
素材常見的包含網(wǎng)格导匣,燈光等許多元素下面我就舉個例子
var geometry = new THREE.SphereBufferGeometry(500, 60, 40); // invert the geometry on the x-axis so that all of the faces point inward
geometry.scale(-1, 1, 1);
//網(wǎng)格
var mesh = new THREE.Mesh(geometry, “shader組件”);
shader參數(shù)集
shader指令創(chuàng)建
<script id="vertexShader" type="x-shader/x-vertex">
varying vec2 vUv;
void main() {
vUv = uv;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform float time;
uniform float scale;
uniform bool isoriginColor;
uniform sampler2D texture3;
uniform sampler2D texture4;
varying vec2 vUv;
void main( void ) {
vec2 position = - 1.0 + 2.0 * vUv;
vec4 color3 = texture2D( texture3, vUv );
vec3 tarcolor =color3.rgb;
float f1 =color3.a*scale;
vec4 color4 = texture2D( texture4, vUv );
float subscale=1.0-scale;
float f2 =color4.a*subscale;
if(isoriginColor == false){
tarcolor =mix(tarcolor.rgb,color4.rgb,f2);
}
gl_FragColor = vec4(tarcolor,1);
}
</script>
調(diào)用指令
var uniforms = {
time: {
value: 1.0
},
scale: {
value: 1.0
},
texture3: {
value: getTextureLoader(0)
},
texture4: {
value: getTextureLoader(1)
}
};
var material = new THREE.ShaderMaterial({
uniforms: uniforms ,
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent
});
全部代碼
應(yīng)用組件包
<script type="text/javascript" src="js/three.min.js"></script>
<script type="text/javascript" src="js/D.min.js"></script>
<script type="text/javascript" src="js/doT.min.js"></script>
<script type="text/javascript" src="js/WebGL.js"></script>
<script type="text/javascript" src="js/OrbitControls.js"></script>
<script type="text/javascript" src="js/stats.min.js"></script>
html代碼
<div class="haorooms_container">
<div id="content">
<div id="container"></div>
<img id="clickfile" src="img/icon_cell.png" style="display: none;position: absolute;bottom: 3rem;right: 1rem;" />
<input type="file" name="pano" id="pano" style="display: none;" />
<ul id="myList" class="imagelist">
<li><img id="0" class="meun_img" src="img/sun.jpg" /></li>
<li><img id="1" class="meun_img" src="img/banner1.jpg" /></li>
<li><img id="2" class="meun_img" src="img/banner2.jpg" /></li>
<li><img id="3" class="meun_img" src="img/banner3.jpg" /></li>
</ul></div>
<div class="dialogs-mask" id="dialog">
<div class="dialog">
<div class="dialog-text-box">
<h3 class="dialog-text-title">提示</h3>
<p class="dialog-text-desc">是否添加標注?</p></div>
<div class="dialog-btn-box">
<button id="btnclose" class="dialog-btn dialog-text-close">取消</button>
<button id="btncommit" class="dialog-btn dialog-text-commit">添加</button>
</div></div>
</div>
</div>
js代碼
// 攝像機 茸时,場景 贡定,渲染器 , 資源加載器
var camera, scene, renderer, textureLoader;
//圖片集合
var imgs = ["img/sun.jpg", "img/banner1.jpg", "img/banner2.jpg", "img/banner3.jpg"];
//當(dāng)前顯示圖片集合
var child = 0;
//是否啟動混合替換
var isupdata = false;
//shader 參數(shù)
var uniforms;
//渲染計時
var interval = 0;
//遞增混合階段值
var count = 0;
var dialog, btnclose, btncommit;
var isUserInteracting = false,
onMouseDownMouseX = 0,
onMouseDownMouseY = 0,
lon = 0,
onMouseDownLon = 0,
lat = 0,
onMouseDownLat = 0,
phi = 0,
theta = 0;
init();
animate();
menuClick();
function menuClick() {
var list = document.getElementById('myList');
var listChild = document.getElementsByTagName('li');
for(var i = 0; i < listChild.length; i++) {
listChild[i].addEventListener('click', function() {
var id = this.children[0].id;
if(child != id && count == 0) {
uniforms.texture3.value = getTextureLoader(child);
uniforms.texture4.value = getTextureLoader(id);
child = id;
isupdata = true;
} else {}
}, false);
}
}
function init() {
var container;
dialog = document.getElementById("dialog");
btnclose = document.getElementById("btnclose");
btncommit = document.getElementById("btncommit");
container = document.getElementById('container');
//新建一個相機
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1100);
camera.target = new THREE.Vector3(0, 0, 0);
//創(chuàng)建一個WebGL渲染器
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
//新建一個場景
scene = new THREE.Scene();
//初始化加載器
textureLoader = new THREE.TextureLoader();
for(var i = 0, len = imgs.length; i < len; i++) {
setMeshChild(imgs[i]);
}
document.addEventListener('mousedown', onPointerStart, false);
document.addEventListener('mousemove', onPointerMove, false);
document.addEventListener('mouseup', onPointerUp, false);
document.addEventListener('wheel', onDocumentMouseWheel, false);
document.addEventListener('touchstart', onPointerStart, false);
document.addEventListener('touchmove', onPointerMove, false);
document.addEventListener('touchend', onPointerUp, false);
document.addEventListener("dblclick", onDocumentMouseDown, false);
//
document.addEventListener('dragover', function(event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
}, false);
document.addEventListener('dragenter', function() {
document.body.style.opacity = 0.5;
}, false);
document.addEventListener('dragleave', function() {
document.body.style.opacity = 1;
}, false);
//
window.addEventListener('resize', onWindowResize, false);
}
function showDialog(clicklistener) {
console.log(dialog.style);
if(dialog.style.display != "block") {
dialog.style.display = "block";
}
btncommit.addEventListener("click", function(event) {
event.preventDefault();
dialog.style.display = "none";
clicklistener(true);
return false;
})
btnclose.addEventListener("click", function(event) {
event.preventDefault();
dialog.style.display = "none";
clicklistener(false);
return false;
})
}
/**
* 獲取三維坐標
* @param {Object} event
*/
function onDocumentMouseDown(event) {
event.preventDefault();
var vector = new THREE.Vector3(); //三維坐標對象
vector.set(
(event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1,
0.5);
vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects(scene.children);
if(intersects.length > 0) {
var selected = intersects[0]; //取第一個物體
console.log("x坐標:" + selected.point.x);
console.log("y坐標:" + selected.point.y);
console.log("z坐標:" + selected.point.z);
var x = selected.point.x;
var y = selected.point.y;
var z = selected.point.z;
showDialog(function(state) {
if(state) {
addLabelMarker(x, y, z, "img/icon_cell.png");
} else {
console.log("取消添加");
}
});
}
}
/**
*
* @param {Object} x
* @param {Object} y
* @param {Object} z
* @param {Object} element
* @param {Object} listener
*/
function addLabelMarker(x, y, z, imgUrl) {
var map = new THREE.TextureLoader().load(imgUrl);
map.wrapS = map.wrapT = THREE.RepeatWrapping;
map.anisotropy = 16;
var material = new THREE.MeshPhongMaterial({
map: map,
side: THREE.DoubleSide
});
var geometry = new THREE.SphereBufferGeometry(20, 20, 20);
// invert the geometry on the x-axis so that all of the faces point inward
geometry.scale(-1, 1, 1);
var mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, y, z);
//添加燈光
var light = new THREE.PointLight(0xffffff, 1, 100);
light.position.set(x, y, z);
scene.add(light);
scene.add(mesh);
}
function setMeshChild(url) {
var geometry = new THREE.SphereBufferGeometry(500, 60, 40);
// invert the geometry on the x-axis so that all of the faces point inward
geometry.scale(-1, 1, 1);
uniforms = {
time: {
value: 1.0
},
scale: {
value: 1.0
},
texture3: {
value: getTextureLoader(0)
},
texture4: {
value: getTextureLoader(1)
}
};
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent
});
//網(wǎng)格
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
document.addEventListener('drop', function(event) {
event.preventDefault();
var reader = new FileReader();
reader.addEventListener('load', function(event) {
material.map.image.src = event.target.result;
material.map.needsUpdate = true;
}, false);
reader.readAsDataURL(event.dataTransfer.files[0]);
document.body.style.opacity = 1;
}, false);
}
function getTextureLoader(index) {
return textureLoader.load(imgs[index]);
}
/**
* 縮放大小
*/
function onWindowResize(event) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
/**
* 按下 鼠標 或則 觸摸
* @param {Object} event
*/
function onPointerStart(event) {
isUserInteracting = true;
var clientX = event.clientX || event.touches[0].clientX;
var clientY = event.clientY || event.touches[0].clientY;
onMouseDownMouseX = clientX;
onMouseDownMouseY = clientY;
onMouseDownLon = lon;
onMouseDownLat = lat;
}
/**
* 移動
* @param {Object} event
*/
function onPointerMove(event) {
if(isUserInteracting === true) {
var clientX = event.clientX || event.touches[0].clientX;
var clientY = event.clientY || event.touches[0].clientY;
lon = (onMouseDownMouseX - clientX) * 0.1 + onMouseDownLon;
lat = (clientY - onMouseDownMouseY) * 0.1 + onMouseDownLat;
}
}
function onPointerUp() {
isUserInteracting = false;
}
function onDocumentMouseWheel(event) {
var fov = camera.fov + event.deltaY * 0.05;
camera.fov = THREE.Math.clamp(fov, 10, 75);
camera.updateProjectionMatrix();
}
function animate(timestamp) {
requestAnimationFrame(animate);
uniforms.time.value = timestamp / 1000;
if(timestamp - interval > 200 && isupdata) {
if(count <= 20) {
var scale = 1.0 - (0.05 * count);
uniforms.scale.value = scale;
count++;
} else {
isupdata = false;
count = 0;
}
interval = timestamp
}
update();
}
function update() {
if(isUserInteracting === false) {
lon += 0.1;
}
lat = Math.max(-85, Math.min(85, lat));
phi = THREE.Math.degToRad(90 - lat);
theta = THREE.Math.degToRad(lon);
camera.target.x = 500 * Math.sin(phi) * Math.cos(theta);
camera.target.y = 500 * Math.cos(phi);
camera.target.z = 500 * Math.sin(phi) * Math.sin(theta);
camera.lookAt(camera.target);
//將場景渲染到攝像機
renderer.render(scene, camera);
}
####
演示地址
http://www.sunql.top/webgldemo/index.html
項目源碼
https://github.com/sunql0827/webgldemo.git
https://gitee.com/sunql-hugh/webgldemo.git
總結(jié)
通過這次基于three.js的webgl全景圖開發(fā)之旅為我對視圖渲染打開了一道新的大門,不過webgl的厲害之處還有很多很多是我還未涉及到了可都,以后還需要更加努力了缓待。