当前位置: 首页 > news >正文

用JavaScript构建3D程序

用JavaScript构建3D程序

概述

使用 JavaScript 构建 3D 程序通常依赖于现成的库或框架来实现高效开发。JavaScript可用的主流 3D 库有Three.js、Babylon.js等

两者对比

特性

Three.js

Babylon.js

核心功能

专注于渲染

渲染 + 内置物理引擎

物理引擎支持

需要集成第三方库(如 Cannon.js)

内置 Cannon.js / Oimo.js / Ammo.js

灵活性

高(开发者可以自由选择物理引擎)

较低(依赖内置的物理引擎)

学习曲线

较低(API 简洁)

较高(API 更复杂,功能更多)

适用场景

WebGL/WebXR、轻量级项目

WebGL/WebXR、游戏开发

学习路径的差异

  • Three.js 的学习路径更线性:从基础几何体、材质、光照到高级功能(如着色器、后期处理)。
  • Babylon.js 的学习路径更广泛:除了渲染功能,还需要学习物理引擎、动画系统、GUI 系统等

Three.js 和 Babylon.js的坐标系比较

Three.js 坐标系

Three.js 使用的是 左手坐标系(Left-Handed Coordinate System)。具体特点如下:

  • X:水平方向,指向右侧。
  • Y:垂直方向,指向上方。
  • Z:深度方向,指向屏幕外(从相机的角度来看,即远离你)。

在 Three.js 中,原点 (0, 0, 0) 通常位于场景的中心,物体的位置是相对于这个原点的坐标来设定的。

Babylon.js 坐标系

Babylon.js 使用的是 右手坐标系(Right-Handed Coordinate System)。其特点与 Three.js 有一些不同,具体如下:

  • X:水平方向,指向右侧。
  • Y:垂直方向,指向上方。
  • Z:深度方向,指向屏幕内(从相机的角度来看,即靠近你)。

在 Babylon.js 中,原点(0, 0, 0)也通常位于场景的中心,物体的坐标是相对于这个原点来设定的。与 Three.js 不同的是,Babylon.js 的 Z 轴朝向相机方向。

说明:大多数图形硬件(如 OpenGL)使用的是左手坐标系,因此 Three.js 选择使用左手坐标系。而 Babylon.js 是为了与 DirectX 兼容,DirectX 使用的是右手坐标系。

Three.js

Three.js是最流行的 Web 3D 库,文档丰富,适合快速原型开发。

英文文档 https://threejs.org/docs/

中文文档 https://threejs.org/docs/index.html#manual/zh/introduction/Creating-a-scene

核心概念

场景 (Scene): 场景是 Three.js 中所有对象的容器。你可以将物体、光源、相机等添加到场景中。

相机 (Camera): 相机决定了场景中的哪些部分会被渲染。常用的相机类型包括透视相机 (PerspectiveCamera) 和正交相机 (OrthographicCamera)。

渲染器 (Renderer): 渲染器负责将场景和相机的内容渲染到网页上。常用的渲染器是 WebGLRenderer。

几何体 (Geometry): 几何体定义了物体的形状,例如立方体 (BoxGeometry)、球体 (SphereGeometry) 等。

材质 (Material): 材质定义了物体的外观,例如颜色、纹理、光照效果等。常用的材质包括 MeshBasicMaterial、MeshPhongMaterial 等。

网格 (Mesh): 网格是几何体和材质的组合,表示一个可渲染的物体。

环境搭建

<!-- 通过 CDN 引入 -->

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

CDN原理与应用简要介绍 https://blog.csdn.net/cnds123/article/details/126268941

JavaScript 3D动画库three.js入门篇 https://blog.csdn.net/cnds123/article/details/121060565

以下是一个简单的 Three.js 示例代码,用于创建一个旋转的立方体:

效果图:

源码如下:

