第30节:大规模地形渲染与LOD技术

第30节:大规模地形渲染与LOD技术

概述

大规模地形渲染是开放世界游戏和仿真应用的核心技术挑战。本节将深入探索地形LOD(Level of Detail)系统、分块加载策略、视距剔除算法,以及如何实现无限地形的程序化生成,构建真正意义上的广阔虚拟世界。

以下是项目截图,以及控制台打印信息,
支持源码下载 ! 支持源码下载! 支持源码下载!

地形渲染系统架构:
地形渲染引擎 数据管理层 渲染优化层 视觉表现层 地形分块 LOD系统 流式加载 视锥剔除 遮挡剔除 实例化渲染 地形着色 细节纹理 动态贴花 动态调度 性能优化 视觉质量

核心原理深度解析

LOD技术体系

多层次细节渲染是现代地形系统的核心技术:

LOD级别 三角形密度 使用距离 性能影响
LOD0 100% 0-50m
LOD1 50% 50-100m
LOD2 25% 100-200m
LOD3 12.5% 200m+ 极低

地形分块策略

基于四叉树的地形分块管理:

  1. 空间分割

    • 递归四叉树分割
    • 动态块加载/卸载
    • 边界裂缝处理
  2. 内存管理

    • LRU缓存淘汰
    • 预加载策略
    • 压缩存储格式

完整代码实现

大规模地形渲染系统

