Three.js实现雪糕地球的使用示例详解


Posted in Javascript onJuly 07, 2022

前言

最近的天气有几分酷热,去实验室的道路也有几分漫长,走着走着,小包感觉灵魂已经被热出窍了。回到实验室,把空调打开,雪糕吃上,静坐了几分钟,才重新感觉到灵魂的滋味,葛优躺在实验室的小床上,思维开始天马行空,世上有一万种方式能让小包凉快,但地球母亲呐,她却日渐炎热,谁能来给她降降温?

躺着躺着,进入了梦乡,小包梦到未来有一天,人类超级发达,可以穿梭时空,但发展的代价也是巨大的,地球母亲不堪重负,热度超标,我们却束手无策,科学家最后想出一个古老的办法,将地球的一周用冰包裹起来,进行物理降温。这很让人惊悚,小包醒来后,枯坐了一会,决定做一个雪糕地球,不只是一种整活调侃,也是一种反思与警示,保护地球,人人有责。

  • style
* {
    -webkit-user-select: none;
       -moz-user-select: none;
        -ms-user-select: none;
            user-select: none;
  }
  body {
    height: 100vh;
    background-color: hotpink;
    margin: 0;
    padding: 0;
    overflow: hidden;
  }
  .loader {
    display: flex;
    color: white;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 5em;
    width: 100%;
    height: 100%;
    font-family: "Baloo Bhaijaan", cursive;
  }
  .loader span {
    text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb, 0 5px #bbb,
      0 6px transparent, 0 7px transparent, 0 8px transparent,
      0 9px transparent, 0 10px 10px rgba(0, 0, 0, 0.4);
    text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb,
        0 5px #bbb, 0 6px #bbb, 0 7px #bbb, 0 8px #bbb, 0 9px #bbb,
        0 50px 25px rgba(0, 0, 0, 0.2);
      transform: translateY(-20px);
  }
  •  script
/*
 * 基础配置
 */
let isLoaded = false; // 纹理资源是否加载完毕
const loadingScreen = {
  scene: new THREE.Scene(),
  camera: new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  ),
  // 移除加载标志的函数
  removeText() {
    const loadingText = document.querySelector("#canvas-loader");
    if (loadingText.parentNode) {
      loadingText.parentNode.removeChild(loadingText);
    }
  },
};
// 初始化加载器
let loadingManager = new THREE.LoadingManager();
// 监听加载器 onLoad 事件
loadingManager.onLoad = () => {
  loadingScreen.removeText();
  isLoaded = true;
};
// 创建场景
const scene = new THREE.Scene();
// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
// 渲染器基本设置
renderer.setClearColor("hotpink");
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
// canvas 外部容器
const canvasWrapper = document.querySelector("#canvas-wrapper");
// 创建透视相机
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
// 设置相机位置
camera.position.set(0, 0, 220);
// 创建平行光源
const light = new THREE.DirectionalLight();
light.position.set(0, 0, 1);
scene.add(light);
// 创建点光源
const point = new THREE.PointLight(0xeeeeee);
point.position.set(400, 200, 300); //点光源位置
scene.add(point); //点光源添加到场景中
// 创建球体
const cRadius = 100;
const geometry = new THREE.SphereBufferGeometry(
  cRadius,
  cRadius * 6.4,
  cRadius * 6.4
);
// 纹理图
const textureLoader = new THREE.TextureLoader(loadingManager);
const textureSurface = textureLoader.load(
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-surface.jpg"
);
const textureElevation = textureLoader.load(
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-elevation.jpg"
);
const textureSpecular = textureLoader.load(
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-specular.jpg"
);
// 材质信息
const materialOpt = {
  map: textureSurface,
  normalMap: textureElevation,
  specularMap: textureSpecular,
  shininess: 80,
};
const material = new THREE.MeshPhongMaterial(materialOpt);
// 创建网格体
const sphere = new THREE.Mesh(geometry, material);
// 设置环境贴图的颜色深浅
sphere.material.normalScale.set(0.5, 0.5);
// 将模型添加到场景中
scene.add(sphere);
// 将 canvas 元素添加到页面中
canvasWrapper.appendChild(renderer.domElement);
/*
 * 事件监听实现动效
 */
