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功能函数(2009-06-04更新)
Jun 04 Javascript
JQery 渐变图片导航效果代码 漂亮
Jan 01 Javascript
QUnit jQuery的TDD框架
Nov 04 Javascript
Javascript 构造函数详解
Oct 22 Javascript
Javascript实现苹果悬浮虚拟按钮
Apr 10 Javascript
js检测离开或刷新页面时表单数据是否更改的方法
Aug 02 Javascript
Vue.js学习示例分享
Feb 05 Javascript
JavaScript使用readAsDataURL读取图像文件
May 10 Javascript
javaScript产生随机数的用法小结
Apr 21 Javascript
Vue实现textarea固定输入行数与添加下划线样式的思路详解
Jun 28 Javascript
使用mixins实现elementUI表单全局验证的解决方法
Apr 02 Javascript
Vue实现开关按钮拖拽效果
Sep 22 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/06/04 PHP
PHP 返回13位时间戳的实现代码
2016/05/13 PHP
js Map List 遍历使用示例
2013/07/10 Javascript
js形成页面的一种遮罩效果实例代码
2014/01/04 Javascript
JavaScript实现大数的运算
2014/11/24 Javascript
使用js获取图片原始尺寸
2014/12/03 Javascript
浅谈javascript回调函数
2014/12/07 Javascript
jquery ui resize 中border-box的bug修正
2015/04/26 Javascript
javascript实现完美拖拽效果
2015/05/06 Javascript
微信小程序开发探究
2016/12/27 Javascript
简单实现jQuery级联菜单
2017/01/09 Javascript
JavaScript实现的冒泡排序法及统计相邻数交换次数示例
2017/04/26 Javascript
Angular2中监听数据更新的方法
2018/08/31 Javascript
VUE 实现复制内容到剪贴板的两种方法
2019/04/24 Javascript
nodejs和react实现即时通讯简易聊天室功能
2019/08/21 NodeJs
vue使用recorder.js实现录音功能
2019/11/22 Javascript
Vue 中使用lodash对事件进行防抖和节流操作
2020/07/26 Javascript
解决vue单页面多个组件嵌套监听浏览器窗口变化问题
2020/07/30 Javascript
python读写ini文件示例(python读写文件)
2014/03/25 Python
python基础教程之字典操作详解
2014/03/25 Python
python抓取最新博客内容并生成Rss
2015/05/17 Python
将Python的Django框架与认证系统整合的方法
2015/07/24 Python
Python中的descriptor描述器简明使用指南
2016/06/02 Python
Python实现自动添加脚本头信息的示例代码
2016/09/02 Python
机器学习python实战之决策树
2017/11/01 Python
符合语言习惯的 Python 优雅编程技巧【推荐】
2018/09/25 Python
Python基于Logistic回归建模计算某银行在降低贷款拖欠率的数据示例
2019/01/23 Python
Python读取Pickle文件信息并计算与当前时间间隔的方法分析
2019/01/30 Python
Pycharm 文件更改目录后,执行路径未更新的解决方法
2019/07/19 Python
python3文件复制、延迟文件复制任务的实现方法
2019/09/02 Python
Win下PyInstaller 安装和使用教程
2019/12/25 Python
pandas 数据类型转换的实现
2020/12/29 Python
Gucci法国官方网站:意大利奢侈品牌
2018/07/25 全球购物
劳保用品管理制度范本
2015/08/06 职场文书
vue中三级导航的菜单权限控制
2021/03/31 Vue.js
直播实况, OMG破敌三路五十分钟大战神技局摩托车
2022/04/01 DOTA