See pendulum

I can see the pendulum, but something is not correct with the resolution
This commit is contained in:
2026-02-20 17:14:58 +01:00
parent 13b59d0b36
commit 0d2e7c97ec
2 changed files with 220 additions and 45 deletions

View File

@@ -2,7 +2,7 @@ import {Component} from '@angular/core';
import {BabylonCanvas, RenderConfig, SceneReadyEvent} from '../../../shared/rendering/canvas/babylon-canvas.component'; import {BabylonCanvas, RenderConfig, SceneReadyEvent} from '../../../shared/rendering/canvas/babylon-canvas.component';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
import {ComputeShader, ShaderLanguage, StorageBuffer, UniformBuffer} from '@babylonjs/core'; import {ComputeShader, ShaderLanguage, StorageBuffer, UniformBuffer} from '@babylonjs/core';
import {PENDULUM_COMPUTE_SHADER_WGSL, PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_VERTEX_SHADER_WGSL} from './pendulum.shader'; import {PENDULUM_FRAGMENT_SHADER_WGSL, PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL, PENDULUM_RENDER_COMPUTE_SHADER_WGSL, PENDULUM_VERTEX_SHADER_WGSL} from './pendulum.shader';
@Component({ @Component({
selector: 'app-pendulum', selector: 'app-pendulum',
@@ -30,33 +30,66 @@ export class PendulumComponent {
const engine = event.engine; const engine = event.engine;
const scene = event.scene; const scene = event.scene;
engine.resize();
const width = engine.getRenderWidth(); const width = engine.getRenderWidth();
const height = engine.getRenderHeight(); const height = engine.getRenderHeight();
const totalPixels = width * height; const totalPixels = width * height;
// Buffer: 1 Float pro Pixel console.log("Width and Height ", width, height);
const bufferSize = totalPixels * 4;
const pixelBuffer = new StorageBuffer(engine, bufferSize);
// Uniform Buffer // A. Buffer Setup
// 1. Pixel Buffer (wie gehabt)
const pixelBuffer = new StorageBuffer(engine, totalPixels * 4);
// 2. State Buffer (4 Floats: theta1, theta2, v1, v2)
const stateBuffer = new StorageBuffer(engine, 4 * 4);
stateBuffer.update(new Float32Array([Math.PI / 4, Math.PI / 2, 0, 0]));
// 3. Params Uniform Buffer
const ubo = new UniformBuffer(engine); const ubo = new UniformBuffer(engine);
ubo.addUniform("resolution", 2); ubo.addUniform("resolution", 2);
ubo.addUniform("time", 1); ubo.addUniform("time", 1);
ubo.addUniform("dt", 1);
ubo.addUniform("g", 1);
ubo.addUniform("m1", 1);
ubo.addUniform("m2", 1);
ubo.addUniform("l1", 1);
ubo.addUniform("l2", 1);
ubo.addUniform("damping", 1);
ubo.addUniform("pad1", 1); //Alignment
ubo.addUniform("pad2", 1); //Alignment
ubo.update(); ubo.update();
// Compute Shader // B. Compute Shaders Setup
const cs = new ComputeShader("Pendulum Compute Shader", engine, {
computeSource: PENDULUM_COMPUTE_SHADER_WGSL // CS1: Physics
const csPhysics = new ComputeShader("physics", engine, {
computeSource: PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL
}, {
bindingsMapping: {
"state": { group: 0, binding: 0 },
"p": { group: 0, binding: 1 }
}
});
csPhysics.setStorageBuffer("state", stateBuffer);
csPhysics.setUniformBuffer("p", ubo);
// CS2: Render/Trail
const csRender = new ComputeShader("render", engine, {
computeSource: PENDULUM_RENDER_COMPUTE_SHADER_WGSL
}, { }, {
bindingsMapping: { bindingsMapping: {
"pixelBuffer": { group: 0, binding: 0 }, "pixelBuffer": { group: 0, binding: 0 },
"params": { group: 0, binding: 1 } "p": { group: 0, binding: 1 },
"state": { group: 0, binding: 2 }
} }
}); });
cs.setStorageBuffer("pixelBuffer", pixelBuffer); csRender.setStorageBuffer("pixelBuffer", pixelBuffer);
cs.setUniformBuffer("params", ubo); csRender.setUniformBuffer("p", ubo);
csRender.setStorageBuffer("state", stateBuffer);
// Material Setup // C. Material Setup (Anzeige)
const plane = scene.getMeshByName("plane"); const plane = scene.getMeshByName("plane");
if (plane?.material) { if (plane?.material) {
const mat = plane.material as any; const mat = plane.material as any;
@@ -64,17 +97,41 @@ export class PendulumComponent {
mat.setUniformBuffer("params", ubo); mat.setUniformBuffer("params", ubo);
} }
// Render Loop // D. Simulation Loop
let time = 0; let time = 0;
scene.onBeforeRenderObservable.add(() => {
time += engine.getDeltaTime() / 1000.0;
// Physik Parameter
const dt = 0.015;
const g = 9.81;
const m1 = 2.0;
const m2 = 1.0;
const l1 = 1.5;
const l2 = 1.2;
const damping = 0.99;
scene.onBeforeRenderObservable.add(() => {
time += dt;
// Update Params
ubo.updateFloat2("resolution", width, height); ubo.updateFloat2("resolution", width, height);
ubo.updateFloat("time", time); ubo.updateFloat("time", time);
ubo.updateFloat("dt", dt);
ubo.updateFloat("g", g);
ubo.updateFloat("m1", m1);
ubo.updateFloat("m2", m2);
ubo.updateFloat("l1", l1);
ubo.updateFloat("l2", l2);
ubo.updateFloat("damping", damping);
ubo.updateFloat("pad1", 0);
ubo.updateFloat("pad2", 0);
ubo.update(); ubo.update();
// Do physics (1 thread)
csPhysics.dispatch(1, 1, 1);
// Paint per pixel
const dispatchCount = Math.ceil(totalPixels / 64); const dispatchCount = Math.ceil(totalPixels / 64);
cs.dispatch(dispatchCount, 1, 1); csRender.dispatch(dispatchCount, 1, 1);
}); });
} }
} }

View File

@@ -14,61 +14,179 @@
export const PENDULUM_FRAGMENT_SHADER_WGSL = ` export const PENDULUM_FRAGMENT_SHADER_WGSL = `
varying vUV : vec2<f32>; varying vUV : vec2<f32>;
var<storage, read> pixelBuffer : array<f32>; var<storage, read> pixelBuffer : array<f32>;
var<uniform> params : Params; var<uniform> params : Params;
struct Params { struct Params {
resolution: vec2<f32>, resolution: vec2<f32>,
time: f32 time: f32,
dt: f32,
g: f32,
m1: f32,
m2: f32,
l1: f32,
l2: f32,
damping: f32,
pad1: f32, // <-- Alignment
pad2: f32 // <-- Alignment
}; };
@fragment @fragment
fn main(input : FragmentInputs) -> FragmentOutputs { fn main(input : FragmentInputs) -> FragmentOutputs {
let width = u32(params.resolution.x);
let x = u32(input.vUV.x * params.resolution.x); let x = u32(input.vUV.x * params.resolution.x);
let y = u32(input.vUV.y * params.resolution.y); let y = u32(input.vUV.y * params.resolution.y);
let width = u32(params.resolution.x);
let index = y * width + x; let index = y * width + x;
let total = u32(params.resolution.x * params.resolution.y);
// Default Color (Black) let val = pixelBuffer[index];
var color = vec4<f32>(0.0, 0.0, 0.0, 1.0);
if (index < total) { // Hintergrund = Dunkelgrau, Linien = Grau, Massen = Weiß
let val = pixelBuffer[index]; var color = vec3<f32>(0.1, 0.1, 0.15);
color = vec4<f32>(val, val * 0.5, 0.2, 1.0); if (val > 0.1) { color = vec3<f32>(0.5, 0.5, 0.5); } // Linie
} if (val > 0.8) { color = vec3<f32>(1.0, 1.0, 1.0); } // Masse
//fragmentOutput is provided by babylon
fragmentOutputs.color = color;
fragmentOutputs.color = vec4<f32>(color, 1.0);
return fragmentOutputs; return fragmentOutputs;
} }
`; `;
export const PENDULUM_COMPUTE_SHADER_WGSL = ` export const PENDULUM_PHYSIC_COMPUTE_SHADER_WGSL = `
@group(0) @binding(0) var<storage, read_write> pixelBuffer : array<f32>; struct State {
@group(0) @binding(1) var<uniform> params : Params; theta1: f32,
theta2: f32,
v1: f32,
v2: f32
};
@group(0) @binding(0) var<storage, read_write> state : State;
@group(0) @binding(1) var<uniform> p : Params;
struct Params { struct Params {
resolution: vec2<f32>, resolution: vec2<f32>,
time: f32 time: f32,
dt: f32,
g: f32,
m1: f32,
m2: f32,
l1: f32,
l2: f32,
damping: f32,
pad1: f32, // <-- Alignment
pad2: f32 // <-- Alignment
}; };
@compute @workgroup_size(1)
fn main() {
let t1 = state.theta1;
let t2 = state.theta2;
let v1 = state.v1;
let v2 = state.v2;
let m1 = p.m1;
let m2 = p.m2;
let l1 = p.l1;
let l2 = p.l2;
let g = p.g;
let num1 = -g * (2.0 * m1 + m2) * sin(t1)
- m2 * g * sin(t1 - 2.0 * t2)
- 2.0 * sin(t1 - t2) * m2 * (v2 * v2 * l2 + v1 * v1 * l1 * cos(t1 - t2));
let den1 = l1 * (2.0 * m1 + m2 - m2 * cos(2.0 * t1 - 2.0 * t2));
let a1 = num1 / den1;
let num2 = 2.0 * sin(t1 - t2) * (v1 * v1 * l1 * (m1 + m2) + g * (m1 + m2) * cos(t1) + v2 * v2 * l2 * m2 * cos(t1 - t2));
let den2 = l2 * (2.0 * m1 + m2 - m2 * cos(2.0 * t1 - 2.0 * t2));
let a2 = num2 / den2;
let new_v1 = (v1 + a1 * p.dt) * p.damping;
let new_v2 = (v2 + a2 * p.dt) * p.damping;
state.v1 = new_v1;
state.v2 = new_v2;
state.theta1 = t1 + new_v1 * p.dt;
state.theta2 = t2 + new_v2 * p.dt;
}
`;
export const PENDULUM_RENDER_COMPUTE_SHADER_WGSL = `
struct State {
theta1: f32,
theta2: f32,
v1: f32,
v2: f32
};
@group(0) @binding(0) var<storage, read_write> pixelBuffer : array<f32>;
@group(0) @binding(1) var<uniform> p : Params;
@group(0) @binding(2) var<storage, read> state : State;
struct Params {
resolution: vec2<f32>,
time: f32,
dt: f32,
g: f32,
m1: f32,
m2: f32,
l1: f32,
l2: f32,
damping: f32,
pad1: f32,
pad2: f32
};
fn sdSegment(p: vec2<f32>, a: vec2<f32>, b: vec2<f32>) -> f32 {
let pa = p - a;
let ba = b - a;
let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h);
}
@compute @workgroup_size(64) @compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id : vec3<u32>) { fn main(@builtin(global_invocation_id) global_id : vec3<u32>) {
let index = global_id.x; let index = global_id.x;
let totalPixels = u32(params.resolution.x * params.resolution.y); let width = u32(p.resolution.x);
if (index >= totalPixels) { return; } let height = u32(p.resolution.y);
let width = u32(params.resolution.x); if (index >= width * height) { return; }
let x = f32(index % width);
let y = f32(index / width);
// Zeit-Variable nutzen für Animation let x = f32(index % width);
let value = sin(x * 0.05 + params.time) * cos(y * 0.05 + params.time); let y = f32(index / width);
let uv = vec2<f32>(x / p.resolution.x, y / p.resolution.y);
pixelBuffer[index] = value * 0.5 + 0.5; let aspect = p.resolution.x / p.resolution.y;
let uv_corr = vec2<f32>(uv.x * aspect, uv.y);
var newVal = 0.0;
let origin = vec2<f32>(0.5 * aspect, 0.7);
let s1 = sin(state.theta1);
let c1 = cos(state.theta1);
let s2 = sin(state.theta2);
let c2 = cos(state.theta2);
let displayScale = 0.15;
let p1 = origin + vec2<f32>(s1, -c1) * p.l1 * displayScale;
let p2 = p1 + vec2<f32>(s2, -c2) * p.l2 * displayScale;
let dLine1 = sdSegment(uv_corr, origin, p1);
let dLine2 = sdSegment(uv_corr, p1, p2);
let dMass1 = length(uv_corr - p1);
let dMass2 = length(uv_corr - p2);
// Geometrie zeichnen
let lineThick = 0.003;
let m1Radius = 0.02;
let m2Radius = 0.02;
if (dLine1 < lineThick || dLine2 < lineThick) {
newVal = 0.5;
}
if (dMass1 < m1Radius || dMass2 < m2Radius) {
newVal = 1.0;
}
pixelBuffer[index] = newVal;
} }
`; `;