效果图:
序幕准备:
需要自己准备地球跟月球的纹理贴图,如下:
图片格式转换网址,如下:
Zamzar - File Conversion progress
开始编码:
实现步骤如下:
-
在JavaScript部分,首先初始化场景、相机、渲染器、光源以及控制器。
-
通过调用
sphereGeometry
函数创建地球和月球的几何体,并应用纹理贴图。 -
调用
createLabel
函数创建标签,将标签绑定到地球和月球模型上。 -
初始化相机位置和渲染器设置。
-
使用
OrbitControls
控制器来实现鼠标和触摸事件的交互控制。 -
实现动画效果的
ani
函数,其中通过计时器对象clock
获取经过的时间,根据时间计算月球绕行轨道的位置,以及地球的自转角度。 -
使用渲染器对象
renderer
和labelRenderer
进行场景渲染。 -
使用浏览器提供的
requestAnimationFrame
函数递归调用ani
函数,实现动画效果。
完整代码展示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
background: url(https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2F1%2F590c2682cc04c.jpg%3Fdown&refer=http%3A%2F%2Fpic1.win4000.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1641349822&t=06301990af74d7bde9f5675864375620) no-repeat;
background-size: cover;
padding: 0;
margin: 0;
}
.label {
color: #ffffff;
font-size: 16px;
}
</style>
</head>
<body>
</body>
</html>
<script type="importmap">
{
"imports": {
"three": "./three.module.js"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from './jsm/controls/OrbitControls.js'
import { CSS2DRenderer, CSS2DObject } from './jsm/renderers/CSS2DRenderer.js'
var scene, camera, renderer, labelRenderer, controls;
var earth, moon;
let oldtime = 0;
// 用于测量时间
var clock = new THREE.Clock();
// 用于加载纹理贴图
const textureLoader = new THREE.TextureLoader();
// 分别表示 地球和月球的半径
const EARCH_RADIUS = 2.6;
const MOON_RADIUS = 0.3;
init();
ani();
//初始化
function init() {
//场景初始化
scene = new THREE.Scene();
//光源初始化 聚光灯光源对象
const light = new THREE.SpotLight(0xffffff);
//沿着z轴打光
light.position.set(0, 0, 50);
scene.add(light);
//物体初始化
let earthUrl = './earthUrl.jpg';
let moonUrl = './seleno.jpg';
// 应用纹理贴图
// sphereGeomery 函数负责创建球体几何体,参数包括纹理贴图的路径、半径、水平和垂直分段数
earth = sphereGeomery(earthUrl, EARCH_RADIUS, 16, 16);
moon = sphereGeomery(moonUrl, MOON_RADIUS, 16, 16);
// 创建标签
createLabel(earth, '地球', EARCH_RADIUS);
createLabel(moon, '月亮', MOON_RADIUS);
// 将地球和月球添加到场景中,使它们可以在渲染中显示
scene.add(earth);
scene.add(moon);
//创建了一个 Three.js 透视相机对象
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 500);
//设置相机的位置
camera.position.set(0, 0, 20);
//创建了一个 Three.js WebGL 渲染器对象
renderer = new THREE.WebGLRenderer({
antialias: true,//消除锯齿
alpha: true //透明
});
// 设置渲染器的尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
// 像素比例
document.body.appendChild(renderer.domElement);
// 创建了一个用于渲染 CSS2DObject 的渲染器对象,并设置其尺寸与窗口大小一致
labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
// 将 CSS2D 渲染器的 DOM 元素添加到 HTML 文档的 <body> 元素中,并将其设置为绝对定位并置于页面顶部
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
document.body.appendChild(labelRenderer.domElement);
// camera 是要进行控制的相机对象,document.body 是用于监听鼠标和触摸事件的 DOM 元素。
controls = new OrbitControls(camera, document.body);
controls.maxPolarAngle = 0.9 * Math.PI / 2
controls.enableZoom = true
controls.enableDamping = true
}
//创建物体
function sphereGeomery(textureUrl, radius, widthSegments, heightSegments) {
// 创建一个 Three.js 的球体几何体对象
// 参数包括球体的半径、水平分段数和垂直分段数
let geometry = new THREE.SphereGeometry(radius, widthSegments, heightSegments);
// 创建了一个基于 Phong 光照模型的网格材质对象 MeshPhongMaterial,并为其指定了一个纹理贴图
let material = new THREE.MeshPhongMaterial({
// 加载指定 URL 的纹理贴图,并将其赋值给材质对象的 map 属性
map: textureLoader.load(textureUrl)
});
// 创建的几何体和材质创建一个网格对象 Mesh,并将几何体与材质进行组合
let mesh = new THREE.Mesh(geometry, material);
return mesh;
}
//创建标签
function createLabel(mesh, text, radius) {
const div = document.createElement('div');
div.className = 'label';
div.textContent = text;
// 创建的 <div> 元素包装在 CSS2DObject 对象中
const label = new CSS2DObject(div);
// 设置标签元素的位置。它将标签元素放置在与球体物体相对应的位置上方,radius + 0.5 表示在球体半径的基础上向上偏移 0.5 的距离
label.position.set(0, radius + 0.5, 0);
// 将标签元素添加到传递进来的 mesh 对象中,使标签元素与物体绑定
mesh.add(label);
}
//动画
function ani() {
// clock 是 Three.js 提供的一个计时器对象,getElapsedTime 方法可以返回自上次重置以来经过的时间
const elapsed = clock.getElapsedTime();
// 设置 moon 对象的位置实现月球的绕行效果。利用正弦函数和余弦函数计算出月球在 x、y 和 z 轴上的坐标,从而使其沿着一个圆形轨道绕着中心点运动
moon.position.set(Math.sin(elapsed) * 5, 0, Math.cos(elapsed) * 5);
// 向量 (0, 1, 0) 表示在三维空间中的 Y 轴方向。该向量可以用于进行旋转操作,指定物体绕着 Y 轴旋转
var axis = new THREE.Vector3(0, 1, 0);
// rotateOnAxis 方法实现地球的自转效果
// 接受一个向量和一个旋转角度作为参数,使物体围绕指定轴进行旋转
// 这里根据经过的时间差计算出旋转角度,并乘以一个系数,控制旋转的速度
earth.rotateOnAxis(axis, (elapsed - oldtime) * Math.PI / 10);
// 渲染
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
// 将当前的时间值存储到变量 oldtime 中,以便在下一帧计算地球自转的角度变化
oldtime = elapsed;
// 使用浏览器提供的 requestAnimationFrame 函数,将 ani 函数递归调用,以便在下一帧执行动画效果
requestAnimationFrame(ani);
}
</script>
注解:
new THREE.SphereGeometry(radius, widthSegments, heightSegments) 水平分段数和垂直分段数
-
水平分段数(
widthSegments
)指的是在球体的水平方向上将经度划分成多少段。较高的数值会在水平方向上增加更多的面片,使球体表面更加平滑。这决定了球体经线(经度线)的数量。 -
垂直分段数(
heightSegments
)指的是在球体的垂直方向上将纬度划分成多少段。同样地,较高的数值会在垂直方向上增加更多的面片,使球体表面更加平滑。这决定了球体纬线(纬度线)的数量。
new THREE.MeshPhongMaterial()是什么
Phong是一种经典的着色模型,可以模拟光照效果
着色模型在Three.js中的作用是定义物体表面的外观,决定了物体在渲染时呈现出的效果
在Three.js中,有多种着色模型可用于渲染网格模型的表面,一些常见的着色模型:
-
基础着色模型(Basic Material):最简单的着色模型,不考虑光照和阴影效果,只显示材质的基本颜色。
-
基础光照着色模型(Lambert Material):使用Lambert光照模型,考虑漫反射光照,适用于呈现无光泽的物体。
-
Phong着色模型(Phong Material):使用Phong光照模型,考虑漫反射、镜面反射和环境光照。可以实现光滑的表面效果。
-
Blinn-Phong着色模型(MeshStandardMaterial):是Phong模型的一种变体,使用Blinn-Phong光照模型,相对于Phong模型计算更加高效。
-
物理着色模型(Physical Material):基于真实物理原理的光照模型,考虑漫反射、镜面反射、金属度、粗糙度等参数。可以实现高度逼真的外观效果。