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