feat: overhaul grass

This commit is contained in:
2026-06-01 17:31:13 -03:00
parent cad47f07bd
commit d506071ce2
4 changed files with 53 additions and 35 deletions
+3 -3
View File
@@ -68,10 +68,10 @@ function Scene() {
grassDryColor: '#495a17',
grassLushColor: '#255825',
grassCount: { value: 8000, min: 1000, max: 30000, step: 500 },
grassSize: { value: 0.65, min: 0.1, max: 2.0, step: 0.05 },
grassSize: { value: 0.85, min: 0.1, max: 2.0, step: 0.05 },
grassLOD: { value: 60, min: 10, max: 200, step: 5 },
grassBlades: { value: 3, min: 1, max: 4, step: 1 },
grassSegments: { value: 3, min: 1, max: 5, step: 1 },
grassBlades: { value: 3, min: 1, max: 5, step: 1 },
grassSegments: { value: 4, min: 1, max: 5, step: 1 },
grassLODStart: { value: 0.15, min: 0.0, max: 0.9, step: 0.05 },
grassLODExponent: { value: 1.8, min: 0.5, max: 3.0, step: 0.1 },
})
+10 -16
View File
@@ -170,30 +170,24 @@ export default function Grass({
dummy.position.set(localX, localY, localZ);
dummy.rotation.y = Math.random() * Math.PI * 2;
dummy.rotation.x = (Math.random() - 0.5) * 0.15;
dummy.rotation.z = (Math.random() - 0.5) * 0.15;
const baseScale = grassSize + Math.random() * grassSize * 0.5;
const heightMult = 0.5 + Math.random() * 1.0;
dummy.scale.set(baseScale, baseScale * heightMult, baseScale);
dummy.updateMatrix();
meshRef.current.setMatrixAt(instanceIndex, dummy.matrix);
dummy.rotation.x = (Math.random() - 0.5) * 0.2;
dummy.rotation.z = (Math.random() - 0.5) * 0.2;
const noiseVal = noise2D(globalX * 0.02, globalZ * 0.02);
const t = (noiseVal + 1) / 2;
const randomInternal = (Math.random() - 0.5) * 0.2;
const finalT = Math.max(0, Math.min(1, t + randomInternal));
color.lerpColors(dryColorObj, lushColorObj, finalT);
meshRef.current.setColorAt(instanceIndex, color);
const macroHeightBonus = (noiseVal + 1.0) * 0.2;
const grassWidth = grassSize * (0.8 + Math.random() * 0.4);
const grassHeight = grassSize * (0.6 + Math.random() * 1.2 + macroHeightBonus);
const heightNoise = noise2D(globalX * 0.08, globalZ * 0.08);
const macroHeight = (heightNoise + 1.0) * 0.5; // 0..1
const microNoise = noise2D(globalX * 0.3, globalZ * 0.3);
const microHeight = (microNoise + 1.0) * 0.25; // 0..0.5
const perBladeRandom = Math.random() * 0.4;
const grassWidth = grassSize * (0.7 + Math.random() * 0.5);
const grassHeight = grassSize * (0.4 + macroHeight * 0.8 + microHeight + perBladeRandom);
dummy.scale.set(grassWidth, grassHeight, grassWidth);
@@ -1,17 +1,31 @@
float ao = smoothstep(0.0, 0.8, vGrassUv.y);
ao = mix(0.15, 1.0, ao);
float ao = smoothstep(0.0, 0.7, vGrassUv.y);
ao = mix(0.05, 1.0, pow(ao, 1.6));
vec3 rootColor = diffuseColor.rgb * 0.35;
vec3 tipColor = diffuseColor.rgb * 1.3;
vec3 grassColor = mix(rootColor, tipColor, vGrassUv.y);
vec3 rootColor = diffuseColor.rgb * 0.15;
vec3 midColor = diffuseColor.rgb;
vec3 tipColor = diffuseColor.rgb * 1.3 + vec3(0.06, 0.08, 0.0);
float heightParam = vGrassUv.y;
vec3 grassColor;
if (heightParam < 0.4) {
float t = smoothstep(0.0, 0.4, heightParam);
grassColor = mix(rootColor, midColor, t);
} else {
float t = smoothstep(0.4, 1.0, heightParam);
grassColor = mix(midColor, tipColor, t);
}
vec3 viewDir = normalize(cameraPosition - vWorldPos);
vec3 lightDir = normalize(vec3(15.0, 25.0, 15.0));
float backLighting = max(0.0, dot(viewDir, -lightDir));
backLighting = pow(backLighting, 3.0) * smoothstep(0.2, 1.0, vGrassUv.y);
float VdotL = max(0.0, dot(viewDir, -lightDir));
float sss = pow(VdotL, 3.0) * smoothstep(0.2, 0.9, vGrassUv.y);
vec3 sssColor = vec3(0.4, 0.8, 0.3) * diffuseColor.rgb;
grassColor += sssColor * backLighting * 1.5;
vec3 sssColor = diffuseColor.rgb * vec3(0.6, 1.0, 0.15) * 1.8;
grassColor += sssColor * sss * 2.0;
float NdotV = 1.0 - max(0.0, dot(normalize(vNormal), viewDir));
float rim = pow(NdotV, 3.0) * smoothstep(0.3, 1.0, vGrassUv.y) * 0.15;
grassColor += vec3(0.3, 0.5, 0.1) * rim;
diffuseColor.rgb = grassColor * ao;
@@ -21,22 +21,32 @@ float spring = sin(uTime * 2.0 + phase) * 0.06 + sin(uTime * 4.5 + phase * 1.5)
float angleNoise = fbm(windSamplePos * 2.0 + uTime * 0.1) - 0.5;
vec2 windDir = normalize(mainWindDir + vec2(-mainWindDir.y, mainWindDir.x) * angleNoise * 0.4);
float taperFactor = pow(uv.y, 4.0);
float taper = 1.0 - taperFactor * 0.6;
// taper (fade)
float taperFactor = uv.y * uv.y * uv.y;
float taper = 1.0 - taperFactor * 0.85;
transformed.x *= taper;
transformed.z *= taper;
// curve
float curveVal = fbm(vec2(gx, gz) * 0.5);
float curveStrength = 2.0 + curveVal * 2.0;
float curveStrength = 1.5 + curveVal * 2.5;
float curveAmount = uv.y * uv.y * curveStrength;
vec2 curveDir = normalize(vec2(curveVal, fbm(vec2(gz, gx))) - 0.5);
transformed.x += curveAmount * curveDir.x * 0.5;
transformed.z += curveAmount * curveDir.y * 0.5;
transformed.x += curveAmount * curveDir.x * 0.4;
transformed.z += curveAmount * curveDir.y * 0.4;
// sway
float swayAmount = (totalWind + spring) * uv.y * uv.y;
transformed.x += swayAmount * windDir.x;
transformed.z += swayAmount * windDir.y;
transformed.y -= abs(swayAmount) * 0.2;
objectNormal = vec3(0.0, 1.0, 0.0);
// normal comp
vec2 totalBend = curveDir * curveAmount * 0.4 + windDir * swayAmount;
float bendMag = length(totalBend);
vec3 bentNormal = normalize(vec3(-totalBend.x * 0.5, 1.0, -totalBend.y * 0.5));
// normal mix
objectNormal = normalize(mix(vec3(0.0, 1.0, 0.0), bentNormal, uv.y));
vWorldPos = (modelMatrix * instanceMatrix * vec4(transformed, 1.0)).xyz;