Files
voxel-tactics-horizon/voxel-tactics-horizon/src/features/Map/components/ChunkRenderer.tsx

1016 lines
37 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useLayoutEffect, useMemo, useRef } from 'react';
import { InstancedMesh, Object3D, Color, BufferGeometry, Float32BufferAttribute, ShaderMaterial } from 'three';
import { useFrame } from '@react-three/fiber';
import { useMapStore } from '../store';
import { useUnitStore } from '../../Units/store';
import { VOXEL_SIZE, TILE_SIZE, TREE_VOXEL_SIZE, type VoxelType } from '../logic/terrain';
import type { ThreeEvent } from '@react-three/fiber';
const tempObject = new Object3D();
const tempColor = new Color();
// ============= 冰块 Shader =============
const iceVertexShader = /* glsl */ `
attribute vec3 color;
varying vec3 vColor;
varying vec3 vNormal;
varying vec3 vWorldPosition;
void main() {
vColor = color;
vNormal = normalize(normalMatrix * normal);
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const iceFragmentShader = /* glsl */ `
uniform float uTime;
varying vec3 vColor;
varying vec3 vNormal;
varying vec3 vWorldPosition;
// 简单的伪随机函数
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
// 平滑噪声函数
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
// Voronoi 用于生成自然裂纹
vec2 voronoi(vec2 x) {
vec2 p = floor(x);
vec2 f = fract(x);
float minDist = 1.0;
float secondMin = 1.0;
for(int j = -1; j <= 1; j++) {
for(int i = -1; i <= 1; i++) {
vec2 b = vec2(float(i), float(j));
vec2 r = b - f + random(p + b);
float d = dot(r, r);
if(d < minDist) {
secondMin = minDist;
minDist = d;
} else if(d < secondMin) {
secondMin = d;
}
}
}
return vec2(sqrt(minDist), sqrt(secondMin));
}
void main() {
vec3 finalColor = vColor;
float alpha = 0.88;
// 判断面的朝向
bool isTopFace = vNormal.y > 0.7;
bool isBottomFace = vNormal.y < -0.7;
bool isVerticalFace = !isTopFace && !isBottomFace;
// 冰块颜色定义 - 更明显的蓝色调
vec3 clearIce = vec3(0.65, 0.85, 0.95); // 清澈冰蓝(主色调,更蓝)
vec3 deepIce = vec3(0.4, 0.65, 0.85); // 深处冰蓝(更深的蓝)
vec3 frostWhite = vec3(0.85, 0.92, 0.98); // 霜白色(带蓝调)
vec3 bubbleWhite = vec3(0.9, 0.95, 1.0); // 气泡白(带蓝调)
vec3 specularWhite = vec3(0.95, 0.98, 1.0); // 镜面反射(带蓝调)
vec2 worldXZ = vWorldPosition.xz;
float worldY = vWorldPosition.y;
// 光源和视角
vec3 lightDir = normalize(vec3(0.5, 1.0, 0.3));
vec3 viewDir = normalize(cameraPosition - vWorldPosition);
// ============= 顶面(冰面)=============
if (isTopFace) {
// 基础颜色 - 清澈的冰蓝色
finalColor = clearIce;
// 1. 深度变化 - 非常柔和的渐变,模拟冰的厚度不均
float depthNoise = noise(worldXZ * 0.8);
finalColor = mix(finalColor, deepIce, depthNoise * 0.15);
// 2. 自然裂纹 - 使用 Voronoi 生成裂纹网络加大20%
vec2 vor = voronoi(worldXZ * 3.3);
float crackEdge = smoothstep(0.025, 0.075, vor.y - vor.x);
float cracks = (1.0 - crackEdge) * 0.09;
finalColor -= vec3(cracks);
// 3. 气泡效果 - 稀疏的小亮点,模拟冻在冰里的气泡
float bubbleNoise = random(floor(worldXZ * 20.0));
float bubble = step(0.92, bubbleNoise) * 0.3;
finalColor = mix(finalColor, bubbleWhite, bubble);
// 4. 表面霜花 - 边缘区域轻微的白霜
float frostNoise = noise(worldXZ * 12.0);
float frost = pow(frostNoise, 3.0) * 0.1;
finalColor = mix(finalColor, frostWhite, frost);
// 5. 镜面反射 - Blinn-Phong 高光
vec3 halfDir = normalize(lightDir + viewDir);
float specAngle = max(dot(vNormal, halfDir), 0.0);
float specular = pow(specAngle, 64.0) * 0.8; // 锐利的镜面高光
finalColor += specularWhite * specular;
// 6. 闪烁高光 - 稀疏的亮点
float sparkle = pow(random(worldXZ * 50.0 + uTime * 0.5), 10.0) * 0.4;
finalColor += vec3(sparkle);
// 透明度 - 更不透明
alpha = 0.88 + depthNoise * 0.08;
}
// ============= 垂直面(冰壁)=============
else if (isVerticalFace) {
// 基础颜色
finalColor = clearIce;
// 深度渐变
float wallDepth = noise(vec2(worldXZ.x + worldXZ.y, worldY) * 1.5);
finalColor = mix(finalColor, deepIce, wallDepth * 0.2);
// 垂直方向的裂纹加大20%
vec2 wallVor = voronoi(vec2(worldXZ.x + worldXZ.y, worldY * 2.0) * 2.5);
float wallCracks = (1.0 - smoothstep(0.025, 0.095, wallVor.y - wallVor.x)) * 0.07;
finalColor -= vec3(wallCracks);
// 气泡
float wallBubble = random(floor(vec2(worldXZ.x + worldXZ.y, worldY) * 15.0));
float bubble = step(0.94, wallBubble) * 0.25;
finalColor = mix(finalColor, bubbleWhite, bubble);
// 镜面反射
vec3 halfDir = normalize(lightDir + viewDir);
float specAngle = max(dot(vNormal, halfDir), 0.0);
float specular = pow(specAngle, 48.0) * 0.6;
finalColor += specularWhite * specular;
// 垂直面不透明度
alpha = 0.85 + wallDepth * 0.08;
}
// ============= 底面 =============
else {
finalColor = deepIce;
alpha = 0.9;
}
// 柔和的环境光照
float diffuse = max(dot(vNormal, lightDir), 0.0) * 0.2 + 0.8;
finalColor *= diffuse;
// 菲涅尔效果 - 边缘反射更强
float fresnel = pow(1.0 - max(dot(vNormal, viewDir), 0.0), 3.0);
finalColor = mix(finalColor, frostWhite, fresnel * 0.35);
// 菲涅尔增加不透明度(边缘更实)
alpha = mix(alpha, 0.98, fresnel * 0.3);
gl_FragColor = vec4(finalColor, alpha);
}
`;
// ============= 水体动画 Shader =============
const waterVertexShader = /* glsl */ `
// 顶点属性
attribute vec3 color;
attribute float isCascade; // 是否是瀑布(垂直面):基于上下邻居判断
attribute vec2 flowDirection; // 流向 (dx, dz)
// 传递给片元着色器
varying vec3 vColor;
varying vec3 vNormal;
varying vec3 vWorldPosition;
varying float vIsCascade;
varying vec2 vFlowDirection;
void main() {
vColor = color;
vNormal = normalize(normalMatrix * normal);
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
vIsCascade = isCascade;
vFlowDirection = flowDirection;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const waterFragmentShader = /* glsl */ `
uniform float uTime;
uniform float uCascadeSpeed; // 瀑布流速
uniform float uRippleSpeed; // 涟漪速度
uniform vec3 uHighlightColor; // 高光颜色(瀑布白沫)
varying vec3 vColor;
varying vec3 vNormal;
varying vec3 vWorldPosition;
varying float vIsCascade;
varying vec2 vFlowDirection;
// 简单的伪随机函数
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
// 噪声函数(用于涟漪和水花)
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
void main() {
vec3 finalColor = vColor;
float alpha = 0.75;
// 判断面的朝向 - 使用更宽松的阈值
bool isTopFace = vNormal.y > 0.7; // 顶面(水平)
bool isBottomFace = vNormal.y < -0.7; // 底面
bool isVerticalFace = !isTopFace && !isBottomFace; // 所有非上下面都是垂直面
// ============= 瀑布效果(所有垂直面)=============
if (isVerticalFace) {
float worldY = vWorldPosition.y;
float worldX = vWorldPosition.x;
float worldZ = vWorldPosition.z;
// 瀑布强度
float cascadeStrength = vIsCascade > 0.5 ? 1.0 : 0.35;
float flowSpeed = uCascadeSpeed * 6.0;
// ====== 1. 主水流 - 不规则的垂直流动 ======
// 用噪声创建水流的"路径",让水流有粗细变化
float pathNoise = noise(vec2(worldX * 5.0 + worldZ * 4.0, worldY * 2.0));
float pathOffset = pathNoise * 0.8; // 水流横向摆动
// 主水流 - 不同位置有不同的速度
float localSpeed = flowSpeed * (0.8 + pathNoise * 0.4);
float mainFlow = sin(worldY * 30.0 + uTime * localSpeed + pathOffset * 15.0) * 0.5 + 0.5;
// 用噪声调制水流宽度(有些地方水多,有些地方水少)
float widthNoise = noise(vec2(worldX * 8.0 + worldZ * 6.0, worldY * 4.0 + uTime * 0.3));
mainFlow *= (0.5 + widthNoise * 0.5);
// ====== 2. 次级水流 - 更小更快的水珠 ======
float secondaryNoise = noise(vec2(worldX * 12.0 - worldZ * 8.0, worldY * 6.0));
float secondaryFlow = sin(worldY * 50.0 + uTime * flowSpeed * 1.3 + secondaryNoise * 10.0) * 0.5 + 0.5;
secondaryFlow *= secondaryNoise; // 用噪声掩码让它不连续
// ====== 3. 泡沫 - 随机分布的白色斑点 ======
float foamNoise1 = noise(vec2(worldX * 15.0 + worldZ * 12.0, worldY * 18.0 + uTime * flowSpeed * 0.5));
float foamNoise2 = noise(vec2(worldX * 20.0 - worldZ * 15.0, worldY * 25.0 + uTime * flowSpeed * 0.8));
float foam = smoothstep(0.55, 0.8, foamNoise1) * smoothstep(0.4, 0.7, foamNoise2);
// ====== 4. 飞溅水滴 - 稀疏的亮点 ======
float dropletNoise = noise(vec2(worldX * 30.0 + worldZ * 25.0, worldY * 35.0 + uTime * flowSpeed * 1.5));
float droplets = pow(dropletNoise, 6.0); // 非常稀疏
// ====== 5. 水帘的透明度变化 ======
float thicknessNoise = noise(vec2(worldX * 6.0 + worldZ * 5.0, worldY * 3.0 + uTime * 0.2));
// ====== 组合所有效果 ======
float waterFlow = mainFlow * 0.5 + secondaryFlow * 0.25 + foam * 0.2 + droplets * 0.05;
waterFlow *= cascadeStrength;
// ====== 颜色混合 - 亮蓝青色调 ======
vec3 deepWater = vec3(0.1, 0.45, 0.8); // 亮蓝 - 主体
vec3 midWater = vec3(0.18, 0.55, 0.88); // 更亮蓝 - 水流
vec3 lightWater = vec3(0.28, 0.65, 0.95); // 高亮蓝 - 高光
vec3 foamBlue = vec3(0.4, 0.75, 0.98); // 泡沫亮蓝
// 基础是亮蓝色
finalColor = deepWater;
// 水流区域变亮
finalColor = mix(finalColor, midWater, mainFlow * 0.6);
// 次级水流更亮
finalColor = mix(finalColor, lightWater, secondaryFlow * 0.4);
// 泡沫区域(亮蓝色调)
finalColor = mix(finalColor, foamBlue, foam * 0.35);
// 水滴闪烁(亮蓝色调)
finalColor = mix(finalColor, vec3(0.5, 0.85, 1.0), droplets * 0.25);
// ====== 透明度 - 水薄的地方更透明 ======
alpha = 0.6 + thicknessNoise * 0.15 + waterFlow * 0.2;
alpha = clamp(alpha, 0.55, 0.95);
}
// ============= 水平面(河流/湖泊)=============
else if (isTopFace) {
vec2 worldXZ = vWorldPosition.xz;
vec2 flowDir = vFlowDirection;
float flowStrength = length(flowDir); // 流向强度,用于区分河流和湖泊
// 颜色定义
vec3 deepWater = vec3(0.12, 0.48, 0.82);
vec3 midWater = vec3(0.2, 0.58, 0.9);
vec3 lightWater = vec3(0.3, 0.68, 0.96);
vec3 highlightColor = vec3(0.42, 0.78, 0.98);
float waterEffect = 0.0;
// ============= 河流(有流向)=============
if (flowStrength > 0.01) {
float flowSpeed = uCascadeSpeed * 3.5; // 比瀑布慢
vec2 normalizedFlow = normalize(flowDir);
vec2 perpFlow = vec2(-normalizedFlow.y, normalizedFlow.x);
// 沿流向和垂直方向的坐标
float flowCoord = dot(worldXZ, normalizedFlow);
float perpCoord = dot(worldXZ, perpFlow);
// ====== 1. 主水流 - 类似瀑布的流向动画 ======
float pathNoise = noise(vec2(perpCoord * 5.0, flowCoord * 2.0));
float pathOffset = pathNoise * 0.8;
float localSpeed = flowSpeed * (0.8 + pathNoise * 0.4);
float mainFlow = sin(flowCoord * 25.0 - uTime * localSpeed + pathOffset * 12.0) * 0.5 + 0.5;
// 用噪声调制水流宽度
float widthNoise = noise(vec2(perpCoord * 8.0, flowCoord * 4.0 - uTime * 0.3));
mainFlow *= (0.5 + widthNoise * 0.5);
// ====== 2. 次级水流 - 更小更快的波纹 ======
float secondaryNoise = noise(vec2(perpCoord * 12.0, flowCoord * 6.0));
float secondaryFlow = sin(flowCoord * 45.0 - uTime * flowSpeed * 1.3 + secondaryNoise * 10.0) * 0.5 + 0.5;
secondaryFlow *= secondaryNoise;
// ====== 3. 泡沫 - 沿流向移动的斑点 ======
float foamNoise1 = noise(vec2(perpCoord * 15.0, flowCoord * 18.0 - uTime * flowSpeed * 0.5));
float foamNoise2 = noise(vec2(perpCoord * 20.0, flowCoord * 25.0 - uTime * flowSpeed * 0.8));
float foam = smoothstep(0.55, 0.8, foamNoise1) * smoothstep(0.4, 0.7, foamNoise2);
// ====== 4. 水面闪烁 ======
float sparkleNoise = noise(vec2(perpCoord * 30.0, flowCoord * 35.0 - uTime * flowSpeed * 1.2));
float sparkle = pow(sparkleNoise, 6.0);
// ====== 5. 水深变化 ======
float depthNoise = noise(vec2(perpCoord * 6.0, flowCoord * 3.0 - uTime * 0.2));
// 组合效果
waterEffect = mainFlow * 0.5 + secondaryFlow * 0.25 + foam * 0.2 + sparkle * 0.05;
// 颜色混合
finalColor = deepWater;
finalColor = mix(finalColor, midWater, mainFlow * 0.6);
finalColor = mix(finalColor, lightWater, secondaryFlow * 0.4);
finalColor = mix(finalColor, highlightColor, foam * 0.35);
finalColor = mix(finalColor, vec3(0.52, 0.85, 1.0), sparkle * 0.25);
// 透明度
alpha = 0.6 + depthNoise * 0.15 + waterEffect * 0.2;
alpha = clamp(alpha, 0.55, 0.85);
}
// ============= 湖泊(无流向)- 静态涟漪 =============
else {
float rippleSpeed = uRippleSpeed * 2.0;
// ====== 1. 涟漪 - 缓慢扩散的同心圆 ======
float rippleOffset = noise(worldXZ * 0.3 + uTime * 0.05) * 5.0;
float ripple1 = sin(length(worldXZ * 3.0 + rippleOffset) - uTime * rippleSpeed * 0.3) * 0.5 + 0.5;
float ripple2 = sin(length((worldXZ + vec2(2.0, 1.5)) * 2.5) - uTime * rippleSpeed * 0.25) * 0.5 + 0.5;
float ripples = ripple1 * 0.5 + ripple2 * 0.5;
ripples *= noise(worldXZ * 1.5 + uTime * 0.08) * 0.4 + 0.6;
// ====== 2. 微波 - 小的表面扰动 ======
float microWave = noise(worldXZ * 8.0 + uTime * 0.15) * 0.5 + 0.5;
// ====== 3. 闪烁高光 - 稀疏的亮点 ======
float sparkleNoise = noise(worldXZ * 20.0 + vec2(uTime * 0.3, -uTime * 0.2));
float sparkle = pow(sparkleNoise, 5.0);
// 组合效果
waterEffect = ripples * 0.5 + microWave * 0.3 + sparkle * 0.2;
// 颜色混合 - 湖水更平静,颜色变化更小
finalColor = deepWater;
finalColor = mix(finalColor, midWater, ripples * 0.4);
finalColor = mix(finalColor, lightWater, microWave * 0.25);
finalColor = mix(finalColor, vec3(0.52, 0.85, 1.0), sparkle * 0.2);
// 透明度 - 湖水更稳定
float depthNoise = noise(worldXZ * 2.0 + uTime * 0.1);
alpha = 0.7 + depthNoise * 0.08 + waterEffect * 0.1;
alpha = clamp(alpha, 0.65, 0.85);
}
}
// ============= 底面 =============
else {
// 底面保持暗色
finalColor *= 0.8;
alpha = 0.7;
}
// 简单的环境光照(减弱以保留动画效果)
vec3 lightDir = normalize(vec3(0.5, 1.0, 0.3));
float diffuse = max(dot(vNormal, lightDir), 0.0) * 0.2 + 0.8;
finalColor *= diffuse;
gl_FragColor = vec4(finalColor, alpha);
}
`;
interface VoxelLayerProps {
// Update to include ix, iy, iz which are present in actual VoxelData
data: {
x: number;
y: number;
z: number;
color: string;
heightScale?: number;
isHighRes?: boolean;
ix: number;
iy: number;
iz: number;
isMergedWater?: boolean;
mergedPositions?: Array<{
ix: number;
iy: number;
iz: number;
flowDirection?: { dx: number; dz: number };
}>;
flowDirection?: { dx: number; dz: number }; // 单个水体素的流向
}[];
isHighRes: boolean;
type: VoxelType;
onClick?: (x: number, z: number) => void;
}
/**
* 专门用于水体的网格渲染器
* 使用 Greedy Meshing / Face Culling 思想,去除相邻水体之间的内部面
* 解决半透明材质叠加导致的颗粒感和反射问题
*
* 动态效果:
* - 瀑布(垂直面):白色光带从上往下流动
* - 河流(水平面):涟漪波动 + 流向光带
*/
const WaterFlowMesh: React.FC<VoxelLayerProps> = ({ data, isHighRes, type, onClick }) => {
const meshRef = useRef<any>(null);
const materialRef = useRef<ShaderMaterial>(null);
// 创建 Shader Material
const material = useMemo(() => {
return new ShaderMaterial({
vertexShader: waterVertexShader,
fragmentShader: waterFragmentShader,
uniforms: {
uTime: { value: 0 },
uCascadeSpeed: { value: 2.0 }, // 瀑布流速(调整为更自然的速度)
uRippleSpeed: { value: 2.0 }, // 涟漪速度
uHighlightColor: { value: new Color('#e0f0ff') }, // 高光颜色(淡蓝白色)
},
transparent: true,
depthWrite: false,
});
}, []);
// 每帧更新 time uniform - 实现动画
useFrame((state) => {
if (material) {
material.uniforms.uTime.value = state.clock.elapsedTime;
}
});
const geometry = useMemo(() => {
if (!data.length) return new BufferGeometry();
// 1. 展开所有合并的水体素,构建完整的体素列表
interface ExpandedVoxel {
x: number;
y: number;
z: number;
ix: number;
iy: number;
iz: number;
color: string;
heightScale: number;
flowDirection?: { dx: number; dz: number }; // 真实的水流方向
}
const expandedVoxels: ExpandedVoxel[] = [];
data.forEach(v => {
if (v.isMergedWater && v.mergedPositions && v.mergedPositions.length > 0) {
// 展开合并的水体素(保留每个位置的流向)
v.mergedPositions.forEach(pos => {
expandedVoxels.push({
x: pos.ix * (isHighRes ? TREE_VOXEL_SIZE : VOXEL_SIZE),
y: pos.iy * (isHighRes ? TREE_VOXEL_SIZE : VOXEL_SIZE),
z: pos.iz * (isHighRes ? TREE_VOXEL_SIZE : VOXEL_SIZE),
ix: pos.ix,
iy: pos.iy,
iz: pos.iz,
color: v.color,
heightScale: v.heightScale || 1,
flowDirection: pos.flowDirection, // 使用该位置的真实流向
});
});
} else {
// 普通水体素(直接添加,使用体素自身的流向)
expandedVoxels.push({
x: v.x,
y: v.y,
z: v.z,
ix: v.ix,
iy: v.iy,
iz: v.iz,
color: v.color,
heightScale: v.heightScale || 1,
flowDirection: v.flowDirection, // 使用体素的流向
});
}
});
// 2. 建立查找表 (Set of "ix|iy|iz")
const lookup = new Set<string>();
expandedVoxels.forEach(v => lookup.add(`${v.ix}|${v.iy}|${v.iz}`));
const positions: number[] = [];
const normals: number[] = [];
const colors: number[] = [];
const isCascadeArr: number[] = []; // 瀑布标记
const flowDirectionArr: number[] = []; // 流向
const voxelSize = isHighRes ? TREE_VOXEL_SIZE : VOXEL_SIZE;
const halfSize = voxelSize / 2;
// 6个方向的邻居偏移和法线
const dirs = [
{ name: 'right', off: [1, 0, 0], normal: [1, 0, 0] },
{ name: 'left', off: [-1, 0, 0], normal: [-1, 0, 0] },
{ name: 'top', off: [0, 1, 0], normal: [0, 1, 0] },
{ name: 'bottom', off: [0, -1, 0], normal: [0, -1, 0] },
{ name: 'front', off: [0, 0, 1], normal: [0, 0, 1] },
{ name: 'back', off: [0, 0, -1], normal: [0, 0, -1] },
];
expandedVoxels.forEach(v => {
const { x, y, z, ix, iy, iz, color, heightScale = 1, flowDirection } = v;
const col = new Color(color);
// 只有当 heightScale 为 1 时才能完美拼接
const isFullBlock = Math.abs(heightScale - 1) < 0.01;
// 检测是否是瀑布:垂直方向有邻居水体
// 如果上方或下方有水体素,说明这是瀑布的一部分
const hasAbove = lookup.has(`${ix}|${iy + 1}|${iz}`);
const hasBelow = lookup.has(`${ix}|${iy - 1}|${iz}`);
const isCascade = hasAbove || hasBelow;
// 【使用真实的流向数据】
let flowDx = 0;
let flowDz = 0;
if (flowDirection) {
// 直接使用河流系统计算的真实流向(湖泊为 0,0
flowDx = flowDirection.dx;
flowDz = flowDirection.dz;
}
// 注意:不再使用回退逻辑,因为真实流向已经在 terrain.ts 中正确设置
// 湖泊的流向就是 (0,0),不需要回退
dirs.forEach(dir => {
// 检查该方向是否有同类邻居
const neighborKey = `${ix + dir.off[0]}|${iy + dir.off[1]}|${iz + dir.off[2]}`;
const hasNeighbor = isFullBlock && lookup.has(neighborKey);
// 如果没有邻居,渲染该面
if (!hasNeighbor) {
// Calculate actual center based on heightScale logic from VoxelLayer
const centerY = y + (heightScale - 1) * (voxelSize / 2);
const scaleY = voxelSize * heightScale;
const halfY = scaleY / 2;
// Vertices relative to center
let v1, v2, v3, v4;
const hs = halfSize;
const hy = halfY;
// Using dir.normal to determine face
const [nx, ny, nz] = dir.normal;
// Logic to generate quad vertices
if (nx === 1) { // Right
v1 = [hs, -hy, hs]; v2 = [hs, -hy, -hs]; v3 = [hs, hy, -hs]; v4 = [hs, hy, hs];
} else if (nx === -1) { // Left
v1 = [-hs, -hy, -hs]; v2 = [-hs, -hy, hs]; v3 = [-hs, hy, hs]; v4 = [-hs, hy, -hs];
} else if (ny === 1) { // Top
v1 = [-hs, hy, hs]; v2 = [ hs, hy, hs]; v3 = [ hs, hy, -hs]; v4 = [-hs, hy, -hs];
} else if (ny === -1) { // Bottom
v1 = [ hs, -hy, hs]; v2 = [-hs, -hy, hs]; v3 = [-hs, -hy, -hs]; v4 = [ hs, -hy, -hs];
} else if (nz === 1) { // Front (Z+)
v1 = [-hs, -hy, hs]; v2 = [ hs, -hy, hs]; v3 = [ hs, hy, hs]; v4 = [-hs, hy, hs];
} else if (nz === -1) { // Back (Z-)
v1 = [ hs, -hy, -hs]; v2 = [-hs, -hy, -hs]; v3 = [-hs, hy, -hs]; v4 = [ hs, hy, -hs];
} else {
// Should not happen
v1=[0,0,0]; v2=[0,0,0]; v3=[0,0,0]; v4=[0,0,0];
}
// Translate to world pos
const applyPos = (v: number[]) => [v[0] + x, v[1] + centerY, v[2] + z];
const p1 = applyPos(v1);
const p2 = applyPos(v2);
const p3 = applyPos(v3);
const p4 = applyPos(v4);
// Push 2 triangles (CCW winding)
// Tri 1: p1, p2, p3
positions.push(...p1, ...p2, ...p3);
// Tri 2: p1, p3, p4
positions.push(...p1, ...p3, ...p4);
// Normals (same for all 6 verts)
for(let k=0; k<6; k++) normals.push(nx, ny, nz);
// Colors (same for all 6 verts)
for(let k=0; k<6; k++) colors.push(col.r, col.g, col.b);
// 瀑布标记:垂直面 + 有上下邻居
const isVerticalFace = Math.abs(ny) < 0.3;
const cascadeValue = (isVerticalFace && isCascade) ? 1.0 : 0.0;
for(let k=0; k<6; k++) isCascadeArr.push(cascadeValue);
// 流向 (same for all 6 verts)
for(let k=0; k<6; k++) {
flowDirectionArr.push(flowDx, flowDz);
}
}
});
});
const bufGeom = new BufferGeometry();
bufGeom.setAttribute('position', new Float32BufferAttribute(positions, 3));
bufGeom.setAttribute('normal', new Float32BufferAttribute(normals, 3));
bufGeom.setAttribute('color', new Float32BufferAttribute(colors, 3));
bufGeom.setAttribute('isCascade', new Float32BufferAttribute(isCascadeArr, 1));
bufGeom.setAttribute('flowDirection', new Float32BufferAttribute(flowDirectionArr, 2));
return bufGeom;
}, [data, isHighRes]);
return (
<mesh
ref={meshRef}
geometry={geometry}
material={material}
castShadow
receiveShadow
/>
);
}
/**
* 专门用于冰块的网格渲染器
* 使用 Face Culling 思想,去除相邻冰块之间的内部面
* 解决半透明材质叠加导致的颗粒感问题
*
* 视觉效果:
* - 冰晶纹理和裂纹
* - 微弱的高光闪烁
* - 菲涅尔边缘高光
*/
const IceMesh: React.FC<VoxelLayerProps> = ({ data, isHighRes, type, onClick }) => {
const meshRef = useRef<any>(null);
// 创建冰块 Shader Material
const material = useMemo(() => {
return new ShaderMaterial({
vertexShader: iceVertexShader,
fragmentShader: iceFragmentShader,
uniforms: {
uTime: { value: 0 },
},
transparent: true,
depthWrite: false,
});
}, []);
// 每帧更新 time uniform - 实现微弱的动画效果
useFrame((state) => {
if (material) {
material.uniforms.uTime.value = state.clock.elapsedTime;
}
});
const geometry = useMemo(() => {
if (!data.length) return new BufferGeometry();
// 构建体素列表
interface IceVoxel {
x: number;
y: number;
z: number;
ix: number;
iy: number;
iz: number;
color: string;
heightScale: number;
}
const iceVoxels: IceVoxel[] = data.map(v => ({
x: v.x,
y: v.y,
z: v.z,
ix: v.ix,
iy: v.iy,
iz: v.iz,
color: v.color,
heightScale: v.heightScale || 1,
}));
// 建立查找表 (Set of "ix|iy|iz")
const lookup = new Set<string>();
iceVoxels.forEach(v => lookup.add(`${v.ix}|${v.iy}|${v.iz}`));
const positions: number[] = [];
const normals: number[] = [];
const colors: number[] = [];
const voxelSize = isHighRes ? TREE_VOXEL_SIZE : VOXEL_SIZE;
const halfSize = voxelSize / 2;
// 6个方向的邻居偏移和法线
const dirs = [
{ name: 'right', off: [1, 0, 0], normal: [1, 0, 0] },
{ name: 'left', off: [-1, 0, 0], normal: [-1, 0, 0] },
{ name: 'top', off: [0, 1, 0], normal: [0, 1, 0] },
{ name: 'bottom', off: [0, -1, 0], normal: [0, -1, 0] },
{ name: 'front', off: [0, 0, 1], normal: [0, 0, 1] },
{ name: 'back', off: [0, 0, -1], normal: [0, 0, -1] },
];
iceVoxels.forEach(v => {
const { x, y, z, ix, iy, iz, color, heightScale = 1 } = v;
const col = new Color(color);
// 只有当 heightScale 为 1 时才能完美拼接
const isFullBlock = Math.abs(heightScale - 1) < 0.01;
dirs.forEach(dir => {
// 检查该方向是否有同类邻居
const neighborKey = `${ix + dir.off[0]}|${iy + dir.off[1]}|${iz + dir.off[2]}`;
const hasNeighbor = isFullBlock && lookup.has(neighborKey);
// 如果没有邻居,渲染该面
if (!hasNeighbor) {
const centerY = y + (heightScale - 1) * (voxelSize / 2);
const scaleY = voxelSize * heightScale;
const halfY = scaleY / 2;
let v1, v2, v3, v4;
const hs = halfSize;
const hy = halfY;
const [nx, ny, nz] = dir.normal;
if (nx === 1) { // Right
v1 = [hs, -hy, hs]; v2 = [hs, -hy, -hs]; v3 = [hs, hy, -hs]; v4 = [hs, hy, hs];
} else if (nx === -1) { // Left
v1 = [-hs, -hy, -hs]; v2 = [-hs, -hy, hs]; v3 = [-hs, hy, hs]; v4 = [-hs, hy, -hs];
} else if (ny === 1) { // Top
v1 = [-hs, hy, hs]; v2 = [ hs, hy, hs]; v3 = [ hs, hy, -hs]; v4 = [-hs, hy, -hs];
} else if (ny === -1) { // Bottom
v1 = [ hs, -hy, hs]; v2 = [-hs, -hy, hs]; v3 = [-hs, -hy, -hs]; v4 = [ hs, -hy, -hs];
} else if (nz === 1) { // Front (Z+)
v1 = [-hs, -hy, hs]; v2 = [ hs, -hy, hs]; v3 = [ hs, hy, hs]; v4 = [-hs, hy, hs];
} else if (nz === -1) { // Back (Z-)
v1 = [ hs, -hy, -hs]; v2 = [-hs, -hy, -hs]; v3 = [-hs, hy, -hs]; v4 = [ hs, hy, -hs];
} else {
v1=[0,0,0]; v2=[0,0,0]; v3=[0,0,0]; v4=[0,0,0];
}
const applyPos = (vtx: number[]) => [vtx[0] + x, vtx[1] + centerY, vtx[2] + z];
const p1 = applyPos(v1);
const p2 = applyPos(v2);
const p3 = applyPos(v3);
const p4 = applyPos(v4);
// Push 2 triangles (CCW winding)
positions.push(...p1, ...p2, ...p3);
positions.push(...p1, ...p3, ...p4);
// Normals
for(let k=0; k<6; k++) normals.push(nx, ny, nz);
// Colors
for(let k=0; k<6; k++) colors.push(col.r, col.g, col.b);
}
});
});
const bufGeom = new BufferGeometry();
bufGeom.setAttribute('position', new Float32BufferAttribute(positions, 3));
bufGeom.setAttribute('normal', new Float32BufferAttribute(normals, 3));
bufGeom.setAttribute('color', new Float32BufferAttribute(colors, 3));
return bufGeom;
}, [data, isHighRes]);
return (
<mesh
ref={meshRef}
geometry={geometry}
material={material}
castShadow
receiveShadow
/>
);
}
const VoxelLayer: React.FC<VoxelLayerProps> = ({ data, isHighRes, type, onClick }) => {
const meshRef = useRef<InstancedMesh<any, any> | null>(null);
const setHoveredTile = useUnitStore(state => state.setHoveredTile);
useLayoutEffect(() => {
const mesh = meshRef.current;
if (!mesh || data.length === 0) return;
// 明确使用配置的尺寸
const voxelSize = isHighRes ? TREE_VOXEL_SIZE : VOXEL_SIZE;
data.forEach((voxel, i) => {
const hScale = voxel.heightScale ?? 1;
const scaleY = voxelSize * hScale;
tempObject.position.set(
voxel.x,
voxel.y + (hScale - 1) * (voxelSize / 2),
voxel.z
);
tempObject.scale.set(voxelSize, scaleY, voxelSize);
tempObject.updateMatrix();
mesh.setMatrixAt(i, tempObject.matrix);
tempColor.set(voxel.color);
mesh.setColorAt(i, tempColor);
});
mesh.instanceMatrix.needsUpdate = true;
}, [data, isHighRes]);
const handlePointerMove = (e: ThreeEvent<PointerEvent>) => {
e.stopPropagation();
if (e.instanceId === undefined) return;
const voxel = data[e.instanceId];
if (voxel) {
const lx = Math.floor(voxel.x / TILE_SIZE);
const lz = Math.floor(voxel.z / TILE_SIZE);
setHoveredTile({ x: lx, z: lz });
}
};
const handleClick = (e: ThreeEvent<MouseEvent>) => {
e.stopPropagation();
if (e.instanceId === undefined || !onClick) return;
const voxel = data[e.instanceId];
if (voxel) {
const lx = Math.floor(voxel.x / TILE_SIZE);
const lz = Math.floor(voxel.z / TILE_SIZE);
onClick(lx, lz);
}
}
const materialProps = useMemo(() => {
// Water is now handled by WaterFlowMesh, but keeping this for fallback or other transparent blocks
if (type === 'water') {
return {
roughness: 0.1,
metalness: 0.2,
transparent: true,
opacity: 0.75
};
} else if (type === 'ice') {
return {
roughness: 0.95, // 提高粗糙度,降低反射强度
metalness: 0.01, // 降低金属感
transparent: true,
opacity: 0.60
};
}
return {
roughness: 0.9,
metalness: 0.2,
transparent: false,
opacity: 1.0
};
}, [type]);
return (
<instancedMesh
ref={meshRef}
args={[undefined, undefined, data.length]}
castShadow
receiveShadow
onPointerMove={handlePointerMove}
onClick={handleClick}
>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial
roughness={materialProps.roughness}
metalness={materialProps.metalness}
transparent={materialProps.transparent}
opacity={materialProps.opacity}
alphaTest={0}
depthWrite={!materialProps.transparent}
/>
</instancedMesh>
);
};
interface ChunkRendererProps {
onVoxelClick?: (x: number, z: number) => void;
}
export const ChunkRenderer: React.FC<ChunkRendererProps> = ({ onVoxelClick }) => {
const voxels = useMapStore((state) => state.voxels);
const groupedVoxels = useMemo(() => {
const groups: Record<string, typeof voxels> = {};
voxels.forEach((v) => {
const key = `${v.type}|${v.isHighRes ? 'true' : 'false'}`;
if (!groups[key]) groups[key] = [];
groups[key].push(v);
});
return groups;
}, [voxels]);
return (
<group>
{Object.keys(groupedVoxels).map((key) => {
const [type, isHighResStr] = key.split('|');
const isHighRes = isHighResStr === 'true';
// 特殊处理水体:使用 Face Culling 网格来消除内部面
if (type === 'water') {
return (
<WaterFlowMesh
key={key}
isHighRes={isHighRes}
type={type as VoxelType}
// @ts-ignore: voxels contain ix/iy/iz but type def in props might be strict
data={groupedVoxels[key]}
onClick={onVoxelClick}
/>
);
}
// 特殊处理冰块类型:使用 Face Culling 网格来消除内部面,解决半透明颗粒感
// 包括ice冰湖、icicle冰锥、ice_boulder冰块、packed_ice压缩冰
if (type === 'ice' || type === 'icicle' || type === 'ice_boulder' || type === 'packed_ice') {
return (
<IceMesh
key={key}
isHighRes={isHighRes}
type={type as VoxelType}
// @ts-ignore: voxels contain ix/iy/iz but type def in props might be strict
data={groupedVoxels[key]}
onClick={onVoxelClick}
/>
);
}
return (
<VoxelLayer
key={key}
isHighRes={isHighRes}
type={type as VoxelType}
// @ts-ignore
data={groupedVoxels[key]}
onClick={onVoxelClick}
/>
);
})}
</group>
);
};