Wavey Lines
"use client";
import { memo } from "react";
import {
Shader,
type ShaderPreset,
type ShaderProps,
type ShaderMotionParams,
} from "@/components/Shader";
import {
colorPropsAreEqual,
declarePI,
defaultPatternSizing,
getShaderColorFromString,
sizingVariablesDeclaration,
} from "@/lib/shader-utils";
import {
ShaderFitOptions,
type ShaderSizingParams,
type ShaderSizingUniforms,
} from "@/lib/shader-utils";
import { cn } from "@/lib/utils";
type WavesUniforms = ShaderSizingUniforms & {
u_colorFront: [number, number, number, number];
u_colorBack: [number, number, number, number];
u_shape: number;
u_frequency: number;
u_amplitude: number;
u_spacing: number;
u_proportion: number;
u_softness: number;
};
type WavesParams = ShaderSizingParams &
ShaderMotionParams & {
colorFront?: string;
colorBack?: string;
rotation?: number;
shape?: number;
frequency?: number;
amplitude?: number;
spacing?: number;
proportion?: number;
softness?: number;
};
export interface WavesProps extends ShaderProps, WavesParams {}
type WavesPreset = ShaderPreset<WavesParams>;
const defaultPreset: WavesPreset = {
name: "Default",
params: {
...defaultPatternSizing,
scale: 2,
speed: 1,
frame: 0,
colorFront: "#ffbb00",
colorBack: "#000000",
shape: 0,
frequency: 0.5,
amplitude: 0.5,
spacing: 1,
proportion: 0.1,
softness: 0,
},
};
// language=GLSL
const fragmentShader: string = `#version 300 es
precision mediump float;
uniform float u_time;
uniform vec4 u_colorFront;
uniform vec4 u_colorBack;
uniform float u_shape;
uniform float u_frequency;
uniform float u_amplitude;
uniform float u_spacing;
uniform float u_proportion;
uniform float u_softness;
${sizingVariablesDeclaration}
out vec4 fragColor;
${declarePI}
void main() {
vec2 shape_uv = v_patternUV;
shape_uv *= 4.;
float time_offset = u_time * 0.5;
float wave = .5 * cos(shape_uv.x * u_frequency * TWO_PI + time_offset);
float zigzag = 2. * abs(fract(shape_uv.x * u_frequency + time_offset * 0.1) - .5);
float irregular = sin(shape_uv.x * .25 * u_frequency * TWO_PI + time_offset * 0.3) * cos(shape_uv.x * u_frequency * TWO_PI + time_offset * 0.7);
float irregular2 = .75 * (sin(shape_uv.x * u_frequency * TWO_PI + time_offset * 0.4) + .5 * cos(shape_uv.x * .5 * u_frequency * TWO_PI + time_offset * 0.6));
float offset = mix(zigzag, wave, smoothstep(0., 1., u_shape));
offset = mix(offset, irregular, smoothstep(1., 2., u_shape));
offset = mix(offset, irregular2, smoothstep(2., 3., u_shape));
offset *= 2. * u_amplitude;
float spacing = (.001 + u_spacing);
float shape = .5 + .5 * sin((shape_uv.y + offset) * PI / spacing);
float aa = .0001 + fwidth(shape);
float dc = 1. - clamp(u_proportion, 0., 1.);
float res = smoothstep(dc - u_softness - aa, dc + u_softness + aa, shape);
vec3 fgColor = u_colorFront.rgb * u_colorFront.a;
float fgOpacity = u_colorFront.a;
vec3 bgColor = u_colorBack.rgb * u_colorBack.a;
float bgOpacity = u_colorBack.a;
vec3 color = fgColor * res;
float opacity = fgOpacity * res;
color += bgColor * (1. - opacity);
opacity += bgOpacity * (1. - opacity);
fragColor = vec4(color, opacity);
}
`;
export const groovyPreset: WavesPreset = {
name: "Groovy",
params: {
...defaultPatternSizing,
scale: 5,
rotation: 90,
speed: 3,
frame: 0,
colorFront: "#fcfcee",
colorBack: "#ff896b",
shape: 3,
frequency: 0.2,
amplitude: 0.25,
spacing: 1.17,
proportion: 0.57,
softness: 0,
},
};
export const tangledUpPreset: WavesPreset = {
name: "Tangled up",
params: {
...defaultPatternSizing,
scale: 0.5,
rotation: 0,
speed: 1.2,
frame: 0,
colorFront: "#133a41",
colorBack: "#c2d8b6",
shape: 2.07,
frequency: 0.44,
amplitude: 0.57,
spacing: 1.05,
proportion: 0.75,
softness: 0,
},
};
export const waveRidePreset: WavesPreset = {
name: "Ride the wave",
params: {
...defaultPatternSizing,
scale: 1.7,
rotation: 0,
speed: 0.6,
frame: 0,
colorFront: "#fdffe6",
colorBack: "#1f1f1f",
shape: 2.25,
frequency: 0.2,
amplitude: 1,
spacing: 1.25,
proportion: 1,
softness: 0,
},
};
export const wavesPresets: WavesPreset[] = [
defaultPreset,
groovyPreset,
tangledUpPreset,
waveRidePreset,
];
export const WaveyLines: React.FC<WavesProps> = memo(function WavesImpl({
// Own props
colorFront = defaultPreset.params.colorFront,
colorBack = defaultPreset.params.colorBack,
shape = defaultPreset.params.shape,
frequency = defaultPreset.params.frequency,
amplitude = defaultPreset.params.amplitude,
spacing = defaultPreset.params.spacing,
proportion = defaultPreset.params.proportion,
softness = defaultPreset.params.softness,
// Motion props
speed = defaultPreset.params.speed,
frame = defaultPreset.params.frame,
// Sizing props
fit = defaultPreset.params.fit,
scale = defaultPreset.params.scale,
rotation = defaultPreset.params.rotation,
offsetX = defaultPreset.params.offsetX,
offsetY = defaultPreset.params.offsetY,
originX = defaultPreset.params.originX,
originY = defaultPreset.params.originY,
worldWidth = defaultPreset.params.worldWidth,
worldHeight = defaultPreset.params.worldHeight,
// Other props
maxPixelCount = 6016 * 3384, // Higher max resolution for this shader
...props
}: WavesProps) {
const uniforms = {
// Own uniforms
u_colorFront: getShaderColorFromString(colorFront),
u_colorBack: getShaderColorFromString(colorBack),
u_shape: shape,
u_frequency: frequency,
u_amplitude: amplitude,
u_spacing: spacing,
u_proportion: proportion,
u_softness: softness,
// Sizing uniforms
u_fit: ShaderFitOptions[fit],
u_scale: scale,
u_rotation: rotation,
u_offsetX: offsetX,
u_offsetY: offsetY,
u_originX: originX,
u_originY: originY,
u_worldWidth: worldWidth,
u_worldHeight: worldHeight,
} satisfies WavesUniforms;
return (
<Shader
{...props}
className={cn("w-full h-full", props.className)}
speed={speed}
frame={frame}
fragmentShader={fragmentShader}
uniforms={uniforms}
/>
);
}, colorPropsAreEqual);
Usage
import { WaveyLines } from "@/components/wavey-lines";
export default function Component() {
return <WaveyLines
colorFront="#ffbb00"
colorBack="#000000"
shape={0}
frequency={0.5}
amplitude={0.5}
spacing={1}
proportion={0.1}
softness={0}
/>;
}
Credit
https://github.com/paper-design/shaders/blob/main/packages/shaders/src/shaders/waves.tsÂ
Last updated on