glre is a simple glsl and wgsl Reactive Engine on the web and native via TypeScript, React, Solid and more.
npm install glre
Docs : glre IntroductionAPI : glre API and featureGuide : Creating a scene |
⛪️ reev: reactive event state manager🔮 refr: request animation frame |
github discussions welcome✨@tseijp twittertsei.jp articles |
| glre simplifies WebGL2 / WebGPU programming via TypeScript, React, Solid and more. |
|
|
ESM <script type="module">
import { createGL } from 'https://esm.sh/glre'
import { vec4, uv } from 'https://esm.sh/glre/node'
createGL({ fs: vec4(uv, 0, 1) }).mount()
</script> |
|
|
|
glre's node system reconstructs shader authoring through TypeScript syntax, dissolving the boundary between CPU logic and GPU computation. Rather than traditional string-based shader composition, this system materializes shaders as abstract syntax trees, enabling unprecedented code mobility across WebGL2 and WebGPU architectures.
// Shader logic materializes through method chaining
const fragment = vec4(fract(position.xy.div(iResolution)), 0, 1)
.mul(uniform(brightness))
.mix(texture(backgroundMap, uv()), blend)The system operates through proxy objects that capture mathematical operations as node graphs, later transpiled to target shader languages. This deconstructed approach eliminates the traditional separation between shader compilation and runtime execution.
Traditional shader types dissolve into factory functions that generate node proxies:
// Types emerge from function calls rather than declarations
const position = vec3(x, y, z) // Becomes position node
const transform = mat4().mul(modelView) // Matrix composition
const sampled = texture(map, uv()) // Sampling operationEach operation generates immutable node structures, building computation graphs that exist independently of their eventual compilation target.
The Fn constructor dissolves function boundaries, creating reusable computation patterns:
// Functions exist as first-class node compositions
const noise = Fn(([coord]) => {
return sin(coord.x.mul(12.9898))
.add(sin(coord.y.mul(78.233)))
.mul(43758.5453)
.fract()
})
// Composition becomes transparent
const surface = noise(position.xz.mul(scale)).mix(noise(position.xz.mul(scale.mul(2))), 0.5)Traditional control structures become node compositions, eliminating imperative sequence:
// Conditional logic as expression trees
If(height.greaterThan(waterLevel), () => {
return grassTexture.sample(worldUV)
}).Else(() => {
return waterTexture.sample(worldUV.add(wave))
})
// Loops decompose into iteration patterns
Loop(samples, ({ i }) => {
accumulator.assign(accumulator.add(sample(position.add(offsets.element(i)))))
})Uniforms transcend static parameter passing, becoming reactive data channels:
const time = uniform(0) // Creates reactive binding
const amplitude = uniform(1) // Automatic GPU synchronization
// Values flow reactively without explicit updates
const wave = sin(time.mul(frequency)).mul(amplitude)
// Runtime updates propagate automatically
time.value = performance.now() / 1000Vertex attributes dissolve into data stream abstractions:
// Attributes become typed data channels
const positions = attribute(vertexData) // Raw data binding
const normals = attribute(normalData) // Parallel stream
const uvs = attribute(textureCoords) // Coordinate mapping
// Streams compose transparently
const worldPosition = positions.transform(modelMatrix)
const viewNormal = normals.transform(normalMatrix)