第31节:流体模拟与Shader实现水效果
概述
流体模拟是计算机图形学中最具挑战性的领域之一。本节将深入探索基于Shader的实时流体模拟技术,从基础的波浪方程到复杂的交互式水效果,构建逼真的动态水域系统。
流体渲染系统架构:
核心原理深度解析
源码 运行效果 上方下载

波浪物理模型
基于Navier-Stokes方程的简化流体模拟:
| 波浪类型 | 物理模型 | 数学公式 | 适用场景 |
|---|---|---|---|
| 正弦波 | 简谐运动 | y = A·sin(ωt + kx) | 基础波浪 |
| Gerstner波 | 粒子运动 | 更真实的波峰形状 | 海洋模拟 |
| FFT波 | 频谱分析 | 基于频率域的合成 | 开放海域 |
光学效果原理
水体渲染的关键光学现象:
-
折射效应
- Snell定律计算光线偏折
- 法线扰动模拟水面起伏
- 深度差异导致的视觉扭曲
-
反射效应
- 环境贴图反射
- 菲涅尔效应控制反射强度
- 镜面高光增强真实感
完整代码实现
高级水体渲染系统
<template>
<div class="water-simulation-container">
<!-- 主渲染画布 -->
<canvas ref="waterCanvas" class="water-canvas"></canvas>
<!-- 水体控制面板 -->
<div class="water-controls">
<div class="control-section">
<h3>🌊 水体模拟控制</h3>
<div class="control-group">
<label>波浪强度: {{ waveIntensity }}</label>
<input
type="range"
v-model="waveIntensity"
min="0"
max="2"
step="0.1"
>
</div>
<div class="control-group">
<label>波浪频率: {{ waveFrequency }}</label>
<input
type="range"
v-model="waveFrequency"
min="0.1"
max="2"
step="0.1"
>
</div>
<div class="control-group">
<label>流动速度: {{ flowSpeed }}</label>
<input
type="range"
v-model="flowSpeed"
min="0"
max="5"
step="0.1"
>
</div>
</div>
<div class="control-section">
<h3>💧 视觉效果</h3>
<div class="control-group">
<label>水体颜色</label>
<input type="color" v-model="waterColor" class="color-picker">
</div>
<div class="control-group">
<label>透明度: {{ waterOpacity }}</label>
<input
type="range"
v-model="waterOpacity"
min="0.1"
max="1"
step="0.05"
>
</div>
<div class="control-group">
<label>折射强度: {{ refractionStrength }}</label>
<input
type="range"
v-model="refractionStrength"
min="0"
max="1"
step="0.05"
>
</div>
<div class="control-group">
<label>反射强度: {{ reflectionStrength }}</label>
<input
type="range"
v-model="reflectionStrength"
min="0"
max="1"
step="0.05"
>
</div>
</div>
<div class="control-section">
<h3>🎮 交互控制</h3>
<div class="interaction-controls">
<button @click="addRainEffect" class="control-button">
🌧️ 降雨效果
</button>
<button @click="addObjectToWater" class="control-button">
🚤 添加物体
</button>
<button @click="clearInteractions" class="control-button">
🧹 清空交互
</button>
</div>
<div class="interaction-stats">
<div class="stat-item">
<span>涟漪数量:</span>
<span>{{ rippleCount }}</span>
</div>
<div class="stat-item">
<span>交互点:</span>
<span>{{ interactionPoints }}</span>
</div>
</div>
</div>
<div class="control-section">
<h3>📊 性能监控</h3>
<div class="performance-stats">
<div class="stat-item">
<span>帧率:</span>
<span>{{ currentFPS }} FPS</span>
</div>
<div class="stat-item">
<span>波浪顶点:</span>
<span>{{ formatNumber(vertexCount) }}</span>
</div>
<div class="stat-item">
<span>计算时间:</span>
<span>{{ computeTime }}ms</span>
</div>
</div>
</div>
</div>
<!-- 交互提示 -->
<div class="interaction-hint">
<p>💡 提示: 点击水面创建涟漪 | 拖动物体与水面交互</p>
</div>
<!-- 加载界面 -->
<div v-if="isLoading" class="loading-overlay">
<div class="water-loader">
<div class="wave"></div>
<div class="wave"></div>
<div class="wave"></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 { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { Water } from 'three/addons/objects/Water.js';
// 高级水体模拟器
class AdvancedWaterSimulator {
constructor(renderer, scene, camera) {
this.renderer = renderer;
this.scene = scene;
this.camera = camera;
this.water = null;
this.waterGeometry = null;
this.waterMaterial = null;
this.ripples = new Map();
this.interactionPoints = [];
this.rainParticles = [];
this.time = 0;
this.isSimulating = false;
this.initWaterSystem();
}
// 初始化水体系统
async initWaterSystem() {
// 创建水几何体
this.waterGeometry = new THREE.PlaneGeometry(100, 100, 128, 128);
// 创建自定义水材质
this.waterMaterial = this.createWaterMaterial();
// 创建水面网格
this.water = new THREE.Mesh(this.waterGeometry, this.waterMaterial);
this.water.rotation.x = -Math.PI / 2;
this.water.position.y = 0;
this.water.receiveShadow = true;
this.scene.add(this.water);
// 设置水底
this.createWaterBottom();
// 设置环境
this.setupWaterEnvironment();
// 启动模拟
this.startSimulation();
}
// 创建自定义水材质
createWaterMaterial() {
const material = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
waveIntensity: { value: 1.0 },
waveFrequency: { value: 1.0 },
flowSpeed: { value: 1.0 },
waterColor: { value: new THREE.Color(0x0077be) },
waterOpacity: { value: 0.8 },
refractionStrength: { value: 0.5 },
reflectionStrength: { value: 0.3 },
// 纹理
normalMap: { value: this.createNormalTexture() },
dudvMap: { value: this.createDudvTexture() },
depthMap: { value: this.createDepthTexture() },
// 环境
cameraPos: { value: new THREE.Vector3() },
lightDir: { value: new THREE.Vector3(1, 1, 1).normalize() }
},
vertexShader: this.getWaterVertexShader(),
fragmentShader: this.getWaterFragmentShader(),
transparent: true,
side: THREE.DoubleSide
});
return material;
}
// 创建法线纹理
createNormalTexture() {
const canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 512;
const context = canvas.getContext('2d');
// 生成水波法线图
const gradient = context.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#8080ff');
gradient.addColorStop(0.5, '#8080ff');
gradient.addColorStop(1, '#8080ff');
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
const texture = new THREE.CanvasTexture(canvas);
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
return texture;
}
// 创建DuDv纹理(用于 distortion)
createDudvTexture() {
const canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 512;
const context = canvas.getContext('2d');
// 生成扭曲图
for (let x = 0; x < canvas.width; x++) {
for (let y = 0; y < canvas.height; y++) {
const value = Math.sin(x * 0.1) * Math.cos(y * 0.1) * 127 + 128;
context.fillStyle = `rgb(${value}, ${value}, 255)`;
context.fillRect(x, y, 1, 1);
}
}
const texture = new THREE.CanvasTexture(canvas);
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
return texture;
}
// 创建深度纹理
createDepthTexture() {
// 简化实现 - 实际应该从深度缓冲区获取
const canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 512;
const context = canvas.getContext('2d');
const gradient = context.createRadialGradient(
canvas.width / 2, canvas.height / 2, 0,
canvas.width / 2, canvas.height / 2, canvas.width / 2
);
gradient.addColorStop(0, '#ffffff');
gradient.addColorStop(1, '#000000');
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
return new THREE.CanvasTexture(canvas);
}
// 获取顶点着色器
getWaterVertexShader() {
return `
uniform float time;
uniform float waveIntensity;
uniform float waveFrequency;
uniform float flowSpeed;
varying vec2 vUv;
varying vec3 vPosition;
varying vec3 vNormal;
varying float vDepth;
// Gerstner波函数
vec3 gerstnerWave(vec3 position, float amplitude, float frequency, float speed, vec2 direction) {
float phase = speed * frequency;
float angle = dot(direction, position.xz) * frequency + time * phase;
float cosine = cos(angle);
float sine = sin(angle);
vec3 result;
result.x = direction.x * amplitude * cosine;
result.y = amplitude * sine;
result.z = direction.y * amplitude * cosine;
return result;
}
void main() {
vUv = uv;
vPosition = position;
// 应用多组Gerstner波
vec3 waveSum = vec3(0.0);
// 波1 - 主要波浪
waveSum += gerstnerWave(position, 0.3 * waveIntensity, 0.5 * waveFrequency, 1.0 * flowSpeed, vec2(1.0, 0.0));
// 波2 - 次要波浪
waveSum += gerstnerWave(position, 0.2 * waveIntensity, 1.0 * waveFrequency, 1.5 * flowSpeed, vec2(0.7, 0.7));
// 波3 - 细节波浪
waveSum += gerstnerWave(position, 0.1 * waveIntensity, 2.0 * waveFrequency, 2.0 * flowSpeed, vec2(0.3, -0.8));
// 应用涟漪效果
waveSum += applyRipples(position);
// 更新顶点位置
vec3 newPosition = position + waveSum;
// 计算法线(基于波函数的导数)
vec3 tangent = vec3(1.0, 0.0, 0.0);
vec3 binormal = vec3(0.0, 0.0, 1.0);
vec3 normal = normalize(cross(tangent, binormal));
vNormal = normal;
vDepth = newPosition.y;
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}
// 应用涟漪效果
vec3 applyRipples(vec3 position) {
vec3 rippleSum = vec3(0.0);
// 这里应该从uniform读取涟漪数据
// 简化实现:使用正弦波模拟涟漪
for (int i = 0; i < 3; i++) {
float dist = length(position.xz - vec2(float(i) * 10.0, 0.0));
if (dist < 10.0) {
float amplitude = 0.1 * exp(-dist * 0.3) * sin(dist * 5.0 - time * 10.0);
rippleSum.y += amplitude;
}
}
return rippleSum;
}
`;
}
// 获取片段着色器
getWaterFragmentShader() {
return `
uniform vec3 waterColor;
uniform float waterOpacity;
uniform float refractionStrength;
uniform float reflectionStrength;
uniform vec3 cameraPos;
uniform vec3 lightDir;
uniform sampler2D normalMap;
uniform sampler2D dudvMap;
uniform sampler2D depthMap;
varying vec2 vUv;
varying vec3 vPosition;
varying vec3 vNormal;
varying float vDepth;
// 菲涅尔效应计算
float fresnel(vec3 viewDir, vec3 normal) {
float cosTheta = dot(viewDir, normal);
float f0 = 0.02; // 水的反射率
return f0 + (1.0 - f0) * pow(1.0 - cosTheta, 5.0);
}
void main() {
// 法线计算
vec3 normal = normalize(vNormal);
vec3 viewDir = normalize(cameraPos - vPosition);
// 应用法线贴图
vec3 normalMapValue = texture2D(normalMap, vUv * 5.0 + time * 0.05).rgb * 2.0 - 1.0;
normal = normalize(normal + normalMapValue * 0.3);
// 菲涅尔计算
float fresnelFactor = fresnel(viewDir, normal);
// 折射计算
vec2 refractionOffset = normal.xz * refractionStrength * 0.1;
vec2 refractionUV = vUv + refractionOffset;
// 反射计算
vec3 reflectDir = reflect(-viewDir, normal);
float reflectionFactor = fresnelFactor * reflectionStrength;
// 基础颜色
vec3 baseColor = waterColor;
// 深度影响颜色
float depthFactor = clamp(vDepth * 0.1, 0.0, 1.0);
baseColor = mix(baseColor, baseColor * 0.5, depthFactor);
// 光照计算
vec3 lightColor = vec3(1.0, 1.0, 0.9);
float diffuse = max(dot(normal, lightDir), 0.0);
vec3 diffuseColor = baseColor * diffuse * lightColor;
// 镜面高光
vec3 halfDir = normalize(lightDir + viewDir);
float specular = pow(max(dot(normal, halfDir), 0.0), 32.0);
vec3 specularColor = lightColor * specular * reflectionFactor;
// 最终颜色合成
vec3 finalColor = diffuseColor + specularColor;
// 透明度处理
float alpha = waterOpacity * (1.0 - depthFactor * 0.5);
gl_FragColor = vec4(finalColor, alpha);
}
`;
}
// 创建水底
createWaterBottom() {
const bottomGeometry = new THREE.PlaneGeometry(100, 100, 1, 1);
const bottomMaterial = new THREE.MeshStandardMaterial({
color: 0x2f4f4f,
roughness: 0.8,
metalness: 0.2
});
const bottom = new THREE.Mesh(bottomGeometry, bottomMaterial);
bottom.rotation.x = -Math.PI / 2;
bottom.position.y = -5;
bottom.receiveShadow = true;
this.scene.add(bottom);
}
// 设置水环境
setupWaterEnvironment() {
// 环境光
const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
this.scene.add(ambientLight);
// 方向光
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(50, 50, 25);
directionalLight.castShadow = true;
this.scene.add(directionalLight);
// 添加一些环境物体
this.addEnvironmentObjects();
}
// 添加环境物体
addEnvironmentObjects() {
// 添加一些石头
const stoneGeometry = new THREE.SphereGeometry(2, 8, 6);
const stoneMaterial = new THREE.MeshStandardMaterial({
color: 0x666666,
roughness: 0.9
});
for (let i = 0; i < 5; i++) {
const stone = new THREE.Mesh(stoneGeometry, stoneMaterial);
stone.position.set(
(Math.random() - 0.5) * 80,
-3,
(Math.random() - 0.5) * 80
);
stone.scale.setScalar(Math.random() * 0.5 + 0.5);
stone.castShadow = true;
this.scene.add(stone);
}
}
// 添加涟漪效果
addRipple(position, strength = 1.0) {
const rippleId = Date.now().toString();
this.ripples.set(rippleId, {
position: new THREE.Vector3(position.x, 0, position.z),
strength: strength,
startTime: this.time,
duration: 3.0,
id: rippleId
});
// 添加视觉反馈
this.createRippleEffect(position, strength);
}
// 创建涟漪视觉效果
createRippleEffect(position, strength) {
const rippleGeometry = new THREE.RingGeometry(0.1, 1, 32);
const rippleMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.6,
side: THREE.DoubleSide
});
const ripple = new THREE.Mesh(rippleGeometry, rippleMaterial);
ripple.position.set(position.x, 0.1, position.z);
ripple.rotation.x = -Math.PI / 2;
this.scene.add(ripple);
// 涟漪动画
const startTime = this.time;
const animateRipple = () => {
const elapsed = this.time - startTime;
const progress = elapsed / 3.0;
if (progress < 1.0) {
const scale = 1 + progress * 10;
ripple.scale.setScalar(scale);
rippleMaterial.opacity = 0.6 * (1 - progress);
requestAnimationFrame(animateRipple);
} else {
this.scene.remove(ripple);
rippleGeometry.dispose();
rippleMaterial.dispose();
}
};
animateRipple();
}
// 添加降雨效果
addRainEffect() {
const rainCount = 50;
for (let i = 0; i < rainCount; i++) {
setTimeout(() => {
const rainPosition = new THREE.Vector3(
(Math.random() - 0.5) * 80,
10,
(Math.random() - 0.5) * 80
);
this.addRipple(rainPosition, 0.3);
// 创建雨滴视觉效果
this.createRainDrop(rainPosition);
}, i * 100);
}
}
// 创建雨滴效果
createRainDrop(position) {
const dropGeometry = new THREE.SphereGeometry(0.1, 4, 4);
const dropMaterial = new THREE.MeshBasicMaterial({
color: 0x88ccff,
transparent: true,
opacity: 0.7
});
const drop = new THREE.Mesh(dropGeometry, dropMaterial);
drop.position.copy(position);
this.scene.add(drop);
// 雨滴下落动画
const startTime = this.time;
const animateDrop = () => {
const elapsed = this.time - startTime;
const progress = elapsed / 1.0;
if (progress < 1.0) {
drop.position.y = position.y - progress * 10;
dropMaterial.opacity = 0.7 * (1 - progress);
requestAnimationFrame(animateDrop);
} else {
this.scene.remove(drop);
dropGeometry.dispose();
dropMaterial.dispose();
}
};
animateDrop();
}
// 添加水面物体
addObjectToWater() {
const geometry = new THREE.BoxGeometry(3, 1, 2);
const material = new THREE.MeshStandardMaterial({
color: 0xff6600,
roughness: 0.7,
metalness: 0.3
});
const object = new THREE.Mesh(geometry, material);
object.position.set(
(Math.random() - 0.5) * 50,
1,
(Math.random() - 0.5) * 50
);
object.castShadow = true;
// 添加浮动动画
object.userData.floatOffset = Math.random() * Math.PI * 2;
this.scene.add(object);
this.interactionPoints.push(object);
}
// 清空交互
clearInteractions() {
this.interactionPoints.forEach(object => {
this.scene.remove(object);
object.geometry.dispose();
object.material.dispose();
});
this.interactionPoints = [];
this.ripples.clear();
}
// 更新水体模拟
update(deltaTime) {
this.time += deltaTime;
// 更新材质uniforms
if (this.waterMaterial && this.waterMaterial.uniforms) {
this.waterMaterial.uniforms.time.value = this.time;
this.waterMaterial.uniforms.cameraPos.value.copy(this.camera.position);
}
// 更新涟漪
this.updateRipples(deltaTime);
// 更新浮动物体
this.updateFloatingObjects(deltaTime);
}
// 更新涟漪
updateRipples(deltaTime) {
const currentTime = this.time;
for (const [rippleId, ripple] of this.ripples) {
const elapsed = currentTime - ripple.startTime;
if (elapsed > ripple.duration) {
this.ripples.delete(rippleId);
}
}
}
// 更新浮动物体
updateFloatingObjects(deltaTime) {
this.interactionPoints.forEach(object => {
// 简单的浮动动画
object.position.y = 1 + Math.sin(this.time * 2 + object.userData.floatOffset) * 0.2;
object.rotation.y = this.time * 0.5 + object.userData.floatOffset;
});
}
// 开始模拟
startSimulation() {
this.isSimulating = true;
}
// 停止模拟
stopSimulation() {
this.isSimulating = false;
}
// 设置材质参数
setMaterialUniforms(params) {
if (!this.waterMaterial || !this.waterMaterial.uniforms) return;
const uniforms = this.waterMaterial.uniforms;
if (params.waveIntensity !== undefined) {
uniforms.waveIntensity.value = params.waveIntensity;
}
if (params.waveFrequency !== undefined) {
uniforms.waveFrequency.value = params.waveFrequency;
}
if (params.flowSpeed !== undefined) {
uniforms.flowSpeed.value = params.flowSpeed;
}
if (params.waterColor !== undefined) {
uniforms.waterColor.value.set(params.waterColor);
}
if (params.waterOpacity !== undefined) {
uniforms.waterOpacity.value = params.waterOpacity;
}
if (params.refractionStrength !== undefined) {
uniforms.refractionStrength.value = params.refractionStrength;
}
if (params.reflectionStrength !== undefined) {
uniforms.reflectionStrength.value = params.reflectionStrength;
}
}
}
export default {
name: 'WaterSimulation',
setup() {
const waterCanvas = ref(null);
const waveIntensity = ref(1.0);
const waveFrequency = ref(1.0);
const flowSpeed = ref(1.0);
const waterColor = ref('#0077be');
const waterOpacity = ref(0.8);
const refractionStrength = ref(0.5);
const reflectionStrength = ref(0.3);
const rippleCount = ref(0);
const interactionPoints = ref(0);
const currentFPS = ref(0);
const vertexCount = ref(0);
const computeTime = ref(0);
const isLoading = ref(true);
const loadingMessage = ref('初始化水体系统...');
let scene, camera, renderer, controls;
let waterSimulator;
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, 200);
// 创建相机
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 30, 50);
// 创建渲染器
renderer = new THREE.WebGLRenderer({
canvas: waterCanvas.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 = 100;
// 初始化水体模拟器
loadingMessage.value = '创建水体材质...';
waterSimulator = new AdvancedWaterSimulator(renderer, scene, camera);
// 等待初始化完成
await new Promise(resolve => setTimeout(resolve, 2000));
loadingMessage.value = '设置交互系统...';
setupInteraction();
isLoading.value = false;
// 启动渲染循环
clock = new THREE.Clock();
animate();
};
// 设置交互
const setupInteraction = () => {
// 点击水面创建涟漪
renderer.domElement.addEventListener('click', onWaterClick);
// 键盘控制
document.addEventListener('keydown', onKeyDown);
};
// 水面点击事件
const onWaterClick = (event) => {
// 转换鼠标坐标到3D空间
const mouse = new THREE.Vector2();
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
// 射线检测
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObject(waterSimulator.water);
if (intersects.length > 0) {
const point = intersects[0].point;
waterSimulator.addRipple(point, 1.0);
updateStats();
}
};
// 键盘控制
const onKeyDown = (event) => {
switch (event.code) {
case 'KeyR':
waterSimulator.addRainEffect();
break;
case 'KeyO':
waterSimulator.addObjectToWater();
break;
case 'KeyC':
waterSimulator.clearInteractions();
break;
}
updateStats();
};
// 添加降雨效果
const addRainEffect = () => {
waterSimulator.addRainEffect();
updateStats();
};
// 添加物体到水面
const addObjectToWater = () => {
waterSimulator.addObjectToWater();
updateStats();
};
// 清空交互
const clearInteractions = () => {
waterSimulator.clearInteractions();
updateStats();
};
// 更新统计信息
const updateStats = () => {
rippleCount.value = waterSimulator.ripples.size;
interactionPoints.value = waterSimulator.interactionPoints.length;
vertexCount.value = waterSimulator.waterGeometry.attributes.position.count;
};
// 动画循环
const animate = () => {
requestAnimationFrame(animate);
const deltaTime = clock.getDelta();
// 更新控制器
controls.update();
// 更新水体模拟
if (waterSimulator) {
const startTime = performance.now();
waterSimulator.update(deltaTime);
computeTime.value = (performance.now() - startTime).toFixed(2);
}
// 渲染场景
renderer.render(scene, camera);
// 更新性能统计
updatePerformanceStats(deltaTime);
};
// 更新性能统计
const updatePerformanceStats = (deltaTime) => {
frameCount++;
lastFpsUpdate += deltaTime;
if (lastFpsUpdate >= 1.0) {
currentFPS.value = Math.round(frameCount / lastFpsUpdate);
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 progressStyle = computed(() => ({
width: '100%'
}));
// 响应式设置
watch(waveIntensity, (newValue) => {
if (waterSimulator) {
waterSimulator.setMaterialUniforms({ waveIntensity: parseFloat(newValue) });
}
});
watch(waveFrequency, (newValue) => {
if (waterSimulator) {
waterSimulator.setMaterialUniforms({ waveFrequency: parseFloat(newValue) });
}
});
watch(flowSpeed, (newValue) => {
if (waterSimulator) {
waterSimulator.setMaterialUniforms({ flowSpeed: parseFloat(newValue) });
}
});
watch(waterColor, (newValue) => {
if (waterSimulator) {
waterSimulator.setMaterialUniforms({ waterColor: newValue });
}
});
watch(waterOpacity, (newValue) => {
if (waterSimulator) {
waterSimulator.setMaterialUniforms({ waterOpacity: parseFloat(newValue) });
}
});
watch(refractionStrength, (newValue) => {
if (waterSimulator) {
waterSimulator.setMaterialUniforms({ refractionStrength: parseFloat(newValue) });
}
});
watch(reflectionStrength, (newValue) => {
if (waterSimulator) {
waterSimulator.setMaterialUniforms({ reflectionStrength: parseFloat(newValue) });
}
});
onMounted(() => {
initScene();
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
if (waterSimulator) {
waterSimulator.stopSimulation();
}
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 {
waterCanvas,
waveIntensity,
waveFrequency,
flowSpeed,
waterColor,
waterOpacity,
refractionStrength,
reflectionStrength,
rippleCount,
interactionPoints,
currentFPS,
vertexCount,
computeTime,
isLoading,
loadingMessage,
progressStyle,
addRainEffect,
addObjectToWater,
clearInteractions,
formatNumber
};
}
};
</script>
<style scoped>
.water-simulation-container {
width: 100%;
height: 100vh;
position: relative;
background: #000;
overflow: hidden;
}
.water-canvas {
width: 100%;
height: 100%;
display: block;
}
.water-controls {
position: absolute;
top: 20px;
right: 20px;
width: 320px;
background: rgba(0, 0, 0, 0.8);
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: #00ffff;
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: #00ffff;
cursor: pointer;
box-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
}
.color-picker {
width: 100%;
height: 40px;
border: 2px solid #00ffff;
border-radius: 6px;
background: transparent;
cursor: pointer;
}
.interaction-controls {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 15px;
}
.control-button {
padding: 12px;
border: none;
border-radius: 8px;
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
cursor: pointer;
font-size: 14px;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.control-button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.interaction-stats,
.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;
}
.interaction-hint {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.7);
padding: 12px 20px;
border-radius: 20px;
color: white;
font-size: 14px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.interaction-hint p {
margin: 0;
display: flex;
align-items: center;
gap: 8px;
}
.loading-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;
}
.water-loader {
text-align: center;
color: white;
position: relative;
}
.wave {
position: absolute;
top: 50%;
left: 50%;
width: 80px;
height: 80px;
margin: -40px 0 0 -40px;
border: 4px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
animation: waveExpand 2s infinite;
}
.wave:nth-child(1) {
animation-delay: 0s;
}
.wave:nth-child(2) {
animation-delay: 0.5s;
}
.wave:nth-child(3) {
animation-delay: 1s;
}
.water-loader h3 {
margin-top: 120px;
color: white;
font-size: 20px;
}
.loading-progress {
width: 300px;
margin: 20px auto 0;
}
.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, #00ffff, #0088ff);
border-radius: 3px;
transition: width 0.3s ease;
}
.progress-text {
color: #00ffff;
font-size: 14px;
}
@keyframes waveExpand {
0% {
transform: scale(0.2);
opacity: 1;
}
100% {
transform: scale(1.5);
opacity: 0;
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.water-controls {
width: 280px;
right: 10px;
top: 10px;
padding: 15px;
}
.interaction-hint {
bottom: 10px;
left: 10px;
right: 10px;
transform: none;
text-align: center;
}
}
</style>
高级流体特性
实时波浪物理计算
// 基于FFT的海洋波浪模拟
class FFTWaterSimulator {
constructor(resolution = 256) {
this.resolution = resolution;
this.fftSize = resolution * resolution;
this.heightField = new Float32Array(this.fftSize);
this.displacementX = new Float32Array(this.fftSize);
this.displacementZ = new Float32Array(this.fftSize);
this.initSpectrum();
this.setupFFT();
}
// 初始化波浪频谱
initSpectrum() {
const windSpeed = 10.0;
const windDirection = new THREE.Vector2(1, 0);
for (let y = 0; y < this.resolution; y++) {
for (let x = 0; x < this.resolution; x++) {
const kx = (x - this.resolution / 2) * (2 * Math.PI / 100);
const ky = (y - this.resolution / 2) * (2 * Math.PI / 100);
const k = new THREE.Vector2(kx, ky);
const magnitude = k.length();
if (magnitude < 0.0001) continue;
// Phillips频谱
const L = (windSpeed * windSpeed) / 9.81;
const kDotW = k.dot(windDirection.normalize());
const phillips = Math.exp(-1.0 / (magnitude * L * magnitude * L))
/ (magnitude * magnitude * magnitude * magnitude)
* Math.pow(kDotW, 2);
const index = y * this.resolution + x;
this.heightField[index] = Math.sqrt(phillips) * this.gaussianRandom();
}
}
}
// 高斯随机数生成
gaussianRandom() {
let u = 0, v = 0;
while(u === 0) u = Math.random();
while(v === 0) v = Math.random();
return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
}
// 设置FFT计算
setupFFT() {
// 这里应该实现FFT算法
// 简化实现:使用预计算的波浪
console.log('FFT系统初始化完成');
}
// 更新波浪状态
update(time) {
for (let y = 0; y < this.resolution; y++) {
for (let x = 0; x < this.resolution; x++) {
const index = y * this.resolution + x;
// 简化的波浪传播计算
const phase = time * Math.sqrt(9.81 * this.getWaveNumber(x, y));
this.heightField[index] *= Math.cos(phase);
}
}
}
// 获取波数
getWaveNumber(x, y) {
const kx = (x - this.resolution / 2) * (2 * Math.PI / 100);
const ky = (y - this.resolution / 2) * (2 * Math.PI / 100);
return Math.sqrt(kx * kx + ky * ky);
}
// 获取高度场纹理
getHeightFieldTexture() {
const canvas = document.createElement('canvas');
canvas.width = this.resolution;
canvas.height = this.resolution;
const context = canvas.getContext('2d');
const imageData = context.createImageData(this.resolution, this.resolution);
for (let i = 0; i < this.fftSize; i++) {
const value = (this.heightField[i] + 1) * 128;
imageData.data[i * 4] = value; // R
imageData.data[i * 4 + 1] = value; // G
imageData.data[i * 4 + 2] = value; // B
imageData.data[i * 4 + 3] = 255; // A
}
context.putImageData(imageData, 0, 0);
return new THREE.CanvasTexture(canvas);
}
}
交互式涟漪传播系统
// 实时涟漪传播模拟
class RipplePropagationSystem {
constructor(resolution = 512) {
this.resolution = resolution;
this.currentState = new Float32Array(resolution * resolution);
this.previousState = new Float32Array(resolution * resolution);
this.damping = 0.99;
this.waveSpeed = 0.5;
this.initGPUCompute();
}
// 初始化GPU计算
initGPUCompute() {
const rippleShader = `
uniform sampler2D currentState;
uniform sampler2D previousState;
uniform float damping;
uniform float waveSpeed;
uniform vec2 resolution;
void main() {
vec2 uv = gl_FragCoord.xy / resolution;
vec2 pixelSize = 1.0 / resolution;
// 获取周围像素的值
float left = texture2D(currentState, uv - vec2(pixelSize.x, 0.0)).r;
float right = texture2D(currentState, uv + vec2(pixelSize.x, 0.0)).r;
float top = texture2D(currentState, uv - vec2(0.0, pixelSize.y)).r;
float bottom = texture2D(currentState, uv + vec2(0.0, pixelSize.y)).r;
// 拉普拉斯算子计算曲率
float curvature = (left + right + top + bottom) * 0.25 - texture2D(currentState, uv).r;
// 波浪方程: new = 2 * current - previous + waveSpeed^2 * curvature
float current = texture2D(currentState, uv).r;
float previous = texture2D(previousState, uv).r;
float newValue = 2.0 * current - previous + waveSpeed * waveSpeed * curvature;
// 应用阻尼
newValue *= damping;
gl_FragColor = vec4(newValue, 0.0, 0.0, 1.0);
}
`;
// 这里应该设置GPU计算渲染器
console.log('涟漪传播系统初始化完成');
}
// 添加涟漪源
addRippleSource(x, y, strength) {
const index = Math.floor(y) * this.resolution + Math.floor(x);
this.currentState[index] += strength;
}
// 更新涟漪传播
update() {
// 交换状态缓冲区
const temp = this.currentState;
this.currentState = this.previousState;
this.previousState = temp;
// 执行GPU计算更新
this.executeGPUUpdate();
}
// 执行GPU更新
executeGPUUpdate() {
// 这里应该执行GPU计算着色器
// 简化实现:CPU模拟
for (let y = 1; y < this.resolution - 1; y++) {
for (let x = 1; x < this.resolution - 1; x++) {
const index = y * this.resolution + x;
// 拉普拉斯算子
const curvature = (
this.previousState[index - 1] +
this.previousState[index + 1] +
this.previousState[index - this.resolution] +
this.previousState[index + this.resolution]
) * 0.25 - this.previousState[index];
// 波浪方程
this.currentState[index] = 2 * this.previousState[index] - this.currentState[index]
+ this.waveSpeed * this.waveSpeed * curvature;
// 应用阻尼
this.currentState[index] *= this.damping;
}
}
}
// 获取涟漪纹理
getRippleTexture() {
const canvas = document.createElement('canvas');
canvas.width = this.resolution;
canvas.height = this.resolution;
const context = canvas.getContext('2d');
const imageData = context.createImageData(this.resolution, this.resolution);
for (let i = 0; i < this.currentState.length; i++) {
const value = (this.currentState[i] * 0.5 + 0.5) * 255;
imageData.data[i * 4] = value; // R
imageData.data[i * 4 + 1] = value; // G
imageData.data[i * 4 + 2] = 255; // B
imageData.data[i * 4 + 3] = 255; // A
}
context.putImageData(imageData, 0, 0);
return new THREE.CanvasTexture(canvas);
}
}
注意事项与最佳实践
-
性能优化关键
- 使用GPU计算进行物理模拟
- 实现多层次细节的水体渲染
- 优化着色器计算复杂度
- 使用纹理压缩减少内存占用
-
视觉质量优化
- 实现真实的菲涅尔效应
- 添加适当的折射和反射
- 使用高质量的法线贴图
- 实现动态光照和阴影
-
交互体验优化
- 响应式涟漪传播系统
- 多物体交互支持
- 实时物理反馈
- 性能自适应调整
下一节预告
第32节:体积渲染与云层烟雾效果
将深入探索体积渲染技术,包括:3D噪声纹理生成、Raymarching算法、实时体积光照计算,以及如何创建逼真的云层、烟雾和雾气效果。


被折叠的 条评论
为什么被折叠?



