Found this cool effect on Codepen which I have forked and added sections below. When you move the cursor it warps the background image. The problem is that if you scroll down and then move the mouse, the effect does not move with the mouse and still warps the image where the cursor was before you scrolled. I need to somehow update the mouse position so that it is always where the cursor actually is. I'm not overly clued up on javacsript so have no idea how to achieve this, any help is appreciated!
var vertex = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
`;
var fragment = `
precision highp float;
precision highp int;
uniform sampler2D tWater;
uniform sampler2D tFlow;
uniform float uTime;
varying vec2 vUv;
uniform vec4 res;
uniform vec2 img;
vec2 centeredAspectRatio(vec2 uvs, vec2 factor){
return uvs * factor - factor /2. + 0.5;
}
void main() {
// R and G values are velocity in the x and y direction
// B value is the velocity length
vec3 flow = texture2D(tFlow, vUv).rgb;
vec2 uv = .5 * gl_FragCoord.xy / res.xy ;
// vec2 uv = .5 * gl_FragCoord.xy / res.xy ;
vec2 myUV = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV -= flow.xy * (0.15 * 1.2);
vec2 myUV2 = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV2 -= flow.xy * (0.125 * 1.2);
vec2 myUV3 = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV3 -= flow.xy * (0.10 * 1.4);
vec3 tex = texture2D(tWater, myUV).rgb;
vec3 tex2 = texture2D(tWater, myUV2).rgb;
vec3 tex3 = texture2D(tWater, myUV3).rgb;
gl_FragColor = vec4(tex.r, tex2.g, tex3.b, 1.0);
}
`;
{
var _size = [2048, 1638];
var renderer = new ogl.Renderer({ dpr: 2 });
var gl = renderer.gl;
document.body.appendChild(gl.canvas);
// Variable inputs to control flowmap
var aspect = 1;
var mouse = new ogl.Vec2(-1);
var velocity = new ogl.Vec2();
function resize() {
gl.canvas.width = window.innerWidth * 2.0;
gl.canvas.height = window.innerHeight * 2.0;
gl.canvas.style.width = window.innerWidth + "px";
gl.canvas.style.height = window.innerHeight + "px";
var a1, a2;
var imageAspect = _size[1] / _size[0];
if (window.innerHeight / window.innerWidth < imageAspect) {
a1 = 1;
a2 = window.innerHeight / window.innerWidth / imageAspect;
} else {
a1 = window.innerWidth / window.innerHeight * imageAspect;
a2 = 1;
}
mesh.program.uniforms.res.value = new ogl.Vec4(
window.innerWidth,
window.innerHeight,
a1,
a2
);
renderer.setSize(window.innerWidth, window.innerHeight);
aspect = window.innerWidth / window.innerHeight;
}
var flowmap = new ogl.Flowmap(gl, {
falloff: 0.3,
dissipation: 0.92,
alpha: 0.5
});
// Triangle that includes -1 to 1 range for 'position', and 0 to 1 range for 'uv'.
var geometry = new ogl.Geometry(gl, {
position: {
size: 2,
data: new Float32Array([-1, -1, 3, -1, -1, 3])
},
uv: { size: 2, data: new Float32Array([0, 0, 2, 0, 0, 2]) }
});
var texture = new ogl.Texture(gl, {
minFilter: gl.LINEAR,
magFilter: gl.LINEAR
});
var img = new Image();
img.onload = () => (texture.image = img);
img.crossOrigin = "Anonymous";
img.src = "https://robindelaporte.fr/codepen/bg3.jpg";
var a1, a2;
var imageAspect = _size[1] / _size[0];
if (window.innerHeight / window.innerWidth < imageAspect) {
a1 = 1;
a2 = window.innerHeight / window.innerWidth / imageAspect;
} else {
a1 = window.innerWidth / window.innerHeight * imageAspect;
a2 = 1;
}
var program = new ogl.Program(gl, {
vertex,
fragment,
uniforms: {
uTime: { value: 0 },
tWater: { value: texture },
res: {
value: new ogl.Vec4(window.innerWidth, window.innerHeight, a1, a2)
},
img: { value: new ogl.Vec2(_size[1], _size[0]) },
// Note that the uniform is applied without using an object and value property
// This is because the class alternates this texture between two render targets
// and updates the value property after each render.
tFlow: flowmap.uniform
}
});
var mesh = new ogl.Mesh(gl, { geometry, program });
window.addEventListener("resize", resize, false);
resize();
// Create handlers to get mouse position and velocity
var isTouchCapable = "ontouchstart" in window;
if (isTouchCapable) {
window.addEventListener("touchstart", updateMouse, false);
window.addEventListener("touchmove", updateMouse, { passive: false });
} else {
window.addEventListener("mousemove", updateMouse, false);
}
var lastTime;
var lastMouse = new ogl.Vec2();
function updateMouse(e) {
e.preventDefault();
if (e.changedTouches && e.changedTouches.length) {
e.x = e.changedTouches[0].pageX;
e.y = e.changedTouches[0].pageY;
}
if (e.x === undefined) {
e.x = e.pageX;
e.y = e.pageY;
}
// Get mouse value in 0 to 1 range, with y flipped
mouse.set(e.x / gl.renderer.width, 1.0 - e.y / gl.renderer.height);
// Calculate velocity
if (!lastTime) {
// First frame
lastTime = performance.now();
lastMouse.set(e.x, e.y);
}
var deltaX = e.x - lastMouse.x;
var deltaY = e.y - lastMouse.y;
lastMouse.set(e.x, e.y);
var time = performance.now();
// Avoid dividing by 0
var delta = Math.max(10.4, time - lastTime);
lastTime = time;
velocity.x = deltaX / delta;
velocity.y = deltaY / delta;
// Flag update to prevent hanging velocity values when not moving
velocity.needsUpdate = true;
}
requestAnimationFrame(update);
function update(t) {
requestAnimationFrame(update);
// Reset velocity when mouse not moving
if (!velocity.needsUpdate) {
mouse.set(-1);
velocity.set(0);
}
velocity.needsUpdate = false;
// Update flowmap inputs
flowmap.aspect = aspect;
flowmap.mouse.copy(mouse);
// Ease velocity input, slower when fading out
flowmap.velocity.lerp(velocity, velocity.len ? 0.15 : 0.1);
flowmap.update();
program.uniforms.uTime.value = t * 0.01;
renderer.render({ scene: mesh });
}
}
canvas {
position: absolute;
width: 100%;
height: 100vh;
left: 0;
top:0;
}
.mask {
position: absolute;
z-index: 2;
background: white;
height: 100vh;
width: 100vw;
top:0;
mix-blend-mode: screen;
/* display: none; */
}
svg {
width: 90%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.sections{
height:100vh;
}
.redtop{
margin-top:100vh;
}
.red{
background-color:red;
}
.blue{
background-color:blue;
}
<script src="https://assets.codepen.io/4691555/fluidText.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script>
<div class="sections red redtop"></div>
<div class="sections blue"></div>
<div class="mask">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1040 205.1" style="enable-background:new 0 0 1040 205.1;" xml:space="preserve">
<style type="text/css">
.st0 {
fill-rule: evenodd;
clip-rule: evenodd;
}
</style>
<g>
<g>
<path
class="st0"
d="M108.7,155.2l24.8,23.3c-2.1,3.1-4.9,6.2-8.3,9.2c-10.2,8.8-26.2,16.4-48.5,16.4C33.4,204.1,1,174.8,1,131.2
c0-43.3,32.4-72.6,75.7-72.6c29.8,0,48.4,13.2,56.8,25.5l-28.7,26.7c-5.5-8-14.3-13.2-26.1-13.2c-18.3,0-32.4,12.6-32.4,33.6
c0,3.7,0.4,7.2,1.3,10.4c1.5,5.9,4.4,10.8,8.2,14.5c5,4.9,11.6,8,19.1,8.7c1.2,0.1,2.5,0.2,3.8,0.2c10.5,0,18.7-4.6,24.2-11.1
c0.7-0.8,1.3-1.6,1.8-2.4L108.7,155.2z"
/>
<path
class="st0"
d="M239.9,59.4v42.5c-2.6-0.6-6.3-1.2-10.9-1.2c-11.5,0-26.7,4.9-32.4,12.6v88.3h-44.2V63.1h44.2V80
C205.8,68.8,223.3,59.4,239.9,59.4z"
/>
<path
class="st0"
d="M381.7,181.7c-9.3,9.7-21.1,16.9-34.5,20.4l0.7-0.1L381.7,181.7z"
/>
<path
class="st0"
d="M539.8,92.8c-8.7-26-34.4-34.1-61.4-34.1c-21.2,0-45,6.3-62.8,21.5l7.2,12.6l8.6,15.2
c11.2-9.5,25.5-14.6,39.3-14.6c17.8,0,28.1,8,28.1,20.1v16.6c-8.3-10.3-24.7-16.1-43-16.1c-7.1,0-14.8,1.2-21.9,4.1
c-14,5.7-25.7,18-25.7,40.3c0,24.8,16.7,39,34.1,43.8c0.7,0.2,1.4,0.4,2.1,0.5c3.8,0.9,7.7,1.3,11.4,1.3c4.3,0,8.5-0.4,12.5-1.2
c1.8-0.3,3.5-0.7,5.2-1.2c10.6-2.8,19.6-8.1,25.2-14.9v13.8h44.1V114C542.9,105.9,541.8,98.8,539.8,92.8z M498.8,165.1
c-4.6,6.6-14.9,10-24.7,10c-11.8,0-22.1-5.2-22.1-16.1c0-10.9,10.3-16.1,22.1-16.1c9.8,0,20.1,3.4,24.7,9.8V165.1z"
/>
<path
class="st0"
d="M605.3,201.1c-5.9-2.5-10.7-6.2-14.1-11.1C595.1,194.7,599.9,198.5,605.3,201.1z"
/>
<path
class="st0"
d="M656.2,101.5h-28.1v19.6c-1.4-0.1-2.8-0.2-4.2-0.2c-18.2,0-33.7,11.5-39.6,27.5v-46.9h-22.9V63.1h22.9V25.2
h43.9v37.9h28.1V101.5z"
/>
<path
class="st0"
d="M660.2,195.9c-2.8,2.6-6.9,4.9-12.4,6.6c-5.1,1.5-11.3,2.5-18.7,2.6c12-1.5,22.4-7.9,29.1-17.2L660.2,195.9z"
/>
<path
class="st0"
d="M725.5,26.2c0,14-11.2,25.5-25.2,25.5s-25.2-11.5-25.2-25.5S686.3,1,700.3,1S725.5,12.2,725.5,26.2z"
/>
<polygon
class="st0"
points="891.5,63.1 879.4,93.8 873.7,108.5 837,201.6 790,201.6 760.2,125.9 747.6,93.8 735.6,63.1 782,63.1
792.9,93.8 802.1,119.5 813.2,150.9 825.8,115.8 833.8,93.8 844.8,63.1 "
/>
<path
class="st0"
d="M939.8,116.6c1.4-8,6.3-18.3,18-22.2c3-1,6.5-1.6,10.4-1.6c20.3,0,27.5,14.1,28.7,23.8H939.8z M968.2,204l2,0
c21.2,0,44.1-6,57.9-18.4l-18.6-27.5c-7.7,7.5-24.6,11.8-34.4,11.8c-20.1,0-32.1-11.8-34.7-24.7h98.6v-9.5
c0-47-29.8-77.2-70.8-77.2c-28.1,0-50.7,13.6-63.2,34.1c-2,3.3-3.8,6.8-5.2,10.5c-3.4,8.6-5.3,18-5.3,27.9
c0,44.3,31.8,71.9,73.5,72.8L968.2,204z"
/>
</g>
</g>
<polyline
class="st0"
points="678.2,201.4 678.2,63.1 722.3,63.1 722.3,201.4 "
/>
<path
class="st0"
d="M395.3,135.8c0-10-1.3-19.2-3.8-27.5c-1.7-5.6-3.9-10.8-6.6-15.5c-12.1-21.6-33.9-34.1-60.4-34.1
c-9.1,0-17.7,1.4-25.5,4.1c-25.9,11.5-43.9,37.5-43.9,67.8c0,37.8,28.1,69,64.5,73.5c3,0.4,6,0.5,9,0.5c6.4,0,12.7-0.8,18.6-2.4
c13.3-3.5,25.2-10.7,34.5-20.4l-15.9-23.5c-7.7,7.5-24.7,11.8-34.4,11.8c-20.1,0-32.1-11.8-34.7-24.7h98.6V135.8z M296.1,116.6
c1.7-10,9.2-23.8,28.4-23.8c6.4,0,11.5,1.4,15.5,3.6c8.7,4.8,12.4,13.5,13.1,20.2H296.1z"
/>
<path
class="st0"
d="M658.1,187.8c-6.7,9.3-17.1,15.8-29.1,17.2h0c-1.7,0.2-3.4,0.3-5.2,0.3c-6.6,0-12.9-1.5-18.5-4.2
c-5.5-2.7-10.3-6.5-14.1-11.1c0,0,0,0,0-0.1c-4.6-6.5-7-15.1-7-25.6v-15.9c6-16.1,21.4-27.5,39.6-27.5c1.4,0,2.8,0.1,4.2,0.2V152
c0,7.1,3.6,12.6,9.8,13.8c0.8,0.1,1.6,0.2,2.5,0.2c4.9,0,9.7-1.7,11.2-3.4l1.9,7.4L658.1,187.8z"
/>
</svg>
</div>
<div class="sections red"></div>
<div class="sections blue"></div>
Related
I've found a cool codepen that has a text hover effect using a background image and a mask on top. I'm trying to change the mask on this Codepen from white to black but if you set is to black it doesn't work at all. I assume there is a reason for this but I have no idea why that is.
Is it possible to make the white mask black?
var vertex = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
`;
var fragment = `
precision highp float;
precision highp int;
uniform sampler2D tWater;
uniform sampler2D tFlow;
uniform float uTime;
varying vec2 vUv;
uniform vec4 res;
uniform vec2 img;
vec2 centeredAspectRatio(vec2 uvs, vec2 factor){
return uvs * factor - factor /2. + 0.5;
}
void main() {
// R and G values are velocity in the x and y direction
// B value is the velocity length
vec3 flow = texture2D(tFlow, vUv).rgb;
vec2 uv = .5 * gl_FragCoord.xy / res.xy ;
// vec2 uv = .5 * gl_FragCoord.xy / res.xy ;
vec2 myUV = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV -= flow.xy * (0.15 * 1.2);
vec2 myUV2 = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV2 -= flow.xy * (0.125 * 1.2);
vec2 myUV3 = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV3 -= flow.xy * (0.10 * 1.4);
vec3 tex = texture2D(tWater, myUV).rgb;
vec3 tex2 = texture2D(tWater, myUV2).rgb;
vec3 tex3 = texture2D(tWater, myUV3).rgb;
gl_FragColor = vec4(tex.r, tex2.g, tex3.b, 1.0);
}
`;
{
var _size = [2048, 1638];
var renderer = new ogl.Renderer({ dpr: 2 });
var gl = renderer.gl;
document.body.appendChild(gl.canvas);
// Variable inputs to control flowmap
var aspect = 1;
var mouse = new ogl.Vec2(-1);
var velocity = new ogl.Vec2();
function resize() {
gl.canvas.width = window.innerWidth * 2.0;
gl.canvas.height = window.innerHeight * 2.0;
gl.canvas.style.width = window.innerWidth + "px";
gl.canvas.style.height = window.innerHeight + "px";
var a1, a2;
var imageAspect = _size[1] / _size[0];
if (window.innerHeight / window.innerWidth < imageAspect) {
a1 = 1;
a2 = window.innerHeight / window.innerWidth / imageAspect;
} else {
a1 = window.innerWidth / window.innerHeight * imageAspect;
a2 = 1;
}
mesh.program.uniforms.res.value = new ogl.Vec4(
window.innerWidth,
window.innerHeight,
a1,
a2
);
renderer.setSize(window.innerWidth, window.innerHeight);
aspect = window.innerWidth / window.innerHeight;
}
var flowmap = new ogl.Flowmap(gl, {
falloff: 0.3,
dissipation: 0.92,
alpha: 0.5
});
// Triangle that includes -1 to 1 range for 'position', and 0 to 1 range for 'uv'.
var geometry = new ogl.Geometry(gl, {
position: {
size: 2,
data: new Float32Array([-1, -1, 3, -1, -1, 3])
},
uv: { size: 2, data: new Float32Array([0, 0, 2, 0, 0, 2]) }
});
var texture = new ogl.Texture(gl, {
minFilter: gl.LINEAR,
magFilter: gl.LINEAR
});
var img = new Image();
img.onload = () => (texture.image = img);
img.crossOrigin = "Anonymous";
img.src = "https://robindelaporte.fr/codepen/bg3.jpg";
var a1, a2;
var imageAspect = _size[1] / _size[0];
if (window.innerHeight / window.innerWidth < imageAspect) {
a1 = 1;
a2 = window.innerHeight / window.innerWidth / imageAspect;
} else {
a1 = window.innerWidth / window.innerHeight * imageAspect;
a2 = 1;
}
var program = new ogl.Program(gl, {
vertex,
fragment,
uniforms: {
uTime: { value: 0 },
tWater: { value: texture },
res: {
value: new ogl.Vec4(window.innerWidth, window.innerHeight, a1, a2)
},
img: { value: new ogl.Vec2(_size[1], _size[0]) },
// Note that the uniform is applied without using an object and value property
// This is because the class alternates this texture between two render targets
// and updates the value property after each render.
tFlow: flowmap.uniform
}
});
var mesh = new ogl.Mesh(gl, { geometry, program });
window.addEventListener("resize", resize, false);
resize();
// Create handlers to get mouse position and velocity
var isTouchCapable = "ontouchstart" in window;
if (isTouchCapable) {
window.addEventListener("touchstart", updateMouse, false);
window.addEventListener("touchmove", updateMouse, { passive: false });
} else {
window.addEventListener("mousemove", updateMouse, false);
}
var lastTime;
var lastMouse = new ogl.Vec2();
function updateMouse(e) {
e.preventDefault();
if (e.changedTouches && e.changedTouches.length) {
e.x = e.changedTouches[0].pageX;
e.y = e.changedTouches[0].pageY;
}
if (e.x === undefined) {
e.x = e.pageX;
e.y = e.pageY;
}
// Get mouse value in 0 to 1 range, with y flipped
mouse.set(e.x / gl.renderer.width, 1.0 - e.y / gl.renderer.height);
// Calculate velocity
if (!lastTime) {
// First frame
lastTime = performance.now();
lastMouse.set(e.x, e.y);
}
var deltaX = e.x - lastMouse.x;
var deltaY = e.y - lastMouse.y;
lastMouse.set(e.x, e.y);
var time = performance.now();
// Avoid dividing by 0
var delta = Math.max(10.4, time - lastTime);
lastTime = time;
velocity.x = deltaX / delta;
velocity.y = deltaY / delta;
// Flag update to prevent hanging velocity values when not moving
velocity.needsUpdate = true;
}
requestAnimationFrame(update);
function update(t) {
requestAnimationFrame(update);
// Reset velocity when mouse not moving
if (!velocity.needsUpdate) {
mouse.set(-1);
velocity.set(0);
}
velocity.needsUpdate = false;
// Update flowmap inputs
flowmap.aspect = aspect;
flowmap.mouse.copy(mouse);
// Ease velocity input, slower when fading out
flowmap.velocity.lerp(velocity, velocity.len ? 0.15 : 0.1);
flowmap.update();
program.uniforms.uTime.value = t * 0.01;
renderer.render({ scene: mesh });
}
}
body {
position: fixed;
height: 100%;
overflow: hidden;
}
canvas {
position: absolute;
width: 100%;
height: 100vh;
top: 0;
left: 0;
}
.mask {
position: absolute;
z-index: 2;
background: white;
height: 100vh;
width: 100vw;
mix-blend-mode: screen;
/* display: none; */
}
svg {
width: 90%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
<script src="https://robindelaporte.fr/codepen/bundle.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="mask">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1040 205.1" style="enable-background:new 0 0 1040 205.1;" xml:space="preserve">
<style type="text/css">
.st0 {
fill-rule: evenodd;
clip-rule: evenodd;
}
</style>
<g>
<g>
<path
class="st0"
d="M108.7,155.2l24.8,23.3c-2.1,3.1-4.9,6.2-8.3,9.2c-10.2,8.8-26.2,16.4-48.5,16.4C33.4,204.1,1,174.8,1,131.2
c0-43.3,32.4-72.6,75.7-72.6c29.8,0,48.4,13.2,56.8,25.5l-28.7,26.7c-5.5-8-14.3-13.2-26.1-13.2c-18.3,0-32.4,12.6-32.4,33.6
c0,3.7,0.4,7.2,1.3,10.4c1.5,5.9,4.4,10.8,8.2,14.5c5,4.9,11.6,8,19.1,8.7c1.2,0.1,2.5,0.2,3.8,0.2c10.5,0,18.7-4.6,24.2-11.1
c0.7-0.8,1.3-1.6,1.8-2.4L108.7,155.2z"
/>
<path
class="st0"
d="M239.9,59.4v42.5c-2.6-0.6-6.3-1.2-10.9-1.2c-11.5,0-26.7,4.9-32.4,12.6v88.3h-44.2V63.1h44.2V80
C205.8,68.8,223.3,59.4,239.9,59.4z"
/>
<path
class="st0"
d="M381.7,181.7c-9.3,9.7-21.1,16.9-34.5,20.4l0.7-0.1L381.7,181.7z"
/>
<path
class="st0"
d="M539.8,92.8c-8.7-26-34.4-34.1-61.4-34.1c-21.2,0-45,6.3-62.8,21.5l7.2,12.6l8.6,15.2
c11.2-9.5,25.5-14.6,39.3-14.6c17.8,0,28.1,8,28.1,20.1v16.6c-8.3-10.3-24.7-16.1-43-16.1c-7.1,0-14.8,1.2-21.9,4.1
c-14,5.7-25.7,18-25.7,40.3c0,24.8,16.7,39,34.1,43.8c0.7,0.2,1.4,0.4,2.1,0.5c3.8,0.9,7.7,1.3,11.4,1.3c4.3,0,8.5-0.4,12.5-1.2
c1.8-0.3,3.5-0.7,5.2-1.2c10.6-2.8,19.6-8.1,25.2-14.9v13.8h44.1V114C542.9,105.9,541.8,98.8,539.8,92.8z M498.8,165.1
c-4.6,6.6-14.9,10-24.7,10c-11.8,0-22.1-5.2-22.1-16.1c0-10.9,10.3-16.1,22.1-16.1c9.8,0,20.1,3.4,24.7,9.8V165.1z"
/>
<path
class="st0"
d="M605.3,201.1c-5.9-2.5-10.7-6.2-14.1-11.1C595.1,194.7,599.9,198.5,605.3,201.1z"
/>
<path
class="st0"
d="M656.2,101.5h-28.1v19.6c-1.4-0.1-2.8-0.2-4.2-0.2c-18.2,0-33.7,11.5-39.6,27.5v-46.9h-22.9V63.1h22.9V25.2
h43.9v37.9h28.1V101.5z"
/>
<path
class="st0"
d="M660.2,195.9c-2.8,2.6-6.9,4.9-12.4,6.6c-5.1,1.5-11.3,2.5-18.7,2.6c12-1.5,22.4-7.9,29.1-17.2L660.2,195.9z"
/>
<path
class="st0"
d="M725.5,26.2c0,14-11.2,25.5-25.2,25.5s-25.2-11.5-25.2-25.5S686.3,1,700.3,1S725.5,12.2,725.5,26.2z"
/>
<polygon
class="st0"
points="891.5,63.1 879.4,93.8 873.7,108.5 837,201.6 790,201.6 760.2,125.9 747.6,93.8 735.6,63.1 782,63.1
792.9,93.8 802.1,119.5 813.2,150.9 825.8,115.8 833.8,93.8 844.8,63.1 "
/>
<path
class="st0"
d="M939.8,116.6c1.4-8,6.3-18.3,18-22.2c3-1,6.5-1.6,10.4-1.6c20.3,0,27.5,14.1,28.7,23.8H939.8z M968.2,204l2,0
c21.2,0,44.1-6,57.9-18.4l-18.6-27.5c-7.7,7.5-24.6,11.8-34.4,11.8c-20.1,0-32.1-11.8-34.7-24.7h98.6v-9.5
c0-47-29.8-77.2-70.8-77.2c-28.1,0-50.7,13.6-63.2,34.1c-2,3.3-3.8,6.8-5.2,10.5c-3.4,8.6-5.3,18-5.3,27.9
c0,44.3,31.8,71.9,73.5,72.8L968.2,204z"
/>
</g>
</g>
<polyline
class="st0"
points="678.2,201.4 678.2,63.1 722.3,63.1 722.3,201.4 "
/>
<path
class="st0"
d="M395.3,135.8c0-10-1.3-19.2-3.8-27.5c-1.7-5.6-3.9-10.8-6.6-15.5c-12.1-21.6-33.9-34.1-60.4-34.1
c-9.1,0-17.7,1.4-25.5,4.1c-25.9,11.5-43.9,37.5-43.9,67.8c0,37.8,28.1,69,64.5,73.5c3,0.4,6,0.5,9,0.5c6.4,0,12.7-0.8,18.6-2.4
c13.3-3.5,25.2-10.7,34.5-20.4l-15.9-23.5c-7.7,7.5-24.7,11.8-34.4,11.8c-20.1,0-32.1-11.8-34.7-24.7h98.6V135.8z M296.1,116.6
c1.7-10,9.2-23.8,28.4-23.8c6.4,0,11.5,1.4,15.5,3.6c8.7,4.8,12.4,13.5,13.1,20.2H296.1z"
/>
<path
class="st0"
d="M658.1,187.8c-6.7,9.3-17.1,15.8-29.1,17.2h0c-1.7,0.2-3.4,0.3-5.2,0.3c-6.6,0-12.9-1.5-18.5-4.2
c-5.5-2.7-10.3-6.5-14.1-11.1c0,0,0,0,0-0.1c-4.6-6.5-7-15.1-7-25.6v-15.9c6-16.1,21.4-27.5,39.6-27.5c1.4,0,2.8,0.1,4.2,0.2V152
c0,7.1,3.6,12.6,9.8,13.8c0.8,0.1,1.6,0.2,2.5,0.2c4.9,0,9.7-1.7,11.2-3.4l1.9,7.4L658.1,187.8z"
/>
</svg>
</div>
I am writing a small sprite sheet renderer in WebGL, where I render quad with set with and height then I take one big sprite sheet and use the texture coordinates to pick what sprite should be shown on what quad. I do this all in one big object with position and texture coordinates array where i push new items in. I'm also using TWGL library to make WebGL a bit less verbose. Currently this system of batching items into one draw per frame isn't working that well, how can it be improved?
class Render {
constructor(selector) {
this.vertexShaderSource = `#version 300 es
in vec2 position;
in vec2 texcoord;
uniform vec2 u_resolution;
out vec2 v_texCoord;
void main() {
vec2 zeroToOne = position / u_resolution;
vec2 zeroToTwo = zeroToOne * 2.0;
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
v_texCoord = texcoord;
}`;
this.fragmentShaderSource = `#version 300 es
precision highp float;
uniform sampler2D u_image;
in vec2 v_texCoord;
out vec4 outColor;
void main() {
outColor = texture(u_image, v_texCoord);
}`;
this.bufferInfo;
this.gl = document.querySelector(selector).getContext("webgl2");
this.programInfo = twgl.createProgramInfo(this.gl, [this.vertexShaderSource, this.fragmentShaderSource]);
this.textures = [];
this.uniforms = {
u_resolution: [innerWidth, innerHeight],
u_image: null,
}
this.arrays = {
position: [],
texcoord: [],
};
this.gl.canvas.width = innerWidth;
this.gl.canvas.height = innerHeight;
}
loadImage(url, callback) {
const texture = twgl.createTexture(this.gl, { src: url, mag: this.gl.NEAREST }, () => { callback ? callback() : "" });
this.textures.push(texture);
return {
texture: texture,
index: this.textures.length - 1
};
}
clearBuffer() {
this.arrays.position = [];
this.arrays.texcoord = [];
}
setTexture(texture) {
this.uniforms.u_image = texture;
}
addQuad(x, y, width, height, srcWidth, srcHeight, srcX, srcY, realWidth, realHeight) {
const x1 = x;
const x2 = x + width;
const y1 = y;
const y2 = y + height;
this.arrays.position.push(
x1, y1, 0,
x2, y1, 0,
x1, y2, 0,
x1, y2, 0,
x2, y1, 0,
x2, y2, 0,
);
this.arrays.texcoord.push(
srcX / srcWidth / 1, srcY / srcHeight / 1,
(srcX + realWidth) / srcWidth / 1, srcY / srcHeight / 1,
srcX / srcWidth / 1, (srcY + realHeight) / srcHeight / 1,
srcX / srcWidth / 1, (srcY + realHeight) / srcHeight / 1,
(srcX + realWidth) / srcWidth / 1, srcY / srcHeight / 1,
(srcX + realWidth) / srcWidth / 1, (srcY + realHeight) / srcHeight / 1,
);
}
setBuffer() {
this.bufferInfo = twgl.createBufferInfoFromArrays(this.gl, this.arrays);
}
resizeCanvas() {
twgl.resizeCanvasToDisplaySize(this.gl.canvas);
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
}
render() {
this.gl.useProgram(this.programInfo.program);
twgl.setUniforms(this.programInfo, this.uniforms);
twgl.setBuffersAndAttributes(this.gl, this.programInfo, this.bufferInfo);
twgl.drawBufferInfo(this.gl, this.bufferInfo);
}
}
const render = new Render("#game");
const texture = render.loadImage("https://i.imgur.com/TQnyNHU.png", () => {
requestAnimationFrame(renderStuff);
render.resizeCanvas();
function renderStuff() {
render.clearBuffer();
render.setTexture(texture);
for(let y = 0; y < 100; y++) {
for(let x = 0; x < 100; x++) {
render.addQuad(x * 48, y * 48, 48, 48, 512, 512, 32, 0, 16, 16);
}
}
render.setBuffer();
render.render();
requestAnimationFrame(renderStuff);
}
});
* {
margin: 0px;
padding: 0px;
}
body, html {
width: 100%;
height: 100%;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdnjs.cloudflare.com/ajax/libs/twgl.js/4.19.5/twgl.min.js"></script>
<script src="main.js" type="module" defer></script>
<link rel="stylesheet" href="style.css">
<title>Prometeus</title>
</head>
<body>
<canvas id="game"></canvas>
</body>
</html>
I have been playing around with the shadows from building geometries example at (https://bl.ocks.org/andrewharvey/9490afae78301c047adddfb06523f6f1) and have been able to blend the transparent layers to become one uniform alpha value with an older version of mapbox-gl-js.
However when I change the mapbox-gl-js version to v0.54.0 or higher it no longer blends the shadows to be a uniform value. I have experimented with gl.blendFunc() and gl.blendFuncSeparate() but still seem to get a mixture of weird anti-aliasing issues or overlapping opacities.
How could I avoid this transparency issue and get a similar result to the first example provided.
0.53.1:
1.6.1:
Code using version 0.53.1:
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.1/mapbox-gl.css' rel='stylesheet' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
#time { position: absolute; width: 90%; top: 10px; left: 10px; }
</style>
</head>
<body>
<div id='map'></div>
<input id='time' type='range' min="0" max="86400" />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.1/mapbox-gl.js'></script>
<script src='https://unpkg.com/suncalc#1.8.0/suncalc.js'></script>
<!-- <script src='/debug/access_token_generated.js'></script> -->
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiYWxhbnRnZW8tcHJlc2FsZXMiLCJhIjoiY2pzcTA4NjRiMTMxczQzcDFqa29maXk3bSJ9.pVYNTFKfcOXA_U_5TUwDWw';
var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 15,
center: [-74.0059, 40.7064],
style: 'mapbox://styles/mapbox/streets-v11',
hash: true
});
var date = new Date();
var time = date.getHours() * 60 * 60 + date.getMinutes() * 60 + date.getSeconds();
var timeInput = document.getElementById('time');
timeInput.value = time;
timeInput.oninput = () => {
time = +timeInput.value;
date.setHours(Math.floor(time / 60 / 60));
date.setMinutes(Math.floor(time / 60) % 60);
date.setSeconds(time % 60);
map.triggerRepaint();
};
map.addControl(new mapboxgl.NavigationControl());
class BuildingShadows {
constructor() {
this.id = 'building-shadows';
this.type = 'custom';
this.renderingMode = '3d';
this.opacity = 0.5;
}
onAdd(map, gl) {
this.map = map;
const vertexSource = `
uniform mat4 u_matrix;
uniform float u_height_factor;
uniform float u_altitude;
uniform float u_azimuth;
attribute vec2 a_pos;
attribute vec4 a_normal_ed;
attribute lowp vec2 a_base;
attribute lowp vec2 a_height;
void main() {
float base = max(0.0, a_base.x);
float height = max(0.0, a_height.x);
float t = mod(a_normal_ed.x, 2.0);
vec4 pos = vec4(a_pos, t > 0.0 ? height : base, 1);
float len = pos.z * u_height_factor / tan(u_altitude);
pos.x += cos(u_azimuth) * len;
pos.y += sin(u_azimuth) * len;
pos.z = 0.0;
gl_Position = u_matrix * pos;
}
`;
const fragmentSource = `
void main() {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.5);
}
`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
console.log(gl.FRAGMENT_SHADER)
console.log(fragmentShader)
console.log(fragmentSource)
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
gl.linkProgram(this.program);
gl.validateProgram(this.program);
this.uMatrix = gl.getUniformLocation(this.program, "u_matrix");
this.uHeightFactor = gl.getUniformLocation(this.program, "u_height_factor");
this.uAltitude = gl.getUniformLocation(this.program, "u_altitude");
this.uAzimuth = gl.getUniformLocation(this.program, "u_azimuth");
this.aPos = gl.getAttribLocation(this.program, "a_pos");
this.aNormal = gl.getAttribLocation(this.program, "a_normal_ed");
this.aBase = gl.getAttribLocation(this.program, "a_base");
this.aHeight = gl.getAttribLocation(this.program, "a_height");
}
render(gl, matrix) {
gl.useProgram(this.program);
const source = this.map.style.sourceCaches['composite'];
const coords = source.getVisibleCoordinates().reverse();
const buildingsLayer = map.getLayer('3d-buildings');
const context = this.map.painter.context;
const {lng, lat} = this.map.getCenter();
const pos = SunCalc.getPosition(date, lat, lng);
gl.uniform1f(this.uAltitude, pos.altitude);
gl.uniform1f(this.uAzimuth, pos.azimuth + 3 * Math.PI / 2);
map.setLight({
anchor: 'map',
position: [1.5, 180 + pos.azimuth * 180 / Math.PI, 90 - pos.altitude * 180 / Math.PI],
'position-transition': {duration: 0},
color: '#fdb'
// color: `hsl(20, ${50 * Math.cos(pos.altitude)}%, ${ 200 * Math.sin(pos.altitude) }%)`
}, {duration: 0});
this.opacity = Math.sin(Math.max(pos.altitude, 0)) * 0.9;
// ADDED: normalises the colour of the shadows
gl.blendFunc(gl.SRC_COLOR, gl.CONSTANT_COLOR)
gl.enable(gl.BLEND)
for (const coord of coords) {
const tile = source.getTile(coord);
const bucket = tile.getBucket(buildingsLayer);
if (!bucket) continue;
const [heightBuffer, baseBuffer] = bucket.programConfigurations.programConfigurations['3d-buildings']._buffers;
gl.uniformMatrix4fv(this.uMatrix, false, coord.posMatrix);
gl.uniform1f(this.uHeightFactor, Math.pow(2, coord.overscaledZ) / tile.tileSize / 8);
for (const segment of bucket.segments.get()) {
const numPrevAttrib = context.currentNumAttributes || 0;
const numNextAttrib = 2;
for (let i = numNextAttrib; i < numPrevAttrib; i++) gl.disableVertexAttribArray(i);
const vertexOffset = segment.vertexOffset || 0;
gl.enableVertexAttribArray(this.aPos);
gl.enableVertexAttribArray(this.aNormal);
gl.enableVertexAttribArray(this.aHeight);
gl.enableVertexAttribArray(this.aBase);
bucket.layoutVertexBuffer.bind();
gl.vertexAttribPointer(this.aPos, 2, gl.SHORT, false, 12, 12 * vertexOffset);
gl.vertexAttribPointer(this.aNormal, 4, gl.SHORT, false, 12, 4 + 12 * vertexOffset);
heightBuffer.bind();
gl.vertexAttribPointer(this.aHeight, 1, gl.FLOAT, false, 4, 4 * vertexOffset);
baseBuffer.bind();
gl.vertexAttribPointer(this.aBase, 1, gl.FLOAT, false, 4, 4 * vertexOffset);
bucket.indexBuffer.bind();
context.currentNumAttributes = numNextAttrib;
gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2);
}
}
}
}
map.on('load', () => {
map.removeLayer('building');
map.addLayer({
'id': '3d-buildings',
'source': 'composite',
'source-layer': 'building',
'type': 'fill-extrusion',
'minzoom': 14,
'paint': {
'fill-extrusion-color': '#ddd',
'fill-extrusion-height': ["number", ["get", "height"], 5],
'fill-extrusion-base': ["number", ["get", "min_height"], 0],
'fill-extrusion-opacity': 1
}
}, 'road-label');
map.addLayer(new BuildingShadows(), '3d-buildings');
});
</script>
</body>
</html>
Code using version 1.6.1:
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.css' rel='stylesheet' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
#time { position: absolute; width: 90%; top: 10px; left: 10px; }
</style>
</head>
<body>
<div id='map'></div>
<input id='time' type='range' min="0" max="86400" />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.js'></script>
<script src='https://unpkg.com/suncalc#1.8.0/suncalc.js'></script>
<!-- <script src='/debug/access_token_generated.js'></script> -->
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiYWxhbnRnZW8tcHJlc2FsZXMiLCJhIjoiY2pzcTA4NjRiMTMxczQzcDFqa29maXk3bSJ9.pVYNTFKfcOXA_U_5TUwDWw';
var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 15,
center: [-74.0059, 40.7064],
style: 'mapbox://styles/mapbox/streets-v11',
hash: true
});
var date = new Date();
var time = date.getHours() * 60 * 60 + date.getMinutes() * 60 + date.getSeconds();
var timeInput = document.getElementById('time');
timeInput.value = time;
timeInput.oninput = () => {
time = +timeInput.value;
date.setHours(Math.floor(time / 60 / 60));
date.setMinutes(Math.floor(time / 60) % 60);
date.setSeconds(time % 60);
map.triggerRepaint();
};
map.addControl(new mapboxgl.NavigationControl());
class BuildingShadows {
constructor() {
this.id = 'building-shadows';
this.type = 'custom';
this.renderingMode = '3d';
this.opacity = 0.5;
}
onAdd(map, gl) {
this.map = map;
const vertexSource = `
uniform mat4 u_matrix;
uniform float u_height_factor;
uniform float u_altitude;
uniform float u_azimuth;
attribute vec2 a_pos;
attribute vec4 a_normal_ed;
attribute lowp vec2 a_base;
attribute lowp vec2 a_height;
void main() {
float base = max(0.0, a_base.x);
float height = max(0.0, a_height.x);
float t = mod(a_normal_ed.x, 2.0);
vec4 pos = vec4(a_pos, t > 0.0 ? height : base, 1);
float len = pos.z * u_height_factor / tan(u_altitude);
pos.x += cos(u_azimuth) * len;
pos.y += sin(u_azimuth) * len;
pos.z = 0.0;
gl_Position = u_matrix * pos;
}
`;
const fragmentSource = `
void main() {
gl_FragColor = vec4(5.0, 0.0, 0.0, 0.1);
}
`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexSource);
gl.compileShader(vertexShader);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentSource);
gl.compileShader(fragmentShader);
this.program = gl.createProgram();
gl.attachShader(this.program, vertexShader);
gl.attachShader(this.program, fragmentShader);
gl.linkProgram(this.program);
gl.validateProgram(this.program);
this.uMatrix = gl.getUniformLocation(this.program, "u_matrix");
this.uHeightFactor = gl.getUniformLocation(this.program, "u_height_factor");
this.uAltitude = gl.getUniformLocation(this.program, "u_altitude");
this.uAzimuth = gl.getUniformLocation(this.program, "u_azimuth");
this.aPos = gl.getAttribLocation(this.program, "a_pos");
this.aNormal = gl.getAttribLocation(this.program, "a_normal_ed");
this.aBase = gl.getAttribLocation(this.program, "a_base");
this.aHeight = gl.getAttribLocation(this.program, "a_height");
}
render(gl, matrix) {
gl.useProgram(this.program);
const source = this.map.style.sourceCaches['composite'];
const coords = source.getVisibleCoordinates().reverse();
const buildingsLayer = map.getLayer('3d-buildings');
const context = this.map.painter.context;
const {lng, lat} = this.map.getCenter();
const pos = SunCalc.getPosition(date, lat, lng);
gl.uniform1f(this.uAltitude, pos.altitude);
gl.uniform1f(this.uAzimuth, pos.azimuth + 3 * Math.PI / 2);
map.setLight({
anchor: 'map',
position: [1.5, 180 + pos.azimuth * 180 / Math.PI, 90 - pos.altitude * 180 / Math.PI],
'position-transition': {duration: 0},
color: '#fdb'
// color: `hsl(20, ${50 * Math.cos(pos.altitude)}%, ${ 200 * Math.sin(pos.altitude) }%)`
}, {duration: 0});
this.opacity = Math.sin(Math.max(pos.altitude, 0)) * 0.9;
// ADDED: New Attempt to normalise the colour of the shadows
gl.depthMask(false);
gl.enable(gl.BLEND)
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
for (const coord of coords) {
const tile = source.getTile(coord);
const bucket = tile.getBucket(buildingsLayer);
if (!bucket) continue;
const [heightBuffer, baseBuffer] = bucket.programConfigurations.programConfigurations['3d-buildings']._buffers;
gl.uniformMatrix4fv(this.uMatrix, false, coord.posMatrix);
gl.uniform1f(this.uHeightFactor, Math.pow(2, coord.overscaledZ) / tile.tileSize / 8);
for (const segment of bucket.segments.get()) {
const numPrevAttrib = context.currentNumAttributes || 0;
const numNextAttrib = 2;
for (let i = numNextAttrib; i < numPrevAttrib; i++) gl.disableVertexAttribArray(i);
const vertexOffset = segment.vertexOffset || 0;
gl.enableVertexAttribArray(this.aPos);
gl.enableVertexAttribArray(this.aNormal);
gl.enableVertexAttribArray(this.aHeight);
gl.enableVertexAttribArray(this.aBase);
bucket.layoutVertexBuffer.bind();
gl.vertexAttribPointer(this.aPos, 2, gl.SHORT, false, 12, 12 * vertexOffset);
gl.vertexAttribPointer(this.aNormal, 4, gl.SHORT, false, 12, 4 + 12 * vertexOffset);
heightBuffer.bind();
gl.vertexAttribPointer(this.aHeight, 1, gl.FLOAT, false, 4, 4 * vertexOffset);
baseBuffer.bind();
gl.vertexAttribPointer(this.aBase, 1, gl.FLOAT, false, 4, 4 * vertexOffset);
bucket.indexBuffer.bind();
context.currentNumAttributes = numNextAttrib;
gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2);
}
}
}
}
map.on('load', () => {
map.removeLayer('building');
map.addLayer({
'id': '3d-buildings',
'source': 'composite',
'source-layer': 'building',
'type': 'fill-extrusion',
'minzoom': 14,
'paint': {
'fill-extrusion-color': '#ddd',
'fill-extrusion-height': ["number", ["get", "height"], 5],
'fill-extrusion-base': ["number", ["get", "min_height"], 0],
'fill-extrusion-opacity': 1
}
}, 'road-label');
map.addLayer(new BuildingShadows(), '3d-buildings');
});
</script>
</body>
</html>
There are various variations but
draw all the shadows into the stencil buffer then draw one quad with the stencil test on so that it only draws where the stencil is set?
draw all the shadows opaquely into a texture. draw the texture as a quad over the map.
draw all the shadows into the depth buffer. draw one quad with the depth test set so it only draws where there were shadows.
also
draw the shadows with the depth test or stencil test on such that no pixel can be drawn more than once.
Various related examples:
Stencil buffer in WebGL
WebGL – Use mesh as mask for background image
Is there a way in WebGL to quickly invert the stencil buffer?
I have seen that it is possible to create a sticky image effect on hover using three.js which inspired me with an idea of doing this effect over a video.
Initially, I converted the video to a gif hover the gif was stuck on the first frame. I then tried canvid.js which uses loops frames instead of video. Finally I tried to pause the video an take an image of the frame that is hovered on and then use the effect on the image.
/* S C R E E N S H O T */
$( document ).ready(function() {
$("#heroVideo").get(0).play();
var video = document.getElementById("heroVideo");
var currentTime = video.currentTime
var imgUrl = "./img/overlay.png";
$(".hoverZone").hover(function(){
$("img").css("opacity","1");
function getVideoImage(path, secs, callback) {
var me = this, video = document.createElement('video');
video.onloadedmetadata = function() {
if ('function' === typeof secs) {
secs = secs(this.duration);
}
this.currentTime = Math.min(Math.max(0, (secs < 0 ? this.duration : 0) + secs), this.duration);
};
video.onseeked = function(e) {
var canvas = document.createElement('canvas');
canvas.height = video.videoHeight;
canvas.width = video.videoWidth;
var ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
var img = new Image();
img.src = canvas.toDataURL();
callback.call(me, img, this.currentTime, e);
};
video.onerror = function(e) {
callback.call(me, undefined, undefined, e);
};
video.src = path;
}
function showImageAt(currentTime) {
var current;
getVideoImage(
'./img/Morph.mp4',
function(currentTime) {
current = video.currentTime;
return current;
},
function(img, secs, event) {
if (event.type == 'seeked') {
var li = document.createElement('li');
li.appendChild(img);
document.getElementById('olFrames').appendChild(li);
var imgUrl = $('img')[0].src;
console.log(imgUrl);
if (current >= ++secs) {
};
}
}
);
}
showImageAt(currentTime);
}, function(){
$("img").css("opacity","0");
});
/* D I S T O R T I O N */
const imgSize = [1250, 10097];
const vertex = `
attribute vec2 uv;
attribute vec2 position;
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 0, 1);
}
`;
const fragment = `
precision highp float;
precision highp int;
uniform sampler2D tWater;
uniform sampler2D tFlow;
uniform float uTime;
varying vec2 vUv;
uniform vec4 res;
void main() {
// R and G values are velocity in the x and y direction
// B value is the velocity length
vec3 flow = texture2D(tFlow, vUv).rgb;
vec2 uv = .5 * gl_FragCoord.xy / res.xy ;
vec2 myUV = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV -= flow.xy * (0.15 * 0.7);
vec2 myUV2 = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV2 -= flow.xy * (0.125 * 0.7);
vec2 myUV3 = (uv - vec2(0.5))*res.zw + vec2(0.5);
myUV3 -= flow.xy * (0.10 * 0.7);
vec3 tex = texture2D(tWater, myUV).rgb;
vec3 tex2 = texture2D(tWater, myUV2).rgb;
vec3 tex3 = texture2D(tWater, myUV3).rgb;
gl_FragColor = vec4(tex.r, tex2.g, tex3.b, 1.0);
}
`;
{
const renderer = new ogl.Renderer({ dpr: 2 });
const gl = renderer.gl;
document.body.appendChild(gl.canvas);
// Variable inputs to control flowmap
let aspect = 1;
const mouse = new ogl.Vec2(-1);
const velocity = new ogl.Vec2();
function resize() {
let a1, a2;
var imageAspect = imgSize[1] / imgSize[0];
if (window.innerHeight / window.innerWidth < imageAspect) {
a1 = 1;
a2 = window.innerHeight / window.innerWidth / imageAspect;
} else {
a1 = (window.innerWidth / window.innerHeight) * imageAspect;
a2 = 1;
}
mesh.program.uniforms.res.value = new ogl.Vec4(
window.innerWidth,
window.innerHeight,
a1,
a2
);
renderer.setSize(window.innerWidth, window.innerHeight);
aspect = window.innerWidth / window.innerHeight;
}
const flowmap = new ogl.Flowmap(gl, {
falloff: 1.0, // size of the stamp, percentage of the size
alpha: 0.7, // opacity of the stamp
dissipation: 0.95// affects the speed that the stamp fades. Closer to 1 is slower
});
// Triangle that includes -1 to 1 range for 'position', and 0 to 1 range for 'uv'.
const geometry = new ogl.Geometry(gl, {
position: {
size: 2,
data: new Float32Array([-1, -1, 3, -1, -1, 3])
},
uv: { size: 2, data: new Float32Array([0, 0, 2, 0, 0, 2]) }
});
const texture = new ogl.Texture(gl, {
minFilter: gl.LINEAR,
magFilter: gl.LINEAR
});
const img = new Image();
img.onload = () => (texture.image = img);
img.crossOrigin = "Anonymous";
img.src = imgUrl;
let a1, a2;
var imageAspect = imgSize[1] / imgSize[0];
if (window.innerHeight / window.innerWidth < imageAspect) {
a1 = 1;
a2 = window.innerHeight / window.innerWidth / imageAspect;
} else {
a1 = (window.innerWidth / window.innerHeight) * imageAspect;
a2 = 1;
}
const program = new ogl.Program(gl, {
vertex,
fragment,
uniforms: {
uTime: { value: 0 },
tWater: { value: texture },
res: {
value: new ogl.Vec4(window.innerWidth, window.innerHeight, a1, a2)
},
img: { value: new ogl.Vec2(imgSize[0], imgSize[1]) },
// Note that the uniform is applied without using an object and value property
// This is because the class alternates this texture between two render targets
// and updates the value property after each render.
tFlow: flowmap.uniform
}
});
const mesh = new ogl.Mesh(gl, { geometry, program });
window.addEventListener("resize", resize, false);
resize();
// Create handlers to get mouse position and velocity
const isTouchCapable = "ontouchstart" in window;
if (isTouchCapable) {
window.addEventListener("touchstart", updateMouse, false);
window.addEventListener("touchmove", updateMouse, { passive: false });
} else {
window.addEventListener("mousemove", updateMouse, false);
}
let lastTime;
const lastMouse = new ogl.Vec2();
function updateMouse(e) {
e.preventDefault();
if (e.changedTouches && e.changedTouches.length) {
e.x = e.changedTouches[0].pageX;
e.y = e.changedTouches[0].pageY;
}
if (e.x === undefined) {
e.x = e.pageX;
e.y = e.pageY;
}
// Get mouse value in 0 to 1 range, with y flipped
mouse.set(e.x / gl.renderer.width, 1.0 - e.y / gl.renderer.height);
// Calculate velocity
if (!lastTime) {
// First frame
lastTime = performance.now();
lastMouse.set(e.x, e.y);
}
const deltaX = e.x - lastMouse.x;
const deltaY = e.y - lastMouse.y;
lastMouse.set(e.x, e.y);
let time = performance.now();
// Avoid dividing by 0
let delta = Math.max(10.4, time - lastTime);
lastTime = time;
velocity.x = deltaX / delta;
velocity.y = deltaY / delta;
// Flag update to prevent hanging velocity values when not moving
velocity.needsUpdate = true;
}
requestAnimationFrame(update);
function update(t) {
requestAnimationFrame(update);
// Reset velocity when mouse not moving
if (!velocity.needsUpdate) {
mouse.set(-1);
velocity.set(0);
}
velocity.needsUpdate = false;
// Update flowmap inputs
flowmap.aspect = aspect;
flowmap.mouse.copy(mouse);
// Ease velocity input, slower when fading out
flowmap.velocity.lerp(velocity, velocity.len ? 0.15 : 0.1);
flowmap.update();
program.uniforms.uTime.value = t * 0.01;
renderer.render({ scene: mesh });
}
}
});
I tried to create the snow effect like the one on the bottom page of this link http://blog.edankwan.com/post/my-first-christmas-experiment. Everything else works fine But just can't make the motion blur effect work. Any ideas?
the texture sprite used to achieve the motion blur effect
here is the code:
(function(global) {
var img = 'https://i.imgur.com/hlmsgWA.png'
var renderer, scene, camera
var w = 800, h = 320
var uniforms
var geometry
var texture, material
var gui
var conf = {
amount: 200,
speed: 0.5,
time: 0
}
var obj = {
init: function() {
renderer = new THREE.WebGLRenderer({
antialias: true
})
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(w, h)
camera = new THREE.Camera
scene = new THREE.Scene()
geometry = new THREE.BufferGeometry()
var positions = []
for(var i = 0, l = conf.amount; i < l; i++) {
positions[i * 3] = Math.random() * 800 - 400
positions[i * 3 + 1] = i
positions[i * 3 + 2] = Math.random() * 800
}
geometry.addAttribute('position', new THREE.Float32BufferAttribute(positions, 3))
var vs = document.getElementById('vertexShader').textContent
var fs = document.getElementById('fragmentShader').textContent
uniforms = {
u_amount: {
type: 'f',
value: conf.amount
},
u_speed: {
type: 'f',
value: conf.speed
},
u_time: {
type: 'f',
value: conf.time
},
u_resolution: {
type: 'vec2',
value: new THREE.Vector2(w, h)
},
u_texture : {
value: new THREE.TextureLoader().load(img)
}
}
material = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: vs,
fragmentShader: fs,
transparent: true
})
var points = new THREE.Points(geometry, material)
scene.add(points)
document.body.appendChild(renderer.domElement)
this.render()
this.createGui()
},
createGui: function() {
gui = new dat.GUI()
gui.add(conf, 'speed', -1, 1)
},
render: function() {
requestAnimationFrame(this.render.bind(this))
uniforms.u_time.value += conf.speed * 0.003
renderer.render(scene, camera)
}
}
obj.init()
})(window)
<script id="vertexShader" type="x-shader/x-vertex">
precision highp float;
vec3 getPosOffset(float ratio, float thershold) {
return vec3(
cos((ratio * 80.0 + 10.0) * thershold) * 20.0 * thershold,
(sin((ratio * 90.0 + 30.0) * thershold) + 1.0) * 5.0 * thershold + mix(500.0, -500.0, ratio / thershold),
sin((ratio * 70.0 + 20.0) * thershold) * 20.0 * thershold
);
}
uniform vec2 u_resolution;
uniform float u_amount;
uniform float u_speed;
uniform float u_time;
varying float v_alpha;
varying float v_rotation;
varying float v_index;
void main() {
float indexRatio = position.y / u_amount;
float thershold = 0.7 + indexRatio * 0.3;
float ratio = mod(u_time - indexRatio * 3.0, thershold);
float prevRatio = mod(u_time - u_speed - indexRatio * 3.0, thershold);
vec3 offsetPos = getPosOffset(ratio, thershold);
vec3 prevOffsetPos = getPosOffset(prevRatio, thershold);
vec3 pos = position;
pos += offsetPos;
float perspective = (2000.0 - pos.z) / 2000.0;
pos.x *= perspective;
pos.y *= perspective;
float delta = length(offsetPos.xy - prevOffsetPos.xy);
float maxDelta = 2.7;
v_index = floor(pow(clamp(delta, 0.0, maxDelta) / maxDelta, 5.0) * 15.99);
v_rotation = atan((offsetPos.x - prevOffsetPos.x) / (offsetPos.y - prevOffsetPos.y));
pos.x *= 2.0 / u_resolution.x;
pos.y *= 2.0 / u_resolution.y;
pos.z = 0.0;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
gl_PointSize = 24.0 * perspective;
v_alpha = perspective;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
uniform sampler2D u_texture;
varying float v_rotation;
varying float v_index;
varying float v_alpha;
void main() {
vec2 coord = gl_PointCoord.xy;
coord = vec2(
clamp(cos(v_rotation) * (coord.x - 0.5) + sin(v_rotation) * (coord.y - 0.5) + 0.5, 0.0, 1.0),
clamp(cos(v_rotation) * (coord.y - 0.5) - sin(v_rotation) * (coord.x - 0.5) + 0.5, 0.0, 1.0)
);
float index = floor(v_index + 0.5);
coord.y = - coord.y + 4.0;
coord.x += (index - floor(index / 4.0) * 4.0);
coord.y -= floor(index / 4.0);
coord *= 0.25;
vec4 color = texture2D(u_texture, coord);
color.a *= v_alpha;
gl_FragColor = color;
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.3/dat.gui.js"></script>
http://blog.edankwan.com/post/my-first-christmas-experiment.
In snippet code you treat variable speed differently in your update loop and in vertex shader.
Update: uniforms.u_time.value += conf.speed * 0.003
Shader usage: float prevRatio = mod(u_time - u_speed - indexRatio * 3.0, thershold);
You'll get desired result if you change u_speed to u_speed * 0.003 in shader code (or better move that multiplication to definition in javascript).
Fast way to debug such things - pass your values to output color and check if it is what you expect.
==================
If someone wants to make tails with non-flat curvature - you can store last N points of each particle path. Then you can fully compute trail mesh geometry on CPU and stream it to GPU.
Or another way: you can upload all paths to single Uniform Buffer Object and find right point to fetch with pointId and uv of tail mesh vertex.