WebGL初探—Three.js全景圖實戰(zhàn)

前段時間公司給了一個新需求就是寫一個裝修室內(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的厲害之處還有很多很多是我還未涉及到了可都,以后還需要更加努力了缓待。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市渠牲,隨后出現(xiàn)的幾起案子旋炒,更是在濱河造成了極大的恐慌,老刑警劉巖签杈,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘫镇,死亡現(xiàn)場離奇詭異,居然都是意外死亡答姥,警方通過查閱死者的電腦和手機铣除,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹦付,“玉大人尚粘,你說我怎么就攤上這事≌霰冢” “怎么了背苦?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長潘明。 經(jīng)常有香客問我行剂,道長,這世上最難降的妖魔是什么钳降? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任厚宰,我火速辦了婚禮,結(jié)果婚禮上遂填,老公的妹妹穿的比我還像新娘铲觉。我一直安慰自己,他們只是感情好吓坚,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布撵幽。 她就那樣靜靜地躺著,像睡著了一般礁击。 火紅的嫁衣襯著肌膚如雪盐杂。 梳的紋絲不亂的頭發(fā)上逗载,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音链烈,去河邊找鬼厉斟。 笑死,一個胖子當(dāng)著我的面吹牛强衡,可吹牛的內(nèi)容都是我干的擦秽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼漩勤,長吁一口氣:“原來是場噩夢啊……” “哼感挥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起锯七,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤链快,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后眉尸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體域蜗,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年噪猾,在試婚紗的時候發(fā)現(xiàn)自己被綠了霉祸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡袱蜡,死狀恐怖丝蹭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坪蚁,我是刑警寧澤奔穿,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站敏晤,受9級特大地震影響贱田,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嘴脾,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一男摧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧译打,春花似錦耗拓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至韵洋,卻和暖如春哥谷,著一層夾襖步出監(jiān)牢的瞬間岸夯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工们妥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人勉吻。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓监婶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親齿桃。 傳聞我的和親對象是個殘疾皇子惑惶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354