【ThreeJS基础教程-初识Threejs】1.4.2更好的视觉效果-综合案例(1)

学习ThreeJS的捷径

本段内容会写在0篇以外所有的,本人所编写的Threejs教程中

对,学习ThreeJS有捷径
当你有哪个函数不懂的时候,第一时间去翻一翻文档
当你有哪个效果不会做的时候,第一时间去翻一翻所有的案例,也许就能找到你想要的效果
最重要的一点,就是,绝对不要怕问问题,越怕找找别人问题,你的问题就会被拖的越久

如果你确定要走WebGL/ThreeJS的开发者路线的话,以下行为可以让你更快的学习ThreeJS

  1. 没事就把所有的文档翻一遍,哪怕看不懂,也要留个印象,至少要知道Threejs有什么
  2. 没事多看看案例效果,当你记忆的案例效果足够多时,下次再遇到相似问题时,你就有可能第一时间来找对应的案例,能更快解决你自己的问题
  3. 上述案例不只是官网的案例,郭隆邦技术博客,跃焱邵隼,暮志未晚等站点均有不少优质案例,记得一并收藏
    http://www.yanhuangxueyuan.com/ 郭隆邦技术博客
    https://www.wellyyss.cn/ 跃焱邵隼
    http://www.wjceo.com/ 暮志未晚
    这三个站点是我最常逛的站点,推荐各位有事没事逛一下,看看他们的案例和写法思路,绝对没坏处

写在前面

本文的案例基于上一篇,【ThreeJS基础教程】1.4.1 更好的视觉效果-使用材质与灯光的最终案例源码做修改,以下会详细说明相对于之前的案例修改了哪些内容并做解析

相机灯

在上一个案例中,修改init函数中的这一部分

        light = new THREE.PointLight(0xffffff,0.7);
        light.position.y += 5;
        scene.add(light);

修改为

	   light = new THREE.PointLight(0xffffff,0.7);
       camera.add(light);
       scene.add(camera);

修改后的案例效果
在这里插入图片描述

修改后,当我们旋转视角时,会发现,不论旋转到什么角度,方块面向屏幕的一侧,会一直保持一个被照亮的状态

关于 camera.add(light)
当相机中添加了光源后,相机无论移动到何处,光源都会始终在相机上,相当于,你开着一个带着手电筒的摄像机,在拍摄东西
但是,仅仅把灯光加进相机中,相机不在场景中的话,灯光是无法照亮场景的
所以我们还需要把相机丢进场景中

这种技巧一般用于【单件商品展示型】项目,比如说:车辆展示,3D商品展示等

唯一父级原则

有个同学问了,为什么不能把灯光同时添加到相机和场景中呢?

threejs规定,一个Object3D对象,只能有一个父对象

这里的Object3D包含了其所有的继承类

访问父子对象的方法:

属性名属性类型默认值属性说明
object3d.parentObject3Dnull这个属性可以用于访问父对象
object3d.children[Object3D][]空数组这个属性可以用于访问其下的所有子对象

相对坐标系

还有一个同学问了,为什么添加进相机后,灯光就跟着相机走了

这里涉及到一个东西叫相对坐标系

相对坐标系是指,一个物体相对于另一个物体位置而创造的坐标系的坐标系位置

比如说,我这个物体在(10,10,10)的位置,但是我如果以这个物体为核心,创造一个相对坐标系,那么,这个物体的所在位置,就为相对(0,0,0),此时,把灯光添加到这个物体的子类中时,我们的灯光的位置依然是(0,0,0),但是这里的坐标,已经是【相对这个物体的相对坐标系的(0,0,0)】,我们的绝对坐标系位置,在(10,10,10)

在threejs中,如果父级的位置发生变化后,其下的所有子级的物体,也会跟随父级而改变,但是所有的物体,仅绝对坐标系发生了改变,而相对坐标系依然为(0,0,0)

如何获取物体的绝对坐标系(相对于Scene的坐标系)

官方提供了4个相关函数,用于获取物体在scene中的绝对坐标
在这里插入图片描述

链接: ThreeJS官方文档对Object3D对象的说明

通过getWorldPosition这个函数,我们可以拿到物体在scene中实际的位置

注意,这个函数在最新版本中,要求你必须传入一个THREE.Vector3()类型的对象,函数会将获取到的结果赋值到这个THREE.Vector3()的对象中,并且将这个结果以返回值的方式返回

我们可以这样写

let worldPosition = mesh.getWorldPosition(new THREE.Vector3());

我们也可以这样写

