@plasius/gpu-renderer 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasius/gpu-renderer",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Framework-agnostic WebGPU renderer runtime for Plasius projects.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
package/src/index.d.ts CHANGED
@@ -315,6 +315,7 @@ export interface WavefrontPathTracingComputeConfig {
315
315
  readonly maxDepth: number;
316
316
  readonly tileSize: number;
317
317
  readonly samplesPerPixel: number;
318
+ readonly maxFramePassesPerSubmission: number;
318
319
  readonly tilePixelCapacity: number;
319
320
  readonly sceneObjects: readonly WavefrontSceneObject[];
320
321
  readonly sceneObjectCount: number;
@@ -413,6 +414,7 @@ export interface CreateWavefrontPathTracingComputeRendererOptions {
413
414
  readonly maxDepth?: number;
414
415
  readonly tileSize?: number;
415
416
  readonly samplesPerPixel?: number;
417
+ readonly maxFramePassesPerSubmission?: number;
416
418
  readonly tilePixelCapacity?: number;
417
419
  readonly sceneObjectCapacity?: number;
418
420
  readonly sceneObjects?: readonly WavefrontSceneObjectInput[];
@@ -458,6 +460,7 @@ export interface WavefrontPathTracingComputeRenderer {
458
460
  }>
459
461
  >;
460
462
  updateSceneObjects(sceneObjects: readonly WavefrontSceneObjectInput[]): WavefrontPathTracingComputeConfig;
463
+ updateCamera(camera: WavefrontCameraOptions): WavefrontPathTracingComputeConfig;
461
464
  getSnapshot(): Readonly<{
462
465
  frame: number;
463
466
  width: number;
@@ -466,6 +469,7 @@ export interface WavefrontPathTracingComputeRenderer {
466
469
  tiles: number;
467
470
  tileSize: number;
468
471
  samplesPerPixel: number;
472
+ maxFramePassesPerSubmission: number;
469
473
  sceneObjectCount: number;
470
474
  triangleCount: number;
471
475
  emissiveTriangleCount: number;
@@ -491,6 +495,7 @@ export interface WavefrontPathTracingComputeFrameStats {
491
495
  readonly tiles: number;
492
496
  readonly tileSize: number;
493
497
  readonly samplesPerPixel: number;
498
+ readonly maxFramePassesPerSubmission: number;
494
499
  readonly screenRays: number;
495
500
  readonly primaryRays: number;
496
501
  readonly sceneObjectCount: number;
@@ -3,6 +3,7 @@ const DEFAULT_HEIGHT = 720;
3
3
  const DEFAULT_MAX_DEPTH = 6;
4
4
  const DEFAULT_TILE_SIZE = 128;
5
5
  const DEFAULT_SAMPLES_PER_PIXEL = 1;
6
+ const DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION = 256;
6
7
  const DEFAULT_SCENE_OBJECT_CAPACITY = 128;
7
8
  const DEFAULT_ENVIRONMENT_PORTAL_CAPACITY = 32;
8
9
  const WORKGROUP_SIZE = 64;
@@ -1193,6 +1194,15 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
1193
1194
  1,
1194
1195
  64
1195
1196
  );
1197
+ const maxFramePassesPerSubmission = clamp(
1198
+ readPositiveInteger(
1199
+ "maxFramePassesPerSubmission",
1200
+ options.maxFramePassesPerSubmission,
1201
+ DEFAULT_MAX_FRAME_PASSES_PER_SUBMISSION
1202
+ ),
1203
+ 1,
1204
+ 4096
1205
+ );
1196
1206
  const tilePixelCapacity = readPositiveInteger(
1197
1207
  "tilePixelCapacity",
1198
1208
  options.tilePixelCapacity,
@@ -1284,6 +1294,7 @@ export function createWavefrontPathTracingComputeConfig(options = {}) {
1284
1294
  maxDepth,
1285
1295
  tileSize,
1286
1296
  samplesPerPixel,
1297
+ maxFramePassesPerSubmission,
1287
1298
  tilePixelCapacity,
1288
1299
  sceneObjects,
1289
1300
  sceneObjectCount: sceneObjects.length,
@@ -2209,6 +2220,80 @@ fn terminal_surface_environment_contribution(ray: RayRecord, hit: HitRecord) ->
2209
2220
  return clamp_sample_radiance(ray.throughput.xyz * surfaceColor * environmentFloor * materialFloor);
2210
2221
  }
2211
2222
 
2223
+ fn direct_environment_portal_irradiance(origin: vec3<f32>, normal: vec3<f32>) -> vec3<f32> {
2224
+ if (config.environmentPortalCount == 0u || config.environmentPortalMode == 0u) {
2225
+ return vec3<f32>(0.0);
2226
+ }
2227
+
2228
+ var irradiance = vec3<f32>(0.0);
2229
+ for (var portalIndex = 0u; portalIndex < config.environmentPortalCount; portalIndex = portalIndex + 1u) {
2230
+ let portal = environmentPortals[portalIndex];
2231
+ if (portal.kind != 1u) {
2232
+ continue;
2233
+ }
2234
+
2235
+ let toPortal = portal.position.xyz - origin;
2236
+ let distanceSquared = max(dot(toPortal, toPortal), 0.01);
2237
+ let direction = safe_normalize(toPortal, normal);
2238
+ let surfaceFacing = saturate(dot(normal, direction));
2239
+ if (surfaceFacing <= 0.0001) {
2240
+ continue;
2241
+ }
2242
+
2243
+ let portalNormal = safe_normalize(portal.normal.xyz, vec3<f32>(0.0, 0.0, 1.0));
2244
+ let twoSided = (portal.flags & 1u) != 0u;
2245
+ let portalFacing = select(
2246
+ saturate(dot(-direction, portalNormal)),
2247
+ max(abs(dot(direction, portalNormal)), 0.15),
2248
+ twoSided
2249
+ );
2250
+ let area = max(portal.position.w, 0.0001);
2251
+ let distanceFalloff = clamp(area / max(distanceSquared, area * 0.25), 0.0, 2.5);
2252
+ irradiance = irradiance +
2253
+ portal.color.rgb *
2254
+ portal.normal.w *
2255
+ portal.color.a *
2256
+ surfaceFacing *
2257
+ portalFacing *
2258
+ distanceFalloff;
2259
+ }
2260
+ return irradiance;
2261
+ }
2262
+
2263
+ fn surface_direct_environment_contribution(ray: RayRecord, hit: HitRecord) -> vec3<f32> {
2264
+ let normal = safe_normalize(hit.shadingNormal.xyz, vec3<f32>(0.0, 1.0, 0.0));
2265
+ let origin = hit.position.xyz + normal * 0.003;
2266
+ let viewDirection = safe_normalize(-ray.direction.xyz, normal);
2267
+ let surfaceColor = clamp(max(hit.color.xyz, config.ambientColor.xyz * 0.35), vec3<f32>(0.0), vec3<f32>(1.0));
2268
+ let roughness = clamp(hit.material.x, 0.0, 1.0);
2269
+ let metallic = clamp(hit.material.y, 0.0, 1.0);
2270
+
2271
+ let normalEnvironment = gated_environment_radiance(origin, normal);
2272
+ let skyVisibility = 0.35 + saturate(normal.y * 0.5 + 0.5) * 0.45;
2273
+ let skyIrradiance = max(config.ambientColor.xyz * 0.72, normalEnvironment * skyVisibility * 0.16);
2274
+
2275
+ let sunDirection = safe_normalize(
2276
+ config.environmentSunDirectionIntensity.xyz,
2277
+ vec3<f32>(0.0, 1.0, 0.0)
2278
+ );
2279
+ let sunFacing = saturate(dot(normal, sunDirection));
2280
+ let sunRadiance = gated_environment_radiance(origin, sunDirection);
2281
+ let sunIrradiance = sunRadiance * sunFacing * 0.2;
2282
+ let portalIrradiance = direct_environment_portal_irradiance(origin, normal);
2283
+
2284
+ let diffuseWeight = select(1.0 - metallic * 0.65, 0.22, hit.materialKind == 1u);
2285
+ let diffuse = surfaceColor * (skyIrradiance + sunIrradiance + portalIrradiance) * diffuseWeight;
2286
+
2287
+ let halfVector = safe_normalize(sunDirection + viewDirection, normal);
2288
+ let specularPower = 8.0 + (1.0 - roughness) * 96.0;
2289
+ let specularFacing = pow(saturate(dot(normal, halfVector)), specularPower) * sunFacing;
2290
+ let specularTint = mix(vec3<f32>(0.04), surfaceColor, metallic);
2291
+ let specular = specularTint * sunRadiance * specularFacing * select(0.16, 0.48, hit.materialKind == 1u || hit.materialKind == 2u);
2292
+
2293
+ let bounceWeight = select(1.0, 0.38, ray.bounce > 0u);
2294
+ return clamp_sample_radiance(ray.throughput.xyz * (diffuse + specular) * bounceWeight);
2295
+ }
2296
+
2212
2297
  fn default_mesh_range() -> MeshRange {
2213
2298
  return MeshRange(
2214
2299
  0u,
@@ -3078,6 +3163,14 @@ fn resolveSurfaceRecords(@builtin(global_invocation_id) globalId: vec3<u32>) {
3078
3163
  return;
3079
3164
  }
3080
3165
 
3166
+ let shouldEstimateDirectEnvironment =
3167
+ (hit.materialKind == 0u || hit.materialKind == 1u) && hit.material.z >= 0.95;
3168
+ if (shouldEstimateDirectEnvironment) {
3169
+ let directEnvironment = surface_direct_environment_contribution(ray, hit);
3170
+ accumulation[ray.rayId] =
3171
+ accumulation[ray.rayId] + vec4<f32>(directEnvironment * sample_weight(), 0.0);
3172
+ }
3173
+
3081
3174
  if (ray.bounce + 1u >= config.maxDepth) {
3082
3175
  let terminalEnvironment = terminal_surface_environment_contribution(ray, hit);
3083
3176
  accumulation[ray.rayId] =
@@ -3787,6 +3880,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
3787
3880
  let frame = 0;
3788
3881
  let accelerationBuilt = !config.gpuAccelerationBuildRequired;
3789
3882
  let accelerationBuildCount = 0;
3883
+ let activeCameraOptions = options.camera ?? DEFAULT_CAMERA;
3790
3884
 
3791
3885
  function createFrameConfigWriter(frameIndex) {
3792
3886
  let slot = 0;
@@ -3957,33 +4051,59 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
3957
4051
 
3958
4052
  function dispatchFrame(frameIndex) {
3959
4053
  const writeFrameConfig = createFrameConfigWriter(frameIndex);
3960
- const encoder = device.createCommandEncoder({
3961
- label: `plasius.wavefront.frame.${frameIndex}.batched`,
4054
+ let submissionCount = 0;
4055
+ let encodedFramePasses = 0;
4056
+ let encoder = device.createCommandEncoder({
4057
+ label: `plasius.wavefront.frame.${frameIndex}.batched.${submissionCount + 1}`,
3962
4058
  });
4059
+
4060
+ function submitCurrentEncoder() {
4061
+ if (encodedFramePasses <= 0) {
4062
+ return;
4063
+ }
4064
+ device.queue.submit([encoder.finish()]);
4065
+ submissionCount += 1;
4066
+ encodedFramePasses = 0;
4067
+ encoder = device.createCommandEncoder({
4068
+ label: `plasius.wavefront.frame.${frameIndex}.batched.${submissionCount + 1}`,
4069
+ });
4070
+ }
4071
+
4072
+ function reserveEncoder(passCount = 1) {
4073
+ if (
4074
+ encodedFramePasses > 0 &&
4075
+ encodedFramePasses + passCount > config.maxFramePassesPerSubmission
4076
+ ) {
4077
+ submitCurrentEncoder();
4078
+ }
4079
+ encodedFramePasses += passCount;
4080
+ return encoder;
4081
+ }
4082
+
3963
4083
  for (const tile of tiles) {
3964
4084
  for (let sampleIndex = 0; sampleIndex < config.samplesPerPixel; sampleIndex += 1) {
3965
4085
  const configOffset = writeFrameConfig(tile, {
3966
4086
  sampleIndex,
3967
4087
  sampleWeight: 1 / config.samplesPerPixel,
3968
4088
  });
3969
- encodeTileSample(encoder, tile, configOffset);
4089
+ encodeTileSample(reserveEncoder(), tile, configOffset);
3970
4090
  }
3971
4091
  const outputConfigOffset = writeFrameConfig(tile, {
3972
4092
  sampleIndex: 0,
3973
4093
  sampleWeight: 1 / config.samplesPerPixel,
3974
4094
  });
3975
- encodeTileOutput(encoder, tile, outputConfigOffset);
4095
+ encodeTileOutput(reserveEncoder(), tile, outputConfigOffset);
3976
4096
  }
3977
4097
  if (config.denoise) {
3978
4098
  const denoiseConfigOffset = writeFrameConfig(
3979
4099
  { x: 0, y: 0, width: config.width, height: config.height },
3980
4100
  { sampleIndex: 0, sampleWeight: 1 / config.samplesPerPixel }
3981
4101
  );
3982
- encodeDenoise(encoder, denoiseConfigOffset);
4102
+ encodeDenoise(reserveEncoder(), denoiseConfigOffset);
3983
4103
  }
3984
- encodePresent(encoder);
3985
- device.queue.submit([encoder.finish()]);
3986
- return 1;
4104
+ encodePresent(reserveEncoder());
4105
+ submitCurrentEncoder();
4106
+ return submissionCount;
3987
4107
  }
3988
4108
 
3989
4109
  function renderOnce() {
@@ -3999,6 +4119,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
3999
4119
  tiles: tiles.length,
4000
4120
  tileSize: config.tileSize,
4001
4121
  samplesPerPixel: config.samplesPerPixel,
4122
+ maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
4002
4123
  screenRays: config.width * config.height,
4003
4124
  primaryRays: config.width * config.height * config.samplesPerPixel,
4004
4125
  sceneObjectCount: config.sceneObjectCount,
@@ -4091,12 +4212,31 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
4091
4212
  samplesPerPixel: config.samplesPerPixel,
4092
4213
  sceneObjectCapacity: config.sceneObjectCapacity,
4093
4214
  sceneObjects: packedScene.objects,
4215
+ camera: activeCameraOptions,
4094
4216
  frameIndex: config.frameIndex,
4095
4217
  });
4096
4218
  device.queue.writeBuffer(sceneObjectBuffer, 0, packedScene.buffer);
4097
4219
  return config;
4098
4220
  }
4099
4221
 
4222
+ function updateCamera(cameraOptions = {}) {
4223
+ activeCameraOptions = cameraOptions;
4224
+ config = createWavefrontPathTracingComputeConfig({
4225
+ ...options,
4226
+ canvas,
4227
+ width: config.width,
4228
+ height: config.height,
4229
+ maxDepth: config.maxDepth,
4230
+ tileSize: config.tileSize,
4231
+ samplesPerPixel: config.samplesPerPixel,
4232
+ sceneObjectCapacity: config.sceneObjectCapacity,
4233
+ sceneObjects: packedScene.objects,
4234
+ camera: activeCameraOptions,
4235
+ frameIndex: config.frameIndex,
4236
+ });
4237
+ return config;
4238
+ }
4239
+
4100
4240
  function getSnapshot() {
4101
4241
  return Object.freeze({
4102
4242
  frame,
@@ -4106,6 +4246,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
4106
4246
  tiles: tiles.length,
4107
4247
  tileSize: config.tileSize,
4108
4248
  samplesPerPixel: config.samplesPerPixel,
4249
+ maxFramePassesPerSubmission: config.maxFramePassesPerSubmission,
4109
4250
  sceneObjectCount: config.sceneObjectCount,
4110
4251
  triangleCount: config.triangleCount,
4111
4252
  emissiveTriangleCount: config.emissiveTriangleCount,
@@ -4155,6 +4296,7 @@ export async function createWavefrontPathTracingComputeRenderer(options = {}) {
4155
4296
  renderFrame,
4156
4297
  readOutputProbe,
4157
4298
  updateSceneObjects,
4299
+ updateCamera,
4158
4300
  getSnapshot,
4159
4301
  destroy,
4160
4302
  });