diff --git a/src/app/niko/page.tsx b/src/app/niko/page.tsx index 6436324..2585089 100644 --- a/src/app/niko/page.tsx +++ b/src/app/niko/page.tsx @@ -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 }, }) diff --git a/src/app/niko/scene-components/grass.tsx b/src/app/niko/scene-components/grass.tsx index 2170aea..a6d7219 100644 --- a/src/app/niko/scene-components/grass.tsx +++ b/src/app/niko/scene-components/grass.tsx @@ -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); diff --git a/src/app/niko/scene-components/shaders/grass.frag b/src/app/niko/scene-components/shaders/grass.frag index 7cf7c99..1bd5f73 100644 --- a/src/app/niko/scene-components/shaders/grass.frag +++ b/src/app/niko/scene-components/shaders/grass.frag @@ -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)); +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; \ No newline at end of file diff --git a/src/app/niko/scene-components/shaders/grass.vert b/src/app/niko/scene-components/shaders/grass.vert index f032e13..5115bc2 100644 --- a/src/app/niko/scene-components/shaders/grass.vert +++ b/src/app/niko/scene-components/shaders/grass.vert @@ -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; \ No newline at end of file