如何在QtQuick3D中生成无限程序化海底网格?
需求:QtQuick3D实现无限延伸的程序化海底
- 使用Qt 6.9的QtQuick3D开发3D场景,核心是随相机移动无限延伸的低多边形海底
- 海底需带细微起伏(用程序化噪声模拟岩质/沙质地形),体现深度变化
- 要求无缝衔接、高性能,无硬边和加载卡顿
已尝试方案(存在局限)
最初用基础PlaneMesh做海底,但只适用于小范围场景:
import QtQuick import QtQuick3D Window { width: 800 height: 600 visible: true Node { id: scene PerspectiveCamera { id: camera eulerRotation.x: -20 position.z: 500 } DirectionalLight {} Model { id: oceanFloor source: "#Rectangle" // 使用内置矩形作为简单平面 position.y: -100 scale: Qt.vector3d(10, 1, 10) // 放大后仍为有限范围 materials: [ DefaultMaterial { diffuseColor: "#4a90e2" // 海底蓝色调 } ] } } }
问题点:
- 相机移动过远会露出平面边缘
- 过度放大导致顶点数量剧增,引发性能问题
解决方案:相机中心的程序化块生成+LOD优化
1. 核心逻辑
以相机当前位置为中心,动态生成周围的海底块:
- 块用程序化噪声生成地形起伏,确保块间无缝衔接
- 结合LOD(细节层次),根据块与相机的距离调整网格精度
- 超出视野的块自动销毁,避免内存浪费
2. 分步实现
(1)自定义海底块组件
创建可复用的OceanFloorChunk.qml,负责单块地形的几何生成和噪声计算:
import QtQuick import QtQuick3D Model { id: chunk property int chunkSize: 200 // 单块边长 property int gridResolution: 32 // 网格细分精度(LOD可调) property vector3d chunkPosition: Qt.vector3d(0,0,0) // 块的世界位置 property real noiseScale: 0.01 // 噪声缩放系数,控制起伏幅度 // 动态生成带噪声的网格 source: Mesh { id: chunkMesh vertices: generateVertices() indices: generateIndices() function generateVertices() { var verts = [] var step = chunkSize / gridResolution for (var z = 0; z <= gridResolution; z++) { for (var x = 0; x <= gridResolution; x++) { // 计算世界坐标 var worldX = chunkPosition.x + (x * step) - chunkSize/2 var worldZ = chunkPosition.z + (z * step) - chunkSize/2 // 用正弦组合模拟噪声生成Y轴起伏(可替换为Perlin/Simplex噪声) var noiseVal = Math.sin(worldX * noiseScale) * Math.cos(worldZ * noiseScale) * 10 verts.push(worldX, noiseVal - 100, worldZ) // Y轴偏移到海底高度 // 添加UV坐标(用于后续纹理映射) verts.push(x/gridResolution, z/gridResolution) } } return verts } function generateIndices() { var indices = [] for (var z = 0; z < gridResolution; z++) { for (var x = 0; x < gridResolution; x++) { var topLeft = z * (gridResolution + 1) + x var topRight = topLeft + 1 var bottomLeft = topLeft + (gridResolution + 1) var bottomRight = bottomLeft + 1 // 两个三角形组成一个四边形 indices.push(topLeft, bottomLeft, topRight) indices.push(topRight, bottomLeft, bottomRight) } } return indices } } materials: [ DefaultMaterial { diffuseColor: Qt.rgba(0.28, 0.56, 0.89, 1) roughness: 0.8 } ] }
(2)相机跟踪与块管理
在主场景中监听相机位置变化,动态维护视野内的块:
import QtQuick import QtQuick3D Window { width: 800 height: 600 visible: true Node { id: scene PerspectiveCamera { id: camera eulerRotation.x: -20 position.z: 500 position.x: 0 // 键盘控制相机移动(示例) Keys.onPressed: { if (event.key === Qt.Key_W) camera.position.z -= 10 if (event.key === Qt.Key_S) camera.position.z += 10 if (event.key === Qt.Key_A) camera.position.x -= 10 if (event.key === Qt.Key_D) camera.position.x += 10 } focus: true } DirectionalLight { eulerRotation.x: -45 eulerRotation.y: 45 intensity: 1.2 } // 块管理容器 Node { id: chunkContainer property int viewDistance: 3 // 相机周围生成3x3范围的块 property int chunkSize: 200 // 相机位置变化时更新块 Connections { target: camera function onPositionChanged() { updateChunks() } } function updateChunks() { // 计算相机所在的块坐标(取整) var cameraChunkX = Math.floor(camera.position.x / chunkSize) var cameraChunkZ = Math.floor(camera.position.z / chunkSize) // 删除超出视野的块 var chunksToRemove = [] for (var i = 0; i < chunkContainer.childCount; i++) { var chunk = chunkContainer.children[i] var chunkX = Math.floor(chunk.chunkPosition.x / chunkSize) var chunkZ = Math.floor(chunk.chunkPosition.z / chunkSize) if (Math.abs(chunkX - cameraChunkX) > viewDistance || Math.abs(chunkZ - cameraChunkZ) > viewDistance) { chunksToRemove.push(chunk) } } chunksToRemove.forEach(chunk => chunk.destroy()) // 生成视野内缺失的块 for (var xOffset = -viewDistance; xOffset <= viewDistance; xOffset++) { for (var zOffset = -viewDistance; zOffset <= viewDistance; zOffset++) { var targetChunkX = cameraChunkX + xOffset var targetChunkZ = cameraChunkZ + zOffset var chunkExists = false // 检查块是否已存在 for (var j = 0; j < chunkContainer.childCount; j++) { var existingChunk = chunkContainer.children[j] var existingX = Math.floor(existingChunk.chunkPosition.x / chunkSize) var existingZ = Math.floor(existingChunk.chunkPosition.z / chunkSize) if (existingX === targetChunkX && existingZ === targetChunkZ) { chunkExists = true break } } if (!chunkExists) { // 创建新块,根据距离调整网格精度(LOD) OceanFloorChunk { chunkSize: chunkContainer.chunkSize gridResolution: Math.max(16, 32 - Math.floor(Math.sqrt(xOffset*xOffset + zOffset*zOffset))*4) chunkPosition: Qt.vector3d(targetChunkX * chunkSize, 0, targetChunkZ * chunkSize) noiseScale: 0.01 } } } } } // 初始化生成初始块 Component.onCompleted: updateChunks() } } }
3. 关键优化细节
- 无缝衔接:所有块基于世界坐标计算噪声,确保块与块之间的地形起伏连续无硬边
- 性能控制:
- LOD机制:距离相机越远的块,网格细分精度越低,减少顶点数量
- 动态销毁:自动移除超出视野的块,降低内存占用
- 低多边形设计:单块网格顶点数控制在合理范围(32x32网格约2000顶点)
- 噪声升级:如果需要更真实的地形,可将示例中的正弦噪声替换为Perlin/Simplex噪声实现(可通过JS或C++扩展实现)
内容的提问来源于stack exchange,提问作者mohamadmahdi
相关产品推荐
相关产品推荐

