Last active
May 1, 2021 15:51
-
-
Save martinlaxenaire/de849b9d3f79fe60d17f2477f2cfa077 to your computer and use it in GitHub Desktop.
Overall concept to on how to use the TextTexture class and to render chosen planes onto a specific render target with curtains.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*** | |
Really basic example of how I'm rendering the WebGL elements of my portfolio: https://www.martin-laxenaire.fr/ | |
***/ | |
import { | |
Curtains, | |
Plane, | |
Vec2, | |
PingPongPlane, | |
RenderTarget, | |
ShaderPass | |
} from 'curtainsjs'; | |
import {TextTexture } from "./TextTexture.js"; // see https://gist.github.com/martinlaxenaire/549b3b01ff4bd9d29ce957edd8b56f16 | |
import { ripplesVs, ripplesFs } from "./path/to/ripples.js"; // assuming you're exporting the shaders here | |
import { textVs, textFs } from "./path/to/text-planes.js"; // assuming you're exporting the shaders here | |
import { scrollFs } from "./path/to/scroll-pass.js"; // assuming you're exporting the shaders here | |
import { renderFs } from "./path/to/render-pass.js"; // assuming you're exporting the shaders here | |
export class WebGLLayer { | |
constructor() { | |
this.curtains = new Curtains({ | |
container: "canvas", | |
pixelRatio: Math.min(1.5, window.devicePixelRatio), | |
antialias: false, // we'll be using render targets that disable WebGL built-in antialiasing | |
}); | |
this.curtains.onSuccess(() => { | |
// used for various resolution uniforms | |
this.size = this.curtains.getBoundingRect(); | |
// add the ripples effect | |
this.addRipples(); | |
// add the different post processing passes | |
this.addRenderPasses(); | |
// add the text planes | |
this.addHeaderPlanes(); | |
this.addTextPlanes(); | |
}).onError(() => { | |
// handle errors | |
}); | |
} | |
onMouseMove(e) { | |
if (this.ripples) { | |
const mousePos = { | |
x: e.targetTouches ? e.targetTouches[0].clientX : e.clientX, | |
y: e.targetTouches ? e.targetTouches[0].clientY : e.clientY, | |
}; | |
this.mouse.last.copy(this.mouse.current); | |
this.mouse.updateVelocity = true; | |
if (!this.mouse.lastTime) { | |
this.mouse.lastTime = (performance || Date).now(); | |
} | |
if ( | |
this.mouse.last.x === 0 && | |
this.mouse.last.y === 0 && | |
this.mouse.current.x === 0 && | |
this.mouse.current.y === 0 | |
) { | |
this.mouse.updateVelocity = false; | |
} | |
this.mouse.current.set(mousePos.x, mousePos.y); | |
const webglCoords = this.ripples.mouseToPlaneCoords(this.mouse.current); | |
this.ripples.uniforms.mousePosition.value = webglCoords; | |
// divided by a frame duration (roughly) | |
if (this.mouse.updateVelocity) { | |
const time = (performance || Date).now(); | |
const delta = Math.max(14, time - this.mouse.lastTime); | |
this.mouse.lastTime = time; | |
this.mouse.velocity.set( | |
(this.mouse.current.x - this.mouse.last.x) / delta, | |
(this.mouse.current.y - this.mouse.last.y) / delta | |
); | |
} | |
} | |
} | |
addRipples() { | |
// see https://codepen.io/martinlaxenaire/pen/wvgObPj for complete implementation | |
this.mouse = { | |
last: new Vec2(), | |
current: new Vec2(), | |
velocity: new Vec2(), | |
updateVelocity: false, | |
lastTime: null, | |
}; | |
this.ripples = new PingPongPlane(this.curtains, | |
document.getElementById("canvas"), { | |
vertexShader: ripplesVs, | |
fragmentShader: ripplesFs, | |
autoloadSources: false, | |
watchScroll: false, | |
sampler: "uRipples", | |
texturesOptions: { | |
floatingPoint: "half-float" | |
}, | |
uniforms: { | |
mousePosition: { | |
name: "uMousePosition", | |
type: "2f", | |
value: this.mouse.current, | |
}, | |
// our velocity | |
velocity: { | |
name: "uVelocity", | |
type: "2f", | |
value: this.mouse.velocity, | |
}, | |
// window aspect ratio to draw a circle | |
resolution: { | |
name: "uResolution", | |
type: "2f", | |
value: new Vec2(this.size.width, this.size.height), | |
}, | |
pixelRatio: { | |
name: "uPixelRatio", | |
type: "1f", | |
value: this.curtains.pixelRatio, | |
}, | |
time: { | |
name: "uTime", | |
type: "1i", | |
value: -1, | |
}, | |
viscosity: { | |
name: "uViscosity", | |
type: "1f", | |
value: 10.75, | |
}, | |
speed: { | |
name: "uSpeed", | |
type: "1f", | |
value: 6.75, | |
}, | |
size: { | |
name: "uSize", | |
type: "1f", | |
value: 2, | |
}, | |
dissipation: { | |
name: "uDissipation", | |
type: "1f", | |
value: 0.9875, | |
} | |
}, | |
} | |
); | |
this.ripples.onRender(() => { | |
this.mouse.velocity.set(this.curtains.lerp(this.mouse.velocity.x, 0, 0.05), this.curtains.lerp(this.mouse.velocity.y, 0, 0.1)); | |
this.ripples.uniforms.velocity.value = this.mouse.velocity.clone(); | |
this.ripples.uniforms.time.value++; | |
}).onAfterResize(() => { | |
// update our window aspect ratio uniform | |
const boundingRect = this.ripples.getBoundingRect(); | |
this.ripples.uniforms.resolution.value.set(boundingRect.width, boundingRect.height); | |
}); | |
// handle mouse move | |
window.addEventListener("mousemove", this.onMouseMove.bind(this)); | |
window.addEventListener("touchmove", this.onMouseMove.bind(this)); | |
} | |
addRenderPasses() { | |
this.scrollTarget = new RenderTarget(this.curtains); | |
// see https://codepen.io/martinlaxenaire/pen/QWdoRrJ for complete implementation | |
this.scroll = { | |
value: 0, | |
lastValue: 0, | |
effect: 0, | |
}; | |
this.scrollPass = new ShaderPass(this.curtains, { | |
fragmentShader: scrollFs, | |
renderTarget: this.scrollTarget, | |
depth: false, | |
uniforms: { | |
scrollEffect: { | |
name: "uScrollEffect", | |
type: "1f", | |
value: this.scroll.effect, | |
}, | |
scrollStrength: { | |
name: "uScrollStrength", | |
type: "1f", | |
value: this.isPortrait ? 3 : 1.5, | |
}, | |
} | |
}); | |
this.scrollPass.onRender(() => { | |
// when using a smooth scroll library, skip this and just use the scroll velocity for the scrollEffect uniform | |
this.scroll.lastValue = this.scroll.value; | |
this.scroll.value = this.curtains.getScrollValues().y; | |
this.scroll.delta = Math.max(-30, Math.min(30, this.scroll.lastValue - this.scroll.value)); | |
this.scroll.effect = this.curtains.lerp(this.scroll.effect, this.scroll.delta, 0.05); | |
this.scrollPass.uniforms.scrollEffect.value = this.scroll.effect; | |
}).onAfterResize(() => { | |
const boundingRect = this.scrollPass.getBoundingRect(); | |
this.scrollPass.uniforms.scrollStrength.value = boundingRect.width >= boundingRect.height ? 1.5 : 3; | |
}); | |
const params = { | |
fragmentShader: renderFs, | |
depth: false, | |
uniforms: { | |
resolution: { | |
name: "uResolution", | |
type: "2f", | |
value: new Vec2(this.size.width, this.size.height), | |
}, | |
}, | |
}; | |
// global post processing | |
this.renderPass = new ShaderPass(this.curtains, params); | |
this.renderPass.onAfterResize(() => { | |
// update our window aspect ratio uniform | |
const boundingRect = this.renderPass.getBoundingRect(); | |
this.renderPass.uniforms.resolution.value.set(boundingRect.width, boundingRect.height); | |
}); | |
// add our ripple texture to the render pass | |
this.renderPass.createTexture({ | |
sampler: "uRipplesTexture", | |
fromTexture: this.ripples.getTexture() | |
}); | |
} | |
// see https://codepen.io/martinlaxenaire/pen/QWdoRrJ | |
addHeaderPlanes() { | |
// add the header planes that will not get affected by the scroll effect | |
document.querySelectorAll("#header .header-plane").forEach(headerEl => { | |
const headerPlane = new Plane(this.curtains, headerEl, { | |
vertexShader: textVs, | |
fragmentShader: textVs, | |
watchScroll: false, // act like CSS fixed position | |
}); | |
const textTexture = new TextTexture({ | |
plane: headerPlane, | |
textElement: headerPlane.htmlElement, | |
sampler: "uTexture", | |
resolution: 1.5, | |
// assuming you've loaded all the fonts beforehand using the document.fonts API | |
// see https://developer.mozilla.org/en-US/docs/Web/API/Document/fonts | |
skipFontLoading: true, | |
}); | |
// easily access the texture if needed | |
headerPlane.userData.textTexture = textTexture; | |
}); | |
} | |
// see https://codepen.io/martinlaxenaire/pen/QWdoRrJ | |
addTextPlanes() { | |
document.querySelectorAll(".text-plane").forEach(textEl => { | |
const textPlane = new Plane(this.curtains, textEl, { | |
vertexShader: textVs, | |
fragmentShader: textVs, | |
}); | |
// here we add them to the scroll effect pass! | |
// all the others planes (images and so forth) that'd need to be distorted on scroll | |
// will have to be added to that render target as well! | |
textPlane.setRenderTarget(this.scrollTarget); | |
const textTexture = new TextTexture({ | |
plane: textPlane, | |
textElement: textPlane.htmlElement, | |
sampler: "uTexture", | |
resolution: 1.5, | |
skipFontLoading: true, | |
}); | |
// easily access the texture if needed | |
textPlane.userData.textTexture = textTexture; | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment