You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

THREE.js中隐藏方块不可见面以优化渲染性能的方案咨询

THREE.js 大量方块场景性能优化方案(基于遮挡面剔除)

我正在开发一个包含大量方块的THREE.js项目,过多方块渲染会导致严重卡顿。目前已实现类似Minecraft的遮挡面剔除逻辑——检测相邻方块,不渲染被遮挡的面,但现有代码效率不足,希望得到更高效的优化方案。

现有检测代码如下:

let blRight = false
let blLeft = false
let blBack = false
let blFront = false
let blTop = false
let blBottom = false

for(let i = 0; i < blocks.coord.x.length; i++) {
    if(blocks.coord.x[i] == x + 10) {
        if(!blocks.coord.y[i] == y) {
            blRight = true
        }
    }
}
for(let i = 0; i < blocks.coord.x.length; i++) { 
    if(blocks.coord.x[i] == x - 10) {
        if(!blocks.coord.y[i] == y) {
            blLeft = true
        }
    }
}
for(let i = 0; i < blocks.coord.y.length; i++) {
    if(blocks.coord.y[i] == y + 10) {
        blTop = false
    }
}
for(let i = 0; i < blocks.coord.y.length; i++) {
    if(blocks.coord.y[i] == y - 10) {
        blBottom = true
    }
}
for(let i = 0; i < blocks.coord.z.length; i++) {
    if(blocks.coord.z[i] == z + 10) {
        if(!blocks.coord.y[i] == y) {
            blFront = true
        }
    }
}
for(let i = 0; i < blocks.coord.z.length; i++) {
    if(blocks.coord.z[i] == x - 10) {
        if(!blocks.coord.y[i] == y) {
            blBack = true
        }
    }
}

if(blRight == false) {
    scene.add(right)
}
if(blLeft == false) {
    scene.add(left)
}
if(blTop == false) {
    scene.add(blockTop)
}
if(blBottom == false) {
    scene.add(bottom)
}
if(blFront == false) {
    scene.add(front)
}
if(blBack == false) {
    scene.add(back)
}

blocks.coord.x.push(x)
blocks.coord.y.push(y)
blocks.coord.z.push(z)
blocks.coord.id.push(3)

注:blTop代表顶面,逻辑类似Minecraft的遮挡面剔除。


核心优化方案

1. 重构坐标存储,用哈希表实现O(1)查找

当前用三个独立数组存储x/y/z坐标,每次检测相邻方块都要遍历整个数组,时间复杂度O(n),方块越多越慢。改成**哈希表(Map)**存储,用x,y,z拼接成唯一键,直接查询是否存在相邻方块:

// 替换原blocks.coord结构
const blockMap = new Map();

// 检测相邻方块时(以右侧为例)
const rightKey = `${x + 10},${y},${z}`;
const blRight = blockMap.has(rightKey);

// 添加新方块时
blockMap.set(`${x},${y},${z}`, { id: 3 });

这样每个方向的检测从O(n)降到O(1),性能提升显著。

2. 合并遍历逻辑,修正代码错误

把6次独立循环合并成1次遍历,一次完成所有方向的检测,同时修正原代码中忽略x/z坐标、变量误用等逻辑错误:

let blRight = false, blLeft = false, blBack = false, blFront = false, blTop = false, blBottom = false;

const count = blocks.coord.x.length;
for(let i = 0; i < count; i++) {
    const bx = blocks.coord.x[i];
    const by = blocks.coord.y[i];
    const bz = blocks.coord.z[i];
    
    if(bx === x + 10 && by === y && bz === z) blRight = true;
    if(bx === x - 10 && by === y && bz === z) blLeft = true;
    if(by === y + 10 && bx === x && bz === z) blTop = true;
    if(by === y - 10 && bx === x && bz === z) blBottom = true;
    if(bz === z + 10 && bx === x && by === y) blFront = true;
    if(bz === z - 10 && bx === x && by === y) blBack = true;
}

3. 合并几何体,减少Draw Call

THREE.js中每个添加到场景的物体都是一次Draw Call,大量独立平面会导致Draw Call暴增。优化方式:

  • 改用BoxGeometry创建方块,通过修改顶点索引剔除不需要的面;
  • 使用BufferGeometryUtils.mergeBufferGeometries将所有可见面合并成一个大的BufferGeometry,再用一个Mesh渲染,把成百上千次Draw Call降到1次。

示例代码:

import { BufferGeometryUtils } from 'three/addons/utils/BufferGeometryUtils.js';

const visibleGeometries = [];
// 收集所有可见的面的几何体
if(!blRight) visibleGeometries.push(right.geometry);
if(!blLeft) visibleGeometries.push(left.geometry);
// ...收集其他可见面

// 合并成单个几何体
const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(visibleGeometries);
const mergedMesh = new Mesh(mergedGeometry, material);
scene.add(mergedMesh);

4. 空间分区,缩小检测范围

当方块数量极大时,将场景划分为固定大小的区块(比如10x10x10个方块为一个区块),每个区块存储该区域内的方块。检测相邻方块时,只查询当前方块所在区块及上下左右前后的6个相邻区块,无需遍历所有方块:

const chunkSize = 100; // 每个区块的尺寸(单位)
const chunkMap = new Map();

// 获取方块所在区块的键
function getChunkKey(x, y, z) {
    return `${Math.floor(x/chunkSize)},${Math.floor(y/chunkSize)},${Math.floor(z/chunkSize)}`;
}

// 添加方块时,存入对应区块
const chunkKey = getChunkKey(x, y, z);
if(!chunkMap.has(chunkKey)) chunkMap.set(chunkKey, []);
chunkMap.get(chunkKey).push({x, y, z, id:3});

// 检测相邻方块时,只查目标区块
const targetChunkKeys = [
    getChunkKey(x+10, y, z),
    getChunkKey(x-10, y, z),
    // ...其他方向的区块键
];

5. 视锥体剔除,跳过不可见方块

只渲染相机视锥体内的方块,用THREE.js的Frustum类检测方块是否在视野内,不在的话直接跳过遮挡检测和渲染:

const frustum = new THREE.Frustum();
const cameraViewProjectionMatrix = new THREE.Matrix4();

// 每帧更新视锥体
camera.updateMatrixWorld();
cameraViewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
frustum.setFromProjectionMatrix(cameraViewProjectionMatrix);

// 检测方块是否在视锥体内(用方块的包围盒)
const box = new THREE.Box3().setFromCenterAndSize(new THREE.Vector3(x,y,z), new THREE.Vector3(10,10,10));
if(frustum.intersectsBox(box)) {
    // 进行遮挡检测和渲染
}

内容的提问来源于stack exchange,提问作者Owen Odroski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 08:45:44