WebGPU渲染器BatchedMesh批量网格渲染分析

项目概述

项目名称

Codrops BatchedMesh & Post Processing Demo

项目类型

3D 交互式 Web 演示项目


开发者

Christophe Choffel (ULuCode)

项目来源

CODROPS - 2024年技术文章演示


一、项目背景与目标

1.1 项目背景

本项目是一个展示 Three.js 最新技术(WebGPU 渲染器、BatchedMesh 对象和高级后处理管线)的交互式 3D 演示。项目旨在向开发者展示如何使用现代 WebGL/WebGPU 技术创建高性能、视觉震撼的 Web 3D 应用。

1.2 核心目标

  • 展示 WebGPURenderer 的强大功能和性能优势
  • 演示 BatchedMesh 批量网格渲染技术,解决大量实例化对象的性能问题
  • 实现高级后处理效果(SSAO、景深、FXAA、暗角)
  • 提供流畅的交互式用户体验
  • 展示主题切换的平滑过渡动画

1.3 技术亮点

  1. 批量渲染优化:使用 BatchedMesh 将数千个方块合并为单个绘制调用
  2. 现代渲染管线:完整展示 WebGPU + TSL (Three.js Shading Language) 的强大功能
  3. 数学驱动动画:使用单纯形噪声和 Inigo Quilez 的数学函数实现自然波动效果
  4. 电影级视觉效果:组合多种后处理效果实现专业级画面

二、技术栈分析

2.1 前端框架与构建工具

技术栈 版本 用途
React 18.3.1 UI 框架,管理应用状态和界面
TypeScript 5.5.3 类型系统,提供类型安全
Vite 5.4.1 现代化构建工具,快速开发服务器
@vitejs/plugin-react 4.3.1 Vite 的 React 插件
vite-plugin-top-level-await 1.4.4 支持顶级 await 语法
ESLint 9.9.0 代码检查工具

2.2 3D 图形库

技术栈 版本 用途
Three.js 0.169.0 核心 3D 库
WebGPURenderer - WebGPU 渲染器(最新特性)
BatchedMesh - 批量网格渲染对象
PostProcessing - 后处理管线
UltraHDRLoader - 超高清图像加载器
OrbitControls - 轨道相机控制器
FastSimplexNoise 0.0.1-a2 快速单纯形噪声生成

2.3 开发工具

  • ESLint 9.9.0 - 代码质量检查
  • typescript-eslint 8.0.1 - TypeScript ESLint 插件

三、项目架构设计

3.1 整体架构图

复制代码
┌─────────────────────────────────────────────────────────┐
│                      React UI 层                         │
│  ┌──────────┐  ┌──────────┐  ┌──────────────────────┐ │
│  │  App.tsx │  │ App.css  │  │  threeCanvas.tsx      │ │
│  └──────────┘  └──────────┘  └──────────────────────┘ │
└─────────────────────────────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────┐
│                   Three.js 3D 渲染层                      │
│  ┌──────────────────────────────────────────────────┐   │
│  │  Demo.ts (核心逻辑类 - 497行)                    │   │
│  │  - 初始化管理                                    │   │
│  │  - 后处理管线配置                                │   │
│  │  - BatchedMesh 管理                             │   │
│  │  - 动画循环和更新                                │   │
│  └──────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘
                           │
           ┌───────────────┼───────────────┐
           ▼               ▼               ▼
┌─────────────────┐ ┌──────────────┐ ┌──────────────┐
│  组件层         │ │  工具库层    │ │  资源层      │
│                 │ │              │ │              │
│ threeCanvas.tsx │ │ ABlock.ts    │ │ blocks.glb   │
│                 │ │ BlockGeometry│ │ 天空盒贴图   │
│                 │ │ Pointer.ts   │ │              │
└─────────────────┘ └──────────────┘ └──────────────┘

3.2 目录结构

复制代码
codrops-batchedmesh-main/
├── src/                          # 源代码目录
│   ├── main.tsx                 # React 应用入口
│   ├── App.tsx                  # 主应用组件(104行)
│   ├── App.css                  # 应用样式(356行)
│   ├── Demo.ts                  # 核心 3D 演示逻辑(497行)
│   ├── vite-env.d.ts            # Vite 类型定义
│   ├── components/              # React 组件
│   │   └── threeCanvas.tsx      # Three.js 画布组件(48行)
│   └── lib/                     # 工具库
│       ├── ABlock.ts            # 方块数据模型(45行)
│       ├── BlockGeometry.ts     # 方块几何体管理(61行)
│       └── Pointer.ts           # 指针/交互处理(75行)
├── public/                      # 静态资源
│   ├── assets/
│   │   ├── models/
│   │   │   └── blocks.glb       # 9种方块3D模型
│   │   └── ultrahdr/
│   │       └── rustig_koppie_puresky_2k.jpg  # 天空盒贴图
│   └── favicon.ico
├── package.json                 # 项目配置和依赖
├── vite.config.ts               # Vite 构建配置(24行)
├── index.html                   # HTML 入口(17行)
├── tsconfig.json                # TypeScript 配置
├── eslint.config.js             # ESLint 配置
└── README.md                    # 项目说明文档

