Created
April 8, 2024 03:39
-
-
Save phobon/fa26c815989941cf4fa51902db55b8e0 to your computer and use it in GitHub Desktop.
Pixellation / UV remapping grid trail
This file contains hidden or 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
uniform vec2 u_resolution; | |
uniform float u_time; | |
uniform sampler2D u_diffuse; | |
uniform sampler2D u_dataTexture; | |
uniform float u_amplitude; | |
varying vec2 v_uv; | |
vec3 correctGamma(vec3 color) { | |
return pow(color, vec3(1.0 / 2.2)); | |
} | |
void main(void) { | |
vec4 offsetColor = texture2D(u_dataTexture, v_uv); | |
vec2 offsetUv = v_uv - u_amplitude * offsetColor.rg; | |
vec4 finalColor = texture2D(u_diffuse, offsetUv); | |
finalColor.rgb = correctGamma(finalColor.rgb); | |
gl_FragColor = finalColor; | |
} | |
This file contains hidden or 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
import { useFrame, extend, useThree } from '@react-three/fiber' | |
import { useRef } from 'react' | |
import { shaderMaterial } from '@react-three/drei' | |
import { v4 as uuid } from 'uuid' | |
import vertexShader from './vertex.glsl' | |
import fragmentShader from './fragment.glsl' | |
import { useGridTrailTexture } from './use_grid_trail_texture' | |
const PixellationMaterial = shaderMaterial( | |
{ | |
u_time: 0, | |
u_resolution: [0, 0], | |
u_diffuse: null, | |
u_dataTexture: null, | |
u_pageRatio: 0, | |
u_amplitude: 0.00001, | |
}, | |
vertexShader, | |
fragmentShader, | |
) | |
PixellationMaterial.key = uuid() | |
extend({ PixellationMaterial }) | |
const Pixellation = ({ texture }) => { | |
const meshRef = useRef<any>() | |
const { width, height } = useThree((state) => state.size) | |
useFrame(({ clock }) => { | |
const mesh = meshRef.current | |
if (!mesh) { | |
return | |
} | |
mesh.material.uniforms.u_time.value = clock.elapsedTime | |
mesh.material.uniforms.u_resolution.value.x = window.innerWidth | |
mesh.material.uniforms.u_resolution.value.y = window.innerWidth | |
}) | |
const [dataTexture, onMove] = useGridTrailTexture({ grid: 32, radius: 0.05, strength: 0.02, decay: 0.9 }) | |
return ( | |
<mesh ref={meshRef} scale={[width, height, 1]} onPointerMove={onMove}> | |
<planeGeometry args={[1, 1]} /> | |
{/* @ts-ignore */} | |
<pixellationMaterial uniforms-u_diffuse-value={texture} uniforms-u_dataTexture-value={dataTexture} /> | |
</mesh> | |
) | |
} | |
export default Pixellation |
This file contains hidden or 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
import { ThreeEvent, useFrame } from '@react-three/fiber' | |
import { useCallback, useEffect, useRef, useState } from 'react' | |
import * as THREE from 'three' | |
const clamp = (min: number, input: number, max: number) => { | |
return Math.max(min, Math.min(input, max)) | |
} | |
export type UseGridTrailTextureProps = { | |
grid?: number | |
radius?: number | |
strength?: number | |
decay?: number | |
} | |
export const useGridTrailTexture = (options?: UseGridTrailTextureProps): any => { | |
const { grid = 50, radius = 0.05, strength = 0.06, decay = 0.75 } = options || {} | |
const [dataTexture, setDataTexture] = useState<THREE.DataTexture>() | |
const previousPointerRef = useRef({ x: 0, y: 0 }) | |
const pointerDeltaRef = useRef({ x: 0, y: 0 }) | |
const pointerRef = useRef({ x: 0, y: 0 }) | |
const viewportRef = useRef({ width: 0, height: 0 }) | |
const size = grid | |
const width = size | |
const height = size | |
const dimensions = width * height | |
useEffect(() => { | |
// Regenerate grid on mount and on window resize | |
const regenerateGrid = () => { | |
viewportRef.current.width = window.innerWidth | |
viewportRef.current.height = window.innerHeight | |
// Populate data texture with initial values | |
const data = new Float32Array(4 * dimensions) | |
for (let stride = 0; stride < dimensions; stride++) { | |
const index = stride * 4 | |
data[index] = 0 | |
data[index + 1] = 0 | |
data[index + 2] = 0 | |
data[index + 3] = 0 | |
} | |
const dataTexture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat, THREE.FloatType) | |
dataTexture.minFilter = dataTexture.magFilter = THREE.NearestFilter | |
dataTexture.needsUpdate = true | |
setDataTexture(dataTexture) | |
} | |
window.addEventListener('resize', regenerateGrid) | |
regenerateGrid() | |
return () => { | |
window.removeEventListener('resize', regenerateGrid) | |
} | |
}, [dimensions, height, width]) | |
const onMove = useCallback((e: ThreeEvent<PointerEvent>) => { | |
const { x, y } = e | |
pointerRef.current.x = x | |
pointerRef.current.y = y | |
// Pointer velocity | |
pointerDeltaRef.current.x = pointerRef.current.x - previousPointerRef.current.x | |
pointerDeltaRef.current.y = pointerRef.current.y - previousPointerRef.current.y | |
// Cache previous pointer position | |
previousPointerRef.current.x = pointerRef.current.x | |
previousPointerRef.current.y = pointerRef.current.y | |
}, []) | |
useFrame(({}) => { | |
if (!dataTexture) { | |
return | |
} | |
const data = dataTexture.image.data | |
const cellX = (size * pointerRef.current.x) / viewportRef.current.width | |
const cellY = size * (1 - pointerRef.current.y / viewportRef.current.height) | |
const mouseRadius = size * radius | |
const aspect = viewportRef.current.height / viewportRef.current.width | |
for (let stride = 0; stride < data.length; stride += 4) { | |
data[stride] *= decay | |
data[stride + 1] *= decay | |
for (let x = 0; x < size; x++) | |
for (let y = 0; y < size; y++) { | |
// Calculate squared euclidian distance between two points | |
const dist = (cellX - x) ** 2 / aspect + (cellY - y) ** 2 | |
const distMax = mouseRadius ** 2 | |
// If we're within the radius here, it's a hit think of this like a 2D raymarching calculation | |
if (dist < distMax) { | |
// Determine the strength and size of the force | |
const dataIndex = 4 * (x + size * y) | |
let force = mouseRadius / Math.sqrt(dist) | |
force = clamp(force, 0, 10) | |
data[dataIndex] += strength * Math.abs(pointerDeltaRef.current.x) * force | |
data[dataIndex + 1] += strength * Math.abs(pointerDeltaRef.current.y) * force | |
} | |
} | |
dataTexture.needsUpdate = true | |
} | |
// Decay pointer velocity | |
if (pointerDeltaRef.current.x > 0) { | |
pointerDeltaRef.current.x *= decay | |
} | |
if (pointerDeltaRef.current.y > 0) { | |
pointerDeltaRef.current.y *= decay | |
} | |
}) | |
return [dataTexture, onMove] | |
} |
This file contains hidden or 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
varying vec2 v_uv; | |
void main() { | |
// gl_Position = vec4(position, 1.0); | |
vec3 localSpacePosition = position; | |
gl_Position = projectionMatrix * modelViewMatrix * vec4(localSpacePosition, 1.0); | |
v_uv = uv; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment