stop implementing
This commit is contained in:
@@ -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"]
|
||||
|
||||
@@ -53,6 +53,7 @@ export class Fractal3dComponent {
|
||||
fractalConfig: RenderConfig = {
|
||||
mode: '3D',
|
||||
pipeline: 'Material',
|
||||
activeAccumulationBuffer: false,
|
||||
initialViewSize: 4,
|
||||
vertexShader: MANDELBULB_VERTEX,
|
||||
fragmentShader: MANDELBULB_FRAGMENT,
|
||||
|
||||
@@ -1,137 +1,126 @@
|
||||
export const PATH_TRACING_SHADER = `
|
||||
struct Camera {
|
||||
position: vec4<f32>,
|
||||
forward: vec4<f32>,
|
||||
right: vec4<f32>,
|
||||
up: vec4<f32>
|
||||
};
|
||||
// path-tracing-shader.ts
|
||||
|
||||
struct SceneParams {
|
||||
values: vec4<f32>
|
||||
};
|
||||
export const PATH_TRACING_VERTEX = `
|
||||
precision highp float;
|
||||
attribute vec3 position;
|
||||
attribute vec2 uv;
|
||||
varying vec2 vUV;
|
||||
|
||||
struct Sphere {
|
||||
center: vec3<f32>,
|
||||
radius: f32,
|
||||
color: vec3<f32>,
|
||||
emission: vec3<f32>
|
||||
};
|
||||
void main(void) {
|
||||
vUV = uv;
|
||||
gl_Position = vec4(position, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
@group(0) @binding(0) var outputTex : texture_storage_2d<rgba8unorm, write>;
|
||||
// Uniform -> Storage (read)
|
||||
@group(0) @binding(1) var<storage, read> cam : Camera;
|
||||
// Uniform -> Storage (read)
|
||||
@group(0) @binding(2) var<storage, read> params : SceneParams;
|
||||
export const PATH_TRACING_FRAGMENT = `
|
||||
precision highp float;
|
||||
varying vec2 vUV;
|
||||
|
||||
fn getSceneSphere(i: i32) -> Sphere {
|
||||
var s: Sphere;
|
||||
s.emission = vec3<f32>(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<f32>(-100.5, 0.0, 0.0); s.radius = 100.0; s.color = vec3<f32>(0.8, 0.1, 0.1); }
|
||||
else if (i == 1) { s.center = vec3<f32>( 100.5, 0.0, 0.0); s.radius = 100.0; s.color = vec3<f32>(0.1, 0.8, 0.1); }
|
||||
else if (i == 2) { s.center = vec3<f32>(0.0, 100.5, 0.0); s.radius = 100.0; s.color = vec3<f32>(0.8, 0.8, 0.8); }
|
||||
else if (i == 3) { s.center = vec3<f32>(0.0, -100.5, 0.0); s.radius = 100.0; s.color = vec3<f32>(0.8, 0.8, 0.8); }
|
||||
else if (i == 4) { s.center = vec3<f32>(0.0, 0.0, 100.5); s.radius = 100.0; s.color = vec3<f32>(0.8, 0.8, 0.8); }
|
||||
else if (i == 5) { s.center = vec3<f32>(0.0, 1.5, 0.0); s.radius = 0.3; s.color = vec3<f32>(1.0); s.emission = vec3<f32>(15.0); }
|
||||
else if (i == 6) { s.center = vec3<f32>(-0.3, -0.3, -0.3); s.radius = 0.25; s.color = vec3<f32>(0.9, 0.9, 0.1); }
|
||||
else { s.center = vec3<f32>(0.3, -0.3, 0.2); s.radius = 0.25; s.color = vec3<f32>(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<f32>, rd: vec3<f32>, 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<function, u32>) -> 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<f32>, seed: ptr<function, u32>) -> vec3<f32> {
|
||||
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<f32>(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<u32>) {
|
||||
let dims = textureDimensions(outputTex);
|
||||
let coord = vec2<i32>(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<f32>(coord) / vec2<f32>(dims)) * 2.0 - 1.0;
|
||||
let aspect = f32(dims.x) / f32(dims.y);
|
||||
let screenPos = vec2<f32>(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<f32>(0.0);
|
||||
var throughput = vec3<f32>(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<f32>(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<f32>(1.0, 0.0, 0.0, 1.0));
|
||||
|
||||
textureStore(outputTex, coord, vec4<f32>(col, 1.0));
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
export const RED_SHADER = `
|
||||
@group(0) @binding(0) var outputTex : texture_storage_2d<rgba8unorm, write>;
|
||||
|
||||
@compute @workgroup_size(1, 1, 1)
|
||||
fn main(@builtin(global_invocation_id) global_id : vec3<u32>) {
|
||||
// Schreibe Rot (R=1, G=0, B=0, A=1) an die Pixel-Position
|
||||
textureStore(outputTex, global_id.xy, vec4<f32>(1.0, 0.0, 0.0, 1.0));
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<mat-card-content>
|
||||
<app-information [algorithmInformation]="algoInformation"/>
|
||||
<app-babylon-canvas
|
||||
[config]="fractalConfig"
|
||||
(sceneReady)="onSceneReady($event)"
|
||||
[config]="config"
|
||||
[renderCallback]="onRender"
|
||||
/>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
@@ -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<rgba8unorm, write>;
|
||||
@compute @workgroup_size(8, 8, 1)
|
||||
fn main(@builtin(global_invocation_id) gid : vec3<u32>) {
|
||||
textureStore(outputTex, gid.xy, vec4<f32>(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<HTMLCanvasElement>;
|
||||
|
||||
@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<void> {
|
||||
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(() => {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user