3.3 文件职责划分

文件 行数 主要职责
src/Demo.ts 497 核心 3D 逻辑,包含初始化、渲染循环、后处理、方块更新等
src/App.tsx 104 React 主组件,UI 状态管理、主题切换、加载状态
src/App.css 356 完整样式系统,主题变量、加载动画、响应式布局
src/lib/ABlock.ts 45 方块数据模型,定义方块属性和颜色
src/lib/BlockGeometry.ts 61 方块几何体管理,从 GLB 加载9种几何体
src/lib/Pointer.ts 75 指针交互处理,屏幕坐标转换和射线投射
src/components/threeCanvas.tsx 48 Canvas 组件包装器,初始化 Demo 实例

四、核心功能模块设计

4.1 WebGPU 渲染器初始化

位置 : Demo.ts:46-75 init() 方法

实现逻辑:

typescript 复制代码
1. 检测 WebGPU 可用性
   └── WebGPU.isAvailable() - 返回浏览器是否支持 WebGPU

2. 创建 WebGPURenderer
   └── new WebGPURenderer({ canvas, antialias: true })
   └── 配置像素比、尺寸、色调映射

3. 初始化相机和控制器
   └── PerspectiveCamera (FOV: 20°, 范围: 0.1-500)
   └── OrbitControls (启用阻尼,限制极角范围)

4. 初始化后处理管线
   └── 创建 PostProcessing 实例
   └── 配置 Scene Pass、AO Pass、DoF Pass、FXAA

5. 加载环境资源
   └── UltraHDR 天空盒
   └── 地面网格

6. 生成网格和创建 BatchedMesh
   └── initGrid() - 随机生成方块布局
   └── initBlocks() - 创建批量渲染网格

7. 启动渲染循环
   └── renderer.setAnimationLoop(this.animate.bind(this))

关键参数:

  • 相机位置: 初始角度 -120° (2π/3),距离 100 单位
  • 相机极角限制: maxPolarAngle = 7π/16 (约78.75°),避免穿入地面
  • 相机距离限制: 20-250 单位
  • 像素比: 1.0 (性能优先)
  • 色调映射: ACESFilmicToneMapping,曝光 0.9

4.2 BatchedMesh 批量渲染

位置 : Demo.ts:234-291 initBlocks() 方法

4.2.1 技术原理

BatchedMesh 是 Three.js 新增的批量渲染对象,允许将多个不同几何体的实例合并为单个绘制调用。其优势包括:

  • 性能优化: 数千个实例合并为 1-2 个 draw call
  • 灵活几何体: 支持混合多种不同几何体
  • 实例级控制: 每个实例可独立设置矩阵和颜色
4.2.2 实现步骤
复制代码
步骤 1: 计算顶点和索引总数
├── 遍历所有方块几何体
├── 统计每种几何体的顶点数和索引数
└── 累加所有方块的顶点/索引总数

步骤 2: 创建 BatchedMesh
├── 实例数量: 方块数 * 2 (顶部 + 底部)
├── 顶点数: 计算得到的总数
├── 索引数: 计算得到的总数
└── 材质: MeshPhysicalNodeMaterial

步骤 3: 添加几何体
├── 循环遍历 9 种方块几何体
└── 调用 blockMesh.addGeometry(geom)

步骤 4: 添加实例
├── 为每个方块添加底部实例
├── 为每个方块添加顶部实例
├── 设置底部颜色
└── 设置顶部颜色
4.2.3 关键代码
typescript 复制代码
// 材质配置
const mat = new MeshPhysicalNodeMaterial({
    roughness: 0.1,
    metalness: 0.0
});
mat.envMapIntensity = 0.25;

// 创建 BatchedMesh
const maxBlocks = this.blocks.length * 2; // 顶部 + 底部
this.blockMesh = new BatchedMesh(maxBlocks, totalV, totalI, mat);
this.blockMesh.sortObjects = false; // 禁用排序以提升性能

// 添加几何体
const geomIds = [];
for (let i = 0; i < geoms.length; i++) {
    geomIds.push(this.blockMesh.addGeometry(geoms[i]));
}

// 添加实例
for (let i = 0; i < this.blocks.length; i++) {
    const block = this.blocks[i];
    this.blockMesh.addInstance(geomIds[block.typeBottom]);
    this.blockMesh.addInstance(geomIds[block.typeTop]);
    this.blockMesh.setColorAt(i * 2, block.baseColor);
    this.blockMesh.setColorAt(i * 2 + 1, block.topColor);
}
4.2.4 性能指标
  • 方块数量: ~2000+ 个 (148x148 网格随机生成)
  • Draw Call: 从 4000+ 降至 1-2 个
  • 性能提升: 约 100x-1000x (取决于硬件)