let vec3 = new THREE.Vector3();
mesh.getWorldPosition( vec3 );

两种方法,都可以获取到这个物体的相对位置

其他的三个函数在后续使用到之后,再做讲解

添加一个地面吧

我们之前所有的场景,都是一个光秃秃的方块,什么都没有,一点也不好看,所以这次我们添加一个地面

    function addMesh(){
        let geometry = new THREE.BoxGeometry(1,1,1);
        let material = new THREE.MeshStandardMaterial({
            color:"#ffffff"
        });
        mesh = new THREE.Mesh(geometry,material);
        scene.add(mesh);
    }

    function addPlane(){
        let geometry = new THREE.PlaneGeometry(10,10).rotateX(-Math.PI/2);
        let material = new THREE.MeshStandardMaterial({
            color:"#8b8b8b"
        });
        let plane = new THREE.Mesh(geometry,material);
        scene.add(plane);
    }

我们在addMesh()的下方,复制了一套代码, 并将其中的BoxGeometry()更换为PlaneGeometry,同时修改颜色为灰色“#8b8b8b”

在这里插入图片描述
哎呀?方块怎么陷入地下了?那不行,我们再对plane进行一次移动

    function addPlane(){
        let geometry = new THREE.PlaneGeometry(10,10).rotateX(-Math.PI/2).translate(0,-1,0);
        let material = new THREE.MeshStandardMaterial({
            color:"#8b8b8b"
        });
        let plane = new THREE.Mesh(geometry,material);
        scene.add(plane);
    }

这样我们的方块就在地面上跳舞了
在这里插入图片描述
如果你希望这个地面无限大,我们只需要设置好geometry的初始大小即可

    function addPlane(){
        let geometry = new THREE.PlaneGeometry(1000,1000).rotateX(-Math.PI/2).translate(0,-1,0);
        let material = new THREE.MeshStandardMaterial({
            color:"#8b8b8b"
        });
        let plane = new THREE.Mesh(geometry,material);
        scene.add(plane);
    }

最终效果
在这里插入图片描述
构造器:PlaneGeometry(width : Float, height : Float, widthSegments : Integer, heightSegments : Integer)

属性名属性值类型默认值说明
widthFloat1平面长度
heightFloat1平面宽度
widthSegmentsInteger1平面在长度方向的分段数,分段数越高,该平面更精细,这个值在后续在Geometry详解的文章中会详细介绍
heightSegmentsInteger1平面在宽度方向的分段数,同上

PlaneGeometry官方文档
BufferGeometry官方文档

函数:BufferGeometry.rotateX( angle:Float )
在 X 轴上旋转几何体。该操作一般在一次处理中完成,不会循环处理
其实我们还可以用
plane.rotation.x = -Math.PI 来代替上述用法,两种方法的不同点是:

当你访问plane.rotation时,使用BufferGeometry.rotateX的方法创建的平面,它的rotation值为(0,0,0),而使用上述方法时,rotation的值为(-3.14,0,0)

这两种方法都可以旋转物体,根据实际情况进行操作即可

个人建议,频繁操作某个物体的rotation时,优先修改Object3D.rotation的值,而非使用BufferGeometry.rotateX()函数

函数:translate( x : Float, y : Float, z : Float )
移动几何体,该操作一般在一次处理中完成,不会循环处理
上面我们对plane的y的值,设置为-1,这时创建的plane就是低于地面的

操作BufferGeometry和操作Mesh的区别

在前面的文章中,我们把geometry比做演员,Mesh比做化妆后的演员

对geometry进行操作,本质上,是修改了演员的基本素质,比如说本次修改了plane的旋转角度和位置,相当于,我们找了一个比正常的演员更矮一点的演员

而对Mesh进行操作,更多的是调整演员在演出时的状态

同样是修改位置,如果我们选择高个子的演员,那么我们需要降低高个子所占的舞台,来确保演员站在了合适的位置,这个属于修改mesh来实现,而操作geometry则是选择了矮个子的演员,而没有降低我们的舞台

操作两者均可达到移动几何体的目的,但是要分清楚情况去使用

添加多个演员

地面我们有了,但是场上只有一个演员,我希望让场景更丰富一些

    function addMesh(){
        for(let i = 0;i< 50;i++){
            let geometry = new THREE.BoxGeometry(1,1,1);
            let material = new THREE.MeshStandardMaterial({
                color:Math.random() * 0xffffff
            }); 
            let mesh = new THREE.Mesh(geometry,material);
            mesh.position.x = Math.random() * 50 - 25;
            mesh.position.z = Math.random() * 50 - 25;
            scene.add(mesh);
        }
    }

