ThreeJS逐步实现室内概念图的效果(渲染,交互)

        近段时间由于项目需求,要在页面中插入3d的室内布局概念图,开始从零学习three.js的知识,下面记录一下这两周研究的结果,本人也是小白一枚,不足之处,还望批评指正!

        第一个遇到的问题是室内地图块的渲染效果问题,使用一个光源时,效果总是不尽如人意,总有光照不到的一面是有黑色阴影,后来想到使用两个光源,两侧打光,渲染效果好了很多。

var ambiColor = "#f2f2f2";
var spotLight = new THREE.SpotLight(ambiColor);
spotLight.position.set( -100, 100, -100);
scene.add(spotLight);
var spotLight2 = new THREE.SpotLight(ambiColor);
spotLight2.position.set( 100, 100, 150);
scene.add(spotLight2);

        第二个问题是cube的材质问题,在研究threejs之前看到一篇有关室内地图的博文,由于实现效果相似,于是引用了博主对cube材质的思路:顶面和侧面使用不同的材质,我的第一想法就是在cube上面加一个plane,覆盖cube原先顶面的MeshLambertMaterial材质,但是这个想法在后来实现旋转时被否决了,因为plane的高度和cube重合时,会使的顶面和plane的颜色冲突,形成一条条的线,如果y轴方向稍微高出0.2,在俯视图上看不出上面问题,但是当相机的视角一调节,立马便看出问题,找了官方文档的案例,发现可以用MeshFaceMaterial自定义cube的六个面的颜色和材质。

var yellow = [];
// 右侧面
yellow.push(new THREE.MeshLambertMaterial({color: "#F8D3A5"}));
// 左侧面
yellow.push(new THREE.MeshLambertMaterial({color: "#F8D3A5"}));
// 顶侧面
yellow.push(new THREE.MeshBasicMaterial({color: "#F8D3A5",transparent:true,opacity:0.8}));
// 地侧面
yellow.push(new THREE.MeshLambertMaterial({color: "#F8D3A5"}));
// 前侧面
yellow.push(new THREE.MeshLambertMaterial({color: "#F8D3A5"}));
// 后侧面
yellow.push(new THREE.MeshLambertMaterial({color: "#F8D3A5"}));
var face1Material = new THREE.MeshFaceMaterial(yellow);
var cube1Geom = new THREE.BoxGeometry(3,2,12);
var cube1 = new THREE.Mesh(cube1Geom, face1Material);

        顶侧使用MeshBasicMaterial可以不受光照的影响,并且可以设置透明度,侧面可以形成阴影,显得真实一点。

        第三步是实现相机角度的旋转,这个是直接拿的官方文档的案例里面的方法,比较生硬,还有待优化,因为是上下360度旋转,稍微一转地图的底面便翘起来。

<script src="libs/TrackballControls.js"></script>

controls = new THREE.TrackballControls( camera );
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
function render() {
    controls.update();}

        只需要引入一个官方的js包,就可以实现了。

        第四步是考虑了最久的一个,实现用户的交互,在点击cube之后,cube可以变色,用以提示用户,该cube被点击了,看了网上很多demo,都是使用射线原理,将鼠标点击屏幕中的点垂直射一条光线,如果触碰到多个对象,取第一个对象即为当前点击的cube。这个原理是很简单,但是实现将cube变色却把我难住了,后来又找了很多博客,找到一个可以使cube变色的案例,但是每次把代码搬到我这里就失灵了,总是报一个getHex()和set()未定义的错误,我以为是版本的问题,后来想了很久,试了好久,终于发现,原来是我定义材质的问题,我定义的材质是一个数组,六个面需要改色的话,需要分别改色,而案例中cube的六个面的材质颜色完全一样,所以只需要直接获取再修改便可以了。