4.3 网格生成算法

位置 : Demo.ts:161-230 initGrid() 方法

4.3.1 算法设计

在 148x148 的区域内随机生成方块,使用占用网格(Occupancy Grid)算法避免重叠。

4.3.2 实现流程
复制代码
初始化阶段
├── 创建 148x148 的占用网格 (初始值 -1 表示空闲)
├── 设置最大方块尺寸 (1-5 单位)
└── 设置方块生成概率 (50% 为方形)

主循环 (按行生成)
├── while (py < 148)
│   ├── while (px < 148)
│   │   ├── 检查当前位置到下一个被占用位置的最大宽度
│   │   ├── 如果宽度为 0,跳过 (px++)
│   │   ├── 随机决定方块形状 (方形或矩形)
│   │   ├── 随机选择方块类型 (0-5)
│   │   ├── 随机设置方块尺寸 (在最大宽度范围内)
│   │   ├── 标记占用网格区域
│   │   └── 推进指针 (px += sx)
│   ├── 下一行 (py++, px = 0)
│   └── 随机更新最大方块尺寸 (20% 概率)
4.3.3 关键数据结构
typescript 复制代码
// 占用网格: 二维数组,-1 表示空闲,其他值为方块ID
const occupied: number[][] = Array.from({ length: 148 }, () =>
    Array(148).fill(-1)
);

// 方块数据结构 (ABlock 类)
interface ABlock {
    id: number;           // 唯一标识
    typeBottom: number;   // 底部几何体类型 (0-2)
    typeTop: number;      // 顶部几何体类型 (0-5)
    box: Box2;            // 2D 边界框
    height: number;       // 高度
    rotation: number;     // 旋转角度
    topColorIndex: number;// 颜色索引
    topColor: Color;      // 顶部颜色
    baseColor: Color;     // 底部颜色
}
4.3.4 方块类型映射
顶部类型索引 顶部几何体名称 底部类型索引 底部几何体名称
0 Square_Top 6 Square_Base
1 Quart_Top 7 Quart_Base
2 Hole_Top 8 Hole_Base
3 Peg_Top 6 Square_Base
4 Divot_Top 6 Square_Base
5 Cross_Top 6 Square_Base

4.4 高级后处理管线

位置 : Demo.ts:96-136 initPostProcessing() 方法

4.4.1 后处理架构
复制代码
Scene (场景)
    │
    ▼
┌───────────────────────────────────────┐
│  Scene Pass (多渲染目标 MRT)          │
│  ├─ output: 颜色缓冲                  │
│  ├─ normal: 世界空间法线              │
│  └─ depth: 深度缓冲                   │
└───────────────────────────────────────┘
    │
    ├─► 输出颜色 ──┐
    │              │
    ├─► 法线 ──────┤
    │              │
    └─► 深度 ──────┤
                   │
    ┌──────────────▼──────────────┐
    │  AO Pass (屏幕空间环境光遮蔽) │
    │  - 输入: 深度 + 法线          │
    │  - 参数: 半径、距离衰减等      │
    └──────────────┬──────────────┘
                   │
                   ▼
    ┌──────────────────────────────┐
    │  混合: AO × SceneColor       │
    └──────────────┬──────────────┘
                   │
    ┌──────────────▼──────────────┐
    │  DoF Pass (景深效果)        │
    │  - 输入: 混合颜色 + 视深度   │
    │  - 动态焦点和光圈            │
    └──────────────┬──────────────┘
                   │
    ┌──────────────▼──────────────┐
    │  Vignette (暗角效果)        │
    │  - 径向渐变遮罩              │
    └──────────────┬──────────────┘
                   │
    ┌──────────────▼──────────────┐
    │  FXAA (快速近似抗锯齿)       │
    └──────────────┬──────────────┘
                   │
                   ▼
            最终渲染输出
4.4.2 各通道详细配置

1. Scene Pass (多渲染目标)

typescript 复制代码
const scenePass = pass(this.scene, this.camera);
scenePass.setMRT(mrt({
    output: output,                    // RGB 颜色输出
    normal: transformedNormalView      // 视图空间法线
}));

2. AO Pass (屏幕空间环境光遮蔽)

typescript 复制代码
const aoPass = ao(scenePassDepth, scenePassNormal, this.camera);
aoPass.distanceExponent.value = 1;      // 距离指数
aoPass.distanceFallOff.value = .1;      // 距离衰减
aoPass.radius.value = 1.0;              // 采样半径
aoPass.scale.value = 1.5;               // 强度缩放
aoPass.thickness.value = 1;             // 厚度

参数说明:

  • radius: AO 采样半径,值越大影响范围越大
  • distanceFallOff: 距离衰减系数,控制 AO 随距离的衰减速度
  • scale: 整体强度,值越大 AO 效果越强
  • thickness: 物体厚度感知,影响遮挡计算

3. DoF Pass (景深效果)

typescript 复制代码
const dofPass = dof(
    blendPassAO,                       // 输入纹理
    scenePassViewZ,                    // 深度
    effectController.focus,            // 焦距 (动态更新)
    effectController.aperture.mul(0.00001), // 光圈 (动态更新)
    effectController.maxblur            // 最大模糊度
);

动态参数:

  • focus: 在 updateCamera() 中动态计算,模拟自动对焦
  • aperture: 根据相机距离动态调整 (100 - distance * 0.5)
  • maxblur: 固定为 0.02

4. Vignette (暗角效果)

typescript 复制代码
const vignetteFactor = clamp(
    viewportUV.sub(0.5).length().mul(1.2), 
    0.0, 
    1.0
).oneMinus().pow(0.5);

效果: 从中心到边缘逐渐变暗,增强画面中心聚焦感

5. FXAA (快速近似抗锯齿)

typescript 复制代码
this.post.outputNode = fxaa(dofPass.mul(vignetteFactor));

4.5 动画系统

位置 : Demo.ts:339-432 updateBlocks() 方法

4.5.1 高度计算算法

方块的实时高度由三个因素共同决定:

typescript 复制代码
targetHeight = noiseHeight + baseHeight + pointerFactor + rippleEffect

1. 噪声高度 (自然波动)

typescript 复制代码
noise = heightNoise.scaled2D(block.box.min.x * 0.1, block.box.min.y + elapsed * 5);
// FastSimplexNoise: 频率 0.05, 八度 2, 范围 0-1, 持续度 0.5

特点:

  • 使用单纯形噪声生成平滑的随机波动
  • 时间参数 elapsed * 5 使波浪随时间移动
  • 空间参数 block.box.min.x * 0.1 控制空间频率

2. 基础高度

typescript 复制代码
baseHeight = 1  // 方块最小高度

3. 鼠标交互因素

typescript 复制代码
dx = blockCenter.x - pointerHandler.scenePointer.x;
dz = blockCenter.y - pointerHandler.scenePointer.z;
cDist = Math.sqrt(dx * dx + dz * dz);
cFactor = MathUtils.clamp(1 - cDist * 0.1, 0, 1);
pointerHeight = cFactor * 5;  // 鼠标附近方块升高

效果: 鼠标位置附近方块隆起,模拟水面波纹效果

4. 主题切换涟漪

typescript 复制代码
from0 = clamp(
    sqrt((blockCenter.x - camDir.x - gridZone.max.x * 0.5)² +
        (blockCenter.y - camDir.z - gridZone.max.y * 0.5)²) / 
    (gridZone.max.x * 0.5),
    0, 1
);
ripple = cubicPulse(gain(transitionTime, 1.1)^0.9, 0.05, from0);
echoRipple = cubicPulse(gain(echoTime, 1.3), 0.025, from0);
rippleHeight = ripple * 10 + echoRipple * 5;

效果:

  • 从屏幕中心向外扩散的双涟漪
  • 第一涟漪影响底部高度
  • 第二涟漪(延迟 0.3 秒)影响顶部高度
4.5.2 平滑插值
typescript 复制代码
if (targetHeight >= block.height) {
    // 上升时较慢 (系数 0.1)
    block.height = lerp(block.height, targetHeight, 0.1);
} else {
    // 下降时较快 (系数 0.3)
    block.height = lerp(block.height, targetHeight, 0.3);
}

原理: 物理模拟中,物体下降通常比上升快(重力效应)

4.5.3 矩阵和颜色更新
typescript 复制代码
// 更新底部
dummy.rotation.y = block.rotation;
dummy.position.set(blockCenter.x, 0, blockCenter.y);
dummy.scale.set(blockSize.x, block.height, blockSize.y);
dummy.updateMatrix();
blockMesh.setMatrixAt(baseI, dummy.matrix);
blockMesh.getColorAt(baseI, tempCol);
tempCol.lerp(baseTargetColor, ripple);
blockMesh.setColorAt(baseI, tempCol);

// 更新顶部
dummy.position.y += block.height;
dummy.scale.set(blockSize.x, 1, blockSize.y);
dummy.updateMatrix();
blockMesh.setMatrixAt(topI, dummy.matrix);
blockMesh.getColorAt(topI, tempCol);
tempCol.lerp(topTargetColors[block.topColorIndex], echoRipple);
blockMesh.setColorAt(topI, tempCol);

4.6 主题切换系统

