Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/components/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import NodeGraph from './node-graph';
import NodeGraphCanvas from './node-graph-canvas';
import TerrainCanvas from './terrain-canvas';
import TerrainSliders from './terrain-sliders';
import type { GlobalParams } from './terrain-sliders';
import Toolbar from './toolbar';
import type { ToolbarProps } from './toolbar';

Expand Down Expand Up @@ -36,9 +37,13 @@ export default function Editor() {
});

// states for size and resolution...
const [globalParams, setGlobalParams] = useState({
const [globalParams, setGlobalParams] = useState<GlobalParams>({
size: 20,
resolution: 100,
fog: {
intensity: 0.0,
color: [0.686, 0.702, 0.725],
},
});

// hook owning node + edge state, and react flow
Expand Down
16 changes: 11 additions & 5 deletions src/components/editor/terrain-canvas.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { useEffect } from 'react';

import type { GlobalParams } from './terrain-sliders';

import WebGPUCanvas, { type WebGPUCanvasProps } from '@/components/common/webgpu-canvas';
import {
TerrainRenderer,
type TerrainRendererGlobalParameters,
} from '@/lib/renderers/terrain-renderer';
import { TerrainRenderer } from '@/lib/renderers/terrain-renderer';

export type TerrainCanvasProps = {
rendererRef: React.RefObject<TerrainRenderer | undefined>;
globalParams: TerrainRendererGlobalParameters;
globalParams: GlobalParams;
};

const createRenderer: WebGPUCanvasProps['createRenderer'] = async (webGPU, stage) => {
Expand All @@ -23,6 +22,13 @@ export default function TerrainCanvas({ rendererRef, globalParams }: TerrainCanv
rendererRef.current?.setMeshUniforms(globalParams.size, globalParams.resolution);
}, [globalParams.resolution, globalParams.size, rendererRef]);

useEffect(() => {
rendererRef.current?.setCameraUniforms({
fogColor: globalParams.fog.color,
fogIntensity: globalParams.fog.intensity,
});
}, [globalParams.fog.color, globalParams.fog.intensity, rendererRef]);

return (
<WebGPUCanvas
createRenderer={createRenderer}
Expand Down
69 changes: 59 additions & 10 deletions src/components/editor/terrain-sliders.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
export type GlobalParams = {
size: number;
resolution: number;
fog: { intensity: number; color: [number, number, number] };
};

export type TerrainSliderProps = {
globalParams: {
size: number;
resolution: number;
};
setGlobalParams: React.Dispatch<
React.SetStateAction<{
size: number;
resolution: number;
}>
>;
globalParams: GlobalParams;
setGlobalParams: React.Dispatch<React.SetStateAction<GlobalParams>>;
};

export default function TerrainSliders({ globalParams, setGlobalParams }: TerrainSliderProps) {
const fogColorChannelHexes = globalParams.fog.color.map((v) =>
Math.round(v * 255)
.toString(16)
.padStart(2, '0'),
);

return (
<div className="mt-6 space-y-10 text-zinc-300">
<div className="space-y-2">
Expand Down Expand Up @@ -53,6 +57,51 @@ export default function TerrainSliders({ globalParams, setGlobalParams }: Terrai
/>
<div className="text-xs opacity-75">{globalParams.resolution}</div>
</div>

<label className="block space-y-2 text-sm font-medium">
<span className="block text-sm font-medium">Fog Color</span>
<input
type="color"
value={`#${fogColorChannelHexes[0]}${fogColorChannelHexes[1]}${fogColorChannelHexes[2]}`}
onChange={(evt) => {
const { value } = evt.target;
if (value.startsWith('#')) {
console.log(value);
const color: [number, number, number] = [
parseInt(value.slice(1, 3), 16) / 255,
parseInt(value.slice(3, 5), 16) / 255,
parseInt(value.slice(5, 7), 16) / 255,
];
setGlobalParams((prev) => ({
...prev,
fog: { ...prev.fog, color },
}));
} else {
console.warn('uh oh we dont have logic for this onChange result');
}
}}
/>
</label>

<div className="space-y-2">
<label className="text-sm font-medium">Fog Intensity</label>
<input
type="range"
min={0.0}
max={1.0}
step={0.01}
value={globalParams.fog.intensity}
onChange={(e) => {
const v = parseFloat(e.target.value);
setGlobalParams((prev) => ({
...prev,
fog: { ...prev.fog, intensity: v },
}));
}}
className="w-full"
/>
<div className="text-xs opacity-75">{globalParams.fog.intensity}</div>
</div>
</div>
);
}
18 changes: 13 additions & 5 deletions src/lib/renderers/terrain-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ import { instanceComputeShaderTemplate } from '@/lib/shaders/jit/templates/insta
import * as shaders from '@/lib/shaders/shaders';
import type { WebGPUContext } from '@/lib/webgpu-context';

export type TerrainRendererGlobalParameters = {
size: number;
resolution: number;
};

export class TerrainRenderer implements IRenderer {
private readonly stage: Stage;

Expand Down Expand Up @@ -1279,6 +1274,19 @@ export class TerrainRenderer implements IRenderer {
this.device.queue.submit([encoder.finish()]);
}

setCameraUniforms({
fogColor,
fogIntensity,
}: {
fogColor?: [number, number, number];
fogIntensity?: number;
}) {
const cameraUniforms = this.stage.camera.uniforms;

if (fogColor) cameraUniforms.fogColor = fogColor;
if (fogIntensity) cameraUniforms.fogIntensity = fogIntensity;
}

setDisplacePipelineUniform(key: string, value: number | [number, number, number]) {
if (!this.displacePipelineConfigured) {
console.log('Cannot set uniform');
Expand Down
12 changes: 11 additions & 1 deletion src/lib/scene/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function toRadians(degrees: number) {
}

class CameraUniforms {
readonly buffer = new ArrayBuffer(304);
readonly buffer = new ArrayBuffer(320);
private readonly floatView = new Float32Array(this.buffer, 0, 16);
private readonly invProjMatView = new Float32Array(this.buffer, 64, 16);
private readonly viewMatView = new Float32Array(this.buffer, 128, 16);
Expand All @@ -19,6 +19,8 @@ class CameraUniforms {
private readonly nearPlaneView = new Float32Array(this.buffer, 280, 1);
private readonly farPlaneView = new Float32Array(this.buffer, 284, 1);
private readonly timeView = new Float32Array(this.buffer, 288, 1);
private readonly fogIntensityView = new Float32Array(this.buffer, 292, 1);
private readonly fogColorView = new Float32Array(this.buffer, 304, 3);

set viewProjMat(mat: Float32Array) {
this.floatView.set(mat.subarray(0, 16), 0);
Expand Down Expand Up @@ -59,6 +61,14 @@ class CameraUniforms {
set time(time: number) {
this.timeView[0] = time;
}

set fogIntensity(intensity: number) {
this.fogIntensityView[0] = intensity;
}

set fogColor(color: [number, number, number]) {
this.fogColorView.set(color);
}
}

export class Camera {
Expand Down
5 changes: 2 additions & 3 deletions src/lib/shaders/common.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ struct CameraUniforms {
nearPlane: f32,
farPlane: f32,
time: f32,
_padding1: f32,
_padding2: f32,
_padding3: f32,
fogIntensity: f32,
fogColor: vec3f,
}

struct DirectionalLightUniforms {
Expand Down
5 changes: 5 additions & 0 deletions src/lib/shaders/instancing.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct VertexOut {
@location(2) uv : vec2f,
@location(3) @interpolate(flat) tex_id: u32,
@location(4) @interpolate(flat) used: u32,
@location(5) camera_view_pos: vec3f,
};

fn hash11(n: f32) -> f32 {
Expand Down Expand Up @@ -98,6 +99,7 @@ fn vs_main(in : VertexIn) -> VertexOut {
out.uv = vert_uv;
out.tex_id = texture_id;
out.used = used;
out.camera_view_pos = (camera.viewMat * vec4f(worldPos, 1.0)).xyz;
return out;
}

Expand All @@ -122,6 +124,9 @@ fn fs_main(in: VertexOut) -> @location(0) vec4f

let ambientLight = vec3f(0.1, 0.1, 0.1);

let fogStrength = 1.0 - exp(-camera.fogIntensity * length(in.camera_view_pos));

var color = diffuse.xyz * (directLight + ambientLight);
color = mix(color.xyz, camera.fogColor, fogStrength);
return vec4f(color, 1.0);
}
41 changes: 22 additions & 19 deletions src/lib/shaders/naive.fs.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ struct FragmentInput
@location(1) nor: vec3f,
@location(2) uv: vec2f,
@location(3) shadow_pos: vec3f,
@location(4) camera_view_pos: vec3f,
}

struct BiomeUniforms {
Expand All @@ -33,16 +34,16 @@ fn hash(p: vec2f) -> f32 {
fn noise(p: vec2f) -> f32 {
let i = floor(p);
let f = fract(p);

// Cubic interpolation
let u = f * f * (3.0 - 2.0 * f);

// Sample corners
let a = hash(i);
let b = hash(i + vec2f(1.0, 0.0));
let c = hash(i + vec2f(0.0, 1.0));
let d = hash(i + vec2f(1.0, 1.0));

// Interpolate
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
Expand All @@ -52,13 +53,13 @@ fn fbm(p: vec2f) -> f32 {
var amplitude = 0.5;
var frequency = 1.0;
var pos = p;

for (var i = 0; i < 4; i++) {
value += amplitude * noise(pos * frequency);
frequency *= 2.0;
amplitude *= 0.5;
}

return value;
}

Expand All @@ -69,9 +70,9 @@ fn celShade(value: f32, bands: f32) -> f32 {
fn grassTexture(pos: vec3f) -> vec3f {
let noise1 = fbm(pos.xz * 8.0);
let noise2 = noise(pos.xz * 2.0);

let combinedNoise = noise1 * 0.6 + noise2 * 0.4;

let grassBright = vec3f(0.45, 0.85, 0.15);
let grassMid = vec3f(0.25, 0.65, 0.10);
let grassDark = vec3f(0.15, 0.45, 0.05);
Expand All @@ -82,48 +83,48 @@ fn grassTexture(pos: vec3f) -> vec3f {
var grassColor = grassDark;
grassColor = mix(grassColor, grassMid, t1);
grassColor = mix(grassColor, grassBright, t2);

return grassColor;
}

fn sandTexture(pos: vec3f) -> vec3f {
let sandScale = 20.0;
let sandNoise = fbm(pos.xz * sandScale);

let sandBase = vec3f(0.95, 0.88, 0.71);
let sandDark = vec3f(0.85, 0.78, 0.61);

return mix(sandDark, sandBase, sandNoise);
}

fn snowTexture(pos: vec3f) -> vec3f {
let sandScale = 20.0;
let sandNoise = fbm(pos.xz * sandScale);

let sandBase = vec3f(0.94, 0.99, 1.0);
let sandDark = vec3f(0.812, 0.953, 0.969);

return mix(sandDark, sandBase, sandNoise);
}

fn mountainTexture(pos: vec3f) -> vec3f {
let noise1 = fbm(pos.xz * 3.0);
let noise2 = noise(pos.xz * 10.0);
let noise3 = noise(pos.xz * 30.0);

let combinedNoise = noise1 * 0.5 + noise2 * 0.3 + noise3 * 0.2;

let rockDark = vec3f(0.35, 0.35, 0.38);
let rockMid = vec3f(0.50, 0.48, 0.45);
let rockLight = vec3f(0.65, 0.62, 0.58);

let t1 = smoothstep(0.25, 0.45, combinedNoise);
let t2 = smoothstep(0.55, 0.75, combinedNoise);

var rockColor = rockDark;
rockColor = mix(rockColor, rockMid, t1);
rockColor = mix(rockColor, rockLight, t2);

return rockColor;
}

Expand All @@ -148,7 +149,7 @@ fn main(in: FragmentInput) -> @location(0) vec4f

let grassStart = waterHeight.height + 0.8;
let sandEnd = waterHeight.height + 0.4;

baseColor = grass;
if (waterHeight.height > in.pos.y) {
baseColor = underwater;
Expand Down Expand Up @@ -212,6 +213,8 @@ fn main(in: FragmentInput) -> @location(0) vec4f

let ambientLight = vec3f(0.1, 0.1, 0.2);

var color = baseColor * (directLight + ambientLight);
let fogStrength = 1.0 - exp(-camera.fogIntensity * length(in.camera_view_pos));

var color = mix(baseColor * (directLight + ambientLight), camera.fogColor, fogStrength);
return vec4f(color, 1.0);
}
2 changes: 2 additions & 0 deletions src/lib/shaders/naive.vs.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct VertexOutput
@location(1) nor: vec3f,
@location(2) uv: vec2f,
@location(3) shadow_pos: vec3f,
@location(4) camera_view_pos: vec3f,
}

@group(0) @binding(0) var<uniform> camera : CameraUniforms;
Expand All @@ -37,6 +38,7 @@ fn main(in: VertexInput) -> VertexOutput
posFromLight.xy * vec2(0.5, -0.5) + vec2(0.5),
posFromLight.z
);
out.camera_view_pos = (camera.viewMat * modelPos).xyz;

return out;
}
Loading