Compare commits

...

2 Commits

Author SHA1 Message Date
8cdf00fdc9 stop implementing 2026-02-14 15:21:31 +01:00
38e256544f No failing webGPU version found 2026-02-14 11:42:00 +01:00
7 changed files with 292 additions and 56 deletions

View File

@@ -10,7 +10,7 @@ import {UrlConstants} from '../../../constants/UrlConstants';
import {FormsModule} from '@angular/forms';
import {BabylonCanvas, RenderCallback, RenderConfig} from '../../../shared/rendering/canvas/babylon-canvas.component';
import {FRACTAL2D_FRAGMENT, FRACTAL2D_VERTEX} from './fractal.shader';
import {PointerEventTypes, PointerInfo, Scene, ShaderMaterial, Vector2} from '@babylonjs/core';
import {PointerEventTypes, PointerInfo, Scene, ShaderMaterial, Vector2, WebGPUEngine} from '@babylonjs/core';
import {MatButton} from '@angular/material/button';
import {MatIcon} from '@angular/material/icon';
import {NgxSliderModule, Options} from '@angular-slider/ngx-slider';
@@ -74,7 +74,9 @@ export class FractalComponent implements OnInit {
renderConfig: RenderConfig = {
mode: '2D',
pipeline: 'Material',
initialViewSize: 100,
activeAccumulationBuffer: false,
vertexShader: FRACTAL2D_VERTEX,
fragmentShader: FRACTAL2D_FRAGMENT,
uniformNames: ["worldViewProjection", "time", "targetPosition","center", "zoom", "maxIterations", "algorithm", "colorScheme", "juliaC"]
@@ -155,8 +157,8 @@ export class FractalComponent implements OnInit {
}
}
onSceneReady(scene: Scene): void {
scene.onPointerObservable.add((pointerInfo) => {
onSceneReady(options: {scene: Scene, engine: WebGPUEngine}): void {
options.scene.onPointerObservable.add((pointerInfo) => {
switch (pointerInfo.type) {
case PointerEventTypes.POINTERDOWN:

View File

@@ -52,6 +52,8 @@ export class Fractal3dComponent {
fractalConfig: RenderConfig = {
mode: '3D',
pipeline: 'Material',
activeAccumulationBuffer: false,
initialViewSize: 4,
vertexShader: MANDELBULB_VERTEX,
fragmentShader: MANDELBULB_FRAGMENT,

View File

@@ -0,0 +1,126 @@
// path-tracing-shader.ts
export const PATH_TRACING_VERTEX = `
precision highp float;
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUV;
void main(void) {
vUV = uv;
gl_Position = vec4(position, 1.0);
}
`;
export const PATH_TRACING_FRAGMENT = `
precision highp float;
varying vec2 vUV;
uniform vec2 resolution;
uniform vec3 cameraPosition;
uniform vec3 cameraForward;
uniform vec3 cameraRight;
uniform vec3 cameraUp;
uniform float frameCount;
uniform float time;
uniform sampler2D accumulationBuffer;
struct Sphere {
vec3 center;
float radius;
vec3 color;
vec3 emission;
float materialType;
};
float random(inout float seed) {
seed = fract(sin(seed) * 43758.5453123);
return seed;
}
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);
}
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);
}
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;
vec3 ro = cameraPosition;
vec3 rd = normalize(cameraForward + cameraRight * screenPos.x * aspect + cameraUp * screenPos.y);
vec3 incomingLight = vec3(0.0);
vec3 throughput = vec3(1.0);
for (int bounce = 0; bounce < 4; bounce++) {
float tMin = 10000.0;
int hitIdx = -1;
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;
if (h > 0.0) {
float t = -b - sqrt(h);
if (t > 0.001 && t < tMin) {
tMin = t;
hitIdx = i;
}
}
}
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);
}
}
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);
}
}
`;

View File

@@ -5,7 +5,7 @@
<mat-card-content>
<app-information [algorithmInformation]="algoInformation"/>
<app-babylon-canvas
[config]="fractalConfig"
[config]="config"
[renderCallback]="onRender"
/>
</mat-card-content>

View File

@@ -1,48 +1,130 @@
import { Component } from '@angular/core';
import {BabylonCanvas, RenderCallback, RenderConfig} from '../../../shared/rendering/canvas/babylon-canvas.component';
import {Information} from '../information/information';
import {
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';
import {PATH_TRACING_VERTEX_SHADER} from './path-tracing.shader';
@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: '',
entries: [
],
entries: [],
disclaimer: '',
disclaimerBottom: '',
disclaimerListEntry: ['']
disclaimerListEntry: []
};
fractalConfig: RenderConfig = {
mode: '3D',
initialViewSize: 4,
vertexShader: PATH_TRACING_VERTEX_SHADER,
fragmentShader: '',
uniformNames: ["time", "power", "fractalType"]
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 time = 0;
private setupAccumulation(scene: Scene, canvas: HTMLCanvasElement): void {
const size = { width: canvas.width, height: canvas.height };
onRender: RenderCallback = () => {
this.time += 0.005;
};
// 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);
// Wir holen uns das Mesh ("background"), das in BabylonCanvas erstellt wurde
this.screenQuad = scene.getMeshByName("background") as Mesh;
// Sicherheits-Check falls Name abweicht
if (!this.screenQuad && scene.meshes.length > 0) {
this.screenQuad = scene.meshes[0] as Mesh;
}
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.displayLayer = new Layer("display", null, scene, true);
}
}

View File

@@ -1,10 +0,0 @@
export const PATH_TRACING_VERTEX_SHADER = /* glsl */`
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUV;
void main(void) {
gl_Position = vec4(position, 1.0);
vUV = uv;
}
`;

View File

@@ -3,10 +3,12 @@ import {ArcRotateCamera, Camera, Engine, MeshBuilder, Scene, ShaderMaterial, Vec
export interface RenderConfig {
mode: '2D' | '3D';
pipeline: 'Material' | 'Compute';
initialViewSize: number;
vertexShader: string;
fragmentShader: string;
uniformNames: string[];
activeAccumulationBuffer: boolean;
vertexShader?: string;
fragmentShader?: string;
uniformNames?: string[];
}
export type RenderCallback = (material: ShaderMaterial, camera: Camera, canvas: HTMLCanvasElement, scene: Scene) => void;
@@ -25,7 +27,7 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy {
@Input({ required: true }) config!: RenderConfig;
@Input() renderCallback?: RenderCallback;
@Output() sceneReady = new EventEmitter<Scene>();
@Output() sceneReady = new EventEmitter<{scene: Scene, engine: Engine}>();
private engine!: Engine;
private scene!: Scene;
@@ -53,14 +55,26 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy {
}
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 Engine(canvas, true);
this.engine = new Engine(canvas, true, {
preserveDrawingBuffer: true,
stencil: true
});
this.scene = new Scene(this.engine);
this.setupCamera(canvas);
canvas.addEventListener('wheel', (evt: WheelEvent) => evt.preventDefault(), { passive: false });
this.createShaderMaterial();
this.createFullScreenRect();
this.sceneReady.emit(this.scene);
if (this.config.pipeline !== 'Compute') {
this.createShaderMaterial();
this.createFullScreenRect();
}
this.sceneReady.emit({ scene: this.scene, engine: this.engine });
this.addRenderLoop(canvas);
this.addResizeHandler();
}
@@ -102,6 +116,8 @@ export class BabylonCanvas implements AfterViewInit, OnDestroy {
}
private createFullScreenRect() {
if (!this.shaderMaterial) return;
const plane = MeshBuilder.CreatePlane("plane", {size: 110}, this.scene);
if (this.config.mode === '3D') {
@@ -111,11 +127,19 @@ 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)
{
console.warn("Bablyon canvas needs a vertex shader, a fragment shader and a uniforms array.\n");
return;
}
this.shaderMaterial = new ShaderMaterial(
"shaderMaterial",
this.scene,
@@ -123,26 +147,36 @@ 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(() => {
// callback call to call specific uniforms
if (this.renderCallback) {
// callback call to call specific uniforms
this.renderCallback(this.shaderMaterial, this.camera, canvas, this.scene);
}
// default uniforms which maybe each scene has
this.shaderMaterial.setVector2("resolution", new Vector2(canvas.width, canvas.height));
this.shaderMaterial.setVector3("cameraPosition", this.camera.position);
if (this.shaderMaterial) {
// default uniforms which maybe each material scene has
this.shaderMaterial.setVector2("resolution", new Vector2(canvas.width, canvas.height));
this.shaderMaterial.setVector3("cameraPosition", this.camera.position);
}
this.scene.render();
});