let mouseX = 0;
let mouseY = 0;
const moveAnimate = {
  coordinates(clientX, clientY) {
    const limit = 270;
    const limitNeg = limit * -1;
    mouseX = clientX - window.innerWidth / 2;
    mouseY = clientY - window.innerHeight / 2;
    mouseX = mouseX >= limit ? limit : mouseX;
    mouseX = mouseX <= limitNeg ? limitNeg : mouseX;
    mouseY = mouseY >= limit ? limit : mouseY;
    mouseY = mouseY <= limitNeg ? limitNeg : mouseY;
  },
  onMouseMove(e) {
    moveAnimate.coordinates(e.clientX, e.clientY);
  },
  onTouchMove(e) {
    const touchX = e.changedTouches[0].clientX;
    const touchY = e.changedTouches[0].clientY;
    moveAnimate.coordinates(touchX, touchY);
  },
};
document.addEventListener("mousemove", moveAnimate.onMouseMove);
document.addEventListener("touchmove", moveAnimate.onTouchMove);
const onWindowResize = () => {
  const w = window.innerWidth;
  const h = window.innerHeight;
  camera.aspect = w / h;
  camera.updateProjectionMatrix();
  renderer.setSize(w, h);
};
window.addEventListener("resize", onWindowResize);
const createAnimRotation = () => {
  const speed = 0.005;
  sphere.rotation.z += speed / 2;
  sphere.rotation.y += speed;
};
// 渲染函数
const render = () => {
  if (!isLoaded) {
    renderer.render(loadingScreen.scene, loadingScreen.camera);
    requestAnimationFrame(render);
    return;
  }
  camera.position.x += (mouseX * -1 - camera.position.x) * 0.05;
  camera.position.y += (mouseY - camera.position.y) * 0.05;
  camera.lookAt(scene.position);
  createAnimRotation();
  renderer.render(scene, camera);
  requestAnimationFrame(render);
};
render();

ThreeJS 基础——实现转动的球体

Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它创建各种三维场景,包括了摄影机、光影、材质等各种对象,大家或多或少应该都见识过 Three 的传说。这是小包第一次使用 Three,因此小包会围绕雪糕地球实现的各种细节讲起。

下面首先来看一下 Three 框架的基本组成要素(图源: Three.js 教程)

Three.js实现雪糕地球的使用示例详解

Three 中最重要的三个对象即场景、相机和渲染器。场景即放置模型、光照的场地;相机设置以何种方式何种角度来观看场景,渲染器将效果渲染到网页中。这三个概念都不难理解,下面我们用代码实现这三个对象。

// 场景
const scene = new THREE.Scene();
// 透视相机
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
// 渲染器
const renderer = new THREE.WebGLRenderer();
// 设置渲染区域尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
// body元素中插入canvas对象
document.body.appendChild(renderer.domElement);
// 设置背景颜色
renderer.setClearColor("hotpink");
// 执行渲染操作   指定场景、相机作为参数
renderer.render(scene, camera);

Three 中有多种相机,本文章主要使用透视相机(PerspectiveCamera),其原理与人眼所看的景象类似,共有四个参数:

PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )

fov: 表示能看到的角度范围,值为角度,类似于人的视角。

aspect: 表示渲染窗口的长宽比,如果网页中只有一个 canvas,其值通常设置为网页视口的宽高比

near/far: near/far 分别代表摄像机的近剪切面和远剪切面

文字有些难以理解,可以参考一下下图:

Three.js实现雪糕地球的使用示例详解

打开浏览器,看一下会渲染出什么?目前只能看到全粉色的网页,这是因为目前的场景中并没有添加 3D 模型对象。

接下来我们来添加一个球体模型,作为地球的基底。

const cRadius = 100;
const geometry = new THREE.SphereBufferGeometry(
  cRadius,
  cRadius * 6.4,
  cRadius * 6.4
);

SphereBufferGeometryThree 中实现球体的 API,参数非常多,这里只介绍前三个参数

radius: 球体半径

widthSegments: 沿经线方向分段数

heightSegments: 沿纬线方向分段数

