Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
<script setup lang="ts">
import type { TresPointerEvent } from '@tresjs/core';
import { useLoop, useTresContext } from '@tresjs/core';
import { BloomPmndrs, ChromaticAberrationPmndrs, EffectComposerPmndrs } from '@tresjs/post-processing';

import { BufferAttribute, CanvasTexture, Color, PlaneGeometry, ShaderMaterial, Uniform, Vector2, Vector3 } from 'three';
import { computed, onMounted, watch } from 'vue';
import fragmentShader from './shaders/fragment.glsl';
import vertexShader from './shaders/vertex.glsl';

const { sizes } = useTresContext();


// --- Displacement canvas (cursor trails) ---
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 128;
const ctx = canvas.getContext('2d')!;
ctx.fillRect(0, 0, canvas.width, canvas.height);

const canvasTexture = new CanvasTexture(canvas);
const canvasCursor = new Vector2(9999, 9999);
const prevCanvasCursor = new Vector2(9999, 9999);

// --- Particle geometry: full plane, uniform grid ---
const aspect = sizes.width.value / sizes.height.value;
const particlesGeometry = new PlaneGeometry(10 * aspect, 10, 128, 128);
particlesGeometry.setIndex(null);
particlesGeometry.deleteAttribute('normal');

const count = particlesGeometry.attributes.position!.count;
const intensities = new Float32Array(count);
const angles = new Float32Array(count);

for (let i = 0; i < count; i++) {
intensities[i] = Math.random();
angles[i] = Math.random() * Math.PI * 2;
}

particlesGeometry.setAttribute('aIntensity', new BufferAttribute(intensities, 1));
particlesGeometry.setAttribute('aAngle', new BufferAttribute(angles, 1));

// --- Shader material ---
const particlesMaterial = new ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {
uResolution: new Uniform(new Vector2(1, 1)),
uTime: new Uniform(0),
uDisplacementTexture: new Uniform(canvasTexture),
uColorPrimary: new Uniform(new Vector3(0.4, 0.267, 1.0)),
uColorSecondary: new Uniform(new Vector3(0.702, 0.635, 1.0)), // #b3a2ff — avoids pink from CSS var
uZoom: new Uniform(0.5),
uSizeContrast: new Uniform(1.0),
uSpeed: new Uniform(1.0),
uDisplacementStrength: new Uniform(0.2),
uMinCellSize: new Uniform(0.5),
uMaxCellSize: new Uniform(50.0),
},
transparent: true,
depthWrite: false,
});

// Reactive resolution
const resolution = computed(
() => new Vector2(sizes.width.value * sizes.pixelRatio.value, sizes.height.value * sizes.pixelRatio.value),
);

watch(
resolution,
(res) => {
particlesMaterial.uniforms.uResolution!.value.copy(res);
},
{ immediate: true },
);

// Controls

const uuid = inject('uuid')

const { palettePrimary, paletteSecondary } = useControls('🫟 palette', {
primary: {
value: '#0091ff',
type: 'color',
},
secondary: {
value: '#d400ff',
type: 'color',
},
}, { uuid })

watch([palettePrimary, paletteSecondary], ([newPalettePrimary, newPaletteSecondary]) => {
particlesMaterial.uniforms.uColorPrimary!.value = new Color(newPalettePrimary);
particlesMaterial.uniforms.uColorSecondary!.value = new Color(newPaletteSecondary);
})

const { particlesZoom, particlesContrast, particlesSpeed, particlesMinSize, particlesMaxSize } = useControls('🎬 particles', {
zoom: {
value: 0.5,
min: 0.1,
max: 2.0,
step: 0.01,
},
contrast: {
value: 1.0,
min: 0.1,
max: 5.0,
step: 0.01,
},
speed: {
value: 1.0,
min: 0.0,
max: 5.0,
step: 0.01,
},
minSize: { value: 0.5, min: 0.0, max: 20.0, step: 0.1 },
maxSize: { value: 50.0, min: 1.0, max: 100.0, step: 0.5 },
}, { uuid })

