diff options
| -rw-r--r-- | CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/gpu/demo_effects.h | 1 | ||||
| -rw-r--r-- | src/gpu/effects/scene1_effect.cc | 28 | ||||
| -rw-r--r-- | src/gpu/effects/scene1_effect.h | 19 | ||||
| -rw-r--r-- | src/gpu/effects/shaders.cc | 4 | ||||
| -rw-r--r-- | src/gpu/effects/shaders.h | 1 | ||||
| -rw-r--r-- | src/tests/gpu/test_demo_effects.cc | 1 | ||||
| -rw-r--r-- | workspaces/main/assets.txt | 1 | ||||
| -rw-r--r-- | workspaces/main/shaders/scene1.wgsl | 256 | ||||
| -rw-r--r-- | workspaces/main/timeline.seq | 3 |
10 files changed, 316 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 753a9b1..48a46e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,6 +150,7 @@ if (DEMO_HEADLESS) src/gpu/effects/gaussian_blur_effect.cc src/gpu/effects/solarize_effect.cc # Disabled: src/gpu/effects/cube_sphere_effect.cc (incomplete conversion) + src/gpu/effects/scene1_effect.cc src/gpu/effects/chroma_aberration_effect.cc src/gpu/effects/vignette_effect.cc src/gpu/effects/cnn_effect.cc @@ -181,6 +182,7 @@ else() src/gpu/effects/gaussian_blur_effect.cc src/gpu/effects/solarize_effect.cc # Disabled: src/gpu/effects/cube_sphere_effect.cc (incomplete conversion) + src/gpu/effects/scene1_effect.cc src/gpu/effects/chroma_aberration_effect.cc src/gpu/effects/vignette_effect.cc src/gpu/effects/cnn_effect.cc diff --git a/src/gpu/demo_effects.h b/src/gpu/demo_effects.h index 72b3f65..1ccf930 100644 --- a/src/gpu/demo_effects.h +++ b/src/gpu/demo_effects.h @@ -15,6 +15,7 @@ #include "gpu/effects/theme_modulation_effect.h" // ThemeModulationEffect with full definition #include "gpu/effects/hybrid_3d_effect.h" #include "gpu/effects/flash_cube_effect.h" +#include "gpu/effects/scene1_effect.h" #include "gpu/gpu.h" #include "gpu/texture_manager.h" #include "gpu/uniform_helper.h" diff --git a/src/gpu/effects/scene1_effect.cc b/src/gpu/effects/scene1_effect.cc new file mode 100644 index 0000000..a6733b7 --- /dev/null +++ b/src/gpu/effects/scene1_effect.cc @@ -0,0 +1,28 @@ +// This file is part of the 64k demo project. +// Scene1 effect - ShaderToy conversion (raymarching scene) + +#include "gpu/demo_effects.h" +#include "gpu/gpu.h" + +Scene1Effect::Scene1Effect(const GpuContext& ctx) : Effect(ctx) { + ResourceBinding bindings[] = {{uniforms_.get(), WGPUBufferBindingType_Uniform}}; + pass_ = gpu_create_render_pass(ctx_.device, ctx_.format, scene1_shader_wgsl, + bindings, 1); + pass_.vertex_count = 3; +} + +void Scene1Effect::render(WGPURenderPassEncoder pass, float t, float b, + float i, float a) { + CommonPostProcessUniforms u = { + .resolution = {(float)width_, (float)height_}, + ._pad = {0.0f, 0.0f}, + .aspect_ratio = a, + .time = t, + .beat = b, + .audio_intensity = i, + }; + uniforms_.update(ctx_.queue, u); + wgpuRenderPassEncoderSetPipeline(pass, pass_.pipeline); + wgpuRenderPassEncoderSetBindGroup(pass, 0, pass_.bind_group, 0, nullptr); + wgpuRenderPassEncoderDraw(pass, pass_.vertex_count, 1, 0, 0); +} diff --git a/src/gpu/effects/scene1_effect.h b/src/gpu/effects/scene1_effect.h new file mode 100644 index 0000000..dc5c747 --- /dev/null +++ b/src/gpu/effects/scene1_effect.h @@ -0,0 +1,19 @@ +// This file is part of the 64k demo project. +// Scene1 effect - ShaderToy conversion (raymarching scene) + +#ifndef SCENE1_EFFECT_H_ +#define SCENE1_EFFECT_H_ + +#include "gpu/effect.h" + +class Scene1Effect : public Effect { + public: + Scene1Effect(const GpuContext& ctx); + void render(WGPURenderPassEncoder pass, float time, float beat, + float intensity, float aspect_ratio) override; + + private: + RenderPass pass_; +}; + +#endif /* SCENE1_EFFECT_H_ */ diff --git a/src/gpu/effects/shaders.cc b/src/gpu/effects/shaders.cc index 6559bf5..5f78298 100644 --- a/src/gpu/effects/shaders.cc +++ b/src/gpu/effects/shaders.cc @@ -98,6 +98,10 @@ const char* solarize_shader_wgsl = SafeGetAsset(AssetId::ASSET_SHADER_SOLARIZE); +const char* scene1_shader_wgsl = + + SafeGetAsset(AssetId::ASSET_SHADER_SCENE1); + const char* distort_shader_wgsl = SafeGetAsset(AssetId::ASSET_SHADER_DISTORT); diff --git a/src/gpu/effects/shaders.h b/src/gpu/effects/shaders.h index 7acc2a6..03fa48c 100644 --- a/src/gpu/effects/shaders.h +++ b/src/gpu/effects/shaders.h @@ -15,6 +15,7 @@ extern const char* ellipse_shader_wgsl; extern const char* particle_spray_compute_wgsl; extern const char* gaussian_blur_shader_wgsl; extern const char* solarize_shader_wgsl; +extern const char* scene1_shader_wgsl; extern const char* distort_shader_wgsl; extern const char* chroma_aberration_shader_wgsl; extern const char* vignette_shader_wgsl; diff --git a/src/tests/gpu/test_demo_effects.cc b/src/tests/gpu/test_demo_effects.cc index 619b9c9..01e6678 100644 --- a/src/tests/gpu/test_demo_effects.cc +++ b/src/tests/gpu/test_demo_effects.cc @@ -134,6 +134,7 @@ static void test_scene_effects() { {"CircleMaskEffect", std::make_shared<CircleMaskEffect>(fixture.ctx())}, {"RotatingCubeEffect", std::make_shared<RotatingCubeEffect>(fixture.ctx())}, + {"Scene1Effect", std::make_shared<Scene1Effect>(fixture.ctx())}, }; int passed = 0; diff --git a/workspaces/main/assets.txt b/workspaces/main/assets.txt index 53c8b3e..af8b9e9 100644 --- a/workspaces/main/assets.txt +++ b/workspaces/main/assets.txt @@ -67,3 +67,4 @@ SHADER_COMPUTE_GEN_MASK, NONE, shaders/compute/gen_mask.wgsl, "GPU Mask Composit CIRCLE_MASK_COMPUTE_SHADER, NONE, shaders/circle_mask_compute.wgsl, "Circle mask compute shader" CIRCLE_MASK_RENDER_SHADER, NONE, shaders/circle_mask_render.wgsl, "Circle mask render shader" MASKED_CUBE_SHADER, NONE, shaders/masked_cube.wgsl, "Masked cube shader" +SHADER_SCENE1, NONE, shaders/scene1.wgsl, "Scene1 effect shader" diff --git a/workspaces/main/shaders/scene1.wgsl b/workspaces/main/shaders/scene1.wgsl new file mode 100644 index 0000000..b0ec2ab --- /dev/null +++ b/workspaces/main/shaders/scene1.wgsl @@ -0,0 +1,256 @@ +// Scene1 effect shader - ShaderToy conversion (raymarching cube & sphere) +// Source: Saturday cubism experiment by skal + +#include "common_uniforms" + +@group(0) @binding(0) var<uniform> uniforms: CommonUniforms; + +const PI: f32 = 3.141592654; +const TAU: f32 = 6.283185307; +const TOLERANCE: f32 = 0.0005; +const MAX_RAY_LENGTH: f32 = 20.0; +const MAX_RAY_MARCHES: i32 = 80; +const MAX_SHD_MARCHES: i32 = 20; +const NORM_OFF: f32 = 0.005; + +fn rot(a: f32) -> mat2x2<f32> { + let c = cos(a); + let s = sin(a); + return mat2x2<f32>(c, s, -s, c); +} + +// HSV to RGB conversion +const hsv2rgb_K = vec4<f32>(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); +fn hsv2rgb(c: vec3<f32>) -> vec3<f32> { + let p = abs(fract(c.xxx + hsv2rgb_K.xyz) * 6.0 - hsv2rgb_K.www); + return c.z * mix(hsv2rgb_K.xxx, clamp(p - hsv2rgb_K.xxx, vec3<f32>(0.0), vec3<f32>(1.0)), c.y); +} + +// Colors (precomputed HSV conversions) +const skyCol = vec3<f32>(0.176, 0.235, 0.25); // HSV(0.57, 0.90, 0.25) +const skylineCol = vec3<f32>(0.5, 0.125, 0.025); // HSV(0.02, 0.95, 0.5) +const sunCol = vec3<f32>(0.5, 0.163, 0.025); // HSV(0.07, 0.95, 0.5) +const diffCol1 = vec3<f32>(0.4, 1.0, 1.0); // HSV(0.60, 0.90, 1.0) +const diffCol2 = vec3<f32>(0.325, 1.0, 0.975); // HSV(0.55, 0.90, 1.0) + +// Lighting (normalized manually) +const sunDir1 = vec3<f32>(0.0, 0.04997, -0.99875); // normalize(0, 0.05, -1) +const lightPos1 = vec3<f32>(10.0, 10.0, 10.0); +const lightPos2 = vec3<f32>(-10.0, 10.0, -10.0); + +fn sRGB(t: vec3<f32>) -> vec3<f32> { + return mix(1.055 * pow(t, vec3<f32>(1.0/2.4)) - 0.055, 12.92 * t, step(t, vec3<f32>(0.0031308))); +} + +fn aces_approx(v_in: vec3<f32>) -> vec3<f32> { + var v = max(v_in, vec3<f32>(0.0)); + v *= 0.6; + let a = 2.51; + let b = 0.03; + let c = 2.43; + let d = 0.59; + let e = 0.14; + return clamp((v * (a * v + b)) / (v * (c * v + d) + e), vec3<f32>(0.0), vec3<f32>(1.0)); +} + +fn tanh_approx(x: f32) -> f32 { + let x2 = x * x; + return clamp(x * (27.0 + x2) / (27.0 + 9.0 * x2), -1.0, 1.0); +} + +fn rayPlane(ro: vec3<f32>, rd: vec3<f32>, plane: vec4<f32>) -> f32 { + return -(dot(ro, plane.xyz) + plane.w) / dot(rd, plane.xyz); +} + +fn box2d(p: vec2<f32>, b: vec2<f32>) -> f32 { + let d = abs(p) - b; + return length(max(d, vec2<f32>(0.0))) + min(max(d.x, d.y), 0.0); +} + +fn box3d(p: vec3<f32>, b: vec3<f32>) -> f32 { + let q = abs(p) - b; + return length(max(q, vec3<f32>(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0); +} + +fn sphere(p: vec3<f32>, r: f32) -> f32 { + return length(p) - r; +} + +var<private> g_rot0: mat2x2<f32>; + +fn render0(ro: vec3<f32>, rd: vec3<f32>) -> vec3<f32> { + var col = vec3<f32>(0.0); + var sf = 1.0001 - max(dot(sunDir1, rd), 0.0); + col += skyCol * pow((1.0 - abs(rd.y)), 8.0); + col += clamp(vec3<f32>(mix(0.0025, 0.125, tanh_approx(0.005 / sf)) / abs(rd.y)) * skylineCol, vec3<f32>(0.0), vec3<f32>(10.0)); + sf *= sf; + col += sunCol * 0.00005 / sf; + + let tp1 = rayPlane(ro, rd, vec4<f32>(0.0, -1.0, 0.0, 6.0)); + if (tp1 > 0.0) { + let pos = ro + tp1 * rd; + let pp = pos.xz; + let db = box2d(pp, vec2<f32>(5.0, 9.0)) - 3.0; + col += vec3<f32>(4.0) * skyCol * rd.y * rd.y * smoothstep(0.25, 0.0, db); + col += vec3<f32>(0.8) * skyCol * exp(-0.5 * max(db, 0.0)); + } + + return clamp(col, vec3<f32>(0.0), vec3<f32>(10.0)); +} + +fn df(p_in: vec3<f32>) -> f32 { + var p = p_in; + p.x = p_in.x * g_rot0[0][0] + p_in.z * g_rot0[0][1]; + p.z = p_in.x * g_rot0[1][0] + p_in.z * g_rot0[1][1]; + + // Cube + var pc = p; + pc -= vec3<f32>(-1.9, 0.0, 0.0); + let dCube = box3d(pc, vec3<f32>(1.6)); + + // Sphere + var ps = p; + ps -= vec3<f32>(1.3, 0.0, 0.0); + let dSphere = sphere(ps, 1.2); + + // Ground plane + let dPlane = p.y + 1.0; + + // Union + var d = min(dCube, dSphere); + d = min(d, dPlane); + + return d; +} + +fn normal(pos: vec3<f32>) -> vec3<f32> { + let eps = vec2<f32>(NORM_OFF, 0.0); + var nor: vec3<f32>; + nor.x = df(pos + eps.xyy) - df(pos - eps.xyy); + nor.y = df(pos + eps.yxy) - df(pos - eps.yxy); + nor.z = df(pos + eps.yyx) - df(pos - eps.yyx); + return normalize(nor); +} + +fn rayMarch(ro: vec3<f32>, rd: vec3<f32>, initt: f32) -> f32 { + var t = initt; + for (var i = 0; i < MAX_RAY_MARCHES; i++) { + if (t > MAX_RAY_LENGTH) { + t = MAX_RAY_LENGTH; + break; + } + let d = df(ro + rd * t); + if (d < TOLERANCE) { + break; + } + t += d; + } + return t; +} + +fn shadow(lp: vec3<f32>, ld: vec3<f32>, mint: f32, maxt: f32) -> f32 { + let ds = 1.0 - 0.4; + var t = mint; + var nd = 1e6; + let soff = 0.05; + let smul = 1.5; + for (var i = 0; i < MAX_SHD_MARCHES; i++) { + let p = lp + ld * t; + let d = df(p); + if (d < TOLERANCE || t >= maxt) { + let sd = 1.0 - exp(-smul * max(t / maxt - soff, 0.0)); + return select(mix(sd, 1.0, smoothstep(0.0, 0.025, nd)), sd, t >= maxt); + } + nd = min(nd, d); + t += ds * d; + } + let sd = 1.0 - exp(-smul * max(t / maxt - soff, 0.0)); + return sd; +} + +fn boxCol(col: vec3<f32>, nsp: vec3<f32>, rd: vec3<f32>, nnor: vec3<f32>, nrcol: vec3<f32>, nshd1: f32, nshd2: f32) -> vec3<f32> { + var nfre = 1.0 + dot(rd, nnor); + nfre *= nfre; + + let nld1 = normalize(lightPos1 - nsp); + let nld2 = normalize(lightPos2 - nsp); + + var ndif1 = max(dot(nld1, nnor), 0.0); + ndif1 *= ndif1; + + var ndif2 = max(dot(nld2, nnor), 0.0); + ndif2 *= ndif2; + + var scol = vec3<f32>(0.0); + let rf = smoothstep(1.0, 0.9, nfre); + scol += diffCol1 * ndif1 * nshd1; + scol += diffCol2 * ndif2 * nshd2; + scol += 0.1 * (skyCol + skylineCol); + scol += nrcol * 0.75 * mix(vec3<f32>(0.25), vec3<f32>(0.5, 0.5, 1.0), nfre); + + return mix(col, scol, rf * smoothstep(90.0, 20.0, dot(nsp, nsp))); +} + +fn render1(ro: vec3<f32>, rd: vec3<f32>) -> vec3<f32> { + let skyCol_local = render0(ro, rd); + var col = skyCol_local; + + let nt = rayMarch(ro, rd, 0.0); + if (nt < MAX_RAY_LENGTH) { + let nsp = ro + rd * nt; + let nnor = normal(nsp); + + let nref = reflect(rd, nnor); + let nrt = rayMarch(nsp, nref, 0.2); + var nrcol = render0(nsp, nref); + + if (nrt < MAX_RAY_LENGTH) { + let nrsp = nsp + nref * nrt; + let nrnor = normal(nrsp); + let nrref = reflect(nref, nrnor); + nrcol = boxCol(nrcol, nrsp, nref, nrnor, render0(nrsp, nrref), 1.0, 1.0); + } + + let nshd1 = mix(0.0, 1.0, shadow(nsp, normalize(lightPos1 - nsp), 0.1, distance(lightPos1, nsp))); + let nshd2 = mix(0.0, 1.0, shadow(nsp, normalize(lightPos2 - nsp), 0.1, distance(lightPos2, nsp))); + + col = boxCol(col, nsp, rd, nnor, nrcol, nshd1, nshd2); + } + + return col; +} + +fn effect(p: vec2<f32>) -> vec3<f32> { + g_rot0 = rot(-0.2 * uniforms.time); + + let fov = tan(TAU / 6.0); + let ro = vec3<f32>(0.0, 2.5, 5.0); + let la = vec3<f32>(0.0, 0.0, 0.0); + let up = vec3<f32>(0.1, 1.0, 0.0); + + let ww = normalize(la - ro); + let uu = normalize(cross(up, ww)); + let vv = cross(ww, uu); + let rd = normalize(-p.x * uu + p.y * vv + fov * ww); + + return render1(ro, rd); +} + +@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { + var pos = array<vec2<f32>, 3>( + vec2<f32>(-1.0, -1.0), + vec2<f32>(3.0, -1.0), + vec2<f32>(-1.0, 3.0) + ); + return vec4<f32>(pos[i], 0.0, 1.0); +} + +@fragment fn fs_main(@builtin(position) p: vec4<f32>) -> @location(0) vec4<f32> { + let q = p.xy / uniforms.resolution; + var coord = -1.0 + 2.0 * q; + coord.x *= uniforms.resolution.x / uniforms.resolution.y; + var col = effect(coord); + col = aces_approx(col); + col = sRGB(col); + return vec4<f32>(col, 1.0); +} diff --git a/workspaces/main/timeline.seq b/workspaces/main/timeline.seq index 083e60a..5cf5574 100644 --- a/workspaces/main/timeline.seq +++ b/workspaces/main/timeline.seq @@ -39,6 +39,9 @@ SEQUENCE 10.50 0 "CNN effect" # EFFECT + Hybrid3DEffect 0.00 12.00 EFFECT + CNNEffect 1.0 12.0 layers=3 blend=1.5 +SEQUENCE 22.50 0 "Scene1 demo" + EFFECT + Scene1Effect 0.0 10.0 + SEQUENCE 22.0 0 "buggy" EFFECT + HeptagonEffect 0.00 0.20 EFFECT + FadeEffect 0.11 1.01 |
