glsx
v0.0.3
Published
Composable GLSL shaders for React
Readme
GLSX
Composable GLSL shaders for React.
Installation
npm install glsx
# or
pnpm add glsx
# or
yarn add glsxFeatures
- Write GLSL fragment shaders as React components
- Compose shaders by nesting them
- Share global variables across shader components
- Mix React state with shader uniforms seamlessly
Basic Example
import { Canvas, fragment } from 'glsx'
function App() {
return (
<Canvas width={400} height={400}>
{fragment`
// Create a simple gradient
return vec4(uv.x, uv.y, 0.5, 1.0);
`}
</Canvas>
)
}Advanced Example: Nested Shaders, Global Values, and React State
Deployed version of this example: glsx.vercel.app
This example demonstrates all key features of glsx:
'use client'
import { useState, useEffect, useRef } from 'react'
import { Canvas, fragment, createGlobal } from 'glsx'
// Create a global variable that can be shared across shaders (like React Context)
const distanceFromCenter = createGlobal('float')
// Inner shader component with time-based animation
function ColorEffect() {
const [time, setTime] = useState(0)
const speedRef = useRef(1.0)
// Animate time
useEffect(() => {
const interval = setInterval(() => {
setTime((t) => t + 0.016 * speedRef.current)
}, 16)
return () => clearInterval(interval)
}, [])
return (
<>
{fragment`
// Read the "context" value calculated by the parent shader
float dist = ${distanceFromCenter};
// Create animated wave patterns
float wave1 = sin(uv.x * 10.0 + ${time}) * 0.5 + 0.5;
float wave2 = cos(uv.y * 8.0 - ${time} * 0.7) * 0.5 + 0.5;
float wave3 = sin((uv.x + uv.y) * 6.0 + ${time} * 1.5) * 0.5 + 0.5;
// Combine waves with context-based modulation
vec3 color = vec3(
wave1 * (1.0 - dist * 0.5),
wave2 * (1.0 - dist * 0.3),
wave3 * (0.8 + dist * 0.4)
);
// Add a pulsing effect based on distance and time
float pulse = sin(dist * 8.0 - ${time} * 2.0) * 0.2 + 0.8;
color *= pulse;
return vec4(color, 1.0);
`}
{/* Mix React UI with shaders */}
<div className="controls">
<label>
Animation Speed:
<input
type="range"
min={0}
max={3}
step={0.1}
defaultValue={1.0}
onChange={(e) => {
speedRef.current = parseFloat(e.target.value)
}}
/>
</label>
</div>
</>
)
}
// Outer shader component
function DistortionEffect() {
const [distortion, setDistortion] = useState(0.1)
return (
<Canvas width={500} height={500}>
{fragment`
// Calculate a value based on UV coordinates
vec2 center = vec2(0.5, 0.5);
float dist = length(uv - center);
// Store it in the global variable to pass as "context" to child shaders
${distanceFromCenter} = dist;
// Apply distortion to UV coordinates
vec2 distortedUV = uv;
distortedUV.x += sin(uv.y * 10.0) * ${distortion};
distortedUV.y += cos(uv.x * 10.0) * ${distortion};
// Update the uv for nested shaders
uv = distortedUV;
// Call the nested shader and store its result
vec4 childColor = ${(<ColorEffect />)};
// Apply additional processing to the child shader's output
// Add a vignette effect based on distance from center
float vignette = 1.0 - (dist * 1.5);
childColor.rgb *= vignette;
return childColor;
`}
<div className="controls">
<label>
Distortion:
<input
type="range"
min={0}
max={0.5}
step={0.01}
value={distortion}
onChange={(e) => setDistortion(parseFloat(e.target.value))}
/>
</label>
</div>
</Canvas>
)
}
export default DistortionEffectWhat's happening:
- Nested Fragment Shaders:
DistortionEffectcallsColorEffectusing${(<ColorEffect />)}, allowing shader composition - Shared Global Value as Context: The parent shader calculates
distanceFromCenterand stores it in a global variable, which child shaders can read - similar to React Context - Mixed React State and UI: Both components use React's
useStateto control shader parameters, and render regular React UI elements alongside shader code
API
Canvas
The root component that creates a WebGL2 canvas and renders your shaders.
<Canvas width={400} height={400} className="my-canvas">
{/* your fragment shader here */}
</Canvas>fragment
A tagged template literal for writing GLSL fragment shader code.
fragment`
return vec4(uv.x, uv.y, 0.5, 1.0);
`Available built-in variables:
uv- UV coordinates (vec2, range 0-1)
createGlobal(type)
Creates a global variable that can be shared across shader components.
const myValue = createGlobal('float') // 'float' | 'int' | 'bool'sampler2D(src)
Load and use images/textures in your shaders.
fragment`
vec4 color = texture(${sampler2D('/image.png')}, uv);
return color;
`Author
Shu Ding (https://shud.in)
License
ISC
