关于从入门three.js到做出3d地球这件事(第三篇:光与影)(代码片段)

lulu_up lulu_up     2022-12-10     545

关键词:

关于从入门three.js到做出3d地球这件事(第三篇: 光与影)

本篇介绍

     通过前面几篇我们了解了坐标系、相机、物体等概念, 这一篇我们要让3d世界里的物体, 更像我们的现实世界的物体, 我们要为3d世界绘制光与影。

1. 高级材料

     如果你看过前两篇文章, 你会发现在生成物体材质的时候我们用的是MeshBasicMaterial, basic这单词意思是基本的,那也就是说与其相对还会有高级属性, MeshLambertMaterial就是高级属性中的一种。

     使用这个属性创建出来的物体, 会产生暗淡不光亮的表面(你可以理解为需要光照时, 它的颜色才会被看到), 本篇我们一起看看它的神奇之处。

2. 物体、墙面、地面

     绘制光源之前, 我们先搭建一套环境, 这个环境很简单有物体、墙面、地面, 我们通过上一篇已经学过如何绘制一个长方体, 那么我们就以薄薄的长方体作为墙面, 最终效果如下。

     物体、墙面、地面他们身上会有辅助线, 这个是使用的:

const edges = new THREE.BoxHelper(cube, 0x00000);
scene.add(edges);
  1. BoxHelper给立方体设置边框。
  2. cube需要设置边框的物体, 后面紧跟着边框的颜色
  3. edges将实例放入场景中。

全部代码如下(../utils/OrbitControls.js的内容在我笔记里):

<html>
<body>
    <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r122/three.min.js"></script>
    <script src="../utils/OrbitControls.js"></script>
    <script>
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 1000);
        camera.position.z = 40;
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0xffffff)
        orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
        document.body.appendChild(renderer.domElement);

        const cube = initCube(
            color: \'red\',
            len: [1, 2, 3],
            position: [-0.5, 5, 1.5]
        )
        const wall = initCube(
            color: \'gray\',
            len: [0.1, 10, 20],
            position: [-10.1, 5, 0]
        )
        const land = initCube(
            color: \'gray\',
            len: [20, 0.1, 20],
            position: [0, 0, 0]
        )
        scene.add(cube);
        scene.add(wall);
        scene.add(land);

        var animate = function () 
            requestAnimationFrame(animate);
            renderer.render(scene, camera);
        ;
        animate();

        function initCube(options) 
            const geometry = new THREE.BoxGeometry(...options.len);
            const material = new THREE.MeshBasicMaterial( color: options.color );
            const cube = new THREE.Mesh(geometry, material);
            cube.position.add(new THREE.Vector3(...options.position))
            scene.add(new THREE.BoxHelper(cube, 0x00000));
            return cube
        
    </script>
</body>
</html>

代码与之前几篇的代码没什么区别, 就是封装了一个initCube方法来创建立方体。

当我们把代码里的MeshBasicMaterial替换为``时如图:

3. AmbientLight 自然光 or 环境光

    我们的第一个主角终于登场了, 下面介绍把光源加入场景的方法。