位置 : Demo.ts:464-478 setColorMode() 方法

4.6.1 主题设计

深色主题 (Dark Mode):

typescript 复制代码
baseColor: 0x000000 (黑色)
topColors: [
    0x101010, 0x181818, 0x202020, 0x282828, 0xbe185d (洋红色)
]

浅色主题 (Light Mode):

typescript 复制代码
baseColor: 0x999999 (灰色)
topColors: [
    0xffffff, 0xcccccc, 0xaaaaaa, 0x999999, 0x086ff0 (蓝色)
]
4.6.2 切换流程
typescript 复制代码
setColorMode(mode: string) {
    this.colorMode = mode;
    this.themeTransitionStart = this.elapsed;  // 记录切换时间
    
    if (mode == 'dark') {
        this.baseTargetColor.copy(ABlock.DARK_BASE_COLOR);
        this.topTargetColors = ABlock.DARK_COLORS;
    } else {
        this.baseTargetColor.copy(ABlock.LIGHT_BASE_COLOR);
        this.topTargetColors = ABlock.LIGHT_COLORS;
    }
}
4.6.3 动画过渡

updateBlocks() 中:

typescript 复制代码
transitionTime = clamp((elapsed - themeTransitionStart) / 5, 0, 1);
echoTime = clamp((elapsed - themeTransitionStart - 0.3) / 5, 0, 1);

// 使用三次脉冲函数计算涟漪强度
ripple = cubicPulse(gain(transitionTime, 1.1)^0.9, 0.05, from0);
echoRipple = cubicPulse(gain(echoTime, 1.3), 0.025, from0);

过渡特点:

  • 持续时间: 5 秒
  • 双涟漪效果: 第二个涟漪延迟 0.3 秒
  • 颜色插值: 使用 lerp() 平滑过渡
  • 高度叠加: 涟漪期间额外升高方块 (10 + 5 单位)

4.7 相机自动对焦系统

位置 : Demo.ts:442-457 updateCamera() 方法

4.7.1 物理模型

模拟相机的自动对焦系统,根据相机到焦平面的距离动态调整焦距和光圈。

4.7.2 实现逻辑
typescript 复制代码
updateCamera(dt: number) {
    // 1. 获取相机朝向
    this.camera.getWorldDirection(camDir);
    
    // 2. 创建地面相交平面 (略低于相机)
    this.groundRayPlane.constant = this.camera.position.y * 0.1;
    
    // 3. 计算相机到地面的射线距离
    this.raycaster.set(this.camera.position, camDir.normalize());
    this.raycaster.ray.intersectPlane(this.groundRayPlane, camDir);
    const dist = camDir.sub(this.camera.position).length();
    
    // 4. 模拟弹簧阻尼系统
    const targetDist = dist;
    const distVel = (targetDist - this.camDist) / dt;
    this.camdistVel = lerp(this.camdistVel, distVel, 0.05);
    this.camDist += this.camdistVel * dt;
    
    // 5. 更新景深参数
    this.effectController.focus.value = lerp(
        this.effectController.focus.value,
        this.camDist * 0.85,
        0.05
    );
    this.effectController.aperture.value = lerp(
        this.effectController.aperture.value,
        100 - this.camDist * 0.5,
        0.025
    );
}
4.7.3 参数说明
参数 含义 更新速率 效果
focus 焦距 0.05 控制清晰平面距离 (目标距离的 85%)
aperture 光圈 0.025 控制模糊范围 (距离越远光圈越大)
camdistVel 速度阻尼 0.05 模拟相机移动的平滑过渡
camK 弹簧常数 0.05 控制响应速度

物理原理:

  • 距离越远 → 光圈越大 → 景深越浅 → 背景模糊越明显
  • 距离越近 → 光圈越小 → 景深越深 → 整体更清晰

4.8 交互系统

位置 : src/lib/Pointer.ts

4.8.1 功能设计

处理鼠标/触摸事件,将屏幕坐标转换为场景坐标,用于方块波动效果。

4.8.2 实现机制
typescript 复制代码
export class Pointer {
    // 核心属性
    camera: Camera;                          // 相机引用
    renderer: WebGPURenderer | WebGLRenderer; // 渲染器引用
    rayCaster: Raycaster;                    // 射线投射器
    
    // 坐标系统
    clientPointer: Vector2;                  // 客户端像素坐标
    pointer: Vector2;                        // NDC 归一化设备坐标 (-1 到 1)
    scenePointer: Vector3;                   // 场景世界坐标
    
    // 状态
    pointerDown: boolean;                    // 鼠标按下状态
    uPointerDown = uniform(0);               // Uniform 输出
    uPointer = uniform(new Vector3());       // Uniform 输出
}
4.8.3 事件处理
typescript 复制代码
onPointerDown(e: PointerEvent) {
    if (e.pointerType === 'mouse' || e.button === 0) {
        this.pointerDown = true;
        this.uPointerDown.value = 1;
    }
    this.clientPointer.set(e.clientX, e.clientY);
    this.updateScreenPointer(e);
}

