joken-前端工程师

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: :: :: 管理 ::
  404 随笔 :: 39 文章 :: 8 评论 :: 20万 阅读

效果

https://threejs.org/examples/#webgl_interactive_cubes
image

代码地址

https://github.com/mrdoob/three.js/blob/master/examples/webgl_interactive_cubes.html

以下是对这段代码的详细解释,按照功能模块逐步解析:


1. HTML 部分

基础结构

<!DOCTYPE html>
<html lang="en">
<head>
    <title>three.js webgl - interactive cubes</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <link type="text/css" rel="stylesheet" href="main.css">
    <style>
        body {
            background-color: #f0f0f0;
            color: #444;
        }
        a {
            color: #08f;
        }
    </style>
</head>
<body>
  • 定义了一个标准的 HTML5 文档。
  • 设置了页面标题为 three.js webgl - interactive cubes
  • 使用 <meta> 标签确保页面在移动设备上正确显示(禁用缩放)。
  • 内联样式设置了页面背景颜色和链接颜色。

信息展示

<div id="info">
    <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - interactive cubes
</div>
  • 在页面中添加了一个 <div> 元素,用于显示三.js 的链接和示例名称。

模块导入

<script type="importmap">
    {
        "imports": {
            "three": "../build/three.module.js",
            "three/addons/": "./jsm/"
        }
    }
</script>
  • 使用 <script type="importmap"> 定义模块路径:
    • three 指向 three.js 的主模块文件。
    • three/addons/ 指向附加模块目录。

2. JavaScript 部分

导入依赖

<script type="module">
    import * as THREE from 'three';
    import Stats from 'three/addons/libs/stats.module.js';
  • 使用 ES6 模块语法导入 three.js 和 Stats 工具:
    • THREE 是 three.js 的核心库。
    • Stats 用于监控性能(帧率、内存占用等)。

变量声明

let stats;
let camera, scene, raycaster, renderer;

let INTERSECTED;
let theta = 0;

const pointer = new THREE.Vector2();
const radius = 5;
  • stats:用于存储性能统计工具实例。
  • camera:相机对象。
  • scene:场景对象。
  • raycaster:射线检测工具。
  • renderer:渲染器。
  • INTERSECTED:存储当前被鼠标悬停的物体。
  • theta:控制相机旋转角度。
  • pointer:存储鼠标的标准化设备坐标。
  • radius:相机绕场景中心旋转的半径。

初始化函数 (init)

function init() {
    // 创建相机
    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);

    // 创建场景
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xf0f0f0);

    // 添加光源
    const light = new THREE.DirectionalLight(0xffffff, 3);
    light.position.set(1, 1, 1).normalize();
    scene.add(light);

    // 创建随机分布的立方体
    const geometry = new THREE.BoxGeometry();
    for (let i = 0; i < 2000; i++) {
        const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff }));
        object.position.x = Math.random() * 40 - 20;
        object.position.y = Math.random() * 40 - 20;
        object.position.z = Math.random() * 40 - 20;
        object.rotation.x = Math.random() * 2 * Math.PI;
        object.rotation.y = Math.random() * 2 * Math.PI;
        object.rotation.z = Math.random() * 2 * Math.PI;
        object.scale.x = Math.random() + 0.5;
        object.scale.y = Math.random() + 0.5;
        object.scale.z = Math.random() + 0.5;
        scene.add(object);
    }

    // 创建渲染器
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setAnimationLoop(animate);
    document.body.appendChild(renderer.domElement);

    // 添加性能统计工具
    stats = new Stats();
    document.body.appendChild(stats.dom);

    // 监听窗口调整事件
    window.addEventListener('resize', onWindowResize);

    // 监听鼠标移动事件
    document.addEventListener('mousemove', onPointerMove);
}
  • 相机:创建了一个透视相机,设置视场角、宽高比、近裁剪面和远裁剪面。
  • 场景:创建了一个新场景,并设置了背景颜色。
  • 光源:添加了一个方向光,用于照亮场景中的物体。
  • 立方体生成:通过循环生成了 2000 个随机位置、旋转角度、缩放比例和颜色的立方体。
  • 渲染器:创建了一个 WebGL 渲染器,并将其添加到页面中。
  • 性能统计工具:初始化并显示 Stats 工具。
  • 事件监听
    • 窗口调整时调用 onWindowResize
    • 鼠标移动时调用 onPointerMove

窗口调整事件处理

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}
  • 当窗口大小发生变化时,更新相机的宽高比和渲染器的尺寸。

鼠标移动事件处理

function onPointerMove(event) {
    pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
    pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
  • 将鼠标的屏幕坐标转换为标准化设备坐标(范围从 -1 到 1)。

动画与渲染

function animate() {
    render();
    stats.update();
}

function render() {
    theta += 0.1;

    camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta));
    camera.position.y = radius * Math.sin(THREE.MathUtils.degToRad(theta));
    camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
    camera.lookAt(scene.position);

    camera.updateMatrixWorld();

    // 射线检测
    raycaster.setFromCamera(pointer, camera);
    const intersects = raycaster.intersectObjects(scene.children, false);

    if (intersects.length > 0) {
        if (INTERSECTED !== intersects[0].object) {
            if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
            INTERSECTED = intersects[0].object;
            INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
            INTERSECTED.material.emissive.setHex(0xff0000);
        }
    } else {
        if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
        INTERSECTED = null;
    }

    renderer.render(scene, camera);
}
  • 动画循环
    • 调用 render 函数进行渲染。
    • 更新性能统计信息。
  • 渲染逻辑
    • 更新相机的位置,使其围绕场景中心旋转。
    • 使用 Raycaster 检测鼠标与场景中物体的交点。
    • 如果检测到交点,则高亮显示被选中的立方体(红色自发光效果)。
    • 最后调用渲染器绘制场景。

总结

这段代码实现了一个交互式的 3D 场景,主要功能包括:

  1. 创建一个包含 2000 个随机分布立方体的场景。
  2. 使用射线检测 (Raycaster) 实现鼠标悬停高亮效果。
  3. 动态调整相机位置,提供环绕观察的效果。
  4. 添加性能统计工具 (Stats) 以监控帧率和内存占用。

通过这些功能,用户可以体验到一个动态且交互性强的 3D 场景。

posted on   joken1310  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2018-03-04 vi 使用小结
点击右上角即可分享
微信分享提示