const light = new THREE.AmbientLight(\'blue\');
scene.add(light)
  1. new THREE.AmbientLight(\'blue\')生成实例时传入光的颜色, 上面是蓝色的光。
  2. 放入场景中。

效果就变成了下面怪异的样子:

地面与墙壁变为了蓝色, 但是在蓝色的光照耀下红色的立方体却是黑色的。

光的颜色符合物理学

红色的物体不能反射蓝色的光, 灰色的物体却能反射蓝色的光。

  1. 自然光符合物理学, 不好计算。
  2. 自然光源没有特别的来源方向,不会产生阴影。
  3. 不能将其作为场景中唯一的光源, 但可以配合其他光源, 起到弱化阴影或给场景添加一些额外的颜色的作用。
  4. 自然光不需要指定位置它会应用到全局。

我们使用红光的时候:

所以要记住, 一些文章说与自然光颜色不同的物体都变为黑色是错的!!!

4. PointLight点光源

     顾名思义他是一个光点, 有人把它比喻成引火虫或是小灯泡, 它向四面八方发射光芒, 光源本身是不可见的所以在我们绘制的时候会在点光源的位置放置一个立方体表示其位置信息。

const light = new THREE.PointLight(\'white\');
light.intensity = 1.8;
light.distance = 30;
light.position.set(2, 8, -5);
scene.add(light)

点光源的属性介绍:

  1. intensity光强, 想要成为最亮的星。
  2. distance光源照射的距离, 默认值为0也就是无限。
  3. visible布尔值, 是否打开光源。
  4. decay衰减值, 越大衰减速度越快。

面上代码的效果如图:

换个角度看看:

当我们把光强加大到3, 明显可以看到区别:

点光源照耀四面八方, 如果生成阴影的话计算量太大, 所以不建议开启阴影。

全部代码:

<html>

<body>
    <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r122/three.min.js"></script>
    <script src="../utils/OrbitControls.js"></script>
    <script>
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 1000);
        camera.position.z = 40;
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0xffffff)
        orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
        document.body.appendChild(renderer.domElement);

        const cube = initCube(
            color: \'red\',
            len: [1, 2, 3],
            position: [-0.5, 5, 1.5]
        )
        const wall = initCube(
            color: \'gray\',
            len: [0.1, 10, 20],
            position: [-10.1, 5, 0]
        )
        const land = initCube(
            color: \'gray\',
            len: [20, 0.1, 20],
            position: [0, 0, 0]
        )
        scene.add(cube);
        scene.add(wall);
        scene.add(land);

        const light = new THREE.PointLight(\'white\');
        light.intensity = 3; // 光强
        light.distance = 30; // 衰减距离
        light.position.set(2, 8, -5);
        scene.add(light)

        const edges = initCube(
            color: \'red\',
            len: [0.2, 0.2, 0.2],
            position: [2, 8, -5]
        )
        scene.add(edges);

        const animate = function () 
            requestAnimationFrame(animate);
            renderer.render(scene, camera);
        ;
        animate();

        function initCube(options) 
            const geometry = new THREE.BoxGeometry(...options.len);
            const material = new THREE.MeshLambertMaterial( color: options.color );
            const cube = new THREE.Mesh(geometry, material);
            cube.position.add(new THREE.Vector3(...options.position))
            scene.add(new THREE.BoxHelper(cube, 0x00000));
            return cube
        
    </script>
</body>
</html>

5. 生成影子的定义

     想要生成影子可不是那么简单的, 因为可想而知在数学方面影子的计算量必然很大的, 在three.js中物体是否可以显示影子是需要单独定义的。

第一步: 渲染器支持

如果使用的WebGLRender 渲染器, 需要如下开启渲染器支持。

const renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;
第二步: 为设置可生成阴影属性
light.castShadow = true;
第三步: 为物体设置可生成阴影属性
cube.castShadow = true;
第四步: 为物体设置可接收阴影属性
cube.receiveShadow = true;

这里注意了, 比如说a物体产生阴影, 阴影映在b物体上, 那么a与b都要设置上述的属性。

6. SpotLight 聚光灯(有方向的光)

     这个光源是有方向的, 也就是说他可以指定照向谁, 并且可以产生阴影。

let light = new THREE.SpotLight("#ffffff");
    light.position.set(1, 1, 1);
    light.target = cube
    scene.add(light);

可配置的属性与上面的基本相似, 多了一个target:
target指定照谁, target必须是一个THREE.Object3D对象, 所以我们经常会先创建一个Object3D对象, 让它不可见然后光源就可以通过照射它, 从而实现任意方向。

我们先看一下光源在上方照射, 下方物体产生阴影的效果:

7. SpotLight 模拟手电(锥形光)

    开发中我们会用SpotLight模拟手电与灯光, 可以利用他的angle角度属性。

const light = new THREE.SpotLight("#ffffff");
scene.add(light);

当我们把背景颜色换成黑色的效果:

上图就如同黑夜里手电照射的效果了。

为了方便调试聚光灯,官方给了我们专属的辅助线。
const helper = new THREE.CameraHelper(light.shadow.camera);
    scene.add(helper);

下面我们标注一下都有哪些知识点:


    重要的是有了这些辅助线我们就知道如何优化自己的项目了, 比如减小光源的远平面。

完整代码

<html>
<body>
    <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r122/three.min.js"></script>
    <script src="../utils/OrbitControls.js"></script>
    <script>
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 1000);
        camera.position.z = 40;
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0xffffff)
        renderer.shadowMap.enabled = true;
        orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
        document.body.appendChild(renderer.domElement);
        const cube = initCube(
            color: \'red\',
            len: [3, 1, 3],
            position: [0, 2, 0]
        )
        const wall = initCube(
            color: \'gray\',
            len: [0.1, 10, 20],
            position: [-10.1, 5, 0]
        )
        const land = initCube(
            color: \'gray\',
            len: [20, 0.1, 20],
            position: [0, 0, 0]
        )
        scene.add(cube);
        scene.add(wall);
        scene.add(land);
        const arr = [8, 8, 0]
        const light = new THREE.SpotLight("#ffffff", 1);
        light.intensity = 2.5;
        light.position.set(...arr);
        light.castShadow = true;
        light.target = cube
        light.decay = 2;
        light.distance = 350;
        light.angle = Math.PI / 5
        light.penumbra = 0.05;
        scene.add(light);
        // 聚光灯助手
        const helper = new THREE.CameraHelper(light.shadow.camera);
        scene.add(helper);
        const edges = initCube(
            color: \'red\',
            len: [0.2, 0.2, 0.2],
            position: [...arr]
        )
        scene.add(edges);

        const animate = function () 
            requestAnimationFrame(animate);
            renderer.render(scene, camera);
        ;
        animate();

        function initCube(options) 
            const geometry = new THREE.BoxGeometry(...options.len);
            const material = new THREE.MeshLambertMaterial( color: options.color );
            const cube = new THREE.Mesh(geometry, material);
            cube.castShadow = true;
            cube.receiveShadow = true;
            cube.position.add(new THREE.Vector3(...options.position))
            scene.add(new THREE.BoxHelper(cube, 0x00000));
            return cube
        
    </script>
