Feat/map-environment #6
@@ -0,0 +1,35 @@
|
|||||||
|
import { TERRAIN_WATER_HEIGHT } from "@/data/world/terrainConfig";
|
||||||
|
import type { Vector3Tuple } from "@/types/three/three";
|
||||||
|
|
||||||
|
export interface WaterSurfaceConfig {
|
||||||
|
position: Vector3Tuple;
|
||||||
|
size: [number, number];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WATER_SHADER_CONFIG = {
|
||||||
|
enabled: true,
|
||||||
|
height: TERRAIN_WATER_HEIGHT,
|
||||||
|
scale: 0.3,
|
||||||
|
smoothness: 0.55,
|
||||||
|
edgeThreshold: 0.067,
|
||||||
|
edgeSoftness: 0.01,
|
||||||
|
flowX: 0,
|
||||||
|
flowZ: 0.05,
|
||||||
|
cellSpeed: 0.3,
|
||||||
|
noiseScale: 1.52,
|
||||||
|
noiseFlowSpeed: 0.2,
|
||||||
|
distortAmount: 0.3,
|
||||||
|
deepColor: "#1a3a5c",
|
||||||
|
midColor: "#59c0e8",
|
||||||
|
midPos: 0.084,
|
||||||
|
highlightColor: "#ffffff",
|
||||||
|
opacity: 0.88,
|
||||||
|
deepOpacity: 0.45,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WATER_SURFACES: WaterSurfaceConfig[] = [
|
||||||
|
{
|
||||||
|
position: [62, TERRAIN_WATER_HEIGHT, -82],
|
||||||
|
size: [75, 42],
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -25,6 +25,7 @@ import { isGeneratedMapModelName } from "@/world/map-generated/generatedMapModel
|
|||||||
import { MapInstancingSystem } from "@/world/map-instancing/MapInstancingSystem";
|
import { MapInstancingSystem } from "@/world/map-instancing/MapInstancingSystem";
|
||||||
import { isInstancedMapNodeName } from "@/world/map-instancing/mapInstancingConfig";
|
import { isInstancedMapNodeName } from "@/world/map-instancing/mapInstancingConfig";
|
||||||
import { VegetationSystem } from "@/world/vegetation/VegetationSystem";
|
import { VegetationSystem } from "@/world/vegetation/VegetationSystem";
|
||||||
|
import { WaterSystem } from "@/world/water/WaterSystem";
|
||||||
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
|
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
|
||||||
import { logger } from "@/utils/core/Logger";
|
import { logger } from "@/utils/core/Logger";
|
||||||
import { loadMapSceneData } from "@/utils/map/loadMapSceneData";
|
import { loadMapSceneData } from "@/utils/map/loadMapSceneData";
|
||||||
@@ -257,6 +258,7 @@ export function GameMap({
|
|||||||
))}
|
))}
|
||||||
</group>
|
</group>
|
||||||
<MapInstancingSystem />
|
<MapInstancingSystem />
|
||||||
|
<WaterSystem />
|
||||||
<VegetationSystem />
|
<VegetationSystem />
|
||||||
{isMapModelVisible("terrain", { groups, models }) ? (
|
{isMapModelVisible("terrain", { groups, models }) ? (
|
||||||
<TerrainModel />
|
<TerrainModel />
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { useMemo, useRef } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { useFrame } from "@react-three/fiber";
|
||||||
|
import { WATER_SHADER_CONFIG } from "@/data/world/waterConfig";
|
||||||
|
import type { WaterSurfaceConfig } from "@/data/world/waterConfig";
|
||||||
|
import {
|
||||||
|
WATER_FRAGMENT_SHADER,
|
||||||
|
WATER_VERTEX_SHADER,
|
||||||
|
} from "@/world/water/waterShaders";
|
||||||
|
|
||||||
|
export function WaterSurface({
|
||||||
|
position,
|
||||||
|
size,
|
||||||
|
}: WaterSurfaceConfig): React.JSX.Element {
|
||||||
|
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
||||||
|
const uniforms = useMemo(
|
||||||
|
() => ({
|
||||||
|
uTime: { value: 0 },
|
||||||
|
uScale: { value: WATER_SHADER_CONFIG.scale },
|
||||||
|
uSmoothness: { value: WATER_SHADER_CONFIG.smoothness },
|
||||||
|
uEdgeThreshold: { value: WATER_SHADER_CONFIG.edgeThreshold },
|
||||||
|
uEdgeSoftness: { value: WATER_SHADER_CONFIG.edgeSoftness },
|
||||||
|
uFlowX: { value: WATER_SHADER_CONFIG.flowX },
|
||||||
|
uFlowZ: { value: WATER_SHADER_CONFIG.flowZ },
|
||||||
|
uCellSpeed: { value: WATER_SHADER_CONFIG.cellSpeed },
|
||||||
|
uNoiseScale: { value: WATER_SHADER_CONFIG.noiseScale },
|
||||||
|
uNoiseFlowSpeed: { value: WATER_SHADER_CONFIG.noiseFlowSpeed },
|
||||||
|
uDistortAmount: { value: WATER_SHADER_CONFIG.distortAmount },
|
||||||
|
uDeepColor: { value: new THREE.Color(WATER_SHADER_CONFIG.deepColor) },
|
||||||
|
uMidColor: { value: new THREE.Color(WATER_SHADER_CONFIG.midColor) },
|
||||||
|
uMidPos: { value: WATER_SHADER_CONFIG.midPos },
|
||||||
|
uHighlight: {
|
||||||
|
value: new THREE.Color(WATER_SHADER_CONFIG.highlightColor),
|
||||||
|
},
|
||||||
|
uOpacity: { value: WATER_SHADER_CONFIG.opacity },
|
||||||
|
uDeepOpacity: { value: WATER_SHADER_CONFIG.deepOpacity },
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
useFrame(({ clock }) => {
|
||||||
|
const uniform = materialRef.current?.uniforms.uTime;
|
||||||
|
if (uniform) {
|
||||||
|
uniform.value = clock.getElapsedTime();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<mesh position={position} rotation={[-Math.PI / 2, 0, 0]} renderOrder={1}>
|
||||||
|
<planeGeometry args={size} />
|
||||||
|
<shaderMaterial
|
||||||
|
ref={materialRef}
|
||||||
|
attach="material"
|
||||||
|
depthWrite={false}
|
||||||
|
fragmentShader={WATER_FRAGMENT_SHADER}
|
||||||
|
side={THREE.FrontSide}
|
||||||
|
transparent
|
||||||
|
uniforms={uniforms}
|
||||||
|
vertexShader={WATER_VERTEX_SHADER}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { WATER_SHADER_CONFIG, WATER_SURFACES } from "@/data/world/waterConfig";
|
||||||
|
import { WaterSurface } from "@/world/water/WaterSurface";
|
||||||
|
|
||||||
|
export function WaterSystem(): React.JSX.Element | null {
|
||||||
|
if (!WATER_SHADER_CONFIG.enabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{WATER_SURFACES.map((surface, index) => (
|
||||||
|
<WaterSurface key={index} {...surface} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
export const WATER_VERTEX_SHADER = /* glsl */ `
|
||||||
|
varying vec2 vWorldPos;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
|
||||||
|
vWorldPos = worldPosition.xz;
|
||||||
|
gl_Position = projectionMatrix * viewMatrix * worldPosition;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const WATER_FRAGMENT_SHADER = /* glsl */ `
|
||||||
|
uniform float uTime;
|
||||||
|
uniform float uScale;
|
||||||
|
uniform float uSmoothness;
|
||||||
|
uniform float uEdgeThreshold;
|
||||||
|
uniform float uEdgeSoftness;
|
||||||
|
uniform float uFlowX;
|
||||||
|
uniform float uFlowZ;
|
||||||
|
uniform float uCellSpeed;
|
||||||
|
uniform float uNoiseScale;
|
||||||
|
uniform float uNoiseFlowSpeed;
|
||||||
|
uniform float uDistortAmount;
|
||||||
|
uniform vec3 uDeepColor;
|
||||||
|
uniform vec3 uMidColor;
|
||||||
|
uniform float uMidPos;
|
||||||
|
uniform vec3 uHighlight;
|
||||||
|
uniform float uOpacity;
|
||||||
|
uniform float uDeepOpacity;
|
||||||
|
|
||||||
|
varying vec2 vWorldPos;
|
||||||
|
|
||||||
|
vec2 hash2(vec2 p) {
|
||||||
|
p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
|
||||||
|
return fract(sin(p) * 43758.5453);
|
||||||
|
}
|
||||||
|
|
||||||
|
float smin(float a, float b, float k) {
|
||||||
|
float h = max(k - abs(a - b), 0.0) / k;
|
||||||
|
return min(a, b) - h * h * h * k / 6.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 cellPoint(vec2 seed) {
|
||||||
|
return 0.5 + 0.5 * sin(uTime * uCellSpeed + 6.2831 * seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
float voronoiF1(vec2 p) {
|
||||||
|
vec2 i = floor(p);
|
||||||
|
vec2 f = fract(p);
|
||||||
|
float nearest = 8.0;
|
||||||
|
|
||||||
|
for (int y = -1; y <= 1; y++) {
|
||||||
|
for (int x = -1; x <= 1; x++) {
|
||||||
|
vec2 neighbor = vec2(float(x), float(y));
|
||||||
|
vec2 point = cellPoint(hash2(i + neighbor));
|
||||||
|
nearest = min(nearest, length(neighbor + point - f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
float voronoiSmoothF1(vec2 p) {
|
||||||
|
vec2 i = floor(p);
|
||||||
|
vec2 f = fract(p);
|
||||||
|
float result = 8.0;
|
||||||
|
|
||||||
|
for (int y = -1; y <= 1; y++) {
|
||||||
|
for (int x = -1; x <= 1; x++) {
|
||||||
|
vec2 neighbor = vec2(float(x), float(y));
|
||||||
|
vec2 point = cellPoint(hash2(i + neighbor));
|
||||||
|
result = smin(result, length(neighbor + point - f), uSmoothness);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
float noiseHash(vec2 p) {
|
||||||
|
p = fract(p * vec2(127.1, 311.7));
|
||||||
|
p += dot(p, p + 45.32);
|
||||||
|
return fract(p.x * p.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
float valueNoise(vec2 p) {
|
||||||
|
vec2 i = floor(p);
|
||||||
|
vec2 f = fract(p);
|
||||||
|
f = f * f * (3.0 - 2.0 * f);
|
||||||
|
|
||||||
|
return mix(
|
||||||
|
mix(noiseHash(i), noiseHash(i + vec2(1.0, 0.0)), f.x),
|
||||||
|
mix(noiseHash(i + vec2(0.0, 1.0)), noiseHash(i + vec2(1.0, 1.0)), f.x),
|
||||||
|
f.y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
float fbm(vec2 p) {
|
||||||
|
float value = 0.0;
|
||||||
|
float amplitude = 0.5;
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
value += amplitude * valueNoise(p);
|
||||||
|
p *= 2.0;
|
||||||
|
amplitude *= 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 noiseUv = vWorldPos * uNoiseScale + vec2(uTime * uNoiseFlowSpeed, 0.0);
|
||||||
|
float noiseFactor = fbm(noiseUv);
|
||||||
|
vec2 distortion = vec2(noiseFactor - 0.5) * uDistortAmount;
|
||||||
|
vec2 uv = vWorldPos * uScale + vec2(uFlowX, uFlowZ) * uTime + distortion;
|
||||||
|
|
||||||
|
float f1 = voronoiF1(uv);
|
||||||
|
float smoothF1 = voronoiSmoothF1(uv);
|
||||||
|
float edge = f1 - smoothF1;
|
||||||
|
float ramp = smoothstep(uEdgeThreshold - uEdgeSoftness, uEdgeThreshold + uEdgeSoftness, edge);
|
||||||
|
float safeMidPosition = max(uMidPos, 0.0001);
|
||||||
|
float firstSegment = clamp(ramp / safeMidPosition, 0.0, 1.0);
|
||||||
|
float secondSegment = clamp((ramp - safeMidPosition) / max(1.0 - safeMidPosition, 0.0001), 0.0, 1.0);
|
||||||
|
float inSecondSegment = step(safeMidPosition, ramp);
|
||||||
|
vec3 color = mix(
|
||||||
|
mix(uDeepColor, uMidColor, firstSegment),
|
||||||
|
mix(uMidColor, uHighlight, secondSegment),
|
||||||
|
inSecondSegment
|
||||||
|
);
|
||||||
|
float alpha = mix(uDeepOpacity, 1.0, ramp) * uOpacity;
|
||||||
|
|
||||||
|
gl_FragColor = vec4(color, alpha);
|
||||||
|
}
|
||||||
|
`;
|
||||||
Reference in New Issue
Block a user