美文网首页IT全栈圈
WebGL初探—Three.js全景图实战

WebGL初探—Three.js全景图实战

作者: 渝聆墨 | 来源:发表于2019-06-28 10:08 被阅读17次

    前段时间公司给了一个新需求就是写一个装修室内3D全景效果图,于是开始我的three.js开发之旅。
    作为一个前端小白,突然接触three.js&webgl除了懵逼还是懵逼,不过作为一个技术人对于挑战也许就是软件开发中真正的乐趣,至少不会埋头调试一遍又一遍重复的页面数据,上上下下左左右右BABA......简直枯燥到极点。不过three.js&webgl不得不说给我打开了新的世界,接下来我就简单讲述一下我的学习之旅。

    Three.js

    Three.js 是一款运行在浏览器中的 3D 引擎,是JavaScript编写的WebGL第三方库,可以用它创建各种三维场景,包括了摄影机、光影、材质等各种对象,three.js内部也是webgl的封装,封装了大量了webgl API ,让比较繁琐的webgl更加简便。

    WEBGL

    WebGL(全写Web Graphics Library)是一种3D绘图协议,它让可以让开发进一步去了解图形渲染,Webgl是JavaScript和OpenGL ES 2.0合并出来的升级版,通过webgl可以让前端开发者们脱离开css渲染,可以了解更加底层的渲染,WebGL也可以为HTML5 Canvas提供硬件3D加速渲染,webgl是通过系统显卡来在浏览器里更流畅地展示3D场景和模型,加入shader(着色器)来对图形渲染,学习webgl需要具备相应的图形学算法,属于目前图形渲染开发的高级技术之一。目前webgl也运用在游戏,视频特效,包含untiy3D也是集成webgl。

    技术讲解

    three.js中主要由摄像机 ,场景 ,渲染器 , 资源加载器,素材组成

    摄像机

    webgl中的所有东西都是基于摄像机去展示的,可以利用摄像头的视角形成对3d视图观测视角,比如鱼眼视角,从而就让我们可以在平面图上可以开发出真实场景的3D视图。接下来我们看看怎么用three.js创建一个摄像机:

    //新建一个相机
    var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1100);
    camera.target = new THREE.Vector3(0, 0, 0);
    

    场景

    摄像机有了但是为了让景物可以更好的展现,这时候我们就需要一个展示景物的场景,three.js也为我们封装好了,如下所示可以创建一个场景:

    var scene = new THREE.Scene();
    scene.add(“资源”);
    

    渲染器

    渲染器是webgl的渲染启动开关,他可以调用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);
    

    素材

    素材常见的包含网格,灯光等许多元素下面我就举个例子

    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);
    //网格
    var mesh = new THREE.Mesh(geometry, “shader组件”);
    

    shader参数集

    shader指令创建
    <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>
    
    调用指令
    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
    });
    

    全部代码

    应用组件包
    <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"];
    //当前显示图片集合
    var child = 0;
    //是否启动混合替换
    var isupdata = false;
    //shader 参数
    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);
    //创建一个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
                    });
    
                    //网格
                    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/

    项目源码

    https://github.com/sunql0827/webgldemo.git

    总结

    通过这次基于three.js的webgl全景图开发之旅为我对视图渲染打开了一道新的大门,不过webgl的厉害之处还有很多很多是我还未涉及到了,以后还需要更加努力了。

    相关文章

      网友评论

        本文标题:WebGL初探—Three.js全景图实战

        本文链接:https://www.haomeiwen.com/subject/kvcdcctx.html