修改addMesh()为上述代码后
在这里插入图片描述
我们的场景变的更加丰富多彩了

前面的文章有提到,在材质中,我们可以使用 16位进制数来表示颜色,忘了16位进制的同学可以先去复习一下
这里我们使用 0xffffff * Math.random 来生成随机颜色

同时,我们使用 Math.random * 50 - 25,生成一个随机数,随机范围为 -25~25,用于控制每个创建的物体的x轴和z轴,如果你需要让模型的高度也有不同的话,对y轴做同样的操作即可

内存优化方案1:减少重复的geometry

但是,上面这种写法并不推荐

当你循环了50次,你就创建了50个geometry,50个material,50个mesh,直接创建了150个对象,这样你的内存中就有了50个对象的内存

这就相当于,我们这场舞台上,有50个规格完全一样的演员,50套衣服,和50个化妆后的演员

其实我们完全可以只用一个演员,50套衣服,然后让一个演员去做50个演员的工作,这样我们会节省很多的内存

    function addMesh(){
        let geometry = new THREE.BoxGeometry(1,1,1);
        for(let i = 0;i< 50;i++){
            let material = new THREE.MeshStandardMaterial({
                color:Math.random() * 0xffffff
            });
            let mesh = new THREE.Mesh(geometry,material);
            mesh.position.x = Math.random() * 50 - 25;
            mesh.position.z = Math.random() * 50 - 25;
            scene.add(mesh);
        }
    }

这样做,我们就只创建了一个演员,50套衣服,50个不同状态的化妆后的演员,我们这样就可以节省下来49个演员(BufferGeometry)所占用的内存

在创建Mesh的时候,Geometry是可以多次使用的,Material也可以多次使用,所以利用这个特性,我们可以对大量重复的物体做这样的优化

比如说,你要做一个森林,如果你把每一棵树的细节都做出来,每棵树都做的完全不一样,那么你这个程序就别想在任何设备上跑起来了。。。
像我们玩过的很多游戏中,树木这种东西都是复用的,比如说,我只需要5棵完全不同的树,然后将它复制几百份,随机分布到整个场景中,这样我们的森林就出来了

这样做,不只是节约了你做模型的成本,也大幅节省了运行你的程序时,所消耗的内存

嗯?你说你想要尽量让用户不看到重复的树?
那简单,我们还能调整树的旋转方向,或者拉伸树木模型来增加不同的感觉

如果你还觉得不够,那么,我们可以把树的种类增加到10棵。。。

在多次调整后,你总能找到一个,既保证了最低的内存,也保证了最满足需求的一个平衡点

让我们的场景更炫酷

二维阵列

    function addMesh(){
        let geometry = new THREE.BoxGeometry(1,1,1);
        for(let i = 0;i< 100;i++){
            let material = new THREE.MeshStandardMaterial({
                color:Math.random() * 0xffffff
            });
            let mesh = new THREE.Mesh(geometry,material);
            // i/10获取到第几排,乘1.1拉开一点距离
            mesh.position.x = Number.parseInt(i /10) * 1.1;
            // 以10对i求余获取到第几列
            mesh.position.z = ( i % 10) * 1.1;
            scene.add(mesh);
        }
    }

在这里插入图片描述

波浪

	    function addMesh(){
        let geometry = new THREE.BoxGeometry(1,1,1);
        for(let i = 0;i< 100;i++){
            let material = new THREE.MeshStandardMaterial({
                color:Math.random() * 0xffffff
            });
            let mesh = new THREE.Mesh(geometry,material);
            //典型sin函数
            mesh.position.x = i * 1.1;
            mesh.position.z = Math.sin(i) * 2;
            scene.add(mesh);
        }
    }

在这里插入图片描述

螺旋升天

    function addMesh(){
        let geometry = new THREE.BoxGeometry(1,1,1);
        for(let i = 0;i< 100;i++){
            let material = new THREE.MeshStandardMaterial({
                color:Math.random() * 0xffffff
            });
            let mesh = new THREE.Mesh(geometry,material);
            //螺旋线
            mesh.position.y = i ;
            mesh.position.z = Math.sin(i) * 4;
            mesh.position.x = Math.cos(i) * 4;
            scene.add(mesh);
        }
    }

在这里插入图片描述

这方面的玩法有很多很多,这里仅拿几个简单的举例,感兴趣的同学可以研究研究相关的函数,看看你们可以做出多少种效果

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值