<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Three.js 示例:旋转的立方体</title><style>body {margin: 0;padding: 0;width: 100%;height: 100%;display: flex;justify-content: center; /* 水平居中 */align-items: center;    /* 垂直居中 */}canvas {display: block;            }</style>
</head>
<body><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script><script>// 创建场景const scene = new THREE.Scene();// 创建相机const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.z = 5;// 创建渲染器const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);// 创建立方体材质(6种不同颜色)const materials = [new THREE.MeshBasicMaterial({ color: 0xff0000 }), // 右面 - 红new THREE.MeshBasicMaterial({ color: 0x00ff00 }), // 左面 - 绿new THREE.MeshBasicMaterial({ color: 0x0000ff }), // 上面 - 蓝new THREE.MeshBasicMaterial({ color: 0xffff00 }), // 下面 - 黄new THREE.MeshBasicMaterial({ color: 0xff00ff }), // 前面 - 品红new THREE.MeshBasicMaterial({ color: 0x00ffff })  // 后面 - 青];// 创建立方体并应用多材质const geometry = new THREE.BoxGeometry();const cube = new THREE.Mesh(geometry, materials);scene.add(cube);// 动画循环function animate() {requestAnimationFrame(animate);cube.rotation.x += 0.01;cube.rotation.y += 0.01;renderer.render(scene, camera);}animate();</script>
</body>
</html>

修改上面的Three.js示例,给立方体加上纹理,并且能用鼠标拖动立方体的位置。

效果图:

使用 TextureLoader 加载棋盘格纹理(示例 URL 来自 Three.js 官方资源)

鼠标拖拽,事件绑定:mousedown 开始拖拽,mousemove 更新位置,mouseup 结束拖拽。

源码如下:

<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Three.js 纹理 + 拖拽立方体</title><style>body { margin: 0; }canvas { display: block; cursor: grab; }canvas:active { cursor: grabbing; }</style>
</head>
<body><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script><script>// 初始化场景、相机、渲染器const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);// 加载纹理贴图(这里使用棋盘格纹理示例)const textureLoader = new THREE.TextureLoader();const texture = textureLoader.load('https://threejs.org/examples/textures/checker.png');texture.wrapS = THREE.RepeatWrapping;texture.wrapT = THREE.RepeatWrapping;texture.repeat.set(4, 4); // 重复纹理4x4次// 创建立方体(应用纹理)const geometry = new THREE.BoxGeometry();const material = new THREE.MeshPhongMaterial({ map: texture, // 使用纹理贴图color: 0xffffff });const cube = new THREE.Mesh(geometry, material);scene.add(cube);// 添加光源const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);scene.add(ambientLight);const pointLight = new THREE.PointLight(0xffffff, 1, 100);pointLight.position.set(10, 10, 10);scene.add(pointLight);camera.position.z = 5;// --- 拖拽逻辑 ---let isDragging = false;let previousMousePosition = { x: 0, y: 0 };const raycaster = new THREE.Raycaster();const mouse = new THREE.Vector2();// 鼠标事件处理function onMouseDown(event) {// 计算鼠标归一化坐标mouse.x = (event.clientX / window.innerWidth) * 2 - 1;mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;// 检测点击物体raycaster.setFromCamera(mouse, camera);const intersects = raycaster.intersectObject(cube);if (intersects.length > 0) {isDragging = true;previousMousePosition = {x: event.clientX,y: event.clientY};}}function onMouseMove(event) {if (!isDragging) return;// 计算移动增量const deltaMove = {x: event.clientX - previousMousePosition.x,y: event.clientY - previousMousePosition.y};// 将屏幕移动转换为3D空间移动(基于相机视角)const deltaX = deltaMove.x * 0.01;const deltaY = -deltaMove.y * 0.01; // Y轴方向反转cube.position.x += deltaX;cube.position.y += deltaY;previousMousePosition = {x: event.clientX,y: event.clientY};}function onMouseUp() {isDragging = false;}// 绑定事件window.addEventListener('mousedown', onMouseDown);window.addEventListener('mousemove', onMouseMove);window.addEventListener('mouseup', onMouseUp);// 动画循环function animate() {requestAnimationFrame(animate);cube.rotation.x += 0.005;cube.rotation.y += 0.005;renderer.render(scene, camera);}animate();// 窗口大小调整window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);});</script>
</body>
</html>

Babylon.js

Babylon.js 内置物理引擎(Cannon.js / Oimo.js / Ammo.js),更适合游戏开发。

英文文档 https://doc.babylonjs.com/

中文文档 https://shawn0326.github.io/babylon-doc-cn/#/

Babylonjs中文网 https://doc.cnbabylon.com/

核心概念

