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 判断浏览器使用的语言示例代码
Mar 22 Javascript
javascript操作数组详解
Dec 17 Javascript
jquery限定文本框只能输入数字(整数和小数)
Jan 08 Javascript
jQuery拖拽排序插件制作拖拽排序效果(附源码下载)
Feb 23 Javascript
10行原生JS实现文字无缝滚动(超简单)
Jan 02 Javascript
OkHttp踩坑随笔为何 response.body().string() 只能调用一次
Jan 08 Javascript
解决iView中时间控件选择的时间总是少一天的问题
Mar 15 Javascript
javascript实现简易聊天室
Jul 12 Javascript
Vue之Mixins(混入)的使用方法
Sep 24 Javascript
Echarts实现多条折线可拖拽效果
Dec 19 Javascript
解决微信小程序scroll-view组件无横向滚动的问题
Feb 04 Javascript
vue 修改 data 数据问题并实时显示操作
Sep 07 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 URL参数获取方式的四种例子
2014/02/28 PHP
php判断输入是否是纯数字,英文,汉字的方法
2015/03/05 PHP
Linux+Nginx+MySQL下配置论坛程序Discuz的基本教程
2015/12/23 PHP
使用PHP+Redis实现延迟任务,实现自动取消订单功能
2019/11/21 PHP
List the Codec Files on a Computer
2007/06/11 Javascript
Javascript 强制类型转换函数
2009/05/17 Javascript
基于jQuery的实现简单的分页控件
2010/10/10 Javascript
nodejs的10个性能优化技巧
2014/07/15 NodeJs
JS动态日期时间的获取方法
2015/09/28 Javascript
使用NodeJs 开发微信公众号(三)微信事件交互实例
2016/03/02 NodeJs
JS中promise化微信小程序api
2018/04/12 Javascript
通过jquery toggleClass()属性制作文章段落更改背景颜色
2018/05/21 jQuery
JS 逻辑判断不要只知道用 if-else 和 switch条件判断(小技巧)
2020/05/27 Javascript
Vue与React的区别和优势对比
2020/12/18 Vue.js
[01:01]2020完美高校联赛(秋)西安落幕
2021/03/11 DOTA
python实现爬虫下载漫画示例
2014/02/16 Python
使用Python编写简单网络爬虫抓取视频下载资源
2014/11/04 Python
python使用range函数计算一组数和的方法
2015/05/07 Python
Python实现ssh批量登录并执行命令
2016/10/25 Python
对pandas中Series的map函数详解
2018/07/25 Python
详解Python函数式编程—高阶函数
2019/03/29 Python
python实现字符串完美拆分split()的方法
2019/07/16 Python
python数据类型之间怎么转换技巧分享
2019/08/20 Python
Python多线程及其基本使用方法实例分析
2019/10/29 Python
使用PyCharm进行远程开发和调试的实现
2019/11/04 Python
Django {{ MEDIA_URL }}无法显示图片的解决方式
2020/04/07 Python
Python使用内置函数setattr设置对象的属性值
2020/10/16 Python
CSS3制作日历实现代码
2012/01/21 HTML / CSS
Vans英国官方网站:美国南加州的原创极限运动潮牌
2017/01/20 全球购物
家用个人磨皮机:Trophy Skin
2017/03/30 全球购物
如何高效率的查找一个月以内的数据
2012/04/15 面试题
JS原生实现轮播图的几种方法
2021/03/23 Javascript
军训自我鉴定100字
2014/02/13 职场文书
公司员工离职证明书
2014/10/04 职场文书
新闻稿标题
2015/07/18 职场文书
简历中的自我评价怎么写呢?
2019/04/30 职场文书