第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+ | 极低 |
地形分块策略
基于四叉树的地形分块管理:
-
空间分割
- 递归四叉树分割
- 动态块加载/卸载
- 边界裂缝处理
-
内存管理
- 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;
}
}
注意事项与最佳实践
-
性能优化关键
- 使用四叉树进行空间分割
- 实现高效的视锥体剔除
- 采用动态LOD系统
- 使用GPU计算进行地形生成
-
内存管理策略
- 实现LRU缓存淘汰机制
- 使用压缩格式存储地形数据
- 动态加载和卸载地形块
- 监控内存使用情况
-
视觉质量优化
- 实现无缝LOD过渡
- 使用细节纹理和法线贴图
- 应用适当的光照和阴影
- 添加环境效果(雾、大气散射)
下一节预告
第31节:流体模拟与Shader实现水效果
将深入探索基于Shader的流体模拟技术,包括:波浪方程实现、法线贴图生成、交互式水波纹、以及实时流体物理计算,创造逼真的水域效果。