vue 复制代码
<template>
  <div class="terrain-container">
    <!-- 主渲染画布 -->
    <canvas ref="terrainCanvas" class="terrain-canvas"></canvas>
    
    <!-- 地形控制面板 -->
    <div class="terrain-controls">
      <div class="control-section">
        <h3>地形渲染设置</h3>
        
        <div class="control-group">
          <label>视距: {{ viewDistance }}m</label>
          <input 
            type="range" 
            v-model="viewDistance" 
            min="500" 
            max="10000" 
            step="100"
          >
        </div>
        
        <div class="control-group">
          <label>LOD级别: {{ lodLevel }}</label>
          <input 
            type="range" 
            v-model="lodLevel" 
            min="0" 
            max="4" 
            step="1"
          >
        </div>
        
        <div class="control-group">
          <label>地形精度: {{ terrainQuality }}</label>
          <select v-model="terrainQuality">
            <option value="low">低</option>
            <option value="medium">中</option>
            <option value="high">高</option>
            <option value="ultra">超高</option>
          </select>
        </div>
      </div>

      <div class="control-section">
        <h3>性能监控</h3>
        <div class="performance-stats">
          <div class="stat-item">
            <span>渲染区块:</span>
            <span>{{ renderedTiles }}</span>
          </div>
          <div class="stat-item">
            <span>三角形数量:</span>
            <span>{{ formatNumber(triangleCount) }}</span>
          </div>
          <div class="stat-item">
            <span>帧率:</span>
            <span>{{ currentFPS }} FPS</span>
          </div>
          <div class="stat-item">
            <span>内存使用:</span>
            <span>{{ formatMemory(memoryUsage) }}</span>
          </div>
        </div>
      </div>

      <div class="control-section">
        <h3>地形生成</h3>
        <div class="generation-controls">
          <button @click="generateProcedural" class="control-button">
            🏔️ 生成程序地形
          </button>
          <button @click="loadHeightMap" class="control-button">
            📁 加载高度图
          </button>
          <button @click="clearTerrain" class="control-button">
            🧹 清空地形
          </button>
        </div>
        
        <div class="noise-controls">
          <div class="control-group">
            <label>噪声强度: {{ noiseIntensity }}</label>
            <input 
              type="range" 
              v-model="noiseIntensity" 
              min="0" 
              max="2" 
              step="0.1"
            >
          </div>
          
          <div class="control-group">
            <label>噪声频率: {{ noiseFrequency }}</label>
            <input 
              type="range" 
              v-model="noiseFrequency" 
              min="0.001" 
              max="0.1" 
              step="0.001"
            >
          </div>
        </div>
      </div>
    </div>

    <!-- 调试信息 -->
    <div class="debug-overlay">
      <div class="debug-info">
        <div>相机位置: X:{{ cameraPos.x.toFixed(1) }} Y:{{ cameraPos.y.toFixed(1) }} Z:{{ cameraPos.z.toFixed(1) }}</div>
        <div>加载区块: {{ loadedTiles }} / 总区块: {{ totalTiles }}</div>
        <div>视锥剔除: {{ culledTiles }} 个区块被剔除</div>
        <div>LOD分布: {{ lodDistribution }}</div>
      </div>
    </div>

    <!-- 加载进度 -->
    <div v-if="isLoading" class="loading-overlay">
      <div class="loading-content">
        <div class="terrain-loader">
          <div class="mountain"></div>
          <div class="mountain"></div>
          <div class="mountain"></div>
        </div>
        <h3>正在生成地形...</h3>
        <div class="loading-progress">
          <div class="progress-bar">
            <div class="progress-fill" :style="progressStyle"></div>
          </div>
          <span class="progress-text">{{ loadingMessage }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { onMounted, onUnmounted, ref, reactive, computed } from 'vue';
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js';

// 四叉树地形管理器
class QuadtreeTerrainManager {
  constructor(renderer, camera) {
    this.renderer = renderer;
    this.camera = camera;
    this.scene = new THREE.Scene();
    
    this.quadtree = new Quadtree();
    this.tileCache = new Map();
    this.visibleTiles = new Set();
    
    this.tileSize = 256;
    this.maxLOD = 4;
    this.viewDistance = 2000;
    
    this.setupScene();
  }

  // 初始化场景
  setupScene() {
    // 环境光
    const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
    this.scene.add(ambientLight);
    
    // 方向光
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(100, 100, 50);
    directionalLight.castShadow = true;
    this.scene.add(directionalLight);
    
    // 天空盒
    this.setupSkybox();
  }

  // 设置天空盒
  setupSkybox() {
    const skyboxGeometry = new THREE.BoxGeometry(10000, 10000, 10000);
    const skyboxMaterials = [
      new THREE.MeshBasicMaterial({ color: 0x87CEEB, side: THREE.BackSide }),
      new THREE.MeshBasicMaterial({ color: 0x87CEEB, side: THREE.BackSide }),
      new THREE.MeshBasicMaterial({ color: 0x87CEEB, side: THREE.BackSide }),
      new THREE.MeshBasicMaterial({ color: 0x87CEEB, side: THREE.BackSide }),
      new THREE.MeshBasicMaterial({ color: 0x87CEEB, side: THREE.BackSide }),
      new THREE.MeshBasicMaterial({ color: 0x87CEEB, side: THREE.BackSide })
    ];
    
    const skybox = new THREE.Mesh(skyboxGeometry, skyboxMaterials);
    this.scene.add(skybox);
  }

  // 更新可见区块
  updateVisibleTiles() {
    const cameraPosition = this.camera.position;
    const frustum = this.getCameraFrustum();
    
    this.visibleTiles.clear();
    this.quadtree.getVisibleTiles(cameraPosition, frustum, this.viewDistance, this.visibleTiles);
    
    this.scheduleTileLoading();
    this.processTileUnloading();
  }

  // 获取相机视锥体
  getCameraFrustum() {
    const frustum = new THREE.Frustum();
    const cameraViewProjectionMatrix = new THREE.Matrix4();
    
    cameraViewProjectionMatrix.multiplyMatrices(
      this.camera.projectionMatrix,
      this.camera.matrixWorldInverse
    );
    
    frustum.setFromProjectionMatrix(cameraViewProjectionMatrix);
    return frustum;
  }

  // 调度区块加载
  async scheduleTileLoading() {
    const tilesToLoad = [];
    
    for (const tileInfo of this.visibleTiles) {
      const tileKey = this.getTileKey(tileInfo);
      
      if (!this.tileCache.has(tileKey)) {
        tilesToLoad.push(tileInfo);
      }
    }
    
    // 按距离排序,优先加载近的区块
    tilesToLoad.sort((a, b) => {
      const distA = this.getTileDistance(a, this.camera.position);
      const distB = this.getTileDistance(b, this.camera.position);
      return distA - distB;
    });
    
    await this.loadTiles(tilesToLoad);
  }

  // 加载多个区块
  async loadTiles(tileInfos) {
    const MAX_CONCURRENT_LOADS = 2;
    const loadPromises = [];
    
    for (let i = 0; i < tileInfos.length && loadPromises.length < MAX_CONCURRENT_LOADS; i++) {
      const tileInfo = tileInfos[i];
      const loadPromise = this.loadTile(tileInfo);
      loadPromises.push(loadPromise);
    }
    
    await Promise.all(loadPromises);
    
    // 继续加载剩余的区块
    if (tileInfos.length > MAX_CONCURRENT_LOADS) {
      await this.loadTiles(tileInfos.slice(MAX_CONCURRENT_LOADS));
    }
  }

  // 加载单个区块
  async loadTile(tileInfo) {
    const tileKey = this.getTileKey(tileInfo);
    
    try {
      const heightData = await this.generateTileHeightData(tileInfo);
      const tileMesh = this.createTileMesh(tileInfo, heightData);
      
      this.tileCache.set(tileKey, {
        mesh: tileMesh,
        tileInfo: tileInfo,
        lastUsed: Date.now()
      });
      
      this.scene.add(tileMesh);
      
    } catch (error) {
      console.error(`Failed to load tile ${tileKey}:`, error);
    }
  }

  // 生成区块高度数据
  async generateTileHeightData(tileInfo) {
    const { x, y, lod } = tileInfo;
    const size = this.tileSize >> lod; // 根据LOD调整分辨率
    const heightData = new Float32Array(size * size);
    
    // 使用多频噪声生成地形
    for (let i = 0; i < size; i++) {
      for (let j = 0; j < size; j++) {
        const worldX = (x * this.tileSize + i) * (1 << lod);
        const worldZ = (y * this.tileSize + j) * (1 << lod);
        
        heightData[i * size + j] = this.sampleHeight(worldX, worldZ);
      }
    }
    
    return heightData;
  }

  // 采样高度值
  sampleHeight(x, z) {
    let height = 0;
    
    // 多频噪声叠加
    const octaves = 6;
    let frequency = this.noiseFrequency;
    let amplitude = this.noiseIntensity;
    let persistence = 0.5;
    
    for (let i = 0; i < octaves; i++) {
      const noiseValue = this.simplexNoise(x * frequency, z * frequency);
      height += noiseValue * amplitude;
      
      frequency *= 2;
      amplitude *= persistence;
    }
    
    return height * 100; // 放大高度值
  }

  // 简化版的Simplex噪声
  simplexNoise(x, z) {
    // 这里使用简化实现,实际项目应该使用完整的噪声库
    return Math.sin(x * 0.01) * Math.cos(z * 0.01) * 0.5 + 0.5;
  }

  // 创建区块网格
  createTileMesh(tileInfo, heightData) {
    const { x, y, lod } = tileInfo;
    const size = this.tileSize >> lod;
    const geometry = this.createTileGeometry(heightData, size, lod);
    const material = this.createTileMaterial(tileInfo);
    
    const mesh = new THREE.Mesh(geometry, material);
    
    // 计算世界位置
    const worldX = x * this.tileSize * (1 << lod);
    const worldZ = y * this.tileSize * (1 << lod);
    mesh.position.set(worldX, 0, worldZ);
    
    mesh.castShadow = true;
    mesh.receiveShadow = true;
    mesh.userData = { tileInfo };
    
    return mesh;
  }

  // 创建区块几何体
  createTileGeometry(heightData, size, lod) {
    const geometry = new THREE.PlaneGeometry(
      this.tileSize * (1 << lod),
      this.tileSize * (1 << lod),
      size - 1,
      size - 1
    );
    
    const vertices = geometry.attributes.position.array;
    
    // 应用高度数据
    for (let i = 0, j = 0; i < vertices.length; i += 3, j++) {
      vertices[i + 1] = heightData[j];
    }
    
    geometry.rotateX(-Math.PI / 2); // 从XY平面转到XZ平面
    geometry.computeVertexNormals();
    
    return geometry;
  }

  // 创建区块材质
  createTileMaterial(tileInfo) {
    const { lod } = tileInfo;
    
    // 根据LOD级别使用不同复杂度的材质
    if (lod === 0) {
      return new THREE.MeshStandardMaterial({
        color: 0x7cfc00, // 绿色
        roughness: 0.8,
        metalness: 0.2
      });
    } else {
      return new THREE.MeshStandardMaterial({
        color: 0x8B4513, // 棕色
        roughness: 0.9,
        metalness: 0.1,
        wireframe: lod > 2 // 高LOD级别使用线框模式
      });
    }
  }

  // 处理区块卸载
  processTileUnloading() {
    const now = Date.now();
    const unusedTimeout = 30000; // 30秒未使用
    
    for (const [tileKey, tileData] of this.tileCache) {
      if (!this.visibleTiles.has(tileData.tileInfo) && 
          now - tileData.lastUsed > unusedTimeout) {
        this.unloadTile(tileKey);
      }
    }
  }

  // 卸载区块
  unloadTile(tileKey) {
    const tileData = this.tileCache.get(tileKey);
    if (tileData) {
      this.scene.remove(tileData.mesh);
      tileData.mesh.geometry.dispose();
      tileData.mesh.material.dispose();
      this.tileCache.delete(tileKey);
    }
  }

  // 获取区块键值
  getTileKey(tileInfo) {
    return `${tileInfo.x},${tileInfo.y},${tileInfo.lod}`;
  }

  // 获取区块距离
  getTileDistance(tileInfo, cameraPos) {
    const worldX = tileInfo.x * this.tileSize * (1 << tileInfo.lod);
    const worldZ = tileInfo.y * this.tileSize * (1 << tileInfo.lod);
    
    return Math.sqrt(
      Math.pow(worldX - cameraPos.x, 2) +
      Math.pow(worldZ - cameraPos.z, 2)
    );
  }

  // 渲染场景
  render() {
    this.renderer.render(this.scene, this.camera);
  }
}

// 四叉树实现
class Quadtree {
  constructor() {
    this.root = new QuadtreeNode(0, 0, 0);
    this.maxDepth = 6;
  }

  // 获取可见区块
  getVisibleTiles(cameraPos, frustum, viewDistance, visibleTiles) {
    this.collectVisibleTiles(this.root, cameraPos, frustum, viewDistance, visibleTiles);
  }

  // 收集可见区块
  collectVisibleTiles(node, cameraPos, frustum, viewDistance, visibleTiles) {
    if (!this.isNodeVisible(node, cameraPos, frustum, viewDistance)) {
      return;
    }

    if (this.shouldSubdivide(node, cameraPos)) {
      this.subdivideNode(node);
      
      for (const child of node.children) {
        this.collectVisibleTiles(child, cameraPos, frustum, viewDistance, visibleTiles);
      }
    } else {
      visibleTiles.add({
        x: node.x,
        y: node.y,
        lod: node.lod
      });
    }
  }

  // 检查节点是否可见
  isNodeVisible(node, cameraPos, frustum, viewDistance) {
    const nodeSize = 256 * (1 << node.lod);
    const nodeCenter = new THREE.Vector3(
      (node.x + 0.5) * nodeSize,
      0,
      (node.y + 0.5) * nodeSize
    );
    
    const distance = cameraPos.distanceTo(nodeCenter);
    if (distance > viewDistance) {
      return false;
    }
    
    // 简化的视锥体检测
    const boundingBox = new THREE.Box3(
      new THREE.Vector3(node.x * nodeSize, -1000, node.y * nodeSize),
      new THREE.Vector3((node.x + 1) * nodeSize, 1000, (node.y + 1) * nodeSize)
    );
    
    return frustum.intersectsBox(boundingBox);
  }

  // 判断是否应该细分节点
  shouldSubdivide(node, cameraPos) {
    if (node.lod >= 4) return false; // 最大LOD级别
    
    const nodeSize = 256 * (1 << node.lod);
    const nodeCenter = new THREE.Vector3(
      (node.x + 0.5) * nodeSize,
      0,
      (node.y + 0.5) * nodeSize
    );
    
    const distance = cameraPos.distanceTo(nodeCenter);
    const threshold = nodeSize * 2; // 细分阈值
    
    return distance < threshold;
  }

  // 细分节点
  subdivideNode(node) {
    if (node.children.length > 0) return; // 已经细分过了
    
    const childLod = node.lod + 1;
    const childSize = 1 << childLod;
    
    for (let i = 0; i < 2; i++) {
      for (let j = 0; j < 2; j++) {
        const childX = node.x * 2 + i;
        const childY = node.y * 2 + j;
        
        node.children.push(new QuadtreeNode(childX, childY, childLod));
      }
    }
  }
}

// 四叉树节点
class QuadtreeNode {
  constructor(x, y, lod) {
    this.x = x;
    this.y = y;
    this.lod = lod;
    this.children = [];
  }
}

// GPU地形计算器
class GPUTerrainComputer {
  constructor(renderer) {
    this.renderer = renderer;
    this.gpuCompute = null;
    this.initGPUCompute();
  }

  initGPUCompute() {
    this.gpuCompute = new GPUComputationRenderer(1024, 1024, this.renderer);
    
    // 创建高度场计算着色器
    const heightFieldShader = `
      uniform float time;
      uniform float noiseFrequency;
      uniform float noiseIntensity;
      
      void main() {
        vec2 uv = gl_FragCoord.xy / resolution.xy;
        
        // 多频噪声计算
        float height = 0.0;
        float frequency = noiseFrequency;
        float amplitude = noiseIntensity;
        
        for (int i = 0; i < 6; i++) {
          vec2 samplePos = uv * frequency;
          float noiseValue = simplexNoise(samplePos);
          height += noiseValue * amplitude;
          
          frequency *= 2.0;
          amplitude *= 0.5;
        }
        
        gl_FragColor = vec4(height, 0.0, 0.0, 1.0);
      }
      
      // 简化版Simplex噪声
      float simplexNoise(vec2 pos) {
        return (sin(pos.x * 10.0) * cos(pos.y * 10.0) + 1.0) * 0.5;
      }
    `;
    
    const heightFieldVariable = this.gpuCompute.addVariable(
      "textureHeight",
      heightFieldShader,
      new Float32Array(1024 * 1024)
    );
    
    this.gpuCompute.setVariableDependencies(heightFieldVariable, [heightFieldVariable]);
    this.gpuCompute.init();
  }

  // 计算高度场
  computeHeightField(noiseFrequency, noiseIntensity) {
    const variable = this.gpuCompute.variables[0];
    variable.material.uniforms.noiseFrequency = { value: noiseFrequency };
    variable.material.uniforms.noiseIntensity = { value: noiseIntensity };
    
    this.gpuCompute.compute();
    
    return this.gpuCompute.getCurrentRenderTarget(variable).texture;
  }
}

export default {
  name: 'LargeScaleTerrain',
  setup() {
    const terrainCanvas = ref(null);
    const viewDistance = ref(2000);
    const lodLevel = ref(2);
    const terrainQuality = ref('high');
    const renderedTiles = ref(0);
    const triangleCount = ref(0);
    const currentFPS = ref(0);
    const memoryUsage = ref(0);
    const noiseIntensity = ref(1.0);
    const noiseFrequency = ref(0.01);
    const isLoading = ref(false);
    const loadingMessage = ref('');
    const cameraPos = reactive({ x: 0, y: 0, z: 0 });
    const loadedTiles = ref(0);
    const totalTiles = ref(0);
    const culledTiles = ref(0);
    const lodDistribution = ref('');

    let scene, camera, renderer, controls;
    let terrainManager, gpuTerrainComputer;
    let stats, clock;
    let frameCount = 0;
    let lastFpsUpdate = 0;

    // 初始化场景
    const initScene = async () => {
      // 创建场景
      scene = new THREE.Scene();
      scene.background = new THREE.Color(0x87CEEB);
      scene.fog = new THREE.Fog(0x87CEEB, 500, 5000);

      // 创建相机
      camera = new THREE.PerspectiveCamera(
        75,
        window.innerWidth / window.innerHeight,
        0.1,
        10000
      );
      camera.position.set(0, 100, 200);

      // 创建渲染器
      renderer = new THREE.WebGLRenderer({
        canvas: terrainCanvas.value,
        antialias: true,
        powerPreference: "high-performance"
      });
      
      renderer.setSize(window.innerWidth, window.innerHeight);
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
      renderer.shadowMap.enabled = true;
      renderer.shadowMap.type = THREE.PCFSoftShadowMap;

      // 添加控制器
      controls = new OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;
      controls.dampingFactor = 0.05;
      controls.minDistance = 10;
      controls.maxDistance = 5000;

      // 初始化地形管理器
      terrainManager = new QuadtreeTerrainManager(renderer, camera);
      
      // 初始化GPU计算
      gpuTerrainComputer = new GPUTerrainComputer(renderer);

      // 启动渲染循环
      clock = new THREE.Clock();
      animate();
    };

    // 生成程序地形
    const generateProcedural = async () => {
      isLoading.value = true;
      loadingMessage.value = '正在生成程序地形...';
      
      // 模拟生成过程
      for (let progress = 0; progress <= 100; progress += 10) {
        loadingMessage.value = `生成地形中... ${progress}%`;
        await new Promise(resolve => setTimeout(resolve, 200));
      }
      
      isLoading.value = false;
    };

    // 加载高度图
    const loadHeightMap = async () => {
      isLoading.value = true;
      loadingMessage.value = '正在加载高度图...';
      
      // 模拟加载过程
      await new Promise(resolve => setTimeout(resolve, 2000));
      
      isLoading.value = false;
    };

    // 清空地形
    const clearTerrain = () => {
      // 实现地形清空逻辑
      console.log('清空地形');
    };

    // 动画循环
    const animate = () => {
      requestAnimationFrame(animate);
      
      const deltaTime = clock.getDelta();
      
      // 更新控制器
      controls.update();
      
      // 更新相机位置
      cameraPos.x = camera.position.x;
      cameraPos.y = camera.position.y;
      cameraPos.z = camera.position.z;
      
      // 更新地形管理器
      if (terrainManager) {
        terrainManager.updateVisibleTiles();
        terrainManager.render();
      }
      
      // 更新性能统计
      updatePerformanceStats(deltaTime);
    };

    // 更新性能统计
    const updatePerformanceStats = (deltaTime) => {
      frameCount++;
      lastFpsUpdate += deltaTime;
      
      if (lastFpsUpdate >= 1.0) {
        currentFPS.value = Math.round(frameCount / lastFpsUpdate);
        
        // 模拟统计数据
        renderedTiles.value = Math.floor(Math.random() * 50) + 20;
        triangleCount.value = renderedTiles.value * 5000;
        memoryUsage.value = triangleCount.value * 32; // 估算内存使用
        loadedTiles.value = renderedTiles.value + Math.floor(Math.random() * 10);
        totalTiles.value = 100;
        culledTiles.value = Math.floor(Math.random() * 20);
        lodDistribution.value = 'LOD0:10 LOD1:15 LOD2:20 LOD3:5';
        
        frameCount = 0;
        lastFpsUpdate = 0;
      }
    };

    // 格式化数字
    const formatNumber = (num) => {
      if (num >= 1000000) {
        return (num / 1000000).toFixed(1) + 'M';
      } else if (num >= 1000) {
        return (num / 1000).toFixed(1) + 'K';
      }
      return num.toString();
    };

    // 格式化内存大小
    const formatMemory = (bytes) => {
      if (bytes >= 1024 * 1024) {
        return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
      } else if (bytes >= 1024) {
        return (bytes / 1024).toFixed(1) + ' KB';
      }
      return bytes + ' B';
    };

    // 进度条样式
    const progressStyle = computed(() => ({
      width: '100%' // 简化实现
    }));

    // 响应式设置
    watch(viewDistance, (newDistance) => {
      if (terrainManager) {
        terrainManager.viewDistance = newDistance;
      }
    });

    watch(noiseIntensity, (newIntensity) => {
      // 更新噪声强度
      if (terrainManager) {
        terrainManager.noiseIntensity = newIntensity;
      }
    });

    watch(noiseFrequency, (newFrequency) => {
      // 更新噪声频率
      if (terrainManager) {
        terrainManager.noiseFrequency = newFrequency;
      }
    });

    onMounted(() => {
      initScene();
      window.addEventListener('resize', handleResize);
    });

    onUnmounted(() => {
      if (renderer) {
        renderer.dispose();
      }
      window.removeEventListener('resize', handleResize);
    });

    const handleResize = () => {
      if (!camera || !renderer) return;
      
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    };

    return {
      terrainCanvas,
      viewDistance,
      lodLevel,
      terrainQuality,
      renderedTiles,
      triangleCount,
      currentFPS,
      memoryUsage,
      noiseIntensity,
      noiseFrequency,
      isLoading,
      loadingMessage,
      cameraPos,
      loadedTiles,
      totalTiles,
      culledTiles,
      lodDistribution,
      progressStyle,
      generateProcedural,
      loadHeightMap,
      clearTerrain,
      formatNumber,
      formatMemory
    };
  }
};
</script>

<style scoped>
.terrain-container {
  width: 100%;
  height: 100vh;
  position: relative;
  background: #000;
}

.terrain-canvas {
  width: 100%;
  height: 100%;
  display: block;
}

.terrain-controls {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 300px;
  background: rgba(0, 0, 0, 0.8);
  padding: 20px;
  border-radius: 10px;
  color: white;
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.1);
}