onPointerMove(e: PointerEvent) {
    this.clientPointer.set(e.clientX, e.clientY);
    this.updateScreenPointer(e);
}

onPointerUp(e: PointerEvent) {
    this.clientPointer.set(e.clientX, e.clientY);
    this.updateScreenPointer(e);
    this.pointerDown = false;
    this.uPointerDown.value = 0;
}
4.8.4 坐标转换
typescript 复制代码
updateScreenPointer(e?: PointerEvent) {
    // 1. 获取客户端坐标 (像素)
    const clientX = e?.clientX || this.clientPointer.x;
    const clientY = e?.clientY || this.clientPointer.y;
    
    // 2. 转换为 NDC 坐标 (-1 到 1)
    this.pointer.set(
        (clientX / window.innerWidth) * 2 - 1,
        -(clientY / window.innerHeight) * 2 + 1
    );
    
    // 3. 使用射线投射转换为场景坐标
    this.rayCaster.setFromCamera(this.pointer, this.camera);
    this.rayCaster.ray.intersectPlane(this.iPlane, this.scenePointer);
    
    // 4. 更新 Uniform 输出 (供 Shader 使用)
    this.uPointer.value.set(
        this.scenePointer.x,
        this.scenePointer.y,
        this.scenePointer.z
    );
}

坐标转换链:

复制代码
屏幕像素 → NDC (-1,1) → 射线投射 → 场景世界坐标

五、数学函数库

位置 : Demo.ts:481-496

项目使用了 Inigo Quilez 的数学函数库,这些函数用于创建平滑的动画曲线。

5.1 pcurve (多项式曲线)

typescript 复制代码
pcurve(x: number, a: number, b: number): number {
    const k = Math.pow(a + b, a + b) / (Math.pow(a, a) * Math.pow(b, b));
    return k * Math.pow(x, a) * Math.pow(1.0 - x, b);
}

用途 : 创建平滑的幂函数曲线
参数:

  • x: 输入值 [0, 1]
  • a, b: 曲线形状参数

特性:

  • 当 a = b = 1 时为线性
  • 当 a > b 时曲线向左偏
  • 当 b > a 时曲线向右偏

5.2 gain (增益函数)

typescript 复制代码
gain(x: number, k: number): number {
    const a = 0.5 * Math.pow(2.0 * ((x < 0.5) ? x : 1.0 - x), k);
    return (x < 0.5) ? a : 1.0 - a;
}

用途 : 调整曲线的对比度/增益
参数:

  • x: 输入值 [0, 1]
  • k: 增益系数

特性:

  • 当 k = 1 时为线性
  • 当 k > 1 时增加对比度 (中间值偏向两端)
  • 当 k < 1 时降低对比度 (中间值偏向 0.5)

应用 : 主题切换涟漪的时间曲线 gain(transitionTime, 1.1)^0.9

5.3 cubicPulse (三次脉冲函数)

typescript 复制代码
cubicPulse(c: number, w: number, x: number): number {
    let x2 = Math.abs(x - c);
    if (x2 > w) return 0.0;
    x2 /= w;
    return 1.0 - x2 * x2 * (3.0 - 2.0 * x2);
}

用途 : 创建平滑的脉冲波形
参数:

  • c: 脉冲中心位置
  • w: 脉冲宽度
  • x: 输入值

特性:

  • |x - c| > w 时返回 0 (完全超出范围)
  • 在中心点 x = c 时返回 1 (最大值)
  • 使用 smoothstep 平滑过渡 (Hermite 插值)

应用: 主题切换涟漪的空间分布

可视化:

复制代码
    1.0 |       /---\
        |      /     \
        |     /       \
    0.5 |----/---------\----
        |   /           \
        |  /             \
    0.0 +--|-------|-------|--
         c-w      c      c+w

六、样式系统

位置 : src/App.css

6.1 主题变量定义

css 复制代码
:root {
    --font-main: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
    --color-frame-bg: rgba(255, 255, 255, 0.05);
    --color-frame-title: #ffffff;
    --color-frame-link: #ffffff;
    --color-frame-link-hover: #ffffff;
}

[data-theme="dark"] {
    --color-frame-bg: rgba(0, 0, 0, 0.1);
    --color-frame-title: #ffffff;
    --color-frame-link: #ffffff;
    --color-frame-link-hover: #ffffff;
}

6.2 加载动画

css 复制代码
.loader-container {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100px;
}