为球体添加材质 Material,目前我们只添加一个颜色属性。

// 材质对象Material
const material = new THREE.MeshLambertMaterial({
  color: 0x0000ff,
});

渲染网格体 Mesh,并将其添加到场景 Scene 中。

// 网格体 Mesh,两个参数分别为几何体和材质
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);

重新打开网站,并没有看到球体,还是一片粉茫茫的寂寥,天理何在?

Three.js实现雪糕地球的使用示例详解

Three 相机的初始位置默认为 (0,0,0),相机焦点默认为 Z 轴负半轴方向,球体的半径是 100,也就是说目前相机位于球体内部,因此我们需要调整相机位置。

// 设置相机的位置
camera.position.set(0, 0, 220);
// 设置相机焦点的方向
camera.lookAt(scene.position);

当当当当,网页中就可以成功看到一个黑色球体了,额有点奇怪,我们明明设置的是 0x0000ff 颜色,怎么会显示一个黑色模型?

Three.js实现雪糕地球的使用示例详解

小包苦思冥想: 万物本没有颜色,颜色是光的反射。在整个场景中,目前是没有光源的,因此下面分别添加平行光(DirectionalLight)和点光源(PointLight)

平行光是沿着特定方向发射的光,其表现类似无限远的阳光,文章使用它来模拟太阳光。点光源是从一个点向各个方向发射的光源,使用它来增加整体的亮度。

// 声明平行光
const light = new THREE.DirectionalLight();
// 设置平行光源位置
light.position.set(0, 0, 1);
// 将平行光源添加到场景中
scene.add(light);
// 声明点光源
const point = new THREE.PointLight(0xeeeeee);
// 设置点光源位置
point.position.set(400, 200, 300);
// 点光源添加到场景中
scene.add(point);

Three.js实现雪糕地球的使用示例详解

立体效果看起来不明显,没事,接下来我们让球体动起来。接下来,给球体添加一个 z 轴和 y 轴的转动。

const createAnimRotation = () =&gt; {
  const speed = 0.005;
  sphere.rotation.z += speed / 2;
  sphere.rotation.y += speed;
};
const render = () =&gt; {
  createAnimRotation();
  renderer.render(scene, camera);
  requestAnimationFrame(render);
};
render();

由于球体是对称的,转动看起来并不明显,如果你特别想看到转动效果,可以将 SphereBufferGeometry 暂时更换为 BoxBufferGeometry

ThreeJS 纹理——实现转动的地球

上文已经成功实现地球,接下来我们来为地球披上衣服。本文实现的是雪糕地球,因此小包直接为其披上雪糕外衣。

Three 可以将一张纹理图映射到几何体上,具体的映射原理我们不做探究,映射的思想可以参考下图。

Three.js实现雪糕地球的使用示例详解

选取一张雪糕地球的纹理图,使用下面的代码实现纹理贴图效果。

Three.js实现雪糕地球的使用示例详解

// 纹理加载器对象
const textureLoader = new THREE.TextureLoader();
const textureSurface = textureLoader.load(
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-surface.jpg"
);
// 设置纹理贴图
const material = new THREE.MeshLambertMaterial({ map: textureSurface });

Three.js实现雪糕地球的使用示例详解

只使用普通贴图的雪糕地球看起来已经非常不错了,但还有进一步美化的空间,Three 提供了高光贴图,使用高光贴图,会有高亮部分显示。

const textureSpecular = textureLoader.load(
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-specular.jpg"
);
const material = new THREE.MeshPhongMaterial({
  map: textureSurface,
  specularMap: textureSpecular,
  shininess: 80, // 高光部分的亮度
});

Three.js实现雪糕地球的使用示例详解

虽然动图录制的帧数太低,还是依稀可以看到一些高亮区域。

Three 还提供了环境贴图,环境贴图可以增加表面的细节,使三维模型更加立体。

const textureElevation = textureLoader.load(
  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/249663/world-elevation.jpg"
);
const material = new THREE.MeshPhongMaterial({
  map: textureSurface,
  normalMap: textureElevation,
  specularMap: textureSpecular,
  shininess: 80,
});

