在这个科技飞速发展、创意不断涌现的时代,许多前沿技术的应用令人目不暇接。就像小米 SU7 凭借 webgl 技术带来的震撼 3D 交互体验,打破了传统 2D 的局限,在数字孪生、VR、游戏等领域绽放出耀眼光芒,通过抖音等Android/iOS 应用以及 html5 网页实现了广泛传播,掀起了一股科技潮流。这一创新成果给我带来了灵感,让我思考能否以一种独特的视角 —— 类似于摄影的视角,去实现一个 3D 旋转地球的效果。
在摄影中,我们通过相机镜头捕捉真实世界的精彩瞬间,而在虚拟世界里实现 3D 旋转地球,HTML5 中的 canvas 标签就如同我们的 “镜头”。它是一个能够在网页上绘制各种图形的区域,为我们开启了通往虚拟创作的大门。而绘制上下文(context)则像是相机的设置参数,决定了我们如何在这个 “镜头” 中呈现出想要的画面。
在实现 3D 旋转地球的过程中,我们借助了强大的 Three.js 框架,它为我们简化了许多复杂的 3D 图形处理工作。以下是实现该功能的具体代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D 地球</title>
<!-- 画地球 选择框架 加速 -->
<script src="https://cdn.bootcss.com/three.js/r83/three.min.js"></script>
</head>
<body>
<canvas id="webglcanvas"></canvas>
<script>
// 3D 地球
// 3D 世界就是镜头拍出的世界 导演
let canvas, // 3D 容器
camera, // 镜头
scene,// 场景
renderer,// 渲染器
group;// 组
// 物品
let mouseX = 0, mouseY = 0; // mousemove 坐标
let windowHalfx = window.innerWidth / 2;
let windowHalfY = window.innerHeight / 2;// 球心
init();
animate();
// 准备
function init() {
canvas = document.getElementById('webglcanvas');// DOM
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 2000);// 实例化 相机
// 相机离 scene 场景的距离
camera.position.z = 500; // 相机位置
scene = new THREE.Scene();// 实例化 场景
scene.background = new THREE.Color(0xffffff);// 背景颜色
group = new THREE.Group();// 实例化 组
scene.add(group);// 场景添加组
// 纹理加载器
let loader = new THREE.TextureLoader(); // 简单的加载器
loader.load('land_ocean_ice_cloud_2048.jpg', function (texture) {
// 纹理
let geometry = new THREE.SphereGeometry(200, 20, 20);// 球的几何
// 材质
let material = new THREE.MeshBasicMaterial({
map: texture
});
// 球
let mesh = new THREE.Mesh(geometry, material);// 网络
group.add(mesh);// 组添加球
// 渲染器 目标是 canvas
renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
// renderer.render(scene, camera);
document.addEventListener('mousemove', onDocumentMouseMove, false);
})
}
function onDocumentMouseMove(event) {
// 鼠标移动的坐标
mouseX = (event.clientX - windowHalfx);
mouseY = (event.clientY - windowHalfY);
}
function animate() {
// 递归 屏幕的刷帧率
requestAnimationFrame(animate);
render()
}
function render() {
camera.position.x += (mouseX - camera.position.x) * 0.05;
camera.position.y += (-mouseY - camera.position.y) * 0.05;
camera.lookAt(scene.position);
group.rotation.y -= 0.005;
// 渲染
renderer.render(scene, camera);
//requestAnimationFrame(render);
}
</script>
</body>
</html>
当我们准备开始绘制 3D 旋转地球时,使用 beginPath() 方法就如同摄影师准备按下快门之前的构图过程。在代码中,我们通过一系列的初始化操作来搭建这个虚拟的 3D 世界。
首先,获取 canvas 元素,它将作为我们展示 3D 内容的容器:
canvas = document.getElementById('webglcanvas');
然后,实例化相机(camera),这就如同摄影师手中的相机,决定了我们观察 3D 场景的视角:
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 2000);
camera.position.z = 500;
这里 THREE.PerspectiveCamera 的参数分别表示视角、宽高比、近裁剪平面和远裁剪平面,而 camera.position.z 则设置了相机离场景的距离。
接着,创建场景(scene),它是我们放置所有 3D 物体的空间:
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
这里我们还设置了场景的背景颜色为白色。
为了方便管理多个物体,我们创建一个组(group),并将其添加到场景中:
group = new THREE.Group();
scene.add(group);
接下来,我们需要加载地球的纹理,使地球看起来更加真实。通过 TextureLoader 来加载纹理图片:
let loader = new THREE.TextureLoader();
loader.load('land_ocean_ice_cloud_2048.jpg', function (texture) {
// 纹理加载成功后的操作
});
在纹理加载成功的回调函数中,我们创建地球的几何形状(geometry)和材质(material),并将它们组合成一个网格(mesh),也就是我们的地球:
let geometry = new THREE.SphereGeometry(200, 20, 20);
let material = new THREE.MeshBasicMaterial({
map: texture
});
let mesh = new THREE.Mesh(geometry, material);
group.add(mesh);
这里 THREE.SphereGeometry 定义了地球的半径、经度分段数和纬度分段数,THREE.MeshBasicMaterial 则定义了材质,将加载的纹理应用到地球表面。
最后,创建渲染器(renderer),它负责将我们创建的 3D 场景渲染到 canvas 上:
renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
为了实现地球的旋转以及相机跟随鼠标移动的效果,我们还需要添加鼠标移动事件的监听函数 onDocumentMouseMove 和动画循环函数 animate、render:
function onDocumentMouseMove(event) {
mouseX = (event.clientX - windowHalfx);
mouseY = (event.clientY - windowHalfY);
}
function animate() {
requestAnimationFrame(animate);
render()
}
function render() {
camera.position.x += (mouseX - camera.position.x) * 0.05;
camera.position.y += (-mouseY - camera.position.y) * 0.05;
camera.lookAt(scene.position);
group.rotation.y -= 0.005;
renderer.render(scene, camera);
}
在 onDocumentMouseMove 函数中,我们获取鼠标的当前位置,并计算出相对于窗口中心的偏移量。在 render 函数中,根据鼠标的偏移量来调整相机的位置,同时让地球绕 y 轴旋转,最后通过渲染器将场景和相机的状态渲染出来。
地球,作为一个复杂而神秘的球体,要在 2D 的 canvas 上呈现出逼真的 3D 旋转效果,需要我们像摄影师研究光线和物体的关系一样,深入研究各种数学知识和绘图技巧。我们要模拟地球表面的光影变化,考虑不同地区的地形地貌所呈现出的独特纹理,如同摄影师捕捉自然景观中的细节一样,让每一个像素都能展现出地球的魅力。
在逐步完成地球的轮廓绘制以及细节填充后,closePath() 方法就如同摄影师拍摄完成后对作品的最后整理。在代码中,虽然没有直接体现 closePath() 这样的操作,但整个渲染过程就像是为我们的 “作品” 进行最后的完善,确保画面的完整性和视觉效果的完美。
在实现 3D 旋转地球的过程中,批量声明变量是一个能够提高代码效率和可读性的小技巧。这就好比摄影师在拍摄前整理好自己的摄影装备,将各种工具分类摆放,方便随时取用。在代码开头我们批量声明了多个变量:
let canvas, // 3D 容器
camera, // 镜头
scene,// 场景
renderer,// 渲染器
group;// 组
这样的方式让代码更加简洁明了,减少了冗余,使整个实现过程更加顺畅。
以摄影视角实现 3D 旋转地球,这不仅仅是一个技术上的实践,更是一种思维方式的转变。摄影让我们学会观察世界、捕捉美好,而将这种视角应用到技术实现中,让我们能够以一种全新的方式去创造和表达。通过这个项目,我希望能够像摄影师用镜头记录世界一样,用代码描绘出地球的壮丽与神秘,让更多人感受到科技与艺术融合所带来的奇妙体验。
在未来的探索中,我相信这种跨领域的思维方式将为我们带来更多的创新可能。无论是在技术领域还是艺术领域,只要我们勇于尝试、敢于突破,就一定能够创造出更多令人惊叹的作品,为这个充满无限可能的世界增添更多的色彩。