watch(particlesZoom, (val) => {
particlesMaterial.uniforms.uZoom!.value = val;
})

watch(particlesContrast, (val) => {
particlesMaterial.uniforms.uSizeContrast!.value = val;
})

watch(particlesSpeed, (val) => {
particlesMaterial.uniforms.uSpeed!.value = val;
})

watch(particlesMinSize, val => particlesMaterial.uniforms.uMinCellSize!.value = val, { immediate: true })
watch(particlesMaxSize, val => particlesMaterial.uniforms.uMaxCellSize!.value = val, { immediate: true })
const { cursorStrength, cursorRadius, cursorRecovery } = useControls('👆 cursor', {
strength: { value: 0.2, min: 0.0, max: 0.5, step: 0.01 },
radius: { value: 0.5, min: 0.1, max: 1.0, step: 0.01 },
recovery: { value: 0.06, min: 0.01, max: 0.3, step: 0.01 },
}, { uuid })

watch(cursorStrength, val => particlesMaterial.uniforms.uDisplacementStrength!.value = val, { immediate: true })

const { postprocessingChromaticAberration, postprocessingBloom } = useControls('👾 postprocessing', {
chromaticAberration: {
value: false,
type: 'boolean',
},
bloom: {
value: false,
type: 'boolean',
},
}, { uuid })

const chromaticAberrationOffset = computed(() =>
postprocessingChromaticAberration.value ? new Vector2(0.001, 0.001) : new Vector2(0, 0),
)

const bloomIntensity = computed(() =>
postprocessingBloom.value ? 1 : 0,
)


// --- Pointer: capture UV for cursor displacement ---
const onMouseMove = (event: TresPointerEvent) => {
const uv = event.intersection?.uv;

if (uv) {
canvasCursor.set(uv.x * canvas.width, (1 - uv.y) * canvas.height);
}
};

// --- Render loop ---
const { onBeforeRender } = useLoop();

onBeforeRender(({ elapsed }) => {
particlesMaterial.uniforms.uTime!.value = elapsed;

// Fade canvas to black
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = cursorRecovery.value;
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);

// Speed-scaled radial glow at cursor
const dist = canvasCursor.distanceTo(prevCanvasCursor);
prevCanvasCursor.copy(canvasCursor);
const speedAlpha = Math.min(dist * 0.1, 1);

if (speedAlpha > 0) {
const r = canvas.width * cursorRadius!.value;
const glow = ctx.createRadialGradient(canvasCursor.x, canvasCursor.y, 0, canvasCursor.x, canvasCursor.y, r);
glow.addColorStop(0, 'rgba(255,255,255,1)');
glow.addColorStop(1, 'rgba(0,0,0,0)');
ctx.globalCompositeOperation = 'lighten';
ctx.globalAlpha = speedAlpha;
ctx.fillStyle = glow;
ctx.beginPath();
ctx.arc(canvasCursor.x, canvasCursor.y, r, 0, Math.PI * 2);
ctx.fill();
}

canvasTexture.needsUpdate = true;
});
</script>

<template>
<TresPoints :geometry="particlesGeometry" :material="particlesMaterial" />
<TresMesh :visible="false" @pointermove="onMouseMove">
<TresPlaneGeometry :args="[15 * aspect, 15]" />
<TresMeshBasicMaterial />
</TresMesh>
<Suspense>
<EffectComposerPmndrs>
<ChromaticAberrationPmndrs :offset="chromaticAberrationOffset" radial-modulation />
<BloomPmndrs :intensity="bloomIntensity" :luminance-threshold="0" :luminance-smoothing="0.3" mipmap-blur />
</EffectComposerPmndrs>
</Suspense>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script setup lang="ts">
// TODO: Implement experiment logic
const uuid = 'pixel-noise-shaders-sandbox-experiment'

