第33节:程序化生成与无限地形算法

概述
程序化生成是现代游戏和虚拟世界的核心技术,能够创建无限多样且性能高效的环境。本节将深入探索噪声算法、地形侵蚀模拟、生物群落系统,以及动态流式加载技术,构建真正无限的虚拟世界。
程序化生成系统架构:
程序化生成系统 噪声生成层 地形生成层 生态生成层 动态加载层 多频噪声 域扭曲技术 缓存优化 高度场生成 侵蚀模拟 纹理合成 生物群落 植被分布 生物分布 流式加载 LOD管理 内存管理 无限变化 真实地形 生态环境 无缝体验
核心原理深度解析
噪声算法体系
程序化生成的数学基础:
| 噪声类型 | 特性 | 适用场景 | 性能 |
|---|---|---|---|
| Perlin噪声 | 平滑连续梯度 | 基础地形、云层 | 中等 |
| Simplex噪声 | 计算效率高 | 实时效果、细节 | 高 |
| Value噪声 | 计算简单 | 基础图案、高度 | 高 |
| Worley噪声 | 细胞图案 | 石头、生物细胞 | 中等 |
分形布朗运动(FBM)
通过多频噪声叠加创造自然细节:
FBM(p) = noise(p) + 0.5*noise(2p) + 0.25*noise(4p) + ...
完整代码实现
无限程序化地形系统
vue
<template>
<div class="procedural-world-container">
<!-- 主渲染画布 -->
<canvas ref="worldCanvas" class="world-canvas"></canvas>
<!-- 生成控制面板 -->
<div class="generation-controls">
<div class="control-section">
<h3>🌍 地形生成设置</h3>
<div class="control-group">
<label>地形规模: {{ terrainScale }}</label>
<input
type="range"
v-model="terrainScale"
min="100"
max="1000"
step="10"
>
</div>
<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>细节层级: {{ detailLevels }}</label>
<input
type="range"
v-model="detailLevels"
min="1"
max="8"
step="1"
>
</div>
<div class="control-group">
<label>侵蚀强度: {{ erosionStrength }}</label>
<input
type="range"
v-model="erosionStrength"
min="0"
max="1"
step="0.05"
>
</div>
</div>
<div class="control-section">
<h3>🌳 生态群落设置</h3>
<div class="biome-controls">
<div class="control-group">
<label>温度: {{ temperature }}</label>
<input
type="range"
v-model="temperature"
min="0"
max="1"
step="0.05"
>
</div>
<div class="control-group">
<label>湿度: {{ humidity }}</label>
<input
type="range"
v-model="humidity"
min="0"
max="1"
step="0.05"
>
</div>
</div>
<div class="vegetation-controls">
<div class="control-group">
<label>树木密度: {{ treeDensity }}</label>
<input
type="range"
v-model="treeDensity"
min="0"
max="1"
step="0.05"
>
</div>
<div class="control-group">
<label>草地密度: {{ grassDensity }}</label>
<input
type="range"
v-model="grassDensity"
min="0"
max="1"
step="0.05"
>
</div>
</div>
</div>
<div class="control-section">
<h3>⚙️ 生成算法</h3>
<div class="algorithm-buttons">
<button
@click="setAlgorithm('perlin')"
class="algo-button"
:class="{ active: currentAlgorithm === 'perlin' }"
>
Perlin噪声
</button>
<button
@click="setAlgorithm('simplex')"
class="algo-button"
:class="{ active: currentAlgorithm === 'simplex' }"
>
Simplex噪声
</button>
<button
@click="setAlgorithm('fbm')"
class="algo-button"
:class="{ active: currentAlgorithm === 'fbm' }"
>
分形噪声
</button>
<button
@click="setAlgorithm('domain')"
class="algo-button"
:class="{ active: currentAlgorithm === 'domain' }"
>
域扭曲
</button>
</div>
<div class="generation-actions">
<button @click="generateNewWorld" class="action-button primary">
🎲 重新生成
</button>
<button @click="exportWorld" class="action-button">
💾 导出世界
</button>
<button @click="clearWorld" class="action-button danger">
🗑️ 清空世界
</button>
</div>
</div>
<div class="control-section">
<h3>📊 世界统计</h3>
<div class="world-stats">
<div class="stat-item">
<span>生成区块:</span>
<span>{{ generatedChunks }}</span>
</div>
<div class="stat-item">
<span>顶点数量:</span>
<span>{{ formatNumber(vertexCount) }}</span>
</div>
<div class="stat-item">
<span>生物群落:</span>
<span>{{ biomeCount }}</span>
</div>
<div class="stat-item">
<span>帧率:</span>
<span>{{ currentFPS }} FPS</span>
</div>
</div>
</div>
</div>
<!-- 世界信息显示 -->
<div class="world-info-overlay">
<div class="info-panel">
<h4>世界信息</h4>
<div class="info-content">
<div>种子: {{ worldSeed }}</div>
<div>坐标: X{{ currentPos.x }}, Y{{ currentPos.y }}, Z{{ currentPos.z }}</div>
<div>生物群系: {{ currentBiome }}</div>
<div>海拔: {{ currentElevation }}m</div>
<div>温度: {{ currentTemperature }}°C</div>
</div>
</div>
</div>
<!-- 加载界面 -->
<div v-if="isGenerating" class="generation-overlay">
<div class="generation-loader">
<div class="terrain-preview">
<div class="mountain-range"></div>
<div class="mountain-range"></div>
<div class="mountain-range"></div>
</div>
<h3>正在生成无限世界...</h3>
<div class="generation-progress">
<div class="progress-bar">
<div class="progress-fill" :style="progressStyle"></div>
</div>
<span class="progress-text">{{ generationMessage }}</span>
</div>
<div class="generation-details">
<span>已生成: {{ loadedChunks }} 个区块</span>
<span>内存使用: {{ formatMemory(memoryUsage) }}</span>
</div>
</div>
</div>
<!-- 调试面板 -->
<div class="debug-panel">
<button @click="toggleDebug" class="debug-toggle">
{{ showDebug ? '🔍 隐藏调试' : '🔍 显示调试' }}
</button>
<div v-if="showDebug" class="debug-info">
<div>噪声采样: {{ noiseSamples }} 次/帧</div>
<div>LOD级别: {{ currentLOD }}</div>
<div>视距: {{ viewDistance }}m</div>
<div>加载队列: {{ loadingQueue }} 个区块</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';
// 高级噪声生成系统
class AdvancedNoiseSystem {
constructor(seed = 12345) {
this.seed = seed;
this.permutation = this.generatePermutation(seed);
this.gradients = this.generateGradients();
}
// 生成排列表
generatePermutation(seed) {
const permutation = new Array(512);
const source = new Array(256);
for (let i = 0; i < 256; i++) {
source[i] = i;
}
// 使用种子随机化
this.seedRandom(seed);
for (let i = 255; i > 0; i--) {
const j = Math.floor(this.random() * (i + 1));
[source[i], source[j]] = [source[j], source[i]];
}
for (let i = 0; i < 512; i++) {
permutation[i] = source[i & 255];
}
return permutation;
}
// 种子随机数生成器
seedRandom(seed) {
this.randomState = seed;
}
random() {
this.randomState = (this.randomState * 9301 + 49297) % 233280;
return this.randomState / 233280;
}
// 生成梯度向量
generateGradients() {
const gradients = [];
for (let i = 0; i < 256; i++) {
const angle = (i / 256) * Math.PI * 2;
gradients[i] = new THREE.Vector2(Math.cos(angle), Math.sin(angle));
}
return gradients;
}
// 2D Perlin噪声
perlin2D(x, y) {
const X = Math.floor(x) & 255;
const Y = Math.floor(y) & 255;
x -= Math.floor(x);
y -= Math.floor(y);
const u = this.fade(x);
const v = this.fade(y);
const aa = this.permutation[this.permutation[X] + Y];
const ab = this.permutation[this.permutation[X] + Y + 1];
const ba = this.permutation[this.permutation[X + 1] + Y];
const bb = this.permutation[this.permutation[X + 1] + Y + 1];
const gradAA = this.gradients[aa];
const gradAB = this.gradients[ab];
const gradBA = this.gradients[ba];
const gradBB = this.gradients[bb];
const x1 = this.lerp(u, this.dot(gradAA, x, y), this.dot(gradBA, x - 1, y));
const x2 = this.lerp(u, this.dot(gradAB, x, y - 1), this.dot(gradBB, x - 1, y - 1));
return this.lerp(v, x1, x2);
}
// 3D Perlin噪声
perlin3D(x, y, z) {
const X = Math.floor(x) & 255;
const Y = Math.floor(y) & 255;
const Z = Math.floor(z) & 255;
x -= Math.floor(x);
y -= Math.floor(y);
z -= Math.floor(z);
const u = this.fade(x);
const v = this.fade(y);
const w = this.fade(z);
const a = this.permutation[X] + Y;
const aa = this.permutation[a] + Z;
const ab = this.permutation[a + 1] + Z;
const b = this.permutation[X + 1] + Y;
const ba = this.permutation[b] + Z;
const bb = this.permutation[b + 1] + Z;
const gradAA = this.grad3D[this.permutation[aa] & 15];
const gradAB = this.grad3D[this.permutation[ab] & 15];
const gradBA = this.grad3D[this.permutation[ba] & 15];
const gradBB = this.grad3D[this.permutation[bb] & 15];
const x1 = this.lerp(u, this.dot3(gradAA, x, y, z), this.dot3(gradBA, x - 1, y, z));
const x2 = this.lerp(u, this.dot3(gradAB, x, y - 1, z), this.dot3(gradBB, x - 1, y - 1, z));
const y1 = this.lerp(v, x1, x2);
const x3 = this.lerp(u, this.dot3(gradAA, x, y, z - 1), this.dot3(gradBA, x - 1, y, z - 1));
const x4 = this.lerp(u, this.dot3(gradAB, x, y - 1, z - 1), this.dot3(gradBB, x - 1, y - 1, z - 1));
const y2 = this.lerp(v, x3, x4);
return this.lerp(w, y1, y2);
}
// 分形布朗运动(FBM)
fbm2D(x, y, octaves = 4, lacunarity = 2.0, gain = 0.5) {
let value = 0.0;
let amplitude = 1.0;
let frequency = 1.0;
let maxValue = 0.0;
for (let i = 0; i < octaves; i++) {
value += this.perlin2D(x * frequency, y * frequency) * amplitude;
maxValue += amplitude;
amplitude *= gain;
frequency *= lacunarity;
}
return value / maxValue;
}
// 域扭曲噪声
domainWarp2D(x, y, strength = 1.0) {
const dx = this.perlin2D(x + 123.4, y + 567.8) * strength;
const dy = this.perlin2D(x - 123.4, y - 567.8) * strength;
return this.fbm2D(x + dx, y + dy);
}
// 工具函数
fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
lerp(t, a, b) { return a + t * (b - a); }
dot(g, x, y) { return g.x * x + g.y * y; }
dot3(g, x, y, z) { return g.x * x + g.y * y + g.z * z; }
}
// 无限地形生成器
class InfiniteTerrainGenerator {
constructor(renderer, camera, chunkSize = 32, viewDistance = 5) {
this.renderer = renderer;
this.camera = camera;
this.scene = new THREE.Scene();
this.chunkSize = chunkSize;
this.viewDistance = viewDistance;
this.chunks = new Map();
this.loadingQueue = [];
this.noiseSystem = new AdvancedNoiseSystem(Date.now());
this.biomeSystem = new BiomeSystem();
this.setupScene();
this.startLoadingSystem();
}
// 初始化场景
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);
}
// 启动加载系统
startLoadingSystem() {
setInterval(() => this.updateChunks(), 100);
}
// 更新区块
updateChunks() {
const cameraChunkX = Math.floor(this.camera.position.x / this.chunkSize);
const cameraChunkZ = Math.floor(this.camera.position.z / this.chunkSize);
// 卸载视野外的区块
this.unloadDistantChunks(cameraChunkX, cameraChunkZ);
// 加载视野内的区块
this.loadVisibleChunks(cameraChunkX, cameraChunkZ);
// 处理加载队列
this.processLoadingQueue();
}
// 卸载远处区块
unloadDistantChunks(cameraChunkX, cameraChunkZ) {
for (const [chunkKey, chunk] of this.chunks) {
const [chunkX, chunkZ] = this.parseChunkKey(chunkKey);
const distance = Math.max(
Math.abs(chunkX - cameraChunkX),
Math.abs(chunkZ - cameraChunkZ)
);
if (distance > this.viewDistance + 1) {
this.scene.remove(chunk.mesh);
chunk.mesh.geometry.dispose();
chunk.mesh.material.dispose();
this.chunks.delete(chunkKey);
}
}
}
// 加载可见区块
loadVisibleChunks(cameraChunkX, cameraChunkZ) {
for (let x = -this.viewDistance; x <= this.viewDistance; x++) {
for (let z = -this.viewDistance; z <= this.viewDistance; z++) {
const chunkX = cameraChunkX + x;
const chunkZ = cameraChunkZ + z;
const chunkKey = this.getChunkKey(chunkX, chunkZ);
if (!this.chunks.has(chunkKey) && !this.isInLoadingQueue(chunkKey)) {
this.loadingQueue.push({ x: chunkX, z: chunkZ, priority: Math.abs(x) + Math.abs(z) });
}
}
}
// 按优先级排序
this.loadingQueue.sort((a, b) => a.priority - b.priority);
}
// 处理加载队列
async processLoadingQueue() {
const MAX_CONCURRENT_LOADS = 2;
const currentLoads = [];
while (this.loadingQueue.length > 0 && currentLoads.length < MAX_CONCURRENT_LOADS) {
const chunkInfo = this.loadingQueue.shift();
const loadPromise = this.generateChunk(chunkInfo.x, chunkInfo.z).finally(() => {
const index = currentLoads.indexOf(loadPromise);
if (index > -1) currentLoads.splice(index, 1);
});
currentLoads.push(loadPromise);
}
}
// 生成区块
async generateChunk(chunkX, chunkZ) {
const chunkKey = this.getChunkKey(chunkX, chunkZ);
try {
const heightData = await this.generateHeightData(chunkX, chunkZ);
const chunkMesh = await this.createChunkMesh(chunkX, chunkZ, heightData);
this.chunks.set(chunkKey, {
mesh: chunkMesh,
x: chunkX,
z: chunkZ,
heightData: heightData
});
this.scene.add(chunkMesh);
} catch (error) {
console.error(`Failed to generate chunk ${chunkKey}:`, error);
}
}
// 生成高度数据
async generateHeightData(chunkX, chunkZ) {
const dataSize = this.chunkSize + 1; // 包含边界顶点
const heightData = new Float32Array(dataSize * dataSize);
const worldScale = 0.01;
for (let x = 0; x < dataSize; x++) {
for (let z = 0; z < dataSize; z++) {
const worldX = (chunkX * this.chunkSize + x) * worldScale;
const worldZ = (chunkZ * this.chunkSize + z) * worldScale;
// 使用FBM生成基础地形
let height = this.noiseSystem.fbm2D(worldX, worldZ, 6, 2.0, 0.5);
// 添加山脉
const mountainNoise = this.noiseSystem.fbm2D(worldX * 0.5, worldZ * 0.5, 4, 2.2, 0.6);
height += Math.max(0, mountainNoise - 0.3) * 2.0;
// 添加丘陵细节
const hillNoise = this.noiseSystem.fbm2D(worldX * 2.0, worldZ * 2.0, 3, 2.5, 0.4);
height += hillNoise * 0.3;
// 缩放高度值
heightData[x * dataSize + z] = height * 50;
}
}
return heightData;
}
// 创建区块网格
async createChunkMesh(chunkX, chunkZ, heightData) {
const dataSize = Math.sqrt(heightData.length);
const geometry = new THREE.PlaneGeometry(
this.chunkSize,
this.chunkSize,
dataSize - 1,
dataSize - 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);
geometry.computeVertexNormals();
// 创建基于生物群落的材质
const centerX = chunkX * this.chunkSize + this.chunkSize / 2;
const centerZ = chunkZ * this.chunkSize + this.chunkSize / 2;
const biome = this.biomeSystem.getBiomeAt(centerX, centerZ, heightData[Math.floor(heightData.length / 2)]);
const material = new THREE.MeshStandardMaterial({
color: biome.color,
roughness: biome.roughness,
metalness: biome.metalness
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(chunkX * this.chunkSize, 0, chunkZ * this.chunkSize);
mesh.castShadow = true;
mesh.receiveShadow = true;
// 添加植被
await this.addVegetation(mesh, chunkX, chunkZ, heightData, biome);
return mesh;
}
// 添加植被
async addVegetation(mesh, chunkX, chunkZ, heightData, biome) {
if (biome.treeDensity > 0) {
const treeCount = Math.floor(biome.treeDensity * this.chunkSize * this.chunkSize * 0.01);
for (let i = 0; i < treeCount; i++) {
const localX = Math.floor(Math.random() * this.chunkSize);
const localZ = Math.floor(Math.random() * this.chunkSize);
const heightIndex = localX * (this.chunkSize + 1) + localZ;
if (heightData[heightIndex] > 1.0 && Math.random() < 0.3) {
const tree = this.createTree();
tree.position.set(
chunkX * this.chunkSize + localX,
heightData[heightIndex],
chunkZ * this.chunkSize + localZ
);
this.scene.add(tree);
}
}
}
}
// 创建树木
createTree() {
const group = new THREE.Group();
// 树干
const trunkGeometry = new THREE.CylinderGeometry(0.2, 0.3, 3, 8);
const trunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
trunk.position.y = 1.5;
group.add(trunk);
// 树冠
const crownGeometry = new THREE.SphereGeometry(1.5, 8, 6);
const crownMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });
const crown = new THREE.Mesh(crownGeometry, crownMaterial);
crown.position.y = 4;
group.add(crown);
return group;
}
// 工具函数
getChunkKey(x, z) { return `${x},${z}`; }
parseChunkKey(key) { return key.split(',').map(Number); }
isInLoadingQueue(chunkKey) {
return this.loadingQueue.some(chunk => this.getChunkKey(chunk.x, chunk.z) === chunkKey);
}
// 渲染场景
render() {
this.renderer.render(this.scene, this.camera);
}
}
// 生物群落系统
class BiomeSystem {
constructor() {
this.biomes = this.initializeBiomes();
}
// 初始化生物群落
initializeBiomes() {
return [
{
name: '平原',
temperature: [0.4, 0.7],
humidity: [0.3, 0.6],
elevation: [0, 50],
color: 0x7CFC00,
roughness: 0.8,
metalness: 0.2,
treeDensity: 0.1
},
{
name: '森林',
temperature: [0.3, 0.6],
humidity: [0.5, 0.9],
elevation: [10, 100],
color: 0x228B22,
roughness: 0.7,
metalness: 0.1,
treeDensity: 0.4
},
{
name: '沙漠',
temperature: [0.7, 1.0],
humidity: [0.0, 0.2],
elevation: [0, 80],
color: 0xF4A460,
roughness: 0.9,
metalness: 0.3,
treeDensity: 0.01
},
{
name: '雪山',
temperature: [0.0, 0.2],
humidity: [0.2, 0.8],
elevation: [80, 200],
color: 0xFFFFFF,
roughness: 0.6,
metalness: 0.4,
treeDensity: 0.0
},
{
name: '海洋',
temperature: [0.2, 0.9],
humidity: [0.8, 1.0],
elevation: [-10, 1],
color: 0x1E90FF,
roughness: 0.3,
metalness: 0.8,
treeDensity: 0.0
}
];
}
// 获取位置的生物群落
getBiomeAt(x, z, elevation) {
// 简化实现:使用噪声生成温度和湿度
const temperature = (Math.sin(x * 0.001) + 1) * 0.5;
const humidity = (Math.cos(z * 0.001) + 1) * 0.5;
// 找到匹配的生物群落
for (const biome of this.biomes) {
if (this.isInRange(temperature, biome.temperature) &&
this.isInRange(humidity, biome.humidity) &&
this.isInRange(elevation, biome.elevation)) {
return biome;
}
}
// 默认返回平原
return this.biomes[0];
}
// 检查值是否在范围内
isInRange(value, range) {
return value >= range[0] && value <= range[1];
}
}
export default {
name: 'ProceduralWorld',
setup() {
const worldCanvas = ref(null);
const terrainScale = ref(100);
const noiseIntensity = ref(1.0);
const detailLevels = ref(4);
const erosionStrength = ref(0.0);
const temperature = ref(0.5);
const humidity = ref(0.5);
const treeDensity = ref(0.3);
const grassDensity = ref(0.7);
const currentAlgorithm = ref('fbm');
const generatedChunks = ref(0);
const vertexCount = ref(0);
const biomeCount = ref(5);
const currentFPS = ref(0);
const worldSeed = ref(Date.now());
const currentPos = reactive({ x: 0, y: 0, z: 0 });
const currentBiome = ref('平原');
const currentElevation = ref(0);
const currentTemperature = ref(20);
const isGenerating = ref(true);
const generationMessage = ref('初始化地形生成器...');
const loadedChunks = ref(0);
const memoryUsage = ref(0);
const showDebug = ref(false);
const noiseSamples = ref(0);
const currentLOD = ref(1);
const viewDistance = ref(5);
const loadingQueue = ref(0);
let scene, camera, renderer, controls;
let terrainGenerator;
let clock, stats;
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, 50, 500);
// 创建相机
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(50, 50, 50);
// 创建渲染器
renderer = new THREE.WebGLRenderer({
canvas: worldCanvas.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 = 500;
// 初始化地形生成器
generationMessage.value = '创建噪声系统...';
terrainGenerator = new InfiniteTerrainGenerator(renderer, camera);
// 模拟生成过程
await simulateGeneration();
isGenerating.value = false;
// 启动渲染循环
clock = new THREE.Clock();
animate();
};
// 模拟生成过程
const simulateGeneration = async () => {
const steps = [
'初始化噪声算法...',
'生成基础地形...',
'应用侵蚀模拟...',
'创建生物群落...',
'生成植被...',
'优化渲染性能...'
];
for (let i = 0; i < steps.length; i++) {
generationMessage.value = steps[i];
loadedChunks.value = Math.floor((i + 1) / steps.length * 25);
memoryUsage.value = loadedChunks.value * 1024 * 1024;
await new Promise(resolve => setTimeout(resolve, 800));
}
};
// 设置生成算法
const setAlgorithm = (algorithm) => {
currentAlgorithm.value = algorithm;
// 这里应该更新地形生成器的算法
};
// 生成新世界
const generateNewWorld = () => {
isGenerating.value = true;
worldSeed.value = Date.now();
generationMessage.value = '生成新世界中...';
setTimeout(() => {
isGenerating.value = false;
generatedChunks.value = 25;
vertexCount.value = 25000;
}, 2000);
};
// 导出世界
const exportWorld = () => {
console.log('导出世界数据...');
// 实际实现应该序列化世界数据
};
// 清空世界
const clearWorld = () => {
generatedChunks.value = 0;
vertexCount.value = 0;
loadedChunks.value = 0;
console.log('世界已清空');
};
// 切换调试显示
const toggleDebug = () => {
showDebug.value = !showDebug.value;
};
// 动画循环
const animate = () => {
requestAnimationFrame(animate);
const deltaTime = clock.getDelta();
// 更新控制器
controls.update();
// 更新相机位置
currentPos.x = Math.floor(camera.position.x);
currentPos.y = Math.floor(camera.position.y);
currentPos.z = Math.floor(camera.position.z);
// 更新地形信息
updateTerrainInfo();
// 渲染场景
if (terrainGenerator) {
terrainGenerator.render();
}
// 更新性能统计
updatePerformanceStats(deltaTime);
};
// 更新地形信息
const updateTerrainInfo = () => {
// 简化实现 - 实际应该采样地形数据
currentElevation.value = Math.floor(20 + Math.sin(currentPos.x * 0.01) * 15);
currentTemperature.value = Math.floor(15 + Math.cos(currentPos.z * 0.005) * 10);
// 基于位置选择生物群落
const biomes = ['平原', '森林', '沙漠', '雪山', '海洋'];
currentBiome.value = biomes[Math.abs(currentPos.x + currentPos.z) % biomes.length];
};
// 更新性能统计
const updatePerformanceStats = (deltaTime) => {
frameCount++;
lastFpsUpdate += deltaTime;
if (lastFpsUpdate >= 1.0) {
currentFPS.value = Math.round(frameCount / lastFpsUpdate);
// 更新模拟数据
noiseSamples.value = Math.floor(Math.random() * 10000) + 5000;
loadingQueue.value = Math.floor(Math.random() * 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%'
}));
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 {
worldCanvas,
terrainScale,
noiseIntensity,
detailLevels,
erosionStrength,
temperature,
humidity,
treeDensity,
grassDensity,
currentAlgorithm,
generatedChunks,
vertexCount,
biomeCount,
currentFPS,
worldSeed,
currentPos,
currentBiome,
currentElevation,
currentTemperature,
isGenerating,
generationMessage,
loadedChunks,
memoryUsage,
showDebug,
noiseSamples,
currentLOD,
viewDistance,
loadingQueue,
progressStyle,
setAlgorithm,
generateNewWorld,
exportWorld,
clearWorld,
toggleDebug,
formatNumber,
formatMemory
};
}
};
</script>
<style scoped>
.procedural-world-container {
width: 100%;
height: 100vh;
position: relative;
background: #000;
overflow: hidden;
}
.world-canvas {
width: 100%;
height: 100%;
display: block;
}
.generation-controls {
position: absolute;
top: 20px;
right: 20px;
width: 320px;
background: rgba(0, 0, 0, 0.85);
padding: 20px;
border-radius: 12px;
color: white;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
max-height: 80vh;
overflow-y: auto;
}
.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: #00ff88;
margin-bottom: 15px;
font-size: 16px;
display: flex;
align-items: center;
gap: 8px;
}
.control-group {
margin-bottom: 15px;
}
.control-group label {
display: block;
margin-bottom: 8px;
color: #ccc;
font-size: 14px;
}
.control-group input[type="range"] {
width: 100%;
height: 6px;
background: #444;
border-radius: 3px;
outline: none;
opacity: 0.7;
transition: opacity 0.2s;
}
.control-group input[type="range"]:hover {
opacity: 1;
}
.control-group input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #00ff88;
cursor: pointer;
box-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
}
.biome-controls,
.vegetation-controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 15px;
}
.algorithm-buttons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 15px;
}
.algo-button {
padding: 10px;
border: 2px solid #444;
border-radius: 6px;
background: rgba(255, 255, 255, 0.1);
color: white;
cursor: pointer;
font-size: 12px;
transition: all 0.3s ease;
}
.algo-button:hover {
border-color: #00ff88;
transform: translateY(-1px);
}
.algo-button.active {
border-color: #00ff88;
background: rgba(0, 255, 136, 0.2);
box-shadow: 0 0 15px rgba(0, 255, 136, 0.3);
}
.generation-actions {
display: flex;
flex-direction: column;
gap: 10px;
}
.action-button {
padding: 12px;
border: none;
border-radius: 6px;
background: rgba(255, 255, 255, 0.1);
color: white;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.action-button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.action-button.primary {
background: linear-gradient(45deg, #667eea, #764ba2);
}
.action-button.danger {
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
}
.world-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: #00ff88;
font-weight: bold;
}
.world-info-overlay {
position: absolute;
top: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.8);
padding: 15px;
border-radius: 8px;
color: white;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.info-panel h4 {
color: #00ff88;
margin-bottom: 10px;
font-size: 14px;
}
.info-content {
display: flex;
flex-direction: column;
gap: 5px;
font-size: 12px;
}
.info-content div {
display: flex;
justify-content: space-between;
gap: 10px;
}
.generation-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.generation-loader {
text-align: center;
color: white;
position: relative;
}
.terrain-preview {
position: relative;
width: 200px;
height: 100px;
margin: 0 auto 30px;
background: linear-gradient(to bottom, #1a2a6c, #b21f1f, #fdbb2d);
border-radius: 10px;
overflow: hidden;
}
.mountain-range {
position: absolute;
bottom: 0;
width: 60px;
height: 40px;
background: linear-gradient(45deg, #2d3748, #4a5568);
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
animation: riseUp 2s infinite ease-in-out;
}
.mountain-range:nth-child(1) {
left: 20px;
animation-delay: 0s;
}
.mountain-range:nth-child(2) {
left: 70px;
height: 60px;
animation-delay: 0.5s;
}
.mountain-range:nth-child(3) {
left: 120px;
animation-delay: 1s;
}
.generation-loader h3 {
margin-bottom: 20px;
color: white;
font-size: 20px;
}
.generation-progress {
width: 300px;
margin: 0 auto 15px;
}
.progress-bar {
width: 100%;
height: 6px;
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
overflow: hidden;
margin-bottom: 10px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #00ff88, #00aaff);
border-radius: 3px;
transition: width 0.3s ease;
}
.progress-text {
color: #00ff88;
font-size: 14px;
}
.generation-details {
display: flex;
justify-content: space-between;
font-size: 12px;
color: #ccc;
}
.debug-panel {
position: absolute;
bottom: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 6px;
backdrop-filter: blur(10px);
}
.debug-toggle {
padding: 8px 12px;
border: none;
border-radius: 4px;
background: rgba(255, 255, 255, 0.1);
color: white;
cursor: pointer;
font-size: 12px;
transition: background 0.3s;
}
.debug-toggle:hover {
background: rgba(255, 255, 255, 0.2);
}
.debug-info {
margin-top: 10px;
display: flex;
flex-direction: column;
gap: 5px;
font-size: 11px;
color: #ccc;
}
@keyframes riseUp {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-5px);
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.generation-controls {
width: 280px;
right: 10px;
top: 10px;
padding: 15px;
}
.world-info-overlay {
left: 10px;
top: 10px;
}
.biome-controls,
.vegetation-controls,
.algorithm-buttons {
grid-template-columns: 1fr;
}
}
</style>
高级程序化特性
地形侵蚀模拟系统
javascript
// 水力侵蚀模拟器
class HydraulicErosionSimulator {
constructor(heightMap, size) {
this.heightMap = heightMap;
this.size = size;
this.waterMap = new Float32Array(size * size);
this.sedimentMap = new Float32Array(size * size);
this.erosionSettings = {
rainRate: 0.01,
evaporationRate: 0.001,
sedimentCapacity: 0.1,
erosionRate: 0.3,
depositionRate: 0.1
};
}
// 模拟侵蚀过程
simulateErosion(iterations = 1000) {
for (let i = 0; i < iterations; i++) {
this.addRainfall();
this.calculateWaterFlow();
this.transportSediment();
this.evaporateWater();
}
}
// 添加降雨
addRainfall() {
for (let i = 0; i < this.waterMap.length; i++) {
this.waterMap[i] += this.erosionSettings.rainRate;
}
}
// 计算水流
calculateWaterFlow() {
const outflow = new Float32Array(this.size * size * 4); // 四个方向
for (let y = 1; y < this.size - 1; y++) {
for (let x = 1; x < this.size - 1; x++) {
const idx = y * this.size + x;
const height = this.heightMap[idx] + this.waterMap[idx];
// 计算到相邻格子的高度差
const neighbors = [
{ idx: idx - 1, height: this.heightMap[idx - 1] + this.waterMap[idx - 1], dir: 0 }, // 左
{ idx: idx + 1, height: this.heightMap[idx + 1] + this.waterMap[idx + 1], dir: 1 }, // 右
{ idx: idx - this.size, height: this.heightMap[idx - this.size] + this.waterMap[idx - this.size], dir: 2 }, // 上
{ idx: idx + this.size, height: this.heightMap[idx + this.size] + this.waterMap[idx + this.size], dir: 3 } // 下
];
let totalOutflow = 0;
for (const neighbor of neighbors) {
const delta = height - neighbor.height;
if (delta > 0) {
const outflowAmount = Math.min(this.waterMap[idx], delta * 0.25);
outflow[idx * 4 + neighbor.dir] = outflowAmount;
totalOutflow += outflowAmount;
}
}
// 标准化流出量
if (totalOutflow > 0) {
for (let dir = 0; dir < 4; dir++) {
outflow[idx * 4 + dir] *= Math.min(1, this.waterMap[idx] / totalOutflow);
}
}
}
}
// 应用水流
this.applyWaterFlow(outflow);
}
// 应用水流
applyWaterFlow(outflow) {
for (let y = 1; y < this.size - 1; y++) {
for (let x = 1; x < this.size - 1; x++) {
const idx = y * this.size + x;
// 计算净流量
let netFlow = 0;
netFlow -= outflow[idx * 4 + 0]; // 向左流出
netFlow -= outflow[idx * 4 + 1]; // 向右流出
netFlow -= outflow[idx * 4 + 2]; // 向上流出
netFlow -= outflow[idx * 4 + 3]; // 向下流出
netFlow += outflow[(idx - 1) * 4 + 1]; // 从右流入
netFlow += outflow[(idx + 1) * 4 + 0]; // 从左流入
netFlow += outflow[(idx - this.size) * 4 + 3]; // 从下流入
netFlow += outflow[(idx + this.size) * 4 + 2]; // 从上流入
this.waterMap[idx] += netFlow;
}
}
}
// 传输沉积物
transportSediment() {
for (let y = 1; y < this.size - 1; y++) {
for (let x = 1; x < this.size - 1; x++) {
const idx = y * this.size + x;
if (this.waterMap[idx] > 0) {
// 计算沉积物容量
const capacity = this.erosionSettings.sedimentCapacity * this.waterMap[idx];
if (this.sedimentMap[idx] > capacity) {
// 沉积
const deposit = (this.sedimentMap[idx] - capacity) * this.erosionSettings.depositionRate;
this.sedimentMap[idx] -= deposit;
this.heightMap[idx] += deposit;
} else {
// 侵蚀
const erosion = (capacity - this.sedimentMap[idx]) * this.erosionSettings.erosionRate;
this.sedimentMap[idx] += erosion;
this.heightMap[idx] -= erosion;
}
}
}
}
}
// 水分蒸发
evaporateWater() {
for (let i = 0; i < this.waterMap.length; i++) {
this.waterMap[i] *= (1 - this.erosionSettings.evaporationRate);
}
}
}
动态生物群落生成
javascript
// 高级生物群落生成器
class AdvancedBiomeGenerator {
constructor(noiseSystem) {
this.noiseSystem = noiseSystem;
this.biomeMaps = new Map();
}
// 生成生物群落图
generateBiomeMap(x, z, size) {
const biomeMap = new Array(size * size);
for (let localZ = 0; localZ < size; localZ++) {
for (let localX = 0; localX < size; localX++) {
const worldX = x + localX;
const worldZ = z + localZ;
// 使用多频噪声生成气候参数
const temperature = this.sampleTemperature(worldX, worldZ);
const humidity = this.sampleHumidity(worldX, worldZ);
const continentalness = this.sampleContinentalness(worldX, worldZ);
const erosion = this.sampleErosion(worldX, worldZ);
// 确定生物群落
biomeMap[localZ * size + localX] = this.determineBiome(
temperature, humidity, continentalness, erosion
);
}
}
return biomeMap;
}
// 采样温度
sampleTemperature(x, z) {
// 纬度效应 + 季节性变化
const latitudeEffect = Math.abs(z * 0.0001);
const baseTemp = 1.0 - latitudeEffect * 2;
// 噪声变化
const tempNoise = this.noiseSystem.fbm2D(x * 0.001, z * 0.001, 2, 2.0, 0.5) * 0.3;
return Math.max(0, Math.min(1, baseTemp + tempNoise));
}
// 采样湿度
sampleHumidity(x, z) {
// 基于与海洋的距离和地形
const distanceToOcean = this.sampleOceanDistance(x, z);
const humidityNoise = this.noiseSystem.fbm2D(x * 0.002, z * 0.002, 3, 2.0, 0.5);
return Math.max(0, Math.min(1, (1 - distanceToOcean) * 0.7 + humidityNoise * 0.3));
}
// 采样大陆性
sampleContinentalness(x, z) {
return this.noiseSystem.fbm2D(x * 0.0001, z * 0.0001, 4, 2.0, 0.5);
}
// 采样侵蚀度
sampleErosion(x, z) {
return this.noiseSystem.fbm2D(x * 0.005, z * 0.005, 2, 2.5, 0.4);
}
// 采样海洋距离
sampleOceanDistance(x, z) {
const continentNoise = this.noiseSystem.fbm2D(x * 0.00005, z * 0.00005, 6, 2.0, 0.5);
return Math.abs(continentNoise - 0.5) * 2;
}
// 确定生物群落
determineBiome(temperature, humidity, continentalness, erosion) {
// 基于Minecraft风格的生物群落确定逻辑
if (continentalness < 0.1) {
return this.getOceanBiome(temperature);
} else if (continentalness < 0.3) {
return this.getCoastalBiome(temperature, humidity);
} else {
return this.getInlandBiome(temperature, humidity, erosion);
}
}
// 获取海洋生物群落
getOceanBiome(temperature) {
if (temperature < 0.3) return 'frozen_ocean';
if (temperature > 0.7) return 'warm_ocean';
return 'ocean';
}
// 获取海岸生物群落
getCoastalBiome(temperature, humidity) {
if (temperature < 0.3) return 'snowy_beach';
if (humidity < 0.3) return 'desert';
return 'beach';
}
// 获取内陆生物群落
getInlandBiome(temperature, humidity, erosion) {
if (temperature < 0.15) {
return erosion > 0.6 ? 'snowy_mountains' : 'snowy_tundra';
} else if (temperature < 0.3) {
if (erosion > 0.7) return 'mountains';
return humidity > 0.5 ? 'taiga' : 'plains';
} else if (temperature < 0.7) {
if (humidity < 0.3) return 'desert';
if (humidity < 0.6) return 'plains';
if (erosion > 0.6) return 'wooded_mountains';
return 'forest';
} else {
if (humidity < 0.4) return 'desert';
if (humidity < 0.7) return 'savanna';
return 'jungle';
}
}
}
注意事项与最佳实践
-
性能优化关键
- 使用适当的区块大小和LOD级别
- 实现高效的流式加载系统
- 优化噪声算法和缓存策略
- 使用Web Workers进行后台生成
-
内存管理策略
- 实现LRU缓存管理生成的区块
- 使用对象池重用几何体和材质
- 监控内存使用并自动清理
- 使用压缩格式存储地形数据
-
视觉质量优化
- 使用多频噪声创造自然细节
- 实现平滑的生物群落过渡
- 添加适当的纹理和光照
- 使用后期处理增强视觉效果
下一节预告
第34节:反向运动学与角色动画自然化
将深入探索角色动画技术,包括:反向运动学原理、CCD算法实现、腿部行走约束、自然动作混合,创造逼真的角色动画系统。