style: format with prettier
This commit is contained in:
+153
-52
@@ -4,7 +4,17 @@ import { OrbitControls, PerspectiveCamera } from '@react-three/drei';
|
||||
import { Canvas, useLoader, useFrame } from '@react-three/fiber';
|
||||
import { forwardRef, useLayoutEffect, useMemo, useRef } from 'react';
|
||||
|
||||
import { BufferAttribute, BufferGeometry, Mesh, Object3D, InstancedMesh, DoubleSide, TextureLoader, Color, MeshStandardMaterial } from 'three';
|
||||
import {
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
Mesh,
|
||||
Object3D,
|
||||
InstancedMesh,
|
||||
DoubleSide,
|
||||
TextureLoader,
|
||||
Color,
|
||||
MeshStandardMaterial
|
||||
} from 'three';
|
||||
|
||||
import './page.css';
|
||||
import { createNoise2D } from 'simplex-noise';
|
||||
@@ -12,7 +22,12 @@ import { useControls } from 'leva';
|
||||
|
||||
import grassVert from './shaders/grass.vert';
|
||||
import grassFrag from './shaders/grass.frag';
|
||||
import { Bloom, EffectComposer, Noise, Pixelation } from '@react-three/postprocessing';
|
||||
import {
|
||||
Bloom,
|
||||
EffectComposer,
|
||||
Noise,
|
||||
Pixelation
|
||||
} from '@react-three/postprocessing';
|
||||
|
||||
interface Shader {
|
||||
uniforms: { [key: string]: { value: unknown } };
|
||||
@@ -35,8 +50,10 @@ function getTerrainHeight(
|
||||
const worldX = (worldXBase + localX) * 0.1;
|
||||
const worldZ = (worldZBase + localZ) * 0.1;
|
||||
|
||||
const noiseHill = noise2D(worldX * hillScale, worldZ * hillScale) * hillHeight;
|
||||
const noiseDetail = noise2D(worldX * detailScale, worldZ * detailScale) * detailHeight;
|
||||
const noiseHill =
|
||||
noise2D(worldX * hillScale, worldZ * hillScale) * hillHeight;
|
||||
const noiseDetail =
|
||||
noise2D(worldX * detailScale, worldZ * detailScale) * detailHeight;
|
||||
|
||||
return (noiseHill + noiseDetail) * scale;
|
||||
}
|
||||
@@ -56,17 +73,39 @@ interface GrassProps {
|
||||
enableShadows?: boolean;
|
||||
}
|
||||
|
||||
function Grass({ x, y, size, count, grassSize, scale, hillScale, hillHeight, detailScale, detailHeight, noise2D }: GrassProps) {
|
||||
function Grass({
|
||||
x,
|
||||
y,
|
||||
size,
|
||||
count,
|
||||
grassSize,
|
||||
scale,
|
||||
hillScale,
|
||||
hillHeight,
|
||||
detailScale,
|
||||
detailHeight,
|
||||
noise2D
|
||||
}: GrassProps) {
|
||||
const meshRef = useRef<InstancedMesh>(null);
|
||||
const dummyRef = useRef<Object3D>(new Object3D());
|
||||
|
||||
const [alphaMap, normalMap] = useLoader(TextureLoader, ['/img/grass_alpha.png', '/img/grass_normal.png']);
|
||||
const [alphaMap, normalMap] = useLoader(TextureLoader, [
|
||||
'/img/grass_alpha.png',
|
||||
'/img/grass_normal.png'
|
||||
]);
|
||||
|
||||
const materialRef = useRef<MeshStandardMaterial & { userData: { shader: Shader } }>(null);
|
||||
const materialRef = useRef<
|
||||
MeshStandardMaterial & { userData: { shader: Shader } }
|
||||
>(null);
|
||||
|
||||
useFrame((state) => {
|
||||
if (materialRef.current && materialRef.current.userData && materialRef.current.userData.shader) {
|
||||
(materialRef.current.userData.shader as Shader).uniforms.uTime.value = state.clock.getElapsedTime();
|
||||
if (
|
||||
materialRef.current &&
|
||||
materialRef.current.userData &&
|
||||
materialRef.current.userData.shader
|
||||
) {
|
||||
(materialRef.current.userData.shader as Shader).uniforms.uTime.value =
|
||||
state.clock.getElapsedTime();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -77,21 +116,40 @@ function Grass({ x, y, size, count, grassSize, scale, hillScale, hillHeight, det
|
||||
const h = 2;
|
||||
|
||||
const positions = [
|
||||
-w, 0, 0, w, 0, 0, -w, h, 0, w, h, 0,
|
||||
0, 0, w, 0, 0, -w, 0, h, w, 0, h, -w
|
||||
-w,
|
||||
0,
|
||||
0,
|
||||
w,
|
||||
0,
|
||||
0,
|
||||
-w,
|
||||
h,
|
||||
0,
|
||||
w,
|
||||
h,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
w,
|
||||
0,
|
||||
0,
|
||||
-w,
|
||||
0,
|
||||
h,
|
||||
w,
|
||||
0,
|
||||
h,
|
||||
-w
|
||||
];
|
||||
|
||||
const uvs = [
|
||||
0, 0, 1, 0, 0, 1, 1, 1,
|
||||
0, 0, 1, 0, 0, 1, 1, 1
|
||||
];
|
||||
const uvs = [0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1];
|
||||
|
||||
const indices = [
|
||||
0, 1, 2, 2, 1, 3,
|
||||
4, 5, 6, 6, 5, 7
|
||||
];
|
||||
const indices = [0, 1, 2, 2, 1, 3, 4, 5, 6, 6, 5, 7];
|
||||
|
||||
geo.setAttribute('position', new BufferAttribute(new Float32Array(positions), 3));
|
||||
geo.setAttribute(
|
||||
'position',
|
||||
new BufferAttribute(new Float32Array(positions), 3)
|
||||
);
|
||||
geo.setAttribute('uv', new BufferAttribute(new Float32Array(uvs), 2));
|
||||
geo.setIndex(indices);
|
||||
geo.computeVertexNormals();
|
||||
@@ -133,7 +191,7 @@ function Grass({ x, y, size, count, grassSize, scale, hillScale, hillHeight, det
|
||||
dummy.rotation.x = (Math.random() - 0.5) * 0.2;
|
||||
dummy.rotation.z = (Math.random() - 0.5) * 0.2;
|
||||
|
||||
const baseScale = grassSize + (Math.random() * grassSize * 0.5);
|
||||
const baseScale = grassSize + Math.random() * grassSize * 0.5;
|
||||
const heightMult = 0.5 + Math.random() * 1.0;
|
||||
dummy.scale.set(baseScale, baseScale * heightMult, baseScale);
|
||||
|
||||
@@ -154,10 +212,24 @@ function Grass({ x, y, size, count, grassSize, scale, hillScale, hillHeight, det
|
||||
meshRef.current.setColorAt(i, color);
|
||||
}
|
||||
meshRef.current.instanceMatrix.needsUpdate = true;
|
||||
if (meshRef.current.instanceColor) meshRef.current.instanceColor.needsUpdate = true;
|
||||
}, [x, y, size, count, grassSize, scale, hillScale, hillHeight, detailScale, detailHeight, noise2D]);
|
||||
if (meshRef.current.instanceColor)
|
||||
meshRef.current.instanceColor.needsUpdate = true;
|
||||
}, [
|
||||
x,
|
||||
y,
|
||||
size,
|
||||
count,
|
||||
grassSize,
|
||||
scale,
|
||||
hillScale,
|
||||
hillHeight,
|
||||
detailScale,
|
||||
detailHeight,
|
||||
noise2D
|
||||
]);
|
||||
|
||||
const onBeforeCompile = useMemo(() => (shader: Shader) => {
|
||||
const onBeforeCompile = useMemo(
|
||||
() => (shader: Shader) => {
|
||||
shader.uniforms.uTime = { value: 0 };
|
||||
|
||||
shader.vertexShader = `
|
||||
@@ -222,7 +294,9 @@ function Grass({ x, y, size, count, grassSize, scale, hillScale, hillHeight, det
|
||||
if (materialRef.current) {
|
||||
materialRef.current.userData.shader = shader;
|
||||
}
|
||||
}, []);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<instancedMesh
|
||||
@@ -233,7 +307,7 @@ function Grass({ x, y, size, count, grassSize, scale, hillScale, hillHeight, det
|
||||
>
|
||||
<meshStandardMaterial
|
||||
ref={materialRef}
|
||||
color="#ffffff"
|
||||
color='#ffffff'
|
||||
side={DoubleSide}
|
||||
alphaMap={alphaMap}
|
||||
alphaTest={0.5}
|
||||
@@ -247,22 +321,37 @@ function Grass({ x, y, size, count, grassSize, scale, hillScale, hillHeight, det
|
||||
}
|
||||
|
||||
interface TerrainChunkProps {
|
||||
x: number,
|
||||
y: number,
|
||||
size: number,
|
||||
resolution: number,
|
||||
scale: number,
|
||||
hillScale: number,
|
||||
hillHeight: number,
|
||||
detailScale: number,
|
||||
detailHeight: number,
|
||||
x: number;
|
||||
y: number;
|
||||
size: number;
|
||||
resolution: number;
|
||||
scale: number;
|
||||
hillScale: number;
|
||||
hillHeight: number;
|
||||
detailScale: number;
|
||||
detailHeight: number;
|
||||
noise2D: (x: number, y: number) => number;
|
||||
wireframe?: boolean;
|
||||
grassCount: number;
|
||||
grassSize: number;
|
||||
grassLOD: number;
|
||||
}
|
||||
function TerrainChunk({ x, y, size, resolution, scale, hillScale, hillHeight, detailScale, detailHeight, noise2D, wireframe = false, grassCount, grassSize, grassLOD }: TerrainChunkProps) {
|
||||
function TerrainChunk({
|
||||
x,
|
||||
y,
|
||||
size,
|
||||
resolution,
|
||||
scale,
|
||||
hillScale,
|
||||
hillHeight,
|
||||
detailScale,
|
||||
detailHeight,
|
||||
noise2D,
|
||||
wireframe = false,
|
||||
grassCount,
|
||||
grassSize,
|
||||
grassLOD
|
||||
}: TerrainChunkProps) {
|
||||
const distance = Math.sqrt((x * size) ** 2 + (y * size) ** 2);
|
||||
|
||||
let adjustedGrassCount = grassCount;
|
||||
@@ -271,7 +360,7 @@ function TerrainChunk({ x, y, size, resolution, scale, hillScale, hillHeight, de
|
||||
} else if (distance > grassLOD * 0.6) {
|
||||
const fadeStart = grassLOD * 0.6;
|
||||
const fadeRange = grassLOD * 0.4;
|
||||
const fadeFactor = 1.0 - ((distance - fadeStart) / fadeRange);
|
||||
const fadeFactor = 1.0 - (distance - fadeStart) / fadeRange;
|
||||
adjustedGrassCount = Math.floor(grassCount * fadeFactor * fadeFactor);
|
||||
}
|
||||
const meshRef = useRef<Mesh>(null);
|
||||
@@ -326,7 +415,10 @@ function TerrainChunk({ x, y, size, resolution, scale, hillScale, hillHeight, de
|
||||
indices.push(topRight, bottomLeft, bottomRight);
|
||||
}
|
||||
}
|
||||
geo.setAttribute('position', new BufferAttribute(new Float32Array(vertices), 3));
|
||||
geo.setAttribute(
|
||||
'position',
|
||||
new BufferAttribute(new Float32Array(vertices), 3)
|
||||
);
|
||||
|
||||
const colors: Array<number> = [];
|
||||
for (let iz = 0; iz < resolution; iz++) {
|
||||
@@ -354,7 +446,18 @@ function TerrainChunk({ x, y, size, resolution, scale, hillScale, hillHeight, de
|
||||
geo.computeVertexNormals();
|
||||
|
||||
return geo;
|
||||
}, [x, y, size, resolution, scale, hillScale, hillHeight, detailScale, detailHeight, noise2D]);
|
||||
}, [
|
||||
x,
|
||||
y,
|
||||
size,
|
||||
resolution,
|
||||
scale,
|
||||
hillScale,
|
||||
hillHeight,
|
||||
detailScale,
|
||||
detailHeight,
|
||||
noise2D
|
||||
]);
|
||||
|
||||
return (
|
||||
<group>
|
||||
@@ -388,7 +491,7 @@ function TerrainChunk({ x, y, size, resolution, scale, hillScale, hillHeight, de
|
||||
/>
|
||||
)}
|
||||
</group>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
interface TerrainProps {
|
||||
@@ -396,10 +499,10 @@ interface TerrainProps {
|
||||
chunkSize?: number;
|
||||
resolution?: number;
|
||||
scale?: number;
|
||||
hillScale: number,
|
||||
hillHeight: number,
|
||||
detailScale: number,
|
||||
detailHeight: number,
|
||||
hillScale: number;
|
||||
hillHeight: number;
|
||||
detailScale: number;
|
||||
detailHeight: number;
|
||||
wireframe?: boolean;
|
||||
grassCount?: number;
|
||||
grassSize?: number;
|
||||
@@ -425,8 +528,7 @@ function Terrain({
|
||||
const chunkPositions = useMemo(() => {
|
||||
const positions: [number, number][] = [];
|
||||
for (let x = 0; x < chunks; x++)
|
||||
for (let y = 0; y < chunks; y++)
|
||||
positions.push([x + offset, y + offset]);
|
||||
for (let y = 0; y < chunks; y++) positions.push([x + offset, y + offset]);
|
||||
|
||||
return positions;
|
||||
}, [chunks, offset]);
|
||||
@@ -502,17 +604,16 @@ export default function Home() {
|
||||
>
|
||||
<EffectComposer>
|
||||
<Noise opacity={0.1} />
|
||||
<Bloom intensity={2}
|
||||
<Bloom
|
||||
intensity={2}
|
||||
luminanceThreshold={0.5}
|
||||
luminanceSmoothing={0.1} />
|
||||
luminanceSmoothing={0.1}
|
||||
/>
|
||||
<Pixelation />
|
||||
</EffectComposer>
|
||||
|
||||
<ambientLight intensity={0.5} />
|
||||
<directionalLight
|
||||
position={[10, 20, 5]}
|
||||
intensity={1}
|
||||
/>
|
||||
<directionalLight position={[10, 20, 5]} intensity={1} />
|
||||
<Terrain
|
||||
chunks={8.0}
|
||||
chunkSize={10.0}
|
||||
|
||||
Reference in New Issue
Block a user