立体效果是有了,但是具体看起来一言难尽,颜色有些许暗淡,不符合雪糕的风格。

Three.js实现雪糕地球的使用示例详解

小包继续开始查看文档,环境贴图中有 normalScale 属性,可以设置颜色的深浅程度,减少对应属性值为 0.5,0.5

sphere.material.normalScale.set(0.5, 0.5);

Three.js实现雪糕地球的使用示例详解

交互式雪糕地球

给地球加一些交互效果:

  • 当鼠标靠近,地球放大;鼠标远离时,地球缩小
  • 地球随鼠标方向转动

上述动效我们可以通过移动相机位置实现。首先设定地球旋转的最大正负角度为 270

// 定义动效对象
let mouseX = 0;
let mouseY = 0;
const moveAnimate = {
  coordinates(clientX, clientY) {
    const limit = 270;
    const limitNeg = limit * -1;
    mouseX = clientX - window.innerWidth / 2;
    mouseY = clientY - window.innerHeight / 2;
    mouseX = mouseX &gt;= limit ? limit : mouseX;
    mouseX = mouseX &lt;= limitNeg ? limitNeg : mouseX;
    mouseY = mouseY &gt;= limit ? limit : mouseY;
    mouseY = mouseY &lt;= limitNeg ? limitNeg : mouseY;
  },
  onMouseMove(e) {
    moveAnimate.coordinates(e.clientX, e.clientY);
  },
};
document.addEventListener("mousemove", moveAnimate.onMouseMove);

通过上述事件计算出 mouseXmouseY 的值,在 render 函数中,修改 camera 的位置。

camera.position.x += (mouseX * -1 - camera.position.x) * 0.05;
camera.position.y += (mouseY - camera.position.y) * 0.05;
camera.lookAt(scene.position);

Three.js实现雪糕地球的使用示例详解

移动端同步监听 touchmove 事件,手机也可以看到雪糕地球的动态效果。

const moveAnimate = {
  onTouchMove(e) {
    const touchX = e.changedTouches[0].clientX;
    const touchY = e.changedTouches[0].clientY;
    moveAnimate.coordinates(touchX, touchY);
  },
};
document.addEventListener("touchmove", moveAnimate.onTouchMove);

添加 loading 效果

纹理的加载需要一定的时间,因此添加一个转场 loading 效果。

loading 效果使用小包前面的实现跃动的文字中的效果。

.loader {
  display: flex;
  color: white;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 5em;
  width: 100%;
  height: 100%;
  font-family: "Baloo Bhaijaan", cursive;
}
.loader span {
  text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb, 0 5px #bbb, 0 6px
      transparent, 0 7px transparent, 0 8px transparent, 0 9px transparent, 0
      10px 10px rgba(0, 0, 0, 0.4);
  text-shadow: 0 1px #bbb, 0 2px #bbb, 0 3px #bbb, 0 4px #bbb, 0 5px #bbb, 0 6px
      #bbb, 0 7px #bbb, 0 8px #bbb, 0 9px #bbb, 0 50px 25px rgba(0, 0, 0, 0.2);
  transform: translateY(-20px);
}

Three 提供了 LoadingManager,其功能是处理并跟踪已加载和待处理的数据。当所有加载器加载完成后,会调用 LoadingManager 上的 onLoad 事件。

因此我们定义一个 LoadingManager,当触发 onLoad 事件后,将页面中的加载标志移除。

const loadingScreen = {
  scene: new THREE.Scene(),
  camera: new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  ),
  // 移除加载标志的函数
  removeText() {
    const loadingText = document.querySelector("#canvas-loader");
    if (loadingText.parentNode) {
      loadingText.parentNode.removeChild(loadingText);
    }
  },
};
// 初始化加载器
let loadingManager = new THREE.LoadingManager();
// 监听加载器 onLoad 事件
loadingManager.onLoad = () =&gt; {
  loadingScreen.removeText();
  isLoaded = true;
};
// 纹理图加载器传入 loadingManager
const textureLoader = new THREE.TextureLoader(loadingManager);

参考链接

Three中文文档

以上就是Three.js实现雪糕地球的使用示例详解的详细内容,更多关于Three.js雪糕地球的资料请关注三水点靠木其它相关文章!