function onDocumentMouseClick(event) {
    event.preventDefault();
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
    if (INTERSECTED !== intersects[0].object) {
        if (INTERSECTED && INTERSECTED instanceof THREE.Mesh && INTERSECTED.material.length === 6) {
            for(var i = 0; i < 6; i++){
                INTERSECTED.material[i].color.setHex(INTERSECTED.currentHex);
            }
        }
        INTERSECTED = intersects[0].object;
        if(INTERSECTED instanceof THREE.Mesh && INTERSECTED.material.length === 6){
            for(var i = 0; i < 6; i++){
                INTERSECTED.currentHex = INTERSECTED.material[i].color.getHex();
                if(INTERSECTED.currentHex !== 16777215)
                    INTERSECTED.material[i].color.set( "#FFC965" );
            }
        }
    }
} else {
    if (INTERSECTED && INTERSECTED instanceof THREE.Mesh && INTERSECTED.material.length === 6) {
        for(var i = 0; i < 6; i++){
            INTERSECTED.material[i].color.set(INTERSECTED.currentHex);
        }
    }
    INTERSECTED = null;
}

        onmouseclick的方法确定鼠标点击的位置,下面部分是render()方法中的一部分。在整体上为了效果的美观,在所有cube的顶上加了一圈直线,这样cube看起来更加明显,但是这就又引起一个小麻烦,在点击物体时要判断是不是mesh,如果点击的直线应该是没有变色效果的,还有点击的是地板时,也不应该有变色效果。

        经过一系列的探索,终于在零的基础上有了一些东西,虽然距离成品还很远(还有很多需要优化的地方),但也算threejs学习上的一小步,最后附上代码。


<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Three.js</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>