</body>
</html>

8. DirectionalLight 平型光

     经常被举例子的就是太阳光, 实际上太阳光也不是平行的, 只是距离太远了几乎可以算是平行。
     这个光源与其他的不同的点是, 他它所照耀的区域接收到的光强是一样的。

const light = new THREE.DirectionalLight("#ffffff");
scene.add(light);

介绍几个新属性:

light.shadow.camera.near = 5; //产生阴影的最近距离
light.shadow.camera.far = 50; //产生阴影的最远距离
light.shadow.camera.left = -3; //产生阴影距离位置的最左边位置
light.shadow.camera.right = 3; //最右边
light.shadow.camera.top = 3; //最上边
light.shadow.camera.bottom = -3; //最下面

通过上图我们可以得知, 这个光源是完全平行的。

完整代码如下:

<html
<body>
    <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r122/three.min.js"></script>
    <script src="../utils/OrbitControls.js"></script>
    <script>
        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 1000);
        camera.position.z = 40;
        const renderer = new THREE.WebGLRenderer();
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setClearColor(0x000)
        renderer.shadowMap.enabled = true;
        orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
        document.body.appendChild(renderer.domElement);

        const cube = initCube(
            color: \'red\',
            len: [3, 1, 3],
            position: [0, 2, 0]
        )
        const wall = initCube(
            color: \'gray\',
            len: [0.1, 10, 20],
            position: [-10.1, 5, 0]
        )
        const land = initCube(
            color: \'gray\',
            len: [20, 0.1, 20],
            position: [0, 0, 0]
        )
        scene.add(cube);
        scene.add(wall);
        scene.add(land);

        const light = new THREE.DirectionalLight("#ffffff");
        light.intensity = 1.5;
        light.position.set(8, 8, 0);
        light.castShadow = true;
        light.target = cube
        light.shadow.camera.near = 5; //产生阴影的最近距离
        light.shadow.camera.far = 50; //产生阴影的最远距离
        light.shadow.camera.left = -3; //产生阴影距离位置的最左边位置
        light.shadow.camera.right = 3; //最右边
        light.shadow.camera.top = 3; //最上边
        light.shadow.camera.bottom = -3; //最下面
        scene.add(light);
        // 聚光灯助手
        const helper = new THREE.CameraHelper(light.shadow.camera);
        scene.add(helper);

        const animate = function () 
            requestAnimationFrame(animate);
            renderer.render(scene, camera);
        ;
        animate();

        function initCube(options) 
            const geometry = new THREE.BoxGeometry(...options.len);
            const material = new THREE.MeshLambertMaterial( color: options.color );
            const cube = new THREE.Mesh(geometry, material);
            cube.castShadow = true;
            cube.receiveShadow = true;
            cube.position.add(new THREE.Vector3(...options.position))
            scene.add(new THREE.BoxHelper(cube, 0x00000));
            return cube
        
    </script>
</body>
</html>

9. 光源要配合使用

     一般情况下不会只有单一光源, 比如我们会先放一个环境光, 然后在灯的模型中放上其他光源, 一些rpg游戏会用聚光灯处理用户视角。

     我们可以同时使用多个光源, 利用gui.js查看各种绚丽的效果, 比如我们可以用束平型光模拟舞台效果。

end.

下章会从绘制一个木块开始, 最后绘制一个贴图地球, 这次就是这样希望与你一起进步。

使用three.js实现炫酷的赛博朋克风格3d数字地球大屏🌐

...Three.js和CSS实现赛博朋克2077风格视觉效果实现炫酷3D数字地球大屏页面。页面使用React+Three.js+Echarts+stylus技术栈,本文涉及到的主要知识点包括:THREE.Spherical球体坐标系的应用、Shader结合TWEEN实现飞线和冲击波动画效果、dat.GUI调... 查看详情

4行代码就可以完成一个web版的3d地球可视化展示——gio.js

参考技术AGio.js是一个基于Three.js的web3D地球数据可视化的开源组件库。使用Gio.js的网页应用开发者,可以快速地以申明的方式创建自定义的Web3D数据可视化模型,添加数据,并且将其作为一个组件整合到自己的应用中。Gio.js是一... 查看详情

第135篇:three.js基础入门(代码片段)

好家伙,这东西太帅了,我要学会 先放张帅图(都是用three.js做出来的,这我学习动力直接拉满)  还有另外一个Junniis...帧数太高,录不了 开始学习官方文档1.Three.js是什么?Three.js是一款运行在浏览器中的3D引擎(基于WebGL... 查看详情

three.js基础入门系列--导入3d模型

先来学习今天的知识——Three.js导入3D模型复杂的3D模型(比如制作一个飞机模型)一般都是用第三方建模工具生成,然后加载到Three.js中。01常用建模制作工具3Dmax链接地址:https://www.autodesk.com.cn/products/3ds-max/overvie... 查看详情

three.js入门——画一个3d正方体(代码片段)

...较新的东西,可供查阅的资料也不多。这篇文章仅是一个入门篇,介绍如何绘制一个3D正方体。介绍完毕,首先奉上实现的效果图:这就是实现的效果图,还是挺有立体感的吧?绘制前的准备写代码前,要先下载最新的three.js框... 查看详情

从 DB 加载 3d 模型并在 Three.js 中使用它

】从DB加载3d模型并在Three.js中使用它【英文标题】:Loadinga3dmodelfromDBandusingitinThree.js【发布时间】:2017-02-0218:17:18【问题描述】:现在我的任务是从MySQL数据库加载3d模型并在Three.js中使用它。这是我所做的,我创建了一个这样的... 查看详情

three.js-(入门四)

好久没更新three.js了,前三章了解了部分基础知识。这一章我们来搞点事情,做点有意思的东西。效果如下图:因为截图是静止的,实际效果是地球在自转。废话不多说,上代码:<!DOCTYPEhtml><htmllang="en"><head><metacha... 查看详情

中秋快乐如何用three.js实现我的太空遐想3d网页(代码片段)

 目录创作背景功能分解创建3d地球创建3d月球 创建3d小火箭设计地球自转轨迹和月球、小火箭旋转的行星轨道1.地球的自转轨迹2.月球的公转轨迹3.小火箭的公转轨迹最终效果展现 彩蛋创作背景        马上又是一年中秋佳... 查看详情

three.js教程:第一个3d场景(代码片段)

...他工具系列:https://nsdt.cloud/下面的代码完整展示了通过three.js引擎创建的一个三维场景,在场景中绘制并渲染了一个立方体的效果,为了大家更好的宏观了解three.js引擎,尽量使用了一段短小但完整的代码实现一个实际的三维效... 查看详情

使用 Three.js 获取两个 3d 向量之间的方向?

】使用Three.js获取两个3d向量之间的方向?【英文标题】:Getdirectionbetweentwo3dvectorsusingThree.js?【发布时间】:2017-03-2204:47:07【问题描述】:我有两点:v1=(0,0,0);v2=(10,4,-3);我想知道这两点之间的方向,这样我就可以从点v1投射到v2。... 查看详情

three.js怎么样知乎

...成果,three.js都有很多优秀的开发项目让你汲取营养。先做出一个作品作品是可以为简历加分的,同时也能够验证你的学习能力,现在的软件生态不缺乏学习资源和技术资料,项目案例是非常好的参考素材。出作品的过程也是在... 查看详情

three.js程序化建模入门(代码片段)

...uvs,法线等)任何具有webgl兼容网络浏览器的机器关于如何运行java 查看详情

将变形目标从 Blender 导出到 Three.js

】将变形目标从Blender导出到Three.js【英文标题】:ExportingmorphtargetsfromBlendertoThree.js【发布时间】:2015-03-2716:18:57【问题描述】:这是我想要实现的一个示例:http://threejs.org/examples/#webgl_animation_skinning_morph我正在使用Three.jsr69中的... 查看详情

Three.js - 3D 空间中的 2D 对象(通过 Vertices)

】Three.js-3D空间中的2D对象(通过Vertices)【英文标题】:Three.js-2Dobjectin3Dspace(byVertices)【发布时间】:2018-10-2016:40:43【问题描述】:我有一个问题:我有一个3D点数组。如何在3D空间中绘制由Vertices给出的2D平面对象?我想画线从Po... 查看详情

wpfmvvm从入门到精通3:数据绑定

我们前面已经说过,现在后端和前端可以分头行事了。我们先来看看后端要做的事情。对应于用户名输入框,ViewModel里面应该有一个相应的对象。当这个对象状态发生改变时,需要向View发出一个通知。因为所有的属性都要做这... 查看详情

如何将 Blender 3D 模型导入到 three.js

】如何将Blender3D模型导入到three.js【英文标题】:HowtoimportBlender3DModeltothree.js【发布时间】:2019-03-0708:16:07【问题描述】:我尝试使用ThreejsGitHubRepo中的three.jsBlenderExport插件将Blender3D模型导入到three.js,但后者已被删除。在不使用T... 查看详情

threejs简介

...让你在较短的学习后就能面对大部分需求场景。Three.js的入门是相对简单的,但是当我们真的去学的时候 查看详情

three.js地球开发—4.three.js渲染场景保存成贴图

...代码如下图所示:此目的是为了把上文的(Three.js地球开发—6.读取坐标数据渲染生成国家轮廓Mesh)渲染结果保存成纹理贴图,方便对后期的三维地球进行贴图操作; 查看详情