UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

413 lines (345 loc) 12.7 kB
import { Vec3, Color } from 'playcanvas'; import { GsplatShaderEffect } from './gsplat-shader-effect.mjs'; const shaderGLSL = /* glsl */` uniform float uTime; uniform vec3 uCenter; uniform float uBlockCount; uniform float uBlockSize; uniform float uDelay; uniform float uDuration; uniform float uDotSize; uniform vec3 uMoveTint; uniform float uMoveTintIntensity; uniform vec3 uLandTint; uniform float uLandDuration; uniform float uEndRadius; // Shared globals (initialized once per vertex) float g_blockDist; float g_tStart; float g_tEnd; void initShared(vec3 center) { // Determine which block this splat belongs to vec3 offset = center - uCenter; ivec3 blockIdx = ivec3(floor(offset / uBlockSize + vec3(uBlockCount * 0.5))); // Calculate block center position vec3 blockCenter = (vec3(blockIdx) - vec3(uBlockCount * 0.5) + vec3(0.5)) * uBlockSize + uCenter; // Euclidean distance from center block g_blockDist = length(blockCenter - uCenter); g_tStart = g_blockDist * uDelay; g_tEnd = g_tStart + uDuration; } void modifyCenter(inout vec3 center) { vec3 originalCenter = center; initShared(center); // Check if animation is complete (after landing transition) float timeSinceLanding = uTime - g_tEnd; if (timeSinceLanding >= 0.3) return; // Effect complete, no modifications // Early exit optimization during animation if (g_blockDist > uEndRadius) return; // Before movement starts: position at center point if (uTime < g_tStart) { center = uCenter; return; } // During movement: lerp from center to original position if (uTime < g_tEnd) { float progress = (uTime - g_tStart) / uDuration; center = mix(uCenter, originalCenter, progress); } // After movement: original position (no change needed) } void modifyCovariance(vec3 originalCenter, vec3 modifiedCenter, inout vec3 covA, inout vec3 covB) { // Check if animation is complete (after landing transition) float timeSinceLanding = uTime - g_tEnd; if (timeSinceLanding >= 0.3) return; // Effect complete, no modifications // Early exit for distant splats during animation if (g_blockDist > uEndRadius) { gsplatMakeRound(covA, covB, 0.0); return; } // Before movement: invisible if (uTime < g_tStart) { gsplatMakeRound(covA, covB, 0.0); return; } // During landing transition after movement if (timeSinceLanding < 0.3) { float originalSize = gsplatExtractSize(covA, covB); if (timeSinceLanding < 0.0) { // During movement: small round dots gsplatMakeRound(covA, covB, min(uDotSize, originalSize)); } else { // Landing transition: lerp from dots to original over 0.3s float t = timeSinceLanding * 3.333333; // normalize [0, 0.3] to [0, 1] float size = mix(uDotSize, originalSize, t); // Lerp between round and original shape vec3 origCovA = covA; vec3 origCovB = covB; gsplatMakeRound(covA, covB, size); covA = mix(covA, origCovA, t); covB = mix(covB, origCovB, t); } } // After transition: original shape/size (no-op) } void modifyColor(vec3 center, inout vec4 color) { // Check if animation is complete float timeSinceLanding = uTime - g_tEnd; if (timeSinceLanding >= uLandDuration) return; // Effect complete, no modifications // Early exit for distant splats during animation if (g_blockDist > uEndRadius) return; // Before movement: no change if (uTime < g_tStart) return; if (timeSinceLanding < 0.0) { // During movement: blend between original and moveTint color.rgb = mix(color.rgb, uMoveTint, uMoveTintIntensity); } else if (timeSinceLanding < uLandDuration) { // Landing: apply landTint with fadeout float fadeOut = 1.0 - (timeSinceLanding / uLandDuration); color.rgb += uLandTint * fadeOut; } // After landing: original color (no change) } `; const shaderWGSL = /* wgsl */` uniform uTime: f32; uniform uCenter: vec3f; uniform uBlockCount: f32; uniform uBlockSize: f32; uniform uDelay: f32; uniform uDuration: f32; uniform uDotSize: f32; uniform uMoveTint: vec3f; uniform uMoveTintIntensity: f32; uniform uLandTint: vec3f; uniform uLandDuration: f32; uniform uEndRadius: f32; // Shared globals (initialized once per vertex) var<private> g_blockDist: f32; var<private> g_tStart: f32; var<private> g_tEnd: f32; fn initShared(center: vec3f) { // Determine which block this splat belongs to let offset = center - uniform.uCenter; let blockIdx = vec3i(floor(offset / uniform.uBlockSize + vec3f(uniform.uBlockCount * 0.5))); // Calculate block center position let blockCenter = (vec3f(blockIdx) - vec3f(uniform.uBlockCount * 0.5) + vec3f(0.5)) * uniform.uBlockSize + uniform.uCenter; // Euclidean distance from center block g_blockDist = length(blockCenter - uniform.uCenter); g_tStart = g_blockDist * uniform.uDelay; g_tEnd = g_tStart + uniform.uDuration; } fn modifyCenter(center: ptr<function, vec3f>) { let originalCenter = *center; initShared(*center); // Check if animation is complete (after landing transition) let timeSinceLanding = uniform.uTime - g_tEnd; if (timeSinceLanding >= 0.3) { return; // Effect complete, no modifications } // Early exit optimization during animation if (g_blockDist > uniform.uEndRadius) { return; } // Before movement starts: position at center point if (uniform.uTime < g_tStart) { *center = uniform.uCenter; return; } // During movement: lerp from center to original position if (uniform.uTime < g_tEnd) { let progress = (uniform.uTime - g_tStart) / uniform.uDuration; *center = mix(uniform.uCenter, originalCenter, progress); } // After movement: original position (no change needed) } fn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr<function, vec3f>, covB: ptr<function, vec3f>) { // Check if animation is complete (after landing transition) let timeSinceLanding = uniform.uTime - g_tEnd; if (timeSinceLanding >= 0.3) { return; // Effect complete, no modifications } // Early exit for distant splats during animation if (g_blockDist > uniform.uEndRadius) { gsplatMakeRound(covA, covB, 0.0); return; } // Before movement: invisible if (uniform.uTime < g_tStart) { gsplatMakeRound(covA, covB, 0.0); return; } // During landing transition after movement if (timeSinceLanding < 0.3) { let originalSize = gsplatExtractSize(*covA, *covB); if (timeSinceLanding < 0.0) { // During movement: small round dots gsplatMakeRound(covA, covB, min(uniform.uDotSize, originalSize)); } else { // Landing transition: lerp from dots to original over 0.3s let t = timeSinceLanding * 3.333333; // normalize [0, 0.3] to [0, 1] let size = mix(uniform.uDotSize, originalSize, t); // Lerp between round and original shape let origCovA = *covA; let origCovB = *covB; gsplatMakeRound(covA, covB, size); *covA = mix(*covA, origCovA, t); *covB = mix(*covB, origCovB, t); } } // After transition: original shape/size (no-op) } fn modifyColor(center: vec3f, color: ptr<function, vec4f>) { // Check if animation is complete let timeSinceLanding = uniform.uTime - g_tEnd; if (timeSinceLanding >= uniform.uLandDuration) { return; // Effect complete, no modifications } // Early exit for distant splats during animation if (g_blockDist > uniform.uEndRadius) { return; } // Before movement: no change if (uniform.uTime < g_tStart) { return; } if (timeSinceLanding < 0.0) { // During movement: blend between original and moveTint (*color) = vec4f(mix((*color).rgb, uniform.uMoveTint, uniform.uMoveTintIntensity), (*color).a); } else if (timeSinceLanding < uniform.uLandDuration) { // Landing: apply landTint with fadeout let fadeOut = 1.0 - (timeSinceLanding / uniform.uLandDuration); (*color) = vec4f((*color).rgb + uniform.uLandTint * fadeOut, (*color).a); } // After landing: original color (no change) } `; /** * Grid Eruption reveal effect for gaussian splats. * Splats shoot out from a center point in blocks based on a 3D grid, * with blocks animating in order of their distance from center. * * @example * // Add the script to a gsplat entity * entity.addComponent('script'); * const gridScript = entity.script.create(GsplatRevealGridEruption); * gridScript.center.set(0, 0, 0); * gridScript.blockCount = 10; */ class GsplatRevealGridEruption extends GsplatShaderEffect { static scriptName = 'gsplatRevealGridEruption'; // Reusable arrays for uniform updates _centerArray = [0, 0, 0]; _moveTintArray = [0, 0, 0]; _landTintArray = [0, 0, 0]; /** * Origin point for the eruption * @attribute */ center = new Vec3(0, 0, 0); /** * Grid divisions per dimension * @attribute * @range [2, 20] */ blockCount = 10; /** * Size of each grid block * @attribute * @range [0.1, 10] */ blockSize = 2; /** * Time between successive blocks starting (seconds) * @attribute * @range [0, 2] */ delay = 0.2; /** * Time to reach final position (seconds) * @attribute * @range [0.1, 4] */ duration = 1.0; /** * Size of particles during movement * @attribute * @range [0, 0.1] */ dotSize = 0.01; /** * Color during movement * @attribute */ moveTint = new Color(1, 0, 1); /** * Blend intensity between original color and movement tint (0=original, 1=full tint) * @attribute * @range [0, 1] */ moveTintIntensity = 0.2; /** * Additive color on landing (flash) * @attribute */ landTint = new Color(2, 2, 0); /** * Duration of landing tint flash in seconds * @attribute * @range [0, 4] */ landDuration = 0.6; /** * Distance at which to disable effect for performance * @attribute * @range [0, 500] */ endRadius = 25; getShaderGLSL() { return shaderGLSL; } getShaderWGSL() { return shaderWGSL; } updateEffect(effectTime, dt) { // Check if effect is complete and disable if so if (this.isEffectComplete()) { this.enabled = false; return; } this.setUniform('uTime', effectTime); this._centerArray[0] = this.center.x; this._centerArray[1] = this.center.y; this._centerArray[2] = this.center.z; this.setUniform('uCenter', this._centerArray); this.setUniform('uBlockCount', this.blockCount); this.setUniform('uBlockSize', this.blockSize); this.setUniform('uDelay', this.delay); this.setUniform('uDuration', this.duration); this.setUniform('uDotSize', this.dotSize); this._moveTintArray[0] = this.moveTint.r; this._moveTintArray[1] = this.moveTint.g; this._moveTintArray[2] = this.moveTint.b; this.setUniform('uMoveTint', this._moveTintArray); this.setUniform('uMoveTintIntensity', this.moveTintIntensity); this._landTintArray[0] = this.landTint.r; this._landTintArray[1] = this.landTint.g; this._landTintArray[2] = this.landTint.b; this.setUniform('uLandTint', this._landTintArray); this.setUniform('uLandDuration', this.landDuration); this.setUniform('uEndRadius', this.endRadius); } /** * Calculate when the effect is complete. * Effect completes when the furthest block within endRadius has finished animating. * @returns {boolean} True if effect is complete */ isEffectComplete() { // Calculate time for furthest block within endRadius const maxTStart = this.endRadius * this.delay; const maxTEnd = maxTStart + this.duration; const maxCompletionTime = maxTEnd + Math.max(0.3, this.landDuration); return this.effectTime >= maxCompletionTime; } } export { GsplatRevealGridEruption };