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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 04:43:12