From afe2a67ba0cca8a72baebff471a4390f2ead50ec Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sun, 30 Apr 2023 16:02:07 +0100 Subject: [PATCH] Improve rendering --- src/game-loop/game-loop.ts | 5 ++- src/index.ts | 2 +- src/pipelines/agents/agent-pipeline.ts | 9 ++-- src/pipelines/agents/agent-settings.ts | 2 +- src/pipelines/agents/agent.wgsl | 29 +++++------- src/pipelines/brush/brush-pipeline.ts | 22 +++++++--- src/pipelines/brush/brush-settings.ts | 2 +- src/pipelines/brush/brush.wgsl | 19 ++++---- src/pipelines/diffusion/diffuse.wgsl | 44 +++++++++++++------ src/pipelines/diffusion/diffusion-pipeline.ts | 5 ++- src/pipelines/render/render-pipeline.ts | 21 ++++++++- src/settings.ts | 18 ++++---- src/utils/array.ts | 1 - .../full-screen-quad/full-screen-quad.ts | 2 +- .../{webgpu => graphics}/initialize-gpu.ts | 0 src/utils/graphics/noise/noise.ts | 5 ++- src/utils/graphics/noise/noise.wgsl | 15 +++---- src/utils/graphics/random.wgsl | 7 +++ src/utils/graphics/smart-compile.ts | 21 +++++++++ src/utils/webgpu/smart-compile.ts | 15 ------- 20 files changed, 148 insertions(+), 96 deletions(-) rename src/utils/{webgpu => graphics}/initialize-gpu.ts (100%) create mode 100644 src/utils/graphics/random.wgsl create mode 100644 src/utils/graphics/smart-compile.ts delete mode 100644 src/utils/webgpu/smart-compile.ts diff --git a/src/game-loop/game-loop.ts b/src/game-loop/game-loop.ts index 833c632..f0036d3 100644 --- a/src/game-loop/game-loop.ts +++ b/src/game-loop/game-loop.ts @@ -48,7 +48,10 @@ export default class GameLoop { window.addEventListener('resize', this.resize.bind(this)); window.addEventListener('mousemove', this.onSwipe.bind(this)); - window.addEventListener('mousedown', (_) => (this.isSwipeActive = true)); + window.addEventListener('mousedown', (e) => { + this.isSwipeActive = true; + this.onSwipe(e); + }); window.addEventListener('mouseup', (_) => { this.isSwipeActive = false; this.brushPipeline.clearSwipes(); diff --git a/src/index.ts b/src/index.ts index 66da258..544dbdd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,8 +2,8 @@ import '../assets/icons/info.svg'; import GameLoop from './game-loop/game-loop'; import './index.scss'; import { applyArrayPlugins } from './utils/array'; +import { initializeGPU } from './utils/graphics/initialize-gpu'; import { handleFullScreen } from './utils/handle-full-screen'; -import { initializeGPU } from './utils/webgpu/initialize-gpu'; declare global { interface Array { diff --git a/src/pipelines/agents/agent-pipeline.ts b/src/pipelines/agents/agent-pipeline.ts index 4b21465..c6f855c 100644 --- a/src/pipelines/agents/agent-pipeline.ts +++ b/src/pipelines/agents/agent-pipeline.ts @@ -1,4 +1,5 @@ -import { smartCompile } from '../../utils/webgpu/smart-compile'; +import random from '../../utils/graphics/random.wgsl'; +import { smartCompile } from '../../utils/graphics/smart-compile'; import { CommonParameters } from '../common-parameters'; import { AGENT_SIZE_IN_BYTES, Agent } from './agent'; import { AgentSettings } from './agent-settings'; @@ -24,7 +25,7 @@ export class AgentPipeline { this.pipeline = device.createComputePipeline({ layout: 'auto', compute: { - module: smartCompile(device, shader), + module: smartCompile(device, random, shader), entryPoint: 'main', }, }); @@ -57,7 +58,7 @@ export class AgentPipeline { canvasSize, deltaTime, time, - trailWeight, + brushTrailWeight, moveSpeed, turnSpeed, sensorAngleDegrees, @@ -71,7 +72,7 @@ export class AgentPipeline { canvasSize[1], deltaTime, time, - trailWeight, + brushTrailWeight, moveSpeed * deltaTime, turnSpeed * deltaTime, (sensorAngleDegrees * Math.PI) / 180, diff --git a/src/pipelines/agents/agent-settings.ts b/src/pipelines/agents/agent-settings.ts index c60587e..6ca626d 100644 --- a/src/pipelines/agents/agent-settings.ts +++ b/src/pipelines/agents/agent-settings.ts @@ -1,5 +1,5 @@ export interface AgentSettings { - trailWeight: number; + brushTrailWeight: number; moveSpeed: number; turnSpeed: number; sensorAngleDegrees: number; diff --git a/src/pipelines/agents/agent.wgsl b/src/pipelines/agents/agent.wgsl index 34ace4a..3a9328c 100644 --- a/src/pipelines/agents/agent.wgsl +++ b/src/pipelines/agents/agent.wgsl @@ -10,7 +10,7 @@ struct Settings { deltaTime: f32, time: f32, - trailWeight: f32, + brushTrailWeight: f32, moveRate: f32, turnRate: f32, sensorAngle: f32, @@ -33,16 +33,18 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { var agent = agents[id]; if (agent.timeToLive <= 0.) { - agent.position = vec2(random(id + u32(settings.time * 10 + agent.position.x * 10000)), - random(id + u32(settings.time * 10 + agent.position.y * 10000))); - agent.angle = random(id + u32(settings.time)) * 3.14159265359 * 2.; + agent.position = vec2( + random_with_seed(agent.position, f32(id) + settings.time), + random_with_seed(agent.position, f32(id) + settings.time + 12), + ); + agent.angle = random_with_seed(vec2(agent.angle), f32(id) + settings.time); agent.species = 1; agent.timeToLive = 1000; agents[id] = agent; return; } - let random = random(id + u32(settings.time * 10000 + agent.position.y * 10 + agent.position.x)); + let random = random_with_seed(agent.position, f32(id) + settings.time); let trailCurrent = sense(agent, 0, 0); var weight: f32; @@ -60,9 +62,9 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { let trailLeft = sense(agent, settings.sensorOffset, settings.sensorAngle); let trailRight = sense(agent, settings.sensorOffset, -settings.sensorAngle); - var weightForward: f32 = trailForward.a; - var weightLeft: f32 = trailLeft.a; - var weightRight: f32 = trailRight.a; + var weightForward: f32 = trailForward.a * settings.brushTrailWeight; + var weightLeft: f32 = trailLeft.a * settings.brushTrailWeight; + var weightRight: f32 = trailRight.a * settings.brushTrailWeight; if (agent.species == 0) { weightForward += trailForward.r - trailForward.g; weightLeft += trailLeft.r - trailLeft.g; @@ -107,13 +109,4 @@ fn sense(agent: Agent, sensorOffset: f32, sensorOffsetAngle: f32) -> vec4 { return textureLoad(TrailMapIn, vec2(sensorPos * settings.size), 0); } -fn random(state0: u32) -> f32 { - var state: u32 = state0; - state = state ^ 2747636419u; - state = state * 2654435769u; - state = state ^ (state >> 16u); - state = state * 2654435769u; - state = state ^ (state >> 16u); - state = state * 2654435769u; - return f32(state) / 4294967295.0; -} + diff --git a/src/pipelines/brush/brush-pipeline.ts b/src/pipelines/brush/brush-pipeline.ts index 9707485..9cf2618 100644 --- a/src/pipelines/brush/brush-pipeline.ts +++ b/src/pipelines/brush/brush-pipeline.ts @@ -1,5 +1,5 @@ import { generateNoise } from '../../utils/graphics/noise/noise'; -import { smartCompile } from '../../utils/webgpu/smart-compile'; +import { smartCompile } from '../../utils/graphics/smart-compile'; import { CommonParameters } from '../common-parameters'; import { BrushSettings } from './brush-settings'; import shader from './brush.wgsl'; @@ -24,10 +24,12 @@ export class BrushPipeline { public constructor(private readonly device: GPUDevice) { this.noise = generateNoise({ device, - octaves: 4, - amplitude: 0.7, - gain: 0.6, - lacunarity: 4, + width: 512, + height: 512, + octaves: 16, + amplitude: 0.5, + gain: 0.8, + lacunarity: 80, }); this.vertexBuffer = device.createBuffer({ @@ -138,12 +140,18 @@ export class BrushPipeline { deltaTime, time, brushWidth, - brushBlurWidth, + brushWidthRandomness, }: CommonParameters & BrushSettings) { this.device.queue.writeBuffer( this.uniforms, 0, - new Float32Array([...canvasSize, deltaTime, time, brushWidth / 2, brushBlurWidth]) + new Float32Array([ + ...canvasSize, + deltaTime, + time, + brushWidth / 2, + brushWidthRandomness, + ]) ); // this.linePoints = [ diff --git a/src/pipelines/brush/brush-settings.ts b/src/pipelines/brush/brush-settings.ts index bbbbdb9..cffc73a 100644 --- a/src/pipelines/brush/brush-settings.ts +++ b/src/pipelines/brush/brush-settings.ts @@ -1,4 +1,4 @@ export interface BrushSettings { brushWidth: number; - brushBlurWidth: number; + brushWidthRandomness: number; } diff --git a/src/pipelines/brush/brush.wgsl b/src/pipelines/brush/brush.wgsl index 071845e..f5a6122 100644 --- a/src/pipelines/brush/brush.wgsl +++ b/src/pipelines/brush/brush.wgsl @@ -3,7 +3,7 @@ struct Settings { deltaTime: f32, time: f32, brushWidth: f32, - brushBlurWidth: f32 + brushWidthRandomness: f32 }; @group(0) @binding(0) var settings: Settings; @@ -34,17 +34,20 @@ fn fragment( @location(1) start: vec2, @location(2) end: vec2 ) -> @location(0) vec4 { - let pa = (screenPosition - start); - let direction = (end - start); - let q = clamp(dot(pa, direction) / dot(direction, direction), 0, 1); + var distance = distanceFromLine(screenPosition, start, end); let noise = textureSample(noise, Sampler, screenPosition / settings.size); - - let distance = length(pa - direction * q) + noise.r * 5; + distance += noise.r * settings.brushWidthRandomness; if(distance > settings.brushWidth) { discard; } - let strength = clamp((settings.brushWidth - distance) / settings.brushBlurWidth, 0, 1); - return vec4(0, 0, 0, strength); + return vec4(0, 0, 0, 1); +} + +fn distanceFromLine(position: vec2, start: vec2, end: vec2) -> f32 { + let pa = position - start; + let direction = end - start; + let q = clamp(dot(pa, direction) / dot(direction, direction), 0, 1); + return length(pa - direction * q); } diff --git a/src/pipelines/diffusion/diffuse.wgsl b/src/pipelines/diffusion/diffuse.wgsl index 2ecb89f..0d4f28f 100644 --- a/src/pipelines/diffusion/diffuse.wgsl +++ b/src/pipelines/diffusion/diffuse.wgsl @@ -12,12 +12,12 @@ struct Settings { @group(0) @binding(0) var settings: Settings; @group(0) @binding(1) var Sampler: sampler; @group(0) @binding(2) var trailMap: texture_2d; -@group(0) @binding(3) var noise: texture_2d; +@group(0) @binding(3) var noiseMap: texture_2d; @fragment fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { var current = textureSample(trailMap, Sampler, uv); - let noise = textureSample(noise, Sampler, uv); + let noise = textureSample(noiseMap, Sampler, uv); let neighbours: vec4 = ( textureSample(trailMap, Sampler, uv + vec2(0, 1) / settings.size) @@ -26,17 +26,33 @@ fn fragment(@location(0) uv: vec2) -> @location(0) vec4 { + textureSample(trailMap, Sampler, uv + vec2(1, 0) / settings.size) ) / 4; - let mixedTrails = mix( + + var q = vec4(0); + for (var x: i32 = -1; x <= 1; x++) { + for (var y: i32 = -1; y <= 1; y++) { + if (x != 0 || y != 0) { + let offset = vec2(f32(x), f32(y)); + let neighbour = textureSample(trailMap, Sampler, uv + offset / settings.size); + // let noise = textureSample(noiseMap, Sampler, uv + offset / settings.size * 0.5).r; + let noise = random(uv + offset / settings.size * 0.5); + let difference = neighbour - current; + + q += vec4( + min(1.0, length(neighbour.rgb)) * pow(noise, settings.diffusionRateTrails) * difference.rgb, + min(1.0, length(neighbour.a)) * pow(noise, settings.diffusionRateBrush) * difference.a + ); + } + } + } + current += q / 4; + + let noise1 = random(uv); + + + let decayed = vec4( current.rgb, - neighbours.rgb, - settings.diffusionRateTrails - ) * (1.0 - settings.decayRateTrails); - - let mixedBrush = mix( - current.a + (noise.a - 0.5) * 0.1, - neighbours.a , - settings.diffusionRateBrush - ) * (1.0 - settings.decayRateBrush); - - return clamp(vec4(mixedTrails, mixedBrush), vec4(0), vec4(1)); + current.a + ) - vec4(vec3(settings.decayRateTrails), settings.decayRateBrush) * settings.deltaTime * ((noise1 - 0.5) * 0.25 + 1); + + return clamp(decayed, vec4(0), vec4(1)); } diff --git a/src/pipelines/diffusion/diffusion-pipeline.ts b/src/pipelines/diffusion/diffusion-pipeline.ts index c2dda95..986503e 100644 --- a/src/pipelines/diffusion/diffusion-pipeline.ts +++ b/src/pipelines/diffusion/diffusion-pipeline.ts @@ -1,6 +1,7 @@ import { setUpFullScreenQuad } from '../../utils/graphics/full-screen-quad/full-screen-quad'; import { generateNoise } from '../../utils/graphics/noise/noise'; -import { smartCompile } from '../../utils/webgpu/smart-compile'; +import random from '../../utils/graphics/random.wgsl'; +import { smartCompile } from '../../utils/graphics/smart-compile'; import { CommonParameters } from '../common-parameters'; import shader from './diffuse.wgsl'; import { DiffusionSettings } from './diffusion-settings'; @@ -34,7 +35,7 @@ export class DiffusionPipeline { layout: 'auto', vertex, fragment: { - module: smartCompile(device, shader), + module: smartCompile(device, random, shader), entryPoint: 'fragment', targets: [ { diff --git a/src/pipelines/render/render-pipeline.ts b/src/pipelines/render/render-pipeline.ts index 44b7be3..88f35ee 100644 --- a/src/pipelines/render/render-pipeline.ts +++ b/src/pipelines/render/render-pipeline.ts @@ -1,5 +1,7 @@ import { setUpFullScreenQuad } from '../../utils/graphics/full-screen-quad/full-screen-quad'; -import { smartCompile } from '../../utils/webgpu/smart-compile'; +import { generateNoise } from '../../utils/graphics/noise/noise'; +import random from '../../utils/graphics/random.wgsl'; +import { smartCompile } from '../../utils/graphics/smart-compile'; import { CommonParameters } from '../common-parameters'; import { RenderSettings } from './render-settings'; import shader from './render.wgsl'; @@ -10,6 +12,7 @@ export class RenderPipeline { private readonly pipeline: GPURenderPipeline; private readonly uniforms: GPUBuffer; private readonly quadVertexBuffer: GPUBuffer; + private readonly noise: GPUTextureView; private bindGroup?: GPUBindGroup; private previousColorTexture?: GPUTexture; @@ -18,6 +21,16 @@ export class RenderPipeline { private readonly context: GPUCanvasContext, private readonly device: GPUDevice ) { + this.noise = generateNoise({ + device, + width: 512, + height: 512, + octaves: 16, + amplitude: 0.3, + gain: 0.8, + lacunarity: 80, + }); + const { buffer, vertex } = setUpFullScreenQuad(device); this.quadVertexBuffer = buffer; @@ -25,7 +38,7 @@ export class RenderPipeline { layout: 'auto', vertex, fragment: { - module: smartCompile(device, shader), + module: smartCompile(device, random, shader), entryPoint: 'fragment', targets: [ { @@ -112,6 +125,10 @@ export class RenderPipeline { binding: 2, resource: colorTexture.createView(), }, + { + binding: 3, + resource: this.noise, + }, ], }); diff --git a/src/settings.ts b/src/settings.ts index 86628ae..1093507 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -19,25 +19,25 @@ export const settings: GameLoopSettings & BrushSettings & DiffusionSettings & RenderSettings = { - agentCount: 1_000_000, + agentCount: 500_000, renderSpeed: 1, startingRadius: 0.15, - brushWidth: 30, - brushBlurWidth: 8, + brushWidth: 20, + brushWidthRandomness: 8, - trailWeight: 5, + brushTrailWeight: 5, moveSpeed: 0.025, turnSpeed: 6, sensorAngleDegrees: 30, sensorOffsetDst: 0.025, - diffusionRateTrails: 0.8, - decayRateTrails: 0.03, - diffusionRateBrush: 0.9, - decayRateBrush: 0.003, + diffusionRateTrails: 6, + decayRateTrails: 1, + diffusionRateBrush: 4, + decayRateBrush: 0.15, - brushColor: palette.yellow, + brushColor: palette.blue, speciesColorA: palette.yellow, speciesColorB: palette.green, }; diff --git a/src/utils/array.ts b/src/utils/array.ts index 05b416b..fcdc8f3 100644 --- a/src/utils/array.ts +++ b/src/utils/array.ts @@ -1,4 +1,3 @@ -/** @internal */ const setIndexAlias = (name: string, index: number, type: any) => { if (!Object.prototype.hasOwnProperty.call(type.prototype, name)) { Object.defineProperty(type.prototype, name, { diff --git a/src/utils/graphics/full-screen-quad/full-screen-quad.ts b/src/utils/graphics/full-screen-quad/full-screen-quad.ts index 0858a5a..4075d38 100644 --- a/src/utils/graphics/full-screen-quad/full-screen-quad.ts +++ b/src/utils/graphics/full-screen-quad/full-screen-quad.ts @@ -1,4 +1,4 @@ -import { smartCompile } from '../../webgpu/smart-compile'; +import { smartCompile } from '../smart-compile'; import shader from './full-screen-quad.wgsl'; export const setUpFullScreenQuad = ( diff --git a/src/utils/webgpu/initialize-gpu.ts b/src/utils/graphics/initialize-gpu.ts similarity index 100% rename from src/utils/webgpu/initialize-gpu.ts rename to src/utils/graphics/initialize-gpu.ts diff --git a/src/utils/graphics/noise/noise.ts b/src/utils/graphics/noise/noise.ts index 656e83c..5224510 100644 --- a/src/utils/graphics/noise/noise.ts +++ b/src/utils/graphics/noise/noise.ts @@ -1,6 +1,7 @@ import { Random } from '../../random'; -import { smartCompile } from '../../webgpu/smart-compile'; import { setUpFullScreenQuad } from '../full-screen-quad/full-screen-quad'; +import random from '../random.wgsl'; +import { smartCompile } from '../smart-compile'; import noise from './noise.wgsl'; const textureCache = new Map(); @@ -31,7 +32,7 @@ export const generateNoise = ({ layout: 'auto', vertex, fragment: { - module: smartCompile(device, noise), + module: smartCompile(device, random, noise), entryPoint: 'fragment', constants: { octaves, diff --git a/src/utils/graphics/noise/noise.wgsl b/src/utils/graphics/noise/noise.wgsl index 78b8565..5b7b31a 100644 --- a/src/utils/graphics/noise/noise.wgsl +++ b/src/utils/graphics/noise/noise.wgsl @@ -29,7 +29,8 @@ fn fbm(uv: vec2, seed: f32) -> f32 { for (var i = 0; i < octaves; i++) { v += a * noise(st, seed); - st = rot * st * lacunarity + shift; + st *= rot * lacunarity; + st += shift; a *= gain; } @@ -40,10 +41,10 @@ fn noise (st: vec2, seed: f32) -> f32 { let i = floor(st); let f = fract(st); - let a = random(i, seed); - let b = random(i + vec2(1.0, 0.0), seed); - let c = random(i + vec2(0.0, 1.0), seed); - let d = random(i + vec2(1.0, 1.0), seed); + let a = random_with_seed(i, seed); + let b = random_with_seed(i + vec2(1.0, 0.0), seed); + let c = random_with_seed(i + vec2(0.0, 1.0), seed); + let d = random_with_seed(i + vec2(1.0, 1.0), seed); let u = f * f * (3.0 - 2.0 * f); @@ -51,7 +52,3 @@ fn noise (st: vec2, seed: f32) -> f32 { (c - a)* u.y * (1.0 - u.x) + (d - b) * u.x * u.y; } - -fn random(st: vec2, seed: f32) -> f32 { - return fract(sin(dot(st.xy, vec2(12.9898 + seed, 78.233 + seed)))* 43758.5453123 + seed); -} diff --git a/src/utils/graphics/random.wgsl b/src/utils/graphics/random.wgsl new file mode 100644 index 0000000..8d7f78d --- /dev/null +++ b/src/utils/graphics/random.wgsl @@ -0,0 +1,7 @@ +fn random_with_seed(uv: vec2, seed: f32) -> f32 { + return fract(sin(dot(uv, vec2(12.9898 + seed, 78.233 + seed)))* 43758.5453123 + seed); +} + +fn random(uv: vec2) -> f32 { + return fract(sin(dot(uv, vec2(12.9898, 78.233)))* 43758.5453123); +} diff --git a/src/utils/graphics/smart-compile.ts b/src/utils/graphics/smart-compile.ts new file mode 100644 index 0000000..877afa0 --- /dev/null +++ b/src/utils/graphics/smart-compile.ts @@ -0,0 +1,21 @@ +export const smartCompile = (device: GPUDevice, ...code: Array) => { + const concatenated = code.join('\n\n'); + + const module = device.createShaderModule({ + code: concatenated, + }); + + module + .getCompilationInfo() + .then((info) => + info.messages.forEach((message) => + console.warn( + message.type, + message.message, + concatenated.split('\n')[message.lineNum - 1] + ) + ) + ); + + return module; +}; diff --git a/src/utils/webgpu/smart-compile.ts b/src/utils/webgpu/smart-compile.ts deleted file mode 100644 index 73fc962..0000000 --- a/src/utils/webgpu/smart-compile.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const smartCompile = (device: GPUDevice, code: string) => { - const module = device.createShaderModule({ - code, - }); - - module - .getCompilationInfo() - .then((info) => - info.messages.forEach((message) => - console.warn(message.type, message.message, code.split('\n')[message.lineNum - 1]) - ) - ); - - return module; -};