fix: stabilize water depth and rounded mask
This commit is contained in:
@@ -25,6 +25,23 @@ function applyTerrainMaterialSettings(
|
|||||||
scene.traverse((child) => {
|
scene.traverse((child) => {
|
||||||
if (child instanceof THREE.Mesh) {
|
if (child instanceof THREE.Mesh) {
|
||||||
child.receiveShadow = receiveShadow;
|
child.receiveShadow = receiveShadow;
|
||||||
|
|
||||||
|
const materials = Array.isArray(child.material)
|
||||||
|
? child.material
|
||||||
|
: [child.material];
|
||||||
|
|
||||||
|
for (const material of materials) {
|
||||||
|
const materialWithAlphaMap = material as THREE.Material & {
|
||||||
|
alphaMap?: THREE.Texture | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
material.depthTest = true;
|
||||||
|
material.depthWrite = true;
|
||||||
|
|
||||||
|
if (material.opacity >= 1 && !materialWithAlphaMap.alphaMap) {
|
||||||
|
material.transparent = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const TERRAIN_SURFACE_PROJECTION = {
|
|||||||
offsetX: 0,
|
offsetX: 0,
|
||||||
offsetZ: 0,
|
offsetZ: 0,
|
||||||
};
|
};
|
||||||
export const TERRAIN_WATER_HEIGHT = 0;
|
export const TERRAIN_WATER_HEIGHT = 0.8;
|
||||||
export const TERRAIN_TILE_SIZE = 1;
|
export const TERRAIN_TILE_SIZE = 1;
|
||||||
export const GRASS_BASE_COLOR = "#1a3a1a";
|
export const GRASS_BASE_COLOR = "#1a3a1a";
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ export interface WaterSurfaceConfig {
|
|||||||
export const WATER_SHADER_CONFIG = {
|
export const WATER_SHADER_CONFIG = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
height: TERRAIN_WATER_HEIGHT,
|
height: TERRAIN_WATER_HEIGHT,
|
||||||
scale: 0.3,
|
depthOffset: -0.04,
|
||||||
|
borderRadius: 0.18,
|
||||||
|
borderSoftness: 0.035,
|
||||||
|
scale: 0.4,
|
||||||
smoothness: 0.55,
|
smoothness: 0.55,
|
||||||
edgeThreshold: 0.067,
|
edgeThreshold: 0.067,
|
||||||
edgeSoftness: 0.01,
|
edgeSoftness: 0.01,
|
||||||
@@ -31,9 +34,9 @@ export const WATER_SHADER_CONFIG = {
|
|||||||
|
|
||||||
export const WATER_SURFACES: WaterSurfaceConfig[] = [
|
export const WATER_SURFACES: WaterSurfaceConfig[] = [
|
||||||
{
|
{
|
||||||
position: [62, TERRAIN_WATER_HEIGHT, -82],
|
position: [40, TERRAIN_WATER_HEIGHT, -102],
|
||||||
rotation: [0, 0, 0],
|
rotation: [0, 0, 0],
|
||||||
size: [75, 42],
|
size: [75, 45],
|
||||||
renderOrder: 0,
|
renderOrder: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -13,3 +13,12 @@ export const WIND_BOUNDS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type WindState = typeof WIND_DEFAULTS;
|
export type WindState = typeof WIND_DEFAULTS;
|
||||||
|
|
||||||
|
export function getWindVector(wind: WindState): { x: number; z: number } {
|
||||||
|
const intensity = wind.speed * wind.strength;
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: Math.cos(wind.direction) * intensity,
|
||||||
|
z: Math.sin(wind.direction) * intensity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { WIND_BOUNDS } from "@/data/world/windConfig";
|
||||||
|
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
|
||||||
|
import { useWorldSettingsStore } from "@/managers/stores/useWorldSettingsStore";
|
||||||
|
|
||||||
|
export function useEnvironmentDebug(): void {
|
||||||
|
useDebugFolder("Environment", (folder) => {
|
||||||
|
const { setWind, wind } = useWorldSettingsStore.getState();
|
||||||
|
const controls = { ...wind };
|
||||||
|
|
||||||
|
folder
|
||||||
|
.add(controls, "speed", WIND_BOUNDS.speed.min, WIND_BOUNDS.speed.max)
|
||||||
|
.step(WIND_BOUNDS.speed.step)
|
||||||
|
.name("Wind speed")
|
||||||
|
.onChange((speed: number) => setWind({ speed }));
|
||||||
|
|
||||||
|
folder
|
||||||
|
.add(
|
||||||
|
controls,
|
||||||
|
"direction",
|
||||||
|
WIND_BOUNDS.direction.min,
|
||||||
|
WIND_BOUNDS.direction.max,
|
||||||
|
)
|
||||||
|
.step(WIND_BOUNDS.direction.step)
|
||||||
|
.name("Wind direction")
|
||||||
|
.onChange((direction: number) => setWind({ direction }));
|
||||||
|
|
||||||
|
folder
|
||||||
|
.add(
|
||||||
|
controls,
|
||||||
|
"strength",
|
||||||
|
WIND_BOUNDS.strength.min,
|
||||||
|
WIND_BOUNDS.strength.max,
|
||||||
|
)
|
||||||
|
.step(WIND_BOUNDS.strength.step)
|
||||||
|
.name("Wind strength")
|
||||||
|
.onChange((strength: number) => setWind({ strength }));
|
||||||
|
|
||||||
|
folder
|
||||||
|
.add(
|
||||||
|
controls,
|
||||||
|
"noiseScale",
|
||||||
|
WIND_BOUNDS.noiseScale.min,
|
||||||
|
WIND_BOUNDS.noiseScale.max,
|
||||||
|
)
|
||||||
|
.step(WIND_BOUNDS.noiseScale.step)
|
||||||
|
.name("Wind noise scale")
|
||||||
|
.onChange((noiseScale: number) => setWind({ noiseScale }));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ interface DebugEvents {
|
|||||||
|
|
||||||
const DEBUG_FOLDER_ORDER = [
|
const DEBUG_FOLDER_ORDER = [
|
||||||
"Lighting",
|
"Lighting",
|
||||||
|
"Environment",
|
||||||
"Game",
|
"Game",
|
||||||
"Interaction",
|
"Interaction",
|
||||||
"Hand Tracking",
|
"Hand Tracking",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
PLAYER_SPAWN_POSITION_PHYSICS,
|
PLAYER_SPAWN_POSITION_PHYSICS,
|
||||||
} from "@/data/player/playerConfig";
|
} from "@/data/player/playerConfig";
|
||||||
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||||
|
import { useEnvironmentDebug } from "@/hooks/debug/useEnvironmentDebug";
|
||||||
import { useMapPerformanceDebug } from "@/hooks/debug/useMapPerformanceDebug";
|
import { useMapPerformanceDebug } from "@/hooks/debug/useMapPerformanceDebug";
|
||||||
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
||||||
import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot";
|
import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot";
|
||||||
@@ -36,6 +37,7 @@ interface WorldProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
|
export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
|
||||||
|
useEnvironmentDebug();
|
||||||
useMapPerformanceDebug();
|
useMapPerformanceDebug();
|
||||||
|
|
||||||
const cameraMode = useCameraMode();
|
const cameraMode = useCameraMode();
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { useMemo, useRef } from "react";
|
import { useMemo, useRef } from "react";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { useFrame } from "@react-three/fiber";
|
import { useFrame } from "@react-three/fiber";
|
||||||
|
import { getWindVector } from "@/data/world/windConfig";
|
||||||
import { WATER_SHADER_CONFIG } from "@/data/world/waterConfig";
|
import { WATER_SHADER_CONFIG } from "@/data/world/waterConfig";
|
||||||
import type { WaterSurfaceConfig } from "@/data/world/waterConfig";
|
import type { WaterSurfaceConfig } from "@/data/world/waterConfig";
|
||||||
|
import { useWind } from "@/hooks/world/useWind";
|
||||||
import {
|
import {
|
||||||
WATER_FRAGMENT_SHADER,
|
WATER_FRAGMENT_SHADER,
|
||||||
WATER_VERTEX_SHADER,
|
WATER_VERTEX_SHADER,
|
||||||
@@ -15,6 +17,7 @@ export function WaterSurface({
|
|||||||
size,
|
size,
|
||||||
}: WaterSurfaceConfig): React.JSX.Element {
|
}: WaterSurfaceConfig): React.JSX.Element {
|
||||||
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
||||||
|
const wind = useWind();
|
||||||
const uniforms = useMemo(
|
const uniforms = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
uTime: { value: 0 },
|
uTime: { value: 0 },
|
||||||
@@ -28,6 +31,8 @@ export function WaterSurface({
|
|||||||
uNoiseScale: { value: WATER_SHADER_CONFIG.noiseScale },
|
uNoiseScale: { value: WATER_SHADER_CONFIG.noiseScale },
|
||||||
uNoiseFlowSpeed: { value: WATER_SHADER_CONFIG.noiseFlowSpeed },
|
uNoiseFlowSpeed: { value: WATER_SHADER_CONFIG.noiseFlowSpeed },
|
||||||
uDistortAmount: { value: WATER_SHADER_CONFIG.distortAmount },
|
uDistortAmount: { value: WATER_SHADER_CONFIG.distortAmount },
|
||||||
|
uBorderRadius: { value: WATER_SHADER_CONFIG.borderRadius },
|
||||||
|
uBorderSoftness: { value: WATER_SHADER_CONFIG.borderSoftness },
|
||||||
uDeepColor: { value: new THREE.Color(WATER_SHADER_CONFIG.deepColor) },
|
uDeepColor: { value: new THREE.Color(WATER_SHADER_CONFIG.deepColor) },
|
||||||
uMidColor: { value: new THREE.Color(WATER_SHADER_CONFIG.midColor) },
|
uMidColor: { value: new THREE.Color(WATER_SHADER_CONFIG.midColor) },
|
||||||
uMidPos: { value: WATER_SHADER_CONFIG.midPos },
|
uMidPos: { value: WATER_SHADER_CONFIG.midPos },
|
||||||
@@ -41,15 +46,28 @@ export function WaterSurface({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useFrame(({ clock }) => {
|
useFrame(({ clock }) => {
|
||||||
const uniform = materialRef.current?.uniforms.uTime;
|
const material = materialRef.current;
|
||||||
if (uniform) {
|
if (!material) return;
|
||||||
uniform.value = clock.getElapsedTime();
|
|
||||||
|
const windVector = getWindVector(wind);
|
||||||
|
|
||||||
|
const { uFlowX, uFlowZ, uNoiseScale, uTime } = material.uniforms;
|
||||||
|
|
||||||
|
if (uTime) uTime.value = clock.getElapsedTime();
|
||||||
|
if (uFlowX) uFlowX.value = WATER_SHADER_CONFIG.flowX + windVector.x;
|
||||||
|
if (uFlowZ) uFlowZ.value = WATER_SHADER_CONFIG.flowZ + windVector.z;
|
||||||
|
if (uNoiseScale) {
|
||||||
|
uNoiseScale.value = WATER_SHADER_CONFIG.noiseScale * wind.noiseScale;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh
|
<mesh
|
||||||
position={position}
|
position={[
|
||||||
|
position[0],
|
||||||
|
position[1] + WATER_SHADER_CONFIG.depthOffset,
|
||||||
|
position[2],
|
||||||
|
]}
|
||||||
rotation={[-Math.PI / 2 + rotation[0], rotation[1], rotation[2]]}
|
rotation={[-Math.PI / 2 + rotation[0], rotation[1], rotation[2]]}
|
||||||
renderOrder={renderOrder}
|
renderOrder={renderOrder}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
export const WATER_VERTEX_SHADER = /* glsl */ `
|
export const WATER_VERTEX_SHADER = /* glsl */ `
|
||||||
|
varying vec2 vUv;
|
||||||
varying vec2 vWorldPos;
|
varying vec2 vWorldPos;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
vUv = uv;
|
||||||
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
|
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
|
||||||
vWorldPos = worldPosition.xz;
|
vWorldPos = worldPosition.xz;
|
||||||
gl_Position = projectionMatrix * viewMatrix * worldPosition;
|
gl_Position = projectionMatrix * viewMatrix * worldPosition;
|
||||||
@@ -20,6 +22,8 @@ export const WATER_FRAGMENT_SHADER = /* glsl */ `
|
|||||||
uniform float uNoiseScale;
|
uniform float uNoiseScale;
|
||||||
uniform float uNoiseFlowSpeed;
|
uniform float uNoiseFlowSpeed;
|
||||||
uniform float uDistortAmount;
|
uniform float uDistortAmount;
|
||||||
|
uniform float uBorderRadius;
|
||||||
|
uniform float uBorderSoftness;
|
||||||
uniform vec3 uDeepColor;
|
uniform vec3 uDeepColor;
|
||||||
uniform vec3 uMidColor;
|
uniform vec3 uMidColor;
|
||||||
uniform float uMidPos;
|
uniform float uMidPos;
|
||||||
@@ -27,8 +31,18 @@ export const WATER_FRAGMENT_SHADER = /* glsl */ `
|
|||||||
uniform float uOpacity;
|
uniform float uOpacity;
|
||||||
uniform float uDeepOpacity;
|
uniform float uDeepOpacity;
|
||||||
|
|
||||||
|
varying vec2 vUv;
|
||||||
varying vec2 vWorldPos;
|
varying vec2 vWorldPos;
|
||||||
|
|
||||||
|
float roundedBoxMask(vec2 uv, float radius, float softness) {
|
||||||
|
vec2 centeredUv = uv * 2.0 - 1.0;
|
||||||
|
vec2 boxSize = vec2(1.0 - radius);
|
||||||
|
vec2 distanceToEdge = abs(centeredUv) - boxSize;
|
||||||
|
float outsideDistance = length(max(distanceToEdge, 0.0)) - radius;
|
||||||
|
|
||||||
|
return 1.0 - smoothstep(-softness, softness, outsideDistance);
|
||||||
|
}
|
||||||
|
|
||||||
vec2 hash2(vec2 p) {
|
vec2 hash2(vec2 p) {
|
||||||
p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
|
p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
|
||||||
return fract(sin(p) * 43758.5453);
|
return fract(sin(p) * 43758.5453);
|
||||||
@@ -126,6 +140,11 @@ export const WATER_FRAGMENT_SHADER = /* glsl */ `
|
|||||||
inSecondSegment
|
inSecondSegment
|
||||||
);
|
);
|
||||||
float alpha = mix(uDeepOpacity, 1.0, ramp) * uOpacity;
|
float alpha = mix(uDeepOpacity, 1.0, ramp) * uOpacity;
|
||||||
|
alpha *= roundedBoxMask(vUv, uBorderRadius, uBorderSoftness);
|
||||||
|
|
||||||
|
if (alpha < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
gl_FragColor = vec4(color, alpha);
|
gl_FragColor = vec4(color, alpha);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user