Tags in this post...

Javascript 相关文章推荐
js阻止冒泡及jquery阻止事件冒泡示例介绍
Nov 19 Javascript
使用jQueryMobile实现滑动翻页效果的方法
Feb 04 Javascript
Javascript基于对象三大特性(封装性、继承性、多态性)
Jan 04 Javascript
js实现多图左右切换功能
Aug 04 Javascript
Bootstrap 源代码分析(未完待续)
Aug 17 Javascript
jQuery Easyui快速入门教程
Aug 21 Javascript
解决VUEX刷新的时候出现数据消失
Jul 03 Javascript
javascript计算对象长度的方法
Oct 25 Javascript
node中modules.exports与exports导出的区别
Jun 08 Javascript
jQuery实现判断滚动条滚动到document底部的方法分析
Aug 27 jQuery
js表达式与运算符简单操作示例
Feb 15 Javascript
JS监听组合按键思路及实现过程
Apr 17 Javascript
二维码条形码生成的JavaScript脚本库
Jul 07 #Javascript
JS实现简单的九宫格抽奖
JS实现九宫格拼图游戏
JavaScript实现九宫格拖拽效果
JS实现简单九宫格抽奖
Jun 28 #Javascript
Vite + React从零开始搭建一个开源组件库
Jun 25 #Javascript
React自定义hook的方法
Jun 25 #Javascript
You might like
php删除与复制文件夹及其文件夹下所有文件的实现代码
2013/01/23 PHP
解析dedecms空间迁移步骤详解
2013/05/15 PHP
PHP实现事件机制实例分析
2015/06/26 PHP
PHP Yaf框架的简单安装使用教程(推荐)
2016/06/08 PHP
PHP实现的简单操作SQLite数据库类与用法示例
2017/06/19 PHP
PHP实现限制IP访问及提交次数的方法详解
2017/07/17 PHP
动态加载js的几种方法
2006/10/23 Javascript
javascript浏览器窗口之间传递数据的方法
2015/01/20 Javascript
javascript排序函数实现数字排序
2015/06/26 Javascript
JavaScript中的Object对象学习教程
2016/05/20 Javascript
JQuery对ASP.NET MVC数据进行更新删除
2016/07/13 Javascript
详细总结Javascript中的焦点管理
2016/09/17 Javascript
值得分享和收藏的xmlplus组件学习教程
2017/05/05 Javascript
详谈javascript精度问题与调整
2017/07/08 Javascript
微信小程序实现刷脸登录
2018/05/25 Javascript
微信小程序开发打开另一个小程序的实现方法
2020/05/17 Javascript
[51:15]完美世界DOTA2联赛PWL S2 PXG vs Magma 第一场 11.21
2020/11/24 DOTA
python从网络读取图片并直接进行处理的方法
2015/05/22 Python
Python使用matplotlib实现的图像读取、切割裁剪功能示例
2018/04/28 Python
从django的中间件直接返回请求的方法
2018/05/30 Python
Python实现求解一元二次方程的方法示例
2018/06/20 Python
Django框架首页和登录页分离操作示例
2019/05/28 Python
详解python环境安装selenium和手动下载安装selenium的方法
2020/03/17 Python
pycharm使用技巧之自动调整代码格式总结
2020/11/04 Python
使用python将微信image下.dat文件解密为.png的方法
2020/11/30 Python
Python3.9.1中使用match方法详解
2021/02/08 Python
教你使用Canvas处理图片的方法
2017/11/28 HTML / CSS
美国爆米花工厂:The Popcorn Factory
2019/09/14 全球购物
计算机网络专业推荐信
2013/11/24 职场文书
业务部主管岗位职责
2014/01/29 职场文书
六年级语文下册教学计划
2015/01/22 职场文书
项目备案申请报告
2015/05/15 职场文书
幼儿园教师教学反思
2016/03/02 职场文书
教你解决往mysql数据库中存入汉字报错的方法
2021/05/06 MySQL
Ruby序列化和持久化存储 Marshal和Pstore介绍
2022/04/18 Ruby
git stash(储藏)的用法总结
2022/06/25 Servers