useControls('fpsgraph', {
uuid,
})

provide('uuid', uuid)

const { canvasClearColor } = useControls('🎨 canvas', {
clearColor: '#0e1c2f',
}, {
uuid,
})
</script>

<template>
<TresLeches :uuid="uuid" />
<TresCanvas :clear-color="canvasClearColor">
<TresPerspectiveCamera :position="[0, 0, 10]" :look-at="[0, 0, 0]" />
<PixelNoiseShadersSandboxThePixelNoiseShadersSandbox />
<TheScreenshot />
</TresCanvas>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
precision highp float;

varying vec3 vColor;

void main() {
vec2 pc = gl_PointCoord;
float edge = min(min(pc.x, 1.0 - pc.x), min(pc.y, 1.0 - pc.y));
float alpha = smoothstep(0.0, 0.15, edge);
gl_FragColor = vec4(vColor, alpha);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
uniform vec2 uResolution;
uniform float uTime;
uniform sampler2D uDisplacementTexture;
uniform vec3 uColorPrimary;
uniform vec3 uColorSecondary;
uniform float uZoom;
uniform float uSizeContrast;
uniform float uSpeed;
uniform float uDisplacementStrength;
uniform float uMinCellSize;
uniform float uMaxCellSize;

attribute float aIntensity;
attribute float aAngle;

varying vec3 vColor;

// Simplex noise (Ashima Arts)
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec3 permute(vec3 x) { return mod289(((x * 34.0) + 10.0) * x); }

float snoise(vec2 v) {
const vec4 C = vec4(
0.211324865405187,
0.366025403784439,
-0.577350269189626,
0.024390243902439
);
vec2 i = floor(v + dot(v, C.yy));
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod289(i);
vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
m = m * m;
m = m * m;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}

float snoise01(vec2 v) {
return (1.0 + snoise(v)) * 0.5;
}

float noise2d(vec2 st, float t) {
return snoise01(vec2(st.x + t * 0.005, st.y - t * 0.008));
}

// Domain warping — iquilezles.org/articles/warp
float pattern(vec2 p, float t) {
vec2 q = vec2(
noise2d(p + vec2(0.0, 0.0), t),
noise2d(p + vec2(5.2, 1.3), t)
);
vec2 r = vec2(
noise2d(p + 4.0 * q + vec2(1.7, 9.2), t),
noise2d(p + 4.0 * q + vec2(8.3, 2.8), t)
);
return noise2d(p + 1.0 * r, t);
}

void main() {
vec3 newPosition = position;
float t = uTime * uSpeed;

// --- Fluid wave intensity: domain-warped noise drives point size across the full plane ---
float n = pattern(uv * uZoom + t * 0.015, t);
float waveIntensity = smoothstep(0.1, 0.85, n);

// --- Color ---
float noiseBlend = snoise01(uv * 3.0 + t * 0.005);
vColor = mix(uColorSecondary, uColorPrimary, waveIntensity * noiseBlend);
vColor = pow(vColor, vec3(2.0));

// --- Cursor displacement (subtle) ---
float displacementIntensity = texture2D(uDisplacementTexture, uv).r;
displacementIntensity = smoothstep(0.1, 0.9, displacementIntensity);
vec3 displacement = vec3(cos(aAngle), sin(aAngle), 0.0);
displacement *= displacementIntensity * uDisplacementStrength * aIntensity;
newPosition += displacement;

// --- Final position ---
vec4 modelPosition = modelMatrix * vec4(newPosition, 1.0);
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;

// --- Point size driven by fluid wave ---
float sizeWave = pow(waveIntensity, uSizeContrast);
gl_PointSize = 0.1 * uResolution.y * sizeWave;
gl_PointSize *= (1.0 / -viewPosition.z);
gl_PointSize = clamp(gl_PointSize, uMinCellSize, uMaxCellSize);
}
Loading
Loading