Babylon.js 是一个功能强大的 WebGL 3D 引擎,支持从简单的场景到复杂的游戏开发。以下是其核心概念:

场景 (Scene): 场景是所有对象的容器,包括网格、光源、相机等。

网格 (Mesh): 网格是几何体和材质的组合,表示一个可渲染的物体。

材质 (Material): 材质定义了物体的外观,例如颜色、纹理、光照效果等。

光源 (Light): Babylon.js 支持多种光源类型,如点光源 (PointLight)、平行光 (DirectionalLight)、聚光灯 (SpotLight) 等。

相机 (Camera): 相机决定了场景中的哪些部分会被渲染。常用的相机类型包括自由相机 (FreeCamera)、弧形旋转相机 (ArcRotateCamera) 等。

物理引擎 (Physics Engine): Babylon.js 内置了物理引擎支持(如 Cannon.js、Oimo.js),可以轻松实现碰撞检测、重力效果等。

环境搭建

<!-- 通过 CDN 引入 -->

<script src="https://cdn.babylonjs.com/babylon.js"></script>

地面上静止的小球

效果图:

源码如下:

<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Babylon.js 示例:地面上的小球</title><style>html, body {width: 100%;height: 100%;margin: 0;padding: 0;overflow: hidden; /* 防止页面滚动 */}#renderCanvas {width: 100%;height: 100%;touch-action: none; /* 禁用触摸默认行为,避免与 3D 交互冲突 */}</style>
</head>
<body><!-- 用于渲染 3D 场景的画布 --><canvas id="renderCanvas"></canvas><!-- 引入 Babylon.js 库 --><script src="https://cdn.babylonjs.com/babylon.js"></script><script>// 获取画布元素const canvas = document.getElementById("renderCanvas");// 初始化 Babylon.js 引擎,传入画布和是否启用抗锯齿const engine = new BABYLON.Engine(canvas, true);// 创建场景的函数const createScene = function () {// 创建一个新的场景const scene = new BABYLON.Scene(engine);// 创建一个自由相机,设置其初始位置为 (0, 5, -10)const camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);// 设置相机的目标点为场景原点 (0, 0, 0)camera.setTarget(BABYLON.Vector3.Zero());// 将相机绑定到画布,允许用户通过鼠标/触摸控制相机camera.attachControl(canvas, true);// 创建一个半球光,设置其方向为 (0, 1, 0),即从上方照射const light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);// 设置光的强度为 0.7light.intensity = 0.7;// 创建一个球体,直径为 2,分段数为 32(更高的分段数会使球体更平滑)const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2, segments: 32 }, scene);// 将球体的 Y 轴位置设置为 1,使其悬浮在地面上方sphere.position.y = 1;// 创建一个地面,宽度和高度均为 6const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 6, height: 6 }, scene);// 返回创建好的场景return scene;};// 调用 createScene 函数创建场景const scene = createScene();// 启动渲染循环,每帧调用 scene.render() 渲染场景engine.runRenderLoop(function () {scene.render();});// 监听窗口大小变化事件,调整引擎的画布大小window.addEventListener("resize", function () {engine.resize();});</script>
</body>
</html>

修改:单击球体可演示弹跳——模拟重力下的地面上的小球弹跳情况,单击球体弹跳。

效果图:

源码如下:

<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Babylon.js 示例:单击球体可演示弹跳</title><style>html, body {width: 100%;height: 100%;margin: 0;padding: 0;overflow: hidden;}#renderCanvas {width: 100%;height: 100%;touch-action: none;}</style>
</head>
<body><canvas id="renderCanvas"></canvas><script src="https://cdn.babylonjs.com/babylon.js"></script><script>const canvas = document.getElementById("renderCanvas");const engine = new BABYLON.Engine(canvas, true);// --- 全局变量声明 ---let sphere; // 将 sphere 提升到全局作用域let isBouncing = false;let verticalSpeed = 0;const gravity = -9.8;        // 使用更真实的重力值const damping = 0.7;const initialJumpForce = 5;  // 调整初始弹跳力const createScene = function () {const scene = new BABYLON.Scene(engine);// 相机设置const camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);camera.setTarget(BABYLON.Vector3.Zero());camera.attachControl(canvas, true);// 灯光设置const light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);light.intensity = 0.7;// 创建球体(确保 sphere 是全局变量)sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2, segments: 32 }, scene);sphere.position.y = 1; // 初始位置高于地面// 创建地面const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 6, height: 6 }, scene);// --- 点击事件绑定 ---sphere.actionManager = new BABYLON.ActionManager(scene);sphere.actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPickTrigger,() => {if (!isBouncing) {verticalSpeed = initialJumpForce;isBouncing = true;}}));return scene;};const scene = createScene();// --- 渲染循环修复 ---engine.runRenderLoop(() => {if (isBouncing) {// 使用更精确的时间计算(deltaTime 单位为毫秒)verticalSpeed += gravity * scene.deltaTime / 1000;sphere.position.y += verticalSpeed * scene.deltaTime / 1000;// 地面碰撞检测(地面Y坐标为0,球体半径为1)if (sphere.position.y <= 1) {sphere.position.y = 1; // 防止穿透地面verticalSpeed = -verticalSpeed * damping;// 停止条件if (Math.abs(verticalSpeed) < 0.5) {isBouncing = false;verticalSpeed = 0;}}}scene.render();});window.addEventListener("resize", () => {engine.resize();});</script>
</body>
</html>

Babylon.js 实现滚球吃金币游戏

游戏说明

☆操作方式

方向键 或 WASD:控制球体滚动,注意控制不要让球在场地边缘跌落。60秒后,可用空格键重新开始游戏。

☆规则

每收集一个金币(球体碰到金币)得10分并移除金币;

60秒内尽可能获得更高分数。

运行效果:

源码如下:

<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>滚球吃金币 - 修复版</title><style>html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; }#renderCanvas { width: 100%; height: 100%; touch-action: none; }#ui { position: fixed; top: 20px; left: 20px; color: white; font-family: Arial; }</style>
</head>
<body><canvas id="renderCanvas"></canvas><div id="ui"><div>分方向键 或 WASD:控制球体滚动,注意控制不要让球在场地边缘跌落。60秒后,可用空格键重新开始游戏。</div> <div>分数: <span id="score">0</span></div><div>时间: <span id="timer">30</span>秒</div><div id="gameOver" style="display: none;">游戏结束! 最终分数: <span id="finalScore">0</span></div></div><script src="https://cdn.babylonjs.com/babylon.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script><script>const canvas = document.getElementById("renderCanvas");const engine = new BABYLON.Engine(canvas, true);const scene = new BABYLON.Scene(engine);let score = 0, timeLeft = 30, isGameActive = false, sphere;//确保输入焦点在 Canvas 上window.addEventListener("load", () => {canvas.focus();});// 物理引擎初始化scene.enablePhysics(new BABYLON.Vector3(0, -9.81, 0), new BABYLON.CannonJSPlugin());// 光源const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);light.intensity = 0.7;// 相机(彻底禁用鼠标控制)const camera = new BABYLON.ArcRotateCamera("camera",Math.PI / 2,Math.PI / 4,10,new BABYLON.Vector3(0, 2, 0),scene);camera.inputs.clear(); // 清除所有输入camera.attachControl(canvas, false); // 禁用默认控制// 地面const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 10, height: 10 }, scene);ground.physicsImpostor = new BABYLON.PhysicsImpostor(ground,BABYLON.PhysicsImpostor.BoxImpostor,{ mass: 0, friction: 0.5, restitution: 0.3 },scene);// 玩家球体const createPlayer = () => {sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 0.8 }, scene);sphere.position.y = 1;sphere.physicsImpostor = new BABYLON.PhysicsImpostor(sphere,BABYLON.PhysicsImpostor.SphereImpostor,{ mass: 1, friction: 0.2, restitution: 0.3 },scene);};// 金币生成const coins = [];const generateCoin = () => {const coin = BABYLON.MeshBuilder.CreateCylinder("coin", { diameter: 0.5, height: 0.1 }, scene);coin.position.set((Math.random() - 0.5) * 8, 0.5, (Math.random() - 0.5) * 8);coin.material = new BABYLON.StandardMaterial("coinMaterial", scene);coin.material.diffuseColor = new BABYLON.Color3(1, 0.84, 0);coin.physicsImpostor = new BABYLON.PhysicsImpostor(coin, BABYLON.PhysicsImpostor.CylinderImpostor, { mass: 0 }, scene);coins.push(coin);};// 输入控制(修复后的统一处理)const setupControls = () => {const inputMap = {};scene.actionManager = new BABYLON.ActionManager(scene);scene.actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnKeyDownTrigger, (evt) => {inputMap[evt.sourceEvent.key.toLowerCase()] = true;}));scene.actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnKeyUpTrigger, (evt) => {inputMap[evt.sourceEvent.key.toLowerCase()] = false;}));scene.onBeforeRenderObservable.add(() => {if (!isGameActive) return;const force = 5, forceVector = new BABYLON.Vector3(0, 0, 0);if (inputMap["arrowup"] || inputMap["w"]) forceVector.z -= force;if (inputMap["arrowdown"] || inputMap["s"]) forceVector.z += force;if (inputMap["arrowleft"] || inputMap["a"]) forceVector.x += force;if (inputMap["arrowright"] || inputMap["d"]) forceVector.x -= force;if (forceVector.length() > 0) {sphere.physicsImpostor.applyForce(forceVector, sphere.getAbsolutePosition());}});};// 碰撞检测const checkCollisions = () => {scene.onBeforeRenderObservable.add(() => {if (!isGameActive) return;coins.forEach((coin, index) => {if (coin.intersectsMesh(sphere)) {coin.dispose();coins.splice(index, 1);score += 10;document.getElementById("score").textContent = score;generateCoin();}});});};// 游戏逻辑const startGame = () => {isGameActive = true;score = 0; timeLeft = 60;  //document.getElementById("score").textContent = "0";document.getElementById("timer").textContent = "60";  //document.getElementById("gameOver").style.display = "none";if (sphere) sphere.dispose();coins.forEach(coin => coin.dispose());coins.length = 0;createPlayer();for (let i = 0; i < 5; i++) generateCoin();const timer = setInterval(() => {timeLeft--;document.getElementById("timer").textContent = timeLeft;if (timeLeft <= 0) {clearInterval(timer);isGameActive = false;document.getElementById("gameOver").style.display = "block";document.getElementById("finalScore").textContent = score;}}, 500);  //};// 初始化setupControls();checkCollisions();startGame();window.addEventListener("keydown", (e) => {if (e.code === "Space" && !isGameActive) startGame();});engine.runRenderLoop(() => scene.render());window.addEventListener("resize", () => engine.resize());</script>
</body>
</html>

http://www.xdnf.cn/news/190477.html

相关文章:

  • 2025系统架构师---论微服务架构及其应用
  • Linux中的系统延时任务和定时任务与时间同步服务和构建时间同步服务器
  • 老电脑优化全知道(包括软件和硬件优化)
  • 【爬虫】一文掌握 adb 的各种指令(adb备忘清单)
  • 【Mybatis】Mybatis基础
  • 集合框架篇-java集合家族汇总
  • 【3D基础】深入解析OBJ与MTL文件格式:Blender导出模型示例及3D开发应用
  • 【KWDB 创作者计划】_企业数据管理的利刃:技术剖析与应用实践
  • CMake:设置编译C++的版本
  • 【北京】昌平区某附小v3700存储双控故障维修案例
  • 分布式链路追踪理论
  • 【Axure视频教程】手电筒效果
  • 【题解-Acwing】867. 分解质因数
  • 【蒸馏(5)】DistillBEV代码分析
  • FPGA-DDS信号发生器
  • 3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目
  • 数据可视化
  • 【C++教程】三目运算符
  • Day8 鼠标控制与32位模式切换
  • AIGC重构元宇宙:从内容生成到沉浸式体验的技术革命
  • 临床试验概述:从定义到实践的关键要素
  • R 语言科研绘图第 43 期 --- 桑基图-冲击
  • 软件设计师速通其一:计算机内部数据表示
  • 数据库学习笔记(十三)---存储过程
  • OpenCV 图形API(68)图像与通道拼接函数------垂直拼接两个图像/矩阵的函数concatVert()
  • 手搓传染病模型(SEIR-拓展)
  • 深度对比:Objective-C与Swift的RunTime机制与底层原理
  • 深入理解缓存淘汰策略:LRU 与 LFU 算法详解及 Java 实现
  • 媒资管理之视频管理
  • Prompt Engineering 提示工程:释放大语言模型潜力的关键技术与实践指南