<body>
<script type="text/javascript" src="libs/three.js"></script>
<script src="libs/TrackballControls.js"></script>
<script>
    var renderer, scene, camera;
    var INTERSECTED;
    var raycaster;
    var mouse;
    var controls ;
    // 边框线的高度
    var lineHeight = 1.75;
    // 块的高度
    var cubeHeight = 1.5;
    function init() {
        renderer = new THREE.WebGLRenderer({
            antialias: true
        });
        renderer.setClearColor(0xF1F2F7);
        renderer.setSize(window.innerWidth, window.innerHeight);
        scene = new THREE.Scene();
        scene.background = new THREE.Color( 0xF1F2F7 );
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.lookAt(new THREE.Vector3(20, 0, 20));
        camera.position.set(0, 40, 50);
        // 光线的照射
        var ambiColor = "#f2f2f2";
        var spotLight = new THREE.SpotLight(ambiColor);
        spotLight.position.set( -100, 100, -100);
        scene.add(spotLight);
        var spotLight2 = new THREE.SpotLight(ambiColor);
        spotLight2.position.set( 100, 100, 150);
        scene.add(spotLight2);

        controls = new THREE.TrackballControls( camera );
        controls.rotateSpeed = 1.0;
        controls.zoomSpeed = 1.2;
        controls.panSpeed = 0.8;
        controls.noZoom = false;
        controls.noPan = false;
        controls.staticMoving = true;
        controls.dynamicDampingFactor = 0.3;

        raycaster = new THREE.Raycaster();
        mouse = new THREE.Vector2();
        document.body.appendChild(renderer.domElement);
        document.addEventListener('click', onDocumentMouseClick, false);
        creatCube();
        render();
    }

    function onDocumentMouseClick(event) {
        event.preventDefault();
        mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
    }

    function creatCube() {
        // 材质定义
        var yellow = [];
        // 右侧面
        yellow.push(new THREE.MeshLambertMaterial({color: "#F8D3A5"}));
        // 左侧面
        yellow.push(new THREE.MeshLambertMaterial({color: "#F8D3A5"}));
        // 顶侧面
        yellow.push(new THREE.MeshBasicMaterial({color: "#F8D3A5",transparent:true,opacity:0.8}));
        // 地侧面
        yellow.push(new THREE.MeshLambertMaterial({color: "#F8D3A5"}));
        // 前侧面
        yellow.push(new THREE.MeshLambertMaterial({color: "#F8D3A5"}));
        // 后侧面
        yellow.push(new THREE.MeshLambertMaterial({color: "#F8D3A5"}));

        var white = [];
        white.push(new THREE.MeshLambertMaterial({color: "#fff"}));
        white.push(new THREE.MeshLambertMaterial({color: "#fff"}));
        white.push(new THREE.MeshBasicMaterial({color: "#fff"}));
        white.push(new THREE.MeshLambertMaterial({color: "#fff"}));
        white.push(new THREE.MeshLambertMaterial({color: "#fff"}));
        white.push(new THREE.MeshLambertMaterial({color: "#fff"}));

        var purple = [];
        purple.push(new THREE.MeshLambertMaterial({color: "#E3B7F7"}));
        purple.push(new THREE.MeshLambertMaterial({color: "#E3B7F7"}));
        purple.push(new THREE.MeshBasicMaterial({color: "#E3B7F7",transparent:true,opacity:0.8}));
        purple.push(new THREE.MeshLambertMaterial({color: "#E3B7F7"}));
        purple.push(new THREE.MeshLambertMaterial({color: "#E3B7F7"}));
        purple.push(new THREE.MeshLambertMaterial({color: "#E3B7F7"}));

        var orange = [];
        orange.push(new THREE.MeshLambertMaterial({color: "#FFC965"}));
        orange.push(new THREE.MeshLambertMaterial({color: "#FFC965"}));
        orange.push(new THREE.MeshBasicMaterial({color: "#FFC965",transparent:true,opacity:0.5}));
        orange.push(new THREE.MeshLambertMaterial({color: "#FFC965"}));
        orange.push(new THREE.MeshLambertMaterial({color: "#FFC965"}));
        orange.push(new THREE.MeshLambertMaterial({color: "#FFC965"}));


        // 底部左侧的地板
        var cubeBottomLeftGeometry = new THREE.BoxGeometry(16,0.5,42);
        var cubeBottomLeftMaterial =  new THREE.MeshFaceMaterial(white);
        var cubeBottomLeft = new THREE.Mesh(cubeBottomLeftGeometry,cubeBottomLeftMaterial);
        cubeBottomLeft.position.x = -10;
        cubeBottomLeft.position.y = 0;
        cubeBottomLeft.position.z = 0;
        scene.add(cubeBottomLeft);

        // 底部右侧的地板
        var cubeBottomRightGeometry = new THREE.BoxGeometry(28,0.5,16);
        var cubeBottomRightMaterial =  new THREE.MeshFaceMaterial(white);
        var cubeBottomRight = new THREE.Mesh(cubeBottomRightGeometry,cubeBottomRightMaterial);
        cubeBottomRight.position.x = 12;
        cubeBottomRight.position.y = 0;
        cubeBottomRight.position.z = 13;
        scene.add(cubeBottomRight);


        // 方块1的下线1
        var line11Material = new THREE.LineBasicMaterial({color:"#F7A540"});
        var line11Geometry = new THREE.Geometry();
        line11Geometry.vertices.push(new THREE.Vector3(-17.5,lineHeight,20.5));
        line11Geometry.vertices.push(new THREE.Vector3(-14.5,lineHeight,20.5));
        var line11 = new THREE.Line(line11Geometry, line11Material);
        scene.add(line11);
        // 方块1的左线2
        var line12Material = new THREE.LineBasicMaterial({color:"#F7A540"});
        var line12Geometry = new THREE.Geometry();
        line12Geometry.vertices.push(new THREE.Vector3(-17.5,lineHeight,20.5));
        line12Geometry.vertices.push(new THREE.Vector3(-17.5,lineHeight,8.5));
        var line12 = new THREE.Line(line12Geometry, line12Material);
        scene.add(line12);
        // 方块1的右线3
        var line13Material = new THREE.LineBasicMaterial({color:"#F7A540"});
        var line13Geometry = new THREE.Geometry();
        line13Geometry.vertices.push(new THREE.Vector3(-14.5,lineHeight,8.5));
        line13Geometry.vertices.push(new THREE.Vector3(-14.5,lineHeight,20.5));
        var line13 = new THREE.Line(line13Geometry, line13Material);
        scene.add(line13);
        // 方块1的上线4
        var line14Material = new THREE.LineBasicMaterial({color:"#F7A540"});
        var line14Geometry = new THREE.Geometry();
        line14Geometry.vertices.push(new THREE.Vector3(-17.5,lineHeight,8.5));
        line14Geometry.vertices.push(new THREE.Vector3(-14.5,lineHeight,8.5));
        var line14 = new THREE.Line(line14Geometry, line14Material);
        scene.add(line14);


        // 方块2的下线1
        var line21Material = new THREE.LineBasicMaterial({color:"#B42EF7"});
        var line21Geometry = new THREE.Geometry();
        line21Geometry.vertices.push(new THREE.Vector3(-9.5,lineHeight,20.5));
        line21Geometry.vertices.push(new THREE.Vector3(-14.5,lineHeight,20.5));
        var line21 = new THREE.Line(line21Geometry, line21Material);
        scene.add(line21);
        // 方块2的右线3
        var line22Material = new THREE.LineBasicMaterial({color:"#B42EF7"});
        var line22Geometry = new THREE.Geometry();
        line22Geometry.vertices.push(new THREE.Vector3(-9.5,lineHeight,20.5));
        line22Geometry.vertices.push(new THREE.Vector3(-9.5,lineHeight,13.5));
        var line22 = new THREE.Line(line22Geometry, line22Material);
        scene.add(line22);
        // 方块2的左线2 与方块1右线重叠 取消
        /*var line23Material = new THREE.LineBasicMaterial({color:"#B42EF7"});
        var line23Geometry = new THREE.Geometry();
        line23Geometry.vertices.push(new THREE.Vector3(-13.7,lineHeight,15.3));
        line23Geometry.vertices.push(new THREE.Vector3(-13.7,lineHeight,22));
        var line23 = new THREE.Line(line23Geometry, line23Material);
        scene.add(line23);*/
        // 方块2的上线4
        var line24Material = new THREE.LineBasicMaterial({color:"#B42EF7"});
        var line24Geometry = new THREE.Geometry();
        line24Geometry.vertices.push(new THREE.Vector3(-9.5,lineHeight,13.5));
        line24Geometry.vertices.push(new THREE.Vector3(-14.5,lineHeight,13.5));
        var line24 = new THREE.Line(line24Geometry, line24Material);
        scene.add(line24);


        // 方块1
        var face1Material = new THREE.MeshFaceMaterial(yellow);
        var cube1Geom = new THREE.BoxGeometry(3,cubeHeight,12);
        var cube1 = new THREE.Mesh(cube1Geom, face1Material);
        cube1.position.x = -16;
        cube1.position.y = 1;
        cube1.position.z = 14.5;
        scene.add(cube1);

        // 方块2
        var face2Material = new THREE.MeshFaceMaterial(purple);
        var cube2Geom = new THREE.BoxGeometry(5,cubeHeight,7);
        var cube2 = new THREE.Mesh(cube2Geom, face2Material);
        cube2.position.x = -12;
        cube2.position.y = 1.;
        cube2.position.z = 17;
        scene.add(cube2);

    }

    function render() {
        controls.update();
        requestAnimationFrame(render);
        renderer.render(scene, camera);
        raycaster.setFromCamera(mouse, camera);
        var intersects = raycaster.intersectObjects(scene.children);
        if (intersects.length > 0) {
            if (INTERSECTED !== intersects[0].object) {
                // 判断对象是否是mesh 并且是有六个面材质的
                if (INTERSECTED && INTERSECTED instanceof THREE.Mesh && INTERSECTED.material.length === 6) {
                    for(var i = 0; i < 6; i++){
                        INTERSECTED.material[i].color.setHex(INTERSECTED.currentHex);
                    }
                }
                INTERSECTED = intersects[0].object;
                if(INTERSECTED instanceof THREE.Mesh && INTERSECTED.material.length === 6){
                    for(var i = 0; i < 6; i++){
                        INTERSECTED.currentHex = INTERSECTED.material[i].color.getHex();
                        // 如果是底面 因为底面的颜色获取后打印结果为:16777215
                        if(INTERSECTED.currentHex !== 16777215)
                            INTERSECTED.material[i].color.set( "#FFC965" );
                    }
                }
            }
        } else {
            if (INTERSECTED && INTERSECTED instanceof THREE.Mesh && INTERSECTED.material.length === 6) {
                for(var i = 0; i < 6; i++){
                    INTERSECTED.material[i].color.set(INTERSECTED.currentHex);
                }
            }
            INTERSECTED = null;
        }
    }
    init();
</script>
</body>

</html>
原创作品,欢迎转载,备注出处。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值