Instead of the default fly-to animation when going from one slide to the next, this sample implements a custom RenderNode that applies a cross-fade transition when changing to different slides.
A new FadeRenderNode is created when a slide change is initiated by the user. When render node is ready, the desired slide is applied to the view, changing the view's camera to the new viewpoint in the background without a fly-to animation.
slideThumbnail.onclick = async () => {
// create a new fade render node, and set its animation speed
const node = new FadeRenderNode({ view });
node.duration = speed.value * 1000;
// Wait for node to capture current view before going to next one
await node.ready;
// apply slide without fly-to animation
slide.applyTo(view, { animate: false });
}
On its creation, the FadeRenderNode immediately captures the current framebuffer (input
), and returns that captured framebuffer until the animation starts.
// hold on to the framebuffer when the render function runs the first time
if (!this._startFramebuffer) {
this._startFramebuffer = input; //retain ensures framebuffer is kept in memory
this._startFramebuffer.retain();
this._resolve?.();
return this._startFramebuffer;
}
Once animation starts, the FadeRenderNode takes the current framebuffer (input
) and the initially captured
framebuffer (_start
) and blends them together in each frame, according to animation time (delta
). This creates the actual cross-fade effect between the start and the target view.
const currentTex = input.getTexture(gl.COLOR_ATTACHMENT0);
const startTex = this._startFramebuffer.getTexture(gl.COLOR_ATTACHMENT0);
#fragment shader
void main() {
vec4 current = texture(currentTex, uv);
vec4 start = texture(startTex, uv);
fragColor = mix(start, current, delta);
}
When the animation is done, FadeRenderNode has finished its job. _start
can be released, and the render node destroys itself.
if (delta >= 1) {
this._startFramebuffer.release();
this._startFramebuffer = null;
this.destroy();
return input;
}