From 8cdf00fdc9f9e9d14bd79ae799360e4d533a0b65 Mon Sep 17 00:00:00 2001 From: Lobo Date: Sat, 14 Feb 2026 15:21:31 +0100 Subject: [PATCH] stop implementing --- .../algorithms/fractal/fractal.component.ts | 1 + .../fractal3d/fractal3d.component.ts | 1 + .../path-tracing/path-tracing-shader.ts | 219 +++++++++--------- .../path-tracing/path-tracing.component.html | 4 +- .../path-tracing/path-tracing.component.ts | 182 +++++++++------ .../canvas/babylon-canvas.component.ts | 41 ++-- 6 files changed, 240 insertions(+), 208 deletions(-) diff --git a/src/app/pages/algorithms/fractal/fractal.component.ts b/src/app/pages/algorithms/fractal/fractal.component.ts index 276e99f..2cf108d 100644 --- a/src/app/pages/algorithms/fractal/fractal.component.ts +++ b/src/app/pages/algorithms/fractal/fractal.component.ts @@ -76,6 +76,7 @@ export class FractalComponent implements OnInit { mode: '2D', pipeline: 'Material', initialViewSize: 100, + activeAccumulationBuffer: false, vertexShader: FRACTAL2D_VERTEX, fragmentShader: FRACTAL2D_FRAGMENT, uniformNames: ["worldViewProjection", "time", "targetPosition","center", "zoom", "maxIterations", "algorithm", "colorScheme", "juliaC"] diff --git a/src/app/pages/algorithms/fractal3d/fractal3d.component.ts b/src/app/pages/algorithms/fractal3d/fractal3d.component.ts index 32c677c..1537a77 100644 --- a/src/app/pages/algorithms/fractal3d/fractal3d.component.ts +++ b/src/app/pages/algorithms/fractal3d/fractal3d.component.ts @@ -53,6 +53,7 @@ export class Fractal3dComponent { fractalConfig: RenderConfig = { mode: '3D', pipeline: 'Material', + activeAccumulationBuffer: false, initialViewSize: 4, vertexShader: MANDELBULB_VERTEX, fragmentShader: MANDELBULB_FRAGMENT, diff --git a/src/app/pages/algorithms/path-tracing/path-tracing-shader.ts b/src/app/pages/algorithms/path-tracing/path-tracing-shader.ts index 998ce51..593c18f 100644 --- a/src/app/pages/algorithms/path-tracing/path-tracing-shader.ts +++ b/src/app/pages/algorithms/path-tracing/path-tracing-shader.ts @@ -1,137 +1,126 @@ -export const PATH_TRACING_SHADER = ` -struct Camera { - position: vec4, - forward: vec4, - right: vec4, - up: vec4 -}; +// path-tracing-shader.ts -struct SceneParams { - values: vec4 -}; +export const PATH_TRACING_VERTEX = ` + precision highp float; + attribute vec3 position; + attribute vec2 uv; + varying vec2 vUV; -struct Sphere { - center: vec3, - radius: f32, - color: vec3, - emission: vec3 -}; + void main(void) { + vUV = uv; + gl_Position = vec4(position, 1.0); + } +`; -@group(0) @binding(0) var outputTex : texture_storage_2d; -// Uniform -> Storage (read) -@group(0) @binding(1) var cam : Camera; -// Uniform -> Storage (read) -@group(0) @binding(2) var params : SceneParams; +export const PATH_TRACING_FRAGMENT = ` + precision highp float; + varying vec2 vUV; -fn getSceneSphere(i: i32) -> Sphere { - var s: Sphere; - s.emission = vec3(0.0); + uniform vec2 resolution; + uniform vec3 cameraPosition; + uniform vec3 cameraForward; + uniform vec3 cameraRight; + uniform vec3 cameraUp; + uniform float frameCount; + uniform float time; - if (i == 0) { s.center = vec3(-100.5, 0.0, 0.0); s.radius = 100.0; s.color = vec3(0.8, 0.1, 0.1); } - else if (i == 1) { s.center = vec3( 100.5, 0.0, 0.0); s.radius = 100.0; s.color = vec3(0.1, 0.8, 0.1); } - else if (i == 2) { s.center = vec3(0.0, 100.5, 0.0); s.radius = 100.0; s.color = vec3(0.8, 0.8, 0.8); } - else if (i == 3) { s.center = vec3(0.0, -100.5, 0.0); s.radius = 100.0; s.color = vec3(0.8, 0.8, 0.8); } - else if (i == 4) { s.center = vec3(0.0, 0.0, 100.5); s.radius = 100.0; s.color = vec3(0.8, 0.8, 0.8); } - else if (i == 5) { s.center = vec3(0.0, 1.5, 0.0); s.radius = 0.3; s.color = vec3(1.0); s.emission = vec3(15.0); } - else if (i == 6) { s.center = vec3(-0.3, -0.3, -0.3); s.radius = 0.25; s.color = vec3(0.9, 0.9, 0.1); } - else { s.center = vec3(0.3, -0.3, 0.2); s.radius = 0.25; s.color = vec3(0.2, 0.2, 0.9); } + uniform sampler2D accumulationBuffer; - return s; -} + struct Sphere { + vec3 center; + float radius; + vec3 color; + vec3 emission; + float materialType; + }; -fn hitSphere(ro: vec3, rd: vec3, s: Sphere) -> f32 { - let oc = ro - s.center; - let b = dot(oc, rd); - let c = dot(oc, oc) - s.radius * s.radius; - let h = b*b - c; - if (h < 0.0) { return -1.0; } - return -b - sqrt(h); -} + float random(inout float seed) { + seed = fract(sin(seed) * 43758.5453123); + return seed; + } -fn rand(seed: ptr) -> f32 { - *seed = *seed * 747796405u + 2891336453u; - let word = ((*seed >> ((*seed >> 28u) + 4u)) ^ *seed) * 277803737u; - return f32((word >> 22u) ^ word) / 4294967296.0; -} + vec3 randomHemisphereDir(vec3 normal, inout float seed) { + float phi = 2.0 * 3.14159265 * random(seed); + float cosTheta = random(seed); + float sinTheta = sqrt(1.0 - cosTheta * cosTheta); + vec3 up = abs(normal.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, normal)); + vec3 bitangent = cross(normal, tangent); + return normalize(tangent * cos(phi) * sinTheta + bitangent * sin(phi) * sinTheta + normal * cosTheta); + } -fn randomHemisphereDir(normal: vec3, seed: ptr) -> vec3 { - let r1 = rand(seed); - let r2 = rand(seed); - let theta = 6.283185 * r1; - let phi = acos(2.0 * r2 - 1.0); - let x = sin(phi) * cos(theta); - let y = sin(phi) * sin(theta); - let z = cos(phi); - let v = normalize(vec3(x, y, z)); - if (dot(v, normal) < 0.0) { return -v; } - return v; -} + Sphere getSphere(int index) { + if (index == 0) { return Sphere(vec3(-100.5, 0.0, 0.0), 100.0, vec3(0.8, 0.1, 0.1), vec3(0.0), 0.0); } + if (index == 1) { return Sphere(vec3( 100.5, 0.0, 0.0), 100.0, vec3(0.1, 0.8, 0.1), vec3(0.0), 0.0); } + if (index == 2) { return Sphere(vec3(0.0, 100.5, 0.0), 100.0, vec3(0.8, 0.8, 0.8), vec3(0.0), 0.0); } + if (index == 3) { return Sphere(vec3(0.0, -100.5, 0.0), 100.0, vec3(0.8, 0.8, 0.8), vec3(0.0), 0.0); } + if (index == 4) { return Sphere(vec3(0.0, 0.0, 100.5), 100.0, vec3(0.8, 0.8, 0.8), vec3(0.0), 0.0); } + if (index == 5) { return Sphere(vec3(0.0, 1.4, 0.0), 0.25, vec3(1.0), vec3(20.0), 0.0); } + if (index == 6) { return Sphere(vec3(-0.4, -0.4, -0.1), 0.25, vec3(1.0), vec3(0.0), 1.0); } + return Sphere(vec3(0.4, -0.4, 0.3), 0.25, vec3(0.2, 0.4, 0.9), vec3(0.0), 0.0); + } -@compute @workgroup_size(8, 8, 1) -fn main(@builtin(global_invocation_id) global_id : vec3) { - let dims = textureDimensions(outputTex); - let coord = vec2(global_id.xy); + void main(void) { + float seed = dot(vUV, vec2(12.9898, 78.233)) + time; + vec2 jitter = vec2(random(seed), random(seed)) / resolution; + vec2 screenPos = (vUV + jitter) * 2.0 - 1.0; + float aspect = resolution.x / resolution.y; - if (coord.x >= i32(dims.x) || coord.y >= i32(dims.y)) { return; } + vec3 ro = cameraPosition; + vec3 rd = normalize(cameraForward + cameraRight * screenPos.x * aspect + cameraUp * screenPos.y); - let uv = (vec2(coord) / vec2(dims)) * 2.0 - 1.0; - let aspect = f32(dims.x) / f32(dims.y); - let screenPos = vec2(uv.x * aspect, -uv.y); + vec3 incomingLight = vec3(0.0); + vec3 throughput = vec3(1.0); - var ro = cam.position.xyz; - var rd = normalize(cam.forward.xyz + cam.right.xyz * screenPos.x + cam.up.xyz * screenPos.y); + for (int bounce = 0; bounce < 4; bounce++) { + float tMin = 10000.0; + int hitIdx = -1; - var col = vec3(0.0); - var throughput = vec3(1.0); - // Zugriff auf params.values statt params.x - var seed = u32(global_id.x + global_id.y * dims.x) + u32(params.values.x) * 719393u; + for (int i = 0; i < 8; i++) { + Sphere s = getSphere(i); + vec3 oc = ro - s.center; + float b = dot(oc, rd); + float c = dot(oc, oc) - s.radius * s.radius; + float h = b * b - c; - for (var i = 0; i < 4; i++) { - var tMin = 10000.0; - var hitIndex = -1; + if (h > 0.0) { + float t = -b - sqrt(h); + if (t > 0.001 && t < tMin) { + tMin = t; + hitIdx = i; + } + } + } - for (var j = 0; j < 8; j++) { - let s = getSceneSphere(j); - let t = hitSphere(ro, rd, s); - if (t > 0.001 && t < tMin) { - tMin = t; - hitIndex = j; + if (hitIdx == -1) { + incomingLight += throughput * vec3(0.01); + break; + } + + Sphere s = getSphere(hitIdx); + vec3 hitPoint = ro + rd * tMin; + vec3 normal = normalize(hitPoint - s.center); + + incomingLight += s.emission * throughput; + throughput *= s.color; + + ro = hitPoint; + if (s.materialType > 0.5) { + rd = reflect(rd, normal); + } else { + rd = randomHemisphereDir(normal, seed); } } - if (hitIndex == -1) { - col = col + throughput * vec3(0.1, 0.1, 0.15); - break; + vec3 prevColor = texture2D(accumulationBuffer, vUV).rgb; + + // Akkumulation + if (frameCount < 1.0) { + gl_FragColor = vec4(incomingLight, 1.0); + } else { + float weight = 1.0 / (frameCount + 1.0); + vec3 final = mix(prevColor, incomingLight, weight); + gl_FragColor = vec4(final, 1.0); } - - let hitSphere = getSceneSphere(hitIndex); - let hitPos = ro + rd * tMin; - let normal = normalize(hitPos - hitSphere.center); - - if (length(hitSphere.emission) > 0.0) { - col = col + throughput * hitSphere.emission; - break; - } - - throughput = throughput * hitSphere.color; - ro = hitPos; - rd = randomHemisphereDir(normal, &seed); } - - // Debug: Falls Bild immer noch schwarz, entkommentieren: - // textureStore(outputTex, coord, vec4(1.0, 0.0, 0.0, 1.0)); - - textureStore(outputTex, coord, vec4(col, 1.0)); -} -`; - - -export const RED_SHADER = ` -@group(0) @binding(0) var outputTex : texture_storage_2d; - -@compute @workgroup_size(1, 1, 1) -fn main(@builtin(global_invocation_id) global_id : vec3) { - // Schreibe Rot (R=1, G=0, B=0, A=1) an die Pixel-Position - textureStore(outputTex, global_id.xy, vec4(1.0, 0.0, 0.0, 1.0)); -} `; diff --git a/src/app/pages/algorithms/path-tracing/path-tracing.component.html b/src/app/pages/algorithms/path-tracing/path-tracing.component.html index d3be535..812685b 100644 --- a/src/app/pages/algorithms/path-tracing/path-tracing.component.html +++ b/src/app/pages/algorithms/path-tracing/path-tracing.component.html @@ -5,8 +5,8 @@ diff --git a/src/app/pages/algorithms/path-tracing/path-tracing.component.ts b/src/app/pages/algorithms/path-tracing/path-tracing.component.ts index 303a457..b852671 100644 --- a/src/app/pages/algorithms/path-tracing/path-tracing.component.ts +++ b/src/app/pages/algorithms/path-tracing/path-tracing.component.ts @@ -1,102 +1,130 @@ import { Component } from '@angular/core'; -import { BabylonCanvas, RenderConfig } from '../../../shared/rendering/canvas/babylon-canvas.component'; -import { Information } from '../information/information'; -import { MatCard, MatCardContent, MatCardHeader, MatCardTitle } from '@angular/material/card'; -import { TranslatePipe } from '@ngx-translate/core'; -import { AlgorithmInformation } from '../information/information.models'; +import {BabylonCanvas, RenderCallback, RenderConfig} from '../../../shared/rendering/canvas/babylon-canvas.component'; import { - ComputeShader, - Layer, - RawTexture, - Scene, - WebGPUEngine, - Constants + Camera, RenderTargetTexture, + Scene, ShaderMaterial, + Vector3, + Constants, Layer, Vector2, Mesh } from '@babylonjs/core'; +import {PATH_TRACING_FRAGMENT, PATH_TRACING_VERTEX} from './path-tracing-shader'; +import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; +import {Information} from '../information/information'; +import {TranslatePipe} from '@ngx-translate/core'; +import {AlgorithmInformation} from '../information/information.models'; @Component({ selector: 'app-path-tracing', - imports: [ - BabylonCanvas, - Information, - MatCard, - MatCardContent, - MatCardHeader, - MatCardTitle, - TranslatePipe - ], + imports: [BabylonCanvas, MatCardContent, MatCard, Information, MatCardTitle, MatCardHeader, TranslatePipe], templateUrl: './path-tracing.component.html', - styleUrl: './path-tracing.component.scss', + styleUrls: ['./path-tracing.component.scss'], + standalone: true, }) export class PathTracingComponent { - algoInformation: AlgorithmInformation = { - title: 'WebGPU Debug', + title: '', entries: [], disclaimer: '', disclaimerBottom: '', disclaimerListEntry: [] }; - fractalConfig: RenderConfig = { - mode: '3D', - pipeline: 'Compute', - initialViewSize: 4, + config: RenderConfig = + { mode: '3D', + pipeline: 'Material', + initialViewSize: 4, + activeAccumulationBuffer: true, + vertexShader: PATH_TRACING_VERTEX, + fragmentShader: PATH_TRACING_FRAGMENT, + uniformNames: ["cameraForward", "cameraRight", "cameraUp", "time", "frameCount"] + }; + + private frameCount: number = 0; + private lastCamPos: Vector3 = new Vector3(); + private rttA?: RenderTargetTexture; + private rttB?: RenderTargetTexture; + private isUsingA: boolean = true; + private displayLayer?: Layer; + private screenQuad?: Mesh; + + onRender: RenderCallback = (material: ShaderMaterial, camera: Camera, canvas: HTMLCanvasElement, scene: Scene) => { + if (!material || !camera) return; + + // 1. Setup beim ersten Frame + if (!this.rttA || !this.rttB) { + this.setupAccumulation(scene, canvas); + return; + } + + // 2. Kamera Bewegung Check + if (!camera.position.equals(this.lastCamPos)) { + this.frameCount = 0; + this.lastCamPos.copyFrom(camera.position); + } else { + this.frameCount++; + } + + const forward = camera.getForwardRay().direction; + const right = Vector3.Cross(forward, camera.upVector).normalize(); + const up = Vector3.Cross(right, forward).normalize(); + + material.setVector3("cameraForward", forward); + material.setVector3("cameraRight", right); + material.setVector3("cameraUp", up); + material.setFloat("time", performance.now() / 1000.0); + material.setFloat("frameCount", this.frameCount); + material.setVector2("resolution", new Vector2(canvas.width, canvas.height)); + + // 3. Ping-Pong Zuweisung + const source = this.isUsingA ? this.rttA : this.rttB; + const target = this.isUsingA ? this.rttB : this.rttA; + + // Shader liest aus alter Textur + material.setTexture("accumulationBuffer", source); + + // Wenn das Mesh existiert, rendern wir es in die NEUE Textur + if (this.screenQuad) { + // WICHTIG: Das Mesh muss im RTT Mode sichtbar sein + this.screenQuad.isVisible = true; + + // Rendert den Shader auf das Quad und speichert es in 'target' + target.render(); + + // Verstecken für den normalen Screen-Render-Pass + this.screenQuad.isVisible = false; + } + + // 4. Anzeige auf dem Bildschirm + if (this.displayLayer) { + this.displayLayer.texture = target; + } + + this.isUsingA = !this.isUsingA; }; - private cs!: ComputeShader; - private texture!: RawTexture; + private setupAccumulation(scene: Scene, canvas: HTMLCanvasElement): void { + const size = { width: canvas.width, height: canvas.height }; - onSceneReady(payload: { scene: Scene, engine: WebGPUEngine }) { - const { scene, engine } = payload; - const canvas = engine.getRenderingCanvas()!; - const width = 512; - const height = 512; + // Float Texturen sind entscheidend für Akkumulation + this.rttA = new RenderTargetTexture("rttA", size, scene, false, true, Constants.TEXTURETYPE_FLOAT); + this.rttB = new RenderTargetTexture("rttB", size, scene, false, true, Constants.TEXTURETYPE_FLOAT); - // 1. Textur erstellen (Storage) - this.texture = new RawTexture( - new Uint8Array(width * height * 4), - width, - height, - Constants.TEXTUREFORMAT_RGBA, - scene, - false, - false, - Constants.TEXTURE_NEAREST_SAMPLINGMODE, - Constants.TEXTURETYPE_UNSIGNED_BYTE, - Constants.TEXTURE_CREATIONFLAG_STORAGE - ); + // Wir holen uns das Mesh ("background"), das in BabylonCanvas erstellt wurde + this.screenQuad = scene.getMeshByName("background") as Mesh; - // 2. Minimal-Shader - const shaderCode = ` - @group(0) @binding(0) var outputTex : texture_storage_2d; - @compute @workgroup_size(8, 8, 1) - fn main(@builtin(global_invocation_id) gid : vec3) { - textureStore(outputTex, gid.xy, vec4(0.0, 1.0, 0.0, 1.0)); - } - `; + // Sicherheits-Check falls Name abweicht + if (!this.screenQuad && scene.meshes.length > 0) { + this.screenQuad = scene.meshes[0] as Mesh; + } - // 3. Shader erstellen - this.cs = new ComputeShader( - "simple", - engine, - { computeSource: shaderCode }, - { - bindingsMapping: { "outputTex": { group: 0, binding: 0 } }, - entryPoint: "main" - } - ); + if (this.screenQuad) { + // Wir sagen den Texturen: "Nur dieses Mesh rendern!" + this.rttA.renderList = [this.screenQuad]; + this.rttB.renderList = [this.screenQuad]; + this.screenQuad.isVisible = false; // Initial aus + } else { + console.error("Screen Quad not found!"); + } - this.cs.setTexture("outputTex", this.texture); - - // 4. Layer - const layer = new Layer("viewLayer", null, scene); - layer.texture = this.texture; - - // 5. Der Trick: Einmaliger Dispatch nach einer kurzen Pause - // Das umgeht alle "isReady" oder Binding-Timing Probleme - setTimeout(() => { - console.log("Forcing Compute Dispatch..."); - this.cs.dispatch(width / 8, height / 8, 1); - }, 200); + this.displayLayer = new Layer("display", null, scene, true); } } diff --git a/src/app/shared/rendering/canvas/babylon-canvas.component.ts b/src/app/shared/rendering/canvas/babylon-canvas.component.ts index 8af4741..924d049 100644 --- a/src/app/shared/rendering/canvas/babylon-canvas.component.ts +++ b/src/app/shared/rendering/canvas/babylon-canvas.component.ts @@ -1,10 +1,11 @@ -import {AfterViewInit, Component, ElementRef, EventEmitter, Input, OnDestroy, Output, ViewChild} from '@angular/core'; -import {ArcRotateCamera, Camera, MeshBuilder, Scene, ShaderMaterial, Vector2, Vector3, WebGPUEngine} from '@babylonjs/core'; +import {AfterViewInit, Component, ElementRef, EventEmitter, inject, Input, NgZone, OnDestroy, Output, ViewChild} from '@angular/core'; +import {ArcRotateCamera, Camera, Engine, MeshBuilder, Scene, ShaderMaterial, Vector2, Vector3} from '@babylonjs/core'; export interface RenderConfig { mode: '2D' | '3D'; pipeline: 'Material' | 'Compute'; initialViewSize: number; + activeAccumulationBuffer: boolean; vertexShader?: string; fragmentShader?: string; uniformNames?: string[]; @@ -19,21 +20,24 @@ export type RenderCallback = (material: ShaderMaterial, camera: Camera, canvas: styleUrl: './babylon-canvas.component.scss', }) export class BabylonCanvas implements AfterViewInit, OnDestroy { + readonly ngZone = inject(NgZone); @ViewChild('renderCanvas', { static: true }) canvasRef!: ElementRef; @Input({ required: true }) config!: RenderConfig; @Input() renderCallback?: RenderCallback; - @Output() sceneReady = new EventEmitter<{scene: Scene, engine: WebGPUEngine}>(); + @Output() sceneReady = new EventEmitter<{scene: Scene, engine: Engine}>(); - private engine!: WebGPUEngine; + private engine!: Engine; private scene!: Scene; private shaderMaterial!: ShaderMaterial; private camera!: Camera; ngAfterViewInit(): void { - this.initBabylon().then(r => console.log("Rendering engine initialized.")); + this.ngZone.runOutsideAngular(() => { + this.initBabylon(); + }); } /*ngOnChanges(changes: SimpleChanges): void { @@ -50,17 +54,17 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { } } - async initBabylon(): Promise { + private initBabylon(): void { if (!navigator.gpu) { alert("Your browser does not support webgpu, maybe you have activate the hardware acceleration.!"); return; } const canvas = this.canvasRef.nativeElement; - this.engine = new WebGPUEngine(canvas, { - antialias: true + this.engine = new Engine(canvas, true, { + preserveDrawingBuffer: true, + stencil: true }); - await this.engine.initAsync(); this.scene = new Scene(this.engine); @@ -123,13 +127,14 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { plane.lookAt(this.camera.position); } plane.alwaysSelectAsActiveMesh = true; + plane.infiniteDistance = true; plane.material = this.shaderMaterial; } private createShaderMaterial() { - if (!this.config.vertexShader || !this.config.fragmentShader || !this.config.uniformNames) + if (!this.config.vertexShader || !this.config.fragmentShader) { console.warn("Bablyon canvas needs a vertex shader, a fragment shader and a uniforms array.\n"); return; @@ -142,15 +147,23 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy { vertexSource: this.config.vertexShader, fragmentSource: this.config.fragmentShader }, - { - attributes: ["position", "uv"], - uniforms: ["resolution", "cameraPosition", ...this.config.uniformNames] - } + this.getOptions() ); this.shaderMaterial.disableDepthWrite = true; this.shaderMaterial.backFaceCulling = false; } + private getOptions() { + const uniforms = ["resolution", "cameraPosition", ...(this.config.uniformNames || [])]; + const samplers = this.config.activeAccumulationBuffer ? ["accumulationBuffer"] : []; + + return { + attributes: ["position", "uv"], + uniforms: uniforms, + samplers: samplers + }; + } + private addRenderLoop(canvas: HTMLCanvasElement) { this.engine.runRenderLoop(() => {