.loader {
    width: 50px;
    height: 50px;
    border: 3px solid rgba(255, 255, 255, 0.3);
    border-radius: 50%;
    border-top-color: #fff;
    animation: spin 1s ease-in-out infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

body.loading {
    overflow: hidden;
}

6.3 切换开关样式

css 复制代码
.theme-toggle {
    margin-top: 20px;
}

.switch {
    position: relative;
    display: inline-block;
    width: 60px;
    height: 34px;
}

.switch input {
    opacity: 0;
    width: 0;
    height: 0;
}

.slider {
    position: absolute;
    cursor: pointer;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: #ccc;
    transition: .4s;
}

.slider:before {
    position: absolute;
    content: "";
    height: 26px;
    width: 26px;
    left: 4px;
    bottom: 4px;
    background-color: white;
    transition: .4s;
}

input:checked + .slider {
    background-color: #2196F3;
}

input:checked + .slider:before {
    transform: translateX(26px);
}

.slider.round {
    border-radius: 34px;
}

.slider.round:before {
    border-radius: 50%;
}

七、性能优化策略

7.1 BatchedMesh 优化

优化点 实现方式 性能提升
批量渲染 单个 DrawCall 渲染数千实例 100x-1000x
禁用排序 sortObjects = false 减少 CPU 开销
统一材质 所有实例共享同一材质 减少 Shader 切换

7.2 渲染优化

typescript 复制代码
// 像素比设置为 1
this.renderer.setPixelRatio(1);

// 使用 WebGPU 渲染器
new WebGPURenderer({ antialias: true });

// 合理限制帧率
// (隐式通过 requestAnimationFrame)

7.3 几何体加载优化

typescript 复制代码
// 预加载所有几何体
await BlockGeometry.init();

// 使用共享几何体
this.geoms.push(topSquare, topQuart, ...);
// 每个方块引用相同的几何体对象

7.4 纹理优化

typescript 复制代码
// Ultra HDR 格式:单文件存储多曝光级别
const texture = await new UltraHDRLoader()
    .setDataType(FloatType)
    .loadAsync('rustig_koppie_puresky_2k.jpg');

优势:

  • 单次加载获得多级曝光
  • 适用于 Tone Mapping

7.5 交互优化

typescript 复制代码
// 使用射线缓存
this.raycaster = new Raycaster();

// 复用临时对象
dummy: Object3D = new Object3D();
tempCol: Color = new Color();
blockSize: Vector2 = new Vector2(1, 1);
blockCenter: Vector2 = new Vector2();

避免: 每帧创建新对象(垃圾回收压力)


八、兼容性与检测

8.1 WebGPU 检测

位置 : Demo.ts:47-50

typescript 复制代码
if (WebGPU.isAvailable() === false) {
    return; // 优雅降级,显示不支持信息
}

位置 : src/App.tsx:12

typescript 复制代码
const [isGPUAvailable, setIsGPUAvailable] = 
    useState(WebGPU.isAvailable());

UI 反馈:

typescript 复制代码
{!isGPUAvailable && (
    <div className='demo__infos'>
        <h1>WebGPU not available</h1>
        <p>WebGPU is not available on your device or browser.</p>
    </div>
)}

8.2 浏览器支持要求

  • Chrome: 113+ (需要 WebGPU 支持)
  • Edge: 113+
  • Firefox: Nightly (实验性支持)
  • Safari: 技术预览版

8.3 降级策略

当前实现不支持 WebGPU 时直接返回,未来可扩展:

  1. 回退到 WebGLRenderer
  2. 禁用高级后处理效果
  3. 使用普通 Mesh 替代 BatchedMesh

九、部署与构建

9.1 开发环境

bash 复制代码
npm install    # 安装依赖
npm run dev    # 启动开发服务器 (http://localhost:5173)

9.2 生产构建

bash 复制代码
npm run build  # 构建 (输出到 dist/)
npm run preview # 预览构建结果

9.3 Vite 配置

位置 : vite.config.ts

typescript 复制代码
export default defineConfig({
  root: '',
  base: './',                    // 相对路径,便于部署
  plugins: [
    react(),                     // React 支持
    topLevelAwait(),             // 顶级 await 支持
    fullReloadAlways             // 热更新全刷新
  ],
})

9.4 TypeScript 配置

  • tsconfig.json: 应用配置
  • tsconfig.node.json: 构建脚本配置
  • tsconfig.app.json: 应用源码配置

十、项目总结

10.1 核心技术亮点

  1. BatchedMesh 批量渲染: 展示了 Three.js 最新批处理技术的强大性能优势
  2. WebGPU 渲染器: 利用现代 GPU 并行计算能力,突破传统 WebGL 性能瓶颈
  3. 高级后处理管线: 完整的 MRT、SSAO、DoF、FXAA 组合,实现电影级视觉效果
  4. 数学驱动动画: 使用噪声和数学函数创建自然流畅的交互效果
  5. 主题切换涟漪: 双涟漪 + 颜色插值 + 高度叠加的复杂过渡动画

10.2 代码质量

指标 评价
架构清晰度 ⭐⭐⭐⭐⭐ (React UI 与 Three.js 逻辑分离)
代码可读性 ⭐⭐⭐⭐⭐ (详细注释,命名清晰)
性能优化 ⭐⭐⭐⭐⭐ (BatchedMesh、WebGPU、复用对象)
类型安全 ⭐⭐⭐⭐⭐ (完整 TypeScript 类型定义)
可维护性 ⭐⭐⭐⭐⭐ (模块化设计,职责单一)

10.3 学习价值

本项目适合学习:

  • Three.js 最新技术(WebGPU、BatchedMesh、PostProcessing)
  • 3D 渲染管线原理(MRT、SSAO、DoF)
  • 性能优化技巧(批量渲染、对象复用)
  • 数学函数在图形学中的应用
  • React 与 Three.js 的集成
  • WebGL/WebGPU 开发实践

10.4 扩展方向

  1. 更多几何体类型: 扩展 blocks.glb,增加更多形状
  2. 物理交互: 使用 Cannon.js 或 Ammo.js 添加物理效果
  3. 音频响应: 使用 Web Audio API 实现音乐可视化
  4. 粒子系统: 添加火花、烟雾等粒子效果
  5. VR/AR: 使用 WebXR 扩展到 VR/AR 平台
  6. 多用户交互: 使用 WebSocket 实现多人实时协作
  7. 自定义着色器: 编写 TSL Shader 实现更多创意效果

十一、关键文件索引

核心逻辑

  • Demo.ts:46-75 - init() 初始化流程
  • Demo.ts:96-136 - initPostProcessing() 后处理管线
  • Demo.ts:161-230 - initGrid() 网格生成算法
  • Demo.ts:234-291 - initBlocks() BatchedMesh 创建
  • Demo.ts:339-432 - updateBlocks() 方块更新动画
  • Demo.ts:442-457 - updateCamera() 自动对焦
  • Demo.ts:481-496 - 数学函数库

数据模型

  • ABlock.ts:8-44 - 方块数据结构定义
  • BlockGeometry.ts:12-56 - 几何体加载与管理
  • Pointer.ts:9-74 - 交互系统实现

UI 组件

  • App.tsx:8-101 - 主应用组件
  • threeCanvas.tsx:4-48 - Canvas 包装器

配置文件

  • vite.config.ts:15-23 - Vite 构建配置
  • package.json:1-33 - 项目依赖和脚本

附录

A. 方块类型索引

类型索引 名称 描述
0 Square 正方形
1 Quart 四分之一圆形
2 Hole 圆孔
3 Peg 凸起
4 Divot 凹陷
5 Cross 十字

B. 颜色方案

浅色主题 (Light Mode)
复制代码
Base: #999999 (灰色)
Colors:
  0: #ffffff (白色)
  1: #cccccc (浅灰)
  2: #aaaaaa (中灰)
  3: #999999 (深灰)
  4: #086ff0 (蓝色)
深色主题 (Dark Mode)
复制代码
Base: #000000 (黑色)
Colors:
  0: #101010 (黑灰)
  1: #181818 (深灰)
  2: #202020 (中灰)
  3: #282828 (浅灰)
  4: #be185d (洋红)

C. 性能指标(参考)

指标
方块数量 ~2000+
顶点总数 ~50,000+
Draw Calls 1-2
目标帧率 60 FPS
内存占用 ~100-200 MB (GPU)

相关推荐
:mnong5 小时前
NASA 3DTilesRenderer 项目需求设计实现分析
3d
大江东去浪淘尽千古风流人物5 小时前
【SEVIS】An Efficient Schmidt-EKF for 3D Visual-Inertial SLAM
人工智能·机器学习·3d
应用市场1 天前
无线充电器原理与电路设计详解——从电磁感应到完整实现
3d·fpga开发
CG_MAGIC1 天前
Redshift烘焙:提升3D渲染效率的技巧
3d·渲云渲染·3d软件·sketchup
抠头专注python环境配置2 天前
解决Windows安装PythonOCC报错:从“No module named ‘OCC’ ”到一键成功
人工智能·windows·python·3d·cad·pythonocc
da_vinci_x2 天前
武器设计实战:一把大剑裂变 5 种属性?Structure Ref 的“换肤”魔法
游戏·3d·设计模式·ai作画·aigc·设计师·游戏美术
yeflx2 天前
Ubuntu下Colmap源码编译调试
ubuntu·3d
军军君012 天前
Three.js基础功能学习十二:常量与核心
前端·javascript·学习·3d·threejs·three·三维
Ulyanov2 天前
PyVista与Tkinter桌面级3D可视化应用实战
开发语言·前端·python·3d·信息可视化·tkinter·gui开发