.control-section {
  margin-bottom: 25px;
  padding-bottom: 15px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.control-section:last-child {
  margin-bottom: 0;
  border-bottom: none;
}

.control-section h3 {
  color: #00ffff;
  margin-bottom: 15px;
  font-size: 16px;
}

.control-group {
  margin-bottom: 15px;
}

.control-group label {
  display: block;
  margin-bottom: 5px;
  color: #ccc;
  font-size: 14px;
}

.control-group input[type="range"],
.control-group select {
  width: 100%;
  padding: 8px;
  border: 1px solid #444;
  border-radius: 4px;
  background: #333;
  color: white;
}

.performance-stats {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.stat-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 0;
  font-size: 14px;
}

.stat-item span:first-child {
  color: #ccc;
}

.stat-item span:last-child {
  color: #00ffff;
  font-weight: bold;
}

.generation-controls {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 15px;
}

.control-button {
  padding: 10px;
  border: none;
  border-radius: 6px;
  background: linear-gradient(45deg, #667eea, #764ba2);
  color: white;
  cursor: pointer;
  font-size: 14px;
  transition: transform 0.2s;
}

.control-button:hover {
  transform: translateY(-2px);
}

.noise-controls {
  margin-top: 15px;
}

.debug-overlay {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background: rgba(0, 0, 0, 0.7);
  padding: 15px;
  border-radius: 8px;
  color: white;
  font-size: 12px;
  backdrop-filter: blur(10px);
}

.debug-info {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.9);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.loading-content {
  text-align: center;
  color: white;
}

.terrain-loader {
  position: relative;
  width: 120px;
  height: 60px;
  margin: 0 auto 30px;
}

.mountain {
  position: absolute;
  bottom: 0;
  width: 0;
  height: 0;
  border-left: 30px solid transparent;
  border-right: 30px solid transparent;
  border-bottom: 50px solid #4a5568;
  animation: float 3s ease-in-out infinite;
}

.mountain:nth-child(1) {
  left: 0;
  animation-delay: 0s;
}

.mountain:nth-child(2) {
  left: 40px;
  animation-delay: 0.5s;
  border-bottom-color: #2d3748;
}

.mountain:nth-child(3) {
  left: 80px;
  animation-delay: 1s;
  border-bottom-color: #1a202c;
}

.loading-content h3 {
  color: #00ffff;
  margin-bottom: 20px;
}

.loading-progress {
  width: 300px;
  margin: 0 auto;
}

.progress-bar {
  width: 100%;
  height: 6px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 3px;
  overflow: hidden;
  margin-bottom: 10px;
}

.progress-fill {
  height: 100%;
  background: linear-gradient(90deg, #00ffff, #0088ff);
  border-radius: 3px;
  transition: width 0.3s ease;
}

.progress-text {
  color: #00aaff;
  font-size: 14px;
}

@keyframes float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
}
</style>

高级地形特性

动态地形变形系统

javascript 复制代码
class DynamicTerrainSystem {
  constructor(terrainManager) {
    this.terrainManager = terrainManager;
    this.modifications = new Map();
    this.gpuCompute = null;
    
    this.initModificationSystem();
  }

  // 初始化变形系统
  initModificationSystem() {
    // 创建GPU计算渲染器用于实时地形变形
    this.gpuCompute = new GPUComputationRenderer(
      1024, 1024, this.terrainManager.renderer
    );
    
    this.setupModificationShaders();
  }

  // 设置变形着色器
  setupModificationShaders() {
    const modificationShader = `
      uniform sampler2D baseHeightmap;
      uniform sampler2D modifications;
      uniform vec3 modificationCenter;
      uniform float modificationRadius;
      uniform float modificationStrength;
      
      void main() {
        vec2 uv = gl_FragCoord.xy / resolution.xy;
        float baseHeight = texture2D(baseHeightmap, uv).r;
        vec4 modification = texture2D(modifications, uv);
        
        // 计算到变形中心的距离
        vec2 worldPos = uv * 1024.0; // 假设世界大小1024单位
        float distanceToCenter = length(worldPos - modificationCenter.xz);
        
        if (distanceToCenter < modificationRadius) {
          // 应用变形影响
          float influence = 1.0 - smoothstep(0.0, modificationRadius, distanceToCenter);
          baseHeight += modificationStrength * influence * modification.r;
        }
        
        gl_FragColor = vec4(baseHeight, 0.0, 0.0, 1.0);
      }
    `;
    
    // 配置GPU计算变量
    const modificationVariable = this.gpuCompute.addVariable(
      "textureHeight",
      modificationShader,
      new Float32Array(1024 * 1024)
    );
    
    this.gpuCompute.setVariableDependencies(modificationVariable, [modificationVariable]);
    this.gpuCompute.init();
  }

  // 应用地形变形
  applyModification(center, radius, strength, type = 'raise') {
    const modification = {
      center: center,
      radius: radius,
      strength: strength * (type === 'raise' ? 1 : -1),
      timestamp: Date.now()
    };
    
    const modificationKey = `${center.x},${center.z}`;
    this.modifications.set(modificationKey, modification);
    
    // 标记受影响的地形块需要更新
    this.markAffectedTilesForUpdate(center, radius);
    
    // 执行GPU计算更新地形
    this.updateTerrainHeightmap();
  }

  // 标记受影响的地形块
  markAffectedTilesForUpdate(center, radius) {
    const affectedTiles = new Set();
    
    for (const [tileKey, tileData] of this.terrainManager.tileCache) {
      if (this.isTileAffected(tileData, center, radius)) {
        affectedTiles.add(tileKey);
      }
    }
    
    // 重新生成受影响的地形块
    this.regenerateAffectedTiles(affectedTiles);
  }

  // 检查地形块是否受影响
  isTileAffected(tileData, center, radius) {
    const tileBounds = this.getTileBounds(tileData.tileInfo);
    const distance = this.pointToRectDistance(center, tileBounds);
    
    return distance <= radius;
  }

  // 重新生成受影响的地形块
  async regenerateAffectedTiles(affectedTiles) {
    for (const tileKey of affectedTiles) {
      const tileData = this.terrainManager.tileCache.get(tileKey);
      if (tileData) {
        await this.terrainManager.regenerateTile(tileData.tileInfo);
      }
    }
  }

  // 更新地形高度图
  updateTerrainHeightmap() {
    // 收集所有变形数据
    const modificationsArray = Array.from(this.modifications.values());
    
    // 更新GPU计算uniforms
    const variable = this.gpuCompute.variables[0];
    
    modificationsArray.forEach((mod, index) => {
      variable.material.uniforms[`modificationCenter${index}`] = { value: mod.center };
      variable.material.uniforms[`modificationRadius${index}`] = { value: mod.radius };
      variable.material.uniforms[`modificationStrength${index}`] = { value: mod.strength };
    });
    
    variable.material.uniforms.modificationCount = { value: modificationsArray.length };
    
    // 执行计算
    this.gpuCompute.compute();
  }
}

无限地形生成器

javascript 复制代码
class InfiniteTerrainGenerator {
  constructor(terrainManager) {
    this.terrainManager = terrainManager;
    this.seed = Math.random() * 1000;
    this.biomes = new Map();
    
    this.setupBiomes();
  }

  // 设置生物群落
  setupBiomes() {
    this.biomes.set('plains', {
      heightRange: [0, 50],
      color: 0x7cfc00,
      treeDensity: 0.1,
      noiseScale: 0.005
    });
    
    this.biomes.set('mountains', {
      heightRange: [100, 500],
      color: 0x8B4513,
      treeDensity: 0.01,
      noiseScale: 0.002
    });
    
    this.biomes.set('forest', {
      heightRange: [20, 80],
      color: 0x228B22,
      treeDensity: 0.3,
      noiseScale: 0.008
    });
    
    this.biomes.set('desert', {
      heightRange: [0, 30],
      color: 0xF4A460,
      treeDensity: 0.02,
      noiseScale: 0.01
    });
  }

  // 获取生物群落
  getBiomeAt(x, z) {
    const temperature = this.sampleTemperature(x, z);
    const humidity = this.sampleHumidity(x, z);
    
    if (temperature > 0.7 && humidity < 0.3) return 'desert';
    if (temperature > 0.5 && humidity > 0.6) return 'forest';
    if (temperature < 0.3) return 'mountains';
    return 'plains';
  }

  // 采样温度
  sampleTemperature(x, z) {
    return (this.noise(x * 0.001, z * 0.001) + 1) * 0.5;
  }

  // 采样湿度
  sampleHumidity(x, z) {
    return (this.noise(x * 0.0005, z * 0.0005) + 1) * 0.5;
  }

  // 生成地形高度
  generateTerrainHeight(x, z) {
    const biome = this.getBiomeAt(x, z);
    const biomeConfig = this.biomes.get(biome);
    
    let height = 0;
    
    // 基础地形噪声
    height += this.ridgedNoise(x * biomeConfig.noiseScale, z * biomeConfig.noiseScale) * 100;
    
    // 添加细节噪声
    height += this.noise(x * 0.01, z * 0.01) * 20;
    height += this.noise(x * 0.05, z * 0.05) * 5;
    
    // 应用生物群落高度范围
    const [minHeight, maxHeight] = biomeConfig.heightRange;
    height = Math.max(minHeight, Math.min(maxHeight, height));
    
    return height;
  }

  // 脊状噪声(用于山脉)
  ridgedNoise(x, z) {
    const value = 1 - Math.abs(this.noise(x, z));
    return value * value;
  }

  // 多频噪声
  noise(x, z) {
    let total = 0;
    let frequency = 1;
    let amplitude = 1;
    let maxValue = 0;
    
    for (let i = 0; i < 6; i++) {
      total += this.simplexNoise(x * frequency, z * frequency) * amplitude;
      maxValue += amplitude;
      amplitude *= 0.5;
      frequency *= 2;
    }
    
    return total / maxValue;
  }

  // Simplex噪声实现
  simplexNoise(x, z) {
    // 简化版的Simplex噪声
    // 实际项目应该使用完整的噪声实现
    const F2 = 0.5 * (Math.sqrt(3.0) - 1.0);
    const G2 = (3.0 - Math.sqrt(3.0)) / 6.0;
    
    const s = (x + z) * F2;
    const i = Math.floor(x + s);
    const j = Math.floor(z + s);
    
    const t = (i + j) * G2;
    const X0 = i - t;
    const Z0 = j - t;
    const x0 = x - X0;
    const z0 = z - Z0;
    
    // 简化计算,返回随机值
    return Math.sin(i * 12.9898 + j * 78.233) * 43758.5453 % 1 * 2 - 1;
  }
}

注意事项与最佳实践

  1. 性能优化关键

    • 使用四叉树进行空间分割
    • 实现高效的视锥体剔除
    • 采用动态LOD系统
    • 使用GPU计算进行地形生成
  2. 内存管理策略

    • 实现LRU缓存淘汰机制
    • 使用压缩格式存储地形数据
    • 动态加载和卸载地形块
    • 监控内存使用情况
  3. 视觉质量优化

    • 实现无缝LOD过渡
    • 使用细节纹理和法线贴图
    • 应用适当的光照和阴影
    • 添加环境效果(雾、大气散射)

下一节预告

第31节:流体模拟与Shader实现水效果

将深入探索基于Shader的流体模拟技术,包括:波浪方程实现、法线贴图生成、交互式水波纹、以及实时流体物理计算,创造逼真的水域效果。

相关推荐
景早2 小时前
商品案例-组件封装(vue)
前端·javascript·vue.js
不说别的就是很菜2 小时前
【前端面试】Vue篇
前端·vue.js·面试
IT_陈寒2 小时前
Java 17实战:我从老旧Spring项目迁移中总结的7个关键避坑点
前端·人工智能·后端
倚肆2 小时前
CSS 动画与变换属性详解
前端·css
blackorbird2 小时前
谷歌 Chrome 浏览器的指纹识别技术,一边反追踪一边搞追踪
前端·chrome
Mintopia3 小时前
🚀 共绩算力:3分钟拥有自己的图像优化服务-CodeFormer:先进的图像算法优化、修复马赛克、提升图片清晰度等
前端·人工智能·ai编程
Lhuu(重开版3 小时前
html语法
前端·html