fix(environment): tune grass and terrain grounding
This commit is contained in:
@@ -144,10 +144,14 @@ function createGrassMaterial(
|
|||||||
uWindDirection: { value: 0 },
|
uWindDirection: { value: 0 },
|
||||||
uWindSpeed: { value: 0 },
|
uWindSpeed: { value: 0 },
|
||||||
uWindNoiseScale: { value: GRASS_CONFIG.windNoiseScale },
|
uWindNoiseScale: { value: GRASS_CONFIG.windNoiseScale },
|
||||||
|
uWindStrength: { value: GRASS_CONFIG.windStrength },
|
||||||
uBaldPatchModifier: { value: GRASS_CONFIG.baldPatchModifier },
|
uBaldPatchModifier: { value: GRASS_CONFIG.baldPatchModifier },
|
||||||
uFalloffSharpness: { value: GRASS_CONFIG.falloffSharpness },
|
uFalloffSharpness: { value: GRASS_CONFIG.falloffSharpness },
|
||||||
uHeightNoiseFrequency: { value: GRASS_CONFIG.heightNoiseFrequency },
|
uHeightNoiseFrequency: { value: GRASS_CONFIG.heightNoiseFrequency },
|
||||||
uHeightNoiseAmplitude: { value: GRASS_CONFIG.heightNoiseAmplitude },
|
uHeightNoiseAmplitude: { value: GRASS_CONFIG.heightNoiseAmplitude },
|
||||||
|
uClumpFrequency: { value: GRASS_CONFIG.clumpFrequency },
|
||||||
|
uClumpThreshold: { value: GRASS_CONFIG.clumpThreshold },
|
||||||
|
uClumpSoftness: { value: GRASS_CONFIG.clumpSoftness },
|
||||||
uMaxBendAngle: { value: GRASS_CONFIG.maxBendAngle },
|
uMaxBendAngle: { value: GRASS_CONFIG.maxBendAngle },
|
||||||
uMaxBladeHeight: { value: GRASS_CONFIG.maxBladeHeight },
|
uMaxBladeHeight: { value: GRASS_CONFIG.maxBladeHeight },
|
||||||
uRandomHeightAmount: { value: GRASS_CONFIG.randomHeightAmount },
|
uRandomHeightAmount: { value: GRASS_CONFIG.randomHeightAmount },
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
export const GRASS_CONFIG = {
|
export const GRASS_CONFIG = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
patchSize: 30,
|
patchSize: 30,
|
||||||
bladeCount: 18000,
|
bladeCount: 32000,
|
||||||
bladeWidth: 0.08,
|
bladeWidth: 0.08,
|
||||||
maxBladeHeight: 0.36,
|
maxBladeHeight: 0.36,
|
||||||
randomHeightAmount: 0.25,
|
randomHeightAmount: 0.25,
|
||||||
surfaceOffset: 0.025,
|
surfaceOffset: 0.025,
|
||||||
heightTextureSize: 128,
|
heightTextureSize: 128,
|
||||||
windNoiseScale: 0.9,
|
windNoiseScale: 0.9,
|
||||||
baldPatchModifier: 2.5,
|
windStrength: 0.35,
|
||||||
|
baldPatchModifier: 1.1,
|
||||||
falloffSharpness: 0.35,
|
falloffSharpness: 0.35,
|
||||||
heightNoiseFrequency: 12,
|
heightNoiseFrequency: 9,
|
||||||
heightNoiseAmplitude: 3,
|
heightNoiseAmplitude: 1,
|
||||||
maxBendAngle: 22,
|
clumpFrequency: 2.6,
|
||||||
|
clumpThreshold: 0.18,
|
||||||
|
clumpSoftness: 0.45,
|
||||||
|
maxBendAngle: 14,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const GRASS_COLORS = ["#84C66B", "#67B058", "#A3CA5B"] as const;
|
export const GRASS_COLORS = ["#84C66B", "#67B058", "#A3CA5B"] as const;
|
||||||
|
|||||||
@@ -17,10 +17,14 @@ export const grassVertexShader = /* glsl */ `
|
|||||||
uniform float uWindDirection;
|
uniform float uWindDirection;
|
||||||
uniform float uWindSpeed;
|
uniform float uWindSpeed;
|
||||||
uniform float uWindNoiseScale;
|
uniform float uWindNoiseScale;
|
||||||
|
uniform float uWindStrength;
|
||||||
uniform float uBaldPatchModifier;
|
uniform float uBaldPatchModifier;
|
||||||
uniform float uFalloffSharpness;
|
uniform float uFalloffSharpness;
|
||||||
uniform float uHeightNoiseFrequency;
|
uniform float uHeightNoiseFrequency;
|
||||||
uniform float uHeightNoiseAmplitude;
|
uniform float uHeightNoiseAmplitude;
|
||||||
|
uniform float uClumpFrequency;
|
||||||
|
uniform float uClumpThreshold;
|
||||||
|
uniform float uClumpSoftness;
|
||||||
uniform float uMaxBendAngle;
|
uniform float uMaxBendAngle;
|
||||||
uniform float uMaxBladeHeight;
|
uniform float uMaxBladeHeight;
|
||||||
uniform float uRandomHeightAmount;
|
uniform float uRandomHeightAmount;
|
||||||
@@ -69,8 +73,13 @@ export const grassVertexShader = /* glsl */ `
|
|||||||
transformed.y = terrainHeight + uSurfaceOffset;
|
transformed.y = terrainHeight + uSurfaceOffset;
|
||||||
|
|
||||||
vec3 heightNoise = texture2D(uNoiseTexture, terrainUv.yx * vec2(uHeightNoiseFrequency)).rgb;
|
vec3 heightNoise = texture2D(uNoiseTexture, terrainUv.yx * vec2(uHeightNoiseFrequency)).rgb;
|
||||||
float heightModifier = ((heightNoise.r + heightNoise.g + heightNoise.b) * uMaxBladeHeight) * uHeightNoiseAmplitude;
|
float heightNoiseAverage = (heightNoise.r + heightNoise.g + heightNoise.b) / 3.0;
|
||||||
|
vec2 clumpUv = (worldPos.xz / uPatchSize) * uClumpFrequency;
|
||||||
|
float clumpNoise = texture2D(uNoiseTexture, clumpUv).r;
|
||||||
|
float clumpMask = smoothstep(uClumpThreshold, uClumpThreshold + uClumpSoftness, clumpNoise);
|
||||||
|
float heightModifier = uMaxBladeHeight * mix(0.35, 1.0, heightNoiseAverage) * uHeightNoiseAmplitude;
|
||||||
heightModifier += random(terrainUv) * (uRandomHeightAmount * 0.1);
|
heightModifier += random(terrainUv) * (uRandomHeightAmount * 0.1);
|
||||||
|
heightModifier = clamp(heightModifier, 0.0, uMaxBladeHeight);
|
||||||
|
|
||||||
float edgeDistanceX = abs(origin.x) / halfPatchSize;
|
float edgeDistanceX = abs(origin.x) / halfPatchSize;
|
||||||
float edgeDistanceZ = abs(origin.z) / halfPatchSize;
|
float edgeDistanceZ = abs(origin.z) / halfPatchSize;
|
||||||
@@ -79,17 +88,18 @@ export const grassVertexShader = /* glsl */ `
|
|||||||
|
|
||||||
float baldPatchOffset = heightNoise.r * (uBaldPatchModifier * (1.0 - edgeFactor));
|
float baldPatchOffset = heightNoise.r * (uBaldPatchModifier * (1.0 - edgeFactor));
|
||||||
heightModifier -= baldPatchOffset;
|
heightModifier -= baldPatchOffset;
|
||||||
|
heightModifier = max(heightModifier, 0.0);
|
||||||
|
|
||||||
float edgeFade =
|
float edgeFade =
|
||||||
smoothstep(uBoundingBoxMin.x, uBoundingBoxMin.x + 2.0, worldPos.x) *
|
smoothstep(uBoundingBoxMin.x, uBoundingBoxMin.x + 2.0, worldPos.x) *
|
||||||
smoothstep(uBoundingBoxMax.x, uBoundingBoxMax.x - 2.0, worldPos.x) *
|
smoothstep(uBoundingBoxMax.x, uBoundingBoxMax.x - 2.0, worldPos.x) *
|
||||||
smoothstep(uBoundingBoxMin.z, uBoundingBoxMin.z + 2.0, worldPos.z) *
|
smoothstep(uBoundingBoxMin.z, uBoundingBoxMin.z + 2.0, worldPos.z) *
|
||||||
smoothstep(uBoundingBoxMax.z, uBoundingBoxMax.z - 2.0, worldPos.z);
|
smoothstep(uBoundingBoxMax.z, uBoundingBoxMax.z - 2.0, worldPos.z);
|
||||||
heightModifier *= edgeFade;
|
heightModifier *= edgeFade * mix(0.45, 1.0, clumpMask);
|
||||||
|
|
||||||
float sideFactor = (color.r == 0.1) ? 1.0 : (color.b == 0.1) ? -1.0 : 0.0;
|
float sideFactor = (color.r == 0.1) ? 1.0 : (color.b == 0.1) ? -1.0 : 0.0;
|
||||||
float tipFactor = color.g;
|
float tipFactor = color.g;
|
||||||
float width = smoothstep(0.5, 1.0, heightModifier * 2.0) * uBladeWidth;
|
float width = smoothstep(0.02, uMaxBladeHeight * 0.85, heightModifier) * uBladeWidth;
|
||||||
transformed += aYaw * (width / 2.0) * sideFactor;
|
transformed += aYaw * (width / 2.0) * sideFactor;
|
||||||
|
|
||||||
vec3 textureColor = texture2D(uDiffuseMap, terrainUv * 10.0).rgb;
|
vec3 textureColor = texture2D(uDiffuseMap, terrainUv * 10.0).rgb;
|
||||||
@@ -110,7 +120,7 @@ export const grassVertexShader = /* glsl */ `
|
|||||||
vec3 windNoise = texture2D(uNoiseTexture, rotatedNoiseUV).rgb;
|
vec3 windNoise = texture2D(uNoiseTexture, rotatedNoiseUV).rgb;
|
||||||
|
|
||||||
vec3 axis = vec3(windNoise.g, 0.0, windNoise.b);
|
vec3 axis = vec3(windNoise.g, 0.0, windNoise.b);
|
||||||
float angle = radians(mapValue(windNoise.g + windNoise.b, 0.0, 2.0, -uMaxBendAngle, uMaxBendAngle)) * tipFactor;
|
float angle = radians(mapValue(windNoise.g + windNoise.b, 0.0, 2.0, -uMaxBendAngle, uMaxBendAngle)) * tipFactor * uWindStrength;
|
||||||
mat3 rotationMatrix = rotate3d(axis, angle);
|
mat3 rotationMatrix = rotate3d(axis, angle);
|
||||||
|
|
||||||
vec3 basePosition = vec3(transformed.x, transformed.y - heightModifier, transformed.z);
|
vec3 basePosition = vec3(transformed.x, transformed.y - heightModifier, transformed.z);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
PLAYER_XZ_DAMPING_FACTOR,
|
PLAYER_XZ_DAMPING_FACTOR,
|
||||||
} from "@/data/player/playerConfig";
|
} from "@/data/player/playerConfig";
|
||||||
import { useRepairMovementLocked } from "@/hooks/gameplay/useRepairMovementLocked";
|
import { useRepairMovementLocked } from "@/hooks/gameplay/useRepairMovementLocked";
|
||||||
|
import { useTerrainHeightSampler } from "@/hooks/three/useTerrainHeight";
|
||||||
import { InteractionManager } from "@/managers/InteractionManager";
|
import { InteractionManager } from "@/managers/InteractionManager";
|
||||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||||
import { useSettingsStore } from "@/managers/stores/useSettingsStore";
|
import { useSettingsStore } from "@/managers/stores/useSettingsStore";
|
||||||
@@ -48,7 +49,8 @@ const DEFAULT_KEYS: Keys = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PLAYER_COLLISION_ITERATIONS = 3;
|
const PLAYER_COLLISION_ITERATIONS = 3;
|
||||||
const PLAYER_FLOOR_NORMAL_MIN = 0.5;
|
const PLAYER_FLOOR_NORMAL_MIN = 0.15;
|
||||||
|
const PLAYER_GROUND_SNAP_DISTANCE = 0.22;
|
||||||
|
|
||||||
interface PlayerControllerProps {
|
interface PlayerControllerProps {
|
||||||
octree: Octree | null;
|
octree: Octree | null;
|
||||||
@@ -116,12 +118,17 @@ function setMovementKey(keys: Keys, key: string, pressed: boolean): boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCapsuleFootY(capsule: Capsule): number {
|
||||||
|
return capsule.start.y - capsule.radius;
|
||||||
|
}
|
||||||
|
|
||||||
export function PlayerController({
|
export function PlayerController({
|
||||||
octree,
|
octree,
|
||||||
spawnPosition,
|
spawnPosition,
|
||||||
}: PlayerControllerProps): null {
|
}: PlayerControllerProps): null {
|
||||||
const camera = useThree((state) => state.camera);
|
const camera = useThree((state) => state.camera);
|
||||||
const movementLocked = useRepairMovementLocked();
|
const movementLocked = useRepairMovementLocked();
|
||||||
|
const terrainHeight = useTerrainHeightSampler();
|
||||||
const movementLockedRef = useRef(movementLocked);
|
const movementLockedRef = useRef(movementLocked);
|
||||||
const keys = useRef<Keys>({ ...DEFAULT_KEYS });
|
const keys = useRef<Keys>({ ...DEFAULT_KEYS });
|
||||||
const velocity = useRef(new THREE.Vector3());
|
const velocity = useRef(new THREE.Vector3());
|
||||||
@@ -319,6 +326,10 @@ export function PlayerController({
|
|||||||
|
|
||||||
if (isFloorCollision) {
|
if (isFloorCollision) {
|
||||||
velocity.current.y = Math.max(0, velocity.current.y);
|
velocity.current.y = Math.max(0, velocity.current.y);
|
||||||
|
capsule.current.translate(
|
||||||
|
_collisionCorrection.set(0, result.depth / result.normal.y, 0),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
capsule.current.translate(
|
capsule.current.translate(
|
||||||
@@ -327,6 +338,22 @@ export function PlayerController({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groundHeight = terrainHeight.getHeight(
|
||||||
|
capsule.current.end.x,
|
||||||
|
capsule.current.end.z,
|
||||||
|
);
|
||||||
|
if (groundHeight !== null && velocity.current.y <= 0) {
|
||||||
|
const groundOffset = getCapsuleFootY(capsule.current) - groundHeight;
|
||||||
|
|
||||||
|
if (groundOffset <= PLAYER_GROUND_SNAP_DISTANCE) {
|
||||||
|
capsule.current.translate(
|
||||||
|
_collisionCorrection.set(0, -groundOffset, 0),
|
||||||
|
);
|
||||||
|
velocity.current.y = 0;
|
||||||
|
onFloor.current = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
camera.position.copy(capsule.current.end);
|
camera.position.copy(capsule.current.end);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user