メインコンテンツまでスキップ

Node System

The Node System is a lightweight shader language that allows you to write shaders in TypeScript, supporting both WebGL and WebGPU. With Three.js Shading Language (TSL) compatible syntax, you can express complex GLSL/WGSL code using intuitive JavaScript constructs.

Why Node System?

The Reality of Shader Development

Shader development presents significant barriers for most developers. Learning GLSL/WGSL, navigating WebGL/WebGPU API differences, and dealing with lack of type safety are just a few obstacles that hinder creativity.

Node System fundamentally solves these problems. Through TypeScript's type system and method chaining, it transforms shader development into an intuitive and safe experience.

Realizing Mathematical Aesthetics

The essence of shaders is mathematics. The combination of vector operations, trigonometric functions, and interpolation functions creates beautiful visual representations. Node System faithfully expresses mathematical concepts in code, providing an environment where thoughts can be directly visualized.

Core Concepts

Type System

const x = float(1.5) // Floating point number
const y = int(2) // Integer
const z = bool(true) // Boolean

Operator Chaining

Natural expression following mathematical intuition.

const result = vec3(1, 2, 3).add(vec3(4, 5, 6)).mul(2).normalize()

Swizzling

Access to vector components.

const pos = vec3(1, 2, 3)
const xy = pos.xy // vec2(1, 2)
const rgb = pos.rgb // Interpreted as color components

Functions and Scope

Function Definition

const boxSDF = Fn((args) => {
const [p, size] = args
const d = abs(p).sub(size).toVar()
const inside = max(d.x, max(d.y, d.z)).min(float(0))
const outside = max(d, vec3(0)).length()
return inside.add(outside)
})

Variable Management

const shader = Fn(() => {
// Variable declaration
const localVar = vec3(1, 2, 3).toVar('myVariable')

// Assignment
localVar.assign(vec3(4, 5, 6))

// Swizzle assignment
localVar.y = float(10)

return localVar
})

Control Flow

Conditional Statements

If(condition, () => {
// Processing for true case
}).Else(() => {
// Processing for false case
})

Loops

Loop(int(10), ({ i }) => {
sum.assign(sum.add(i))
})

Mathematical Functions

Basic Functions

const wave = sin(time).mul(0.5).add(0.5)
const circular = vec2(cos(angle), sin(angle))

Practical Patterns

Distance Functions

Define distance functions that form the foundation of ray marching.

const sphereSDF = Fn((args) => {
const [p, radius] = args
return length(p).sub(radius)
})

const boxSDF = Fn((args) => {
const [p, size] = args
const d = abs(p).sub(size)
return max(d, vec3(0))
.length()
.add(min(max(d.x, max(d.y, d.z)), float(0)))
})

Normal Calculation

Normal computation using numerical differentiation.

const calculateNormal = Fn((args) => {
const [p, sdf] = args
const eps = float(0.001)

const dx = sdf(p.add(vec3(eps, 0, 0))).sub(sdf(p.sub(vec3(eps, 0, 0))))
const dy = sdf(p.add(vec3(0, eps, 0))).sub(sdf(p.sub(vec3(0, eps, 0))))
const dz = sdf(p.add(vec3(0, 0, eps))).sub(sdf(p.sub(vec3(0, 0, eps))))

return normalize(vec3(dx, dy, dz))
})

Ray Marching

Basic ray marching algorithm.

const rayMarch = Fn((args) => {
const [origin, direction] = args

const totalDistance = float(0).toVar()
const position = origin.toVar()

Loop(int(100), ({ i }) => {
const distance = sceneSDF(position)

If(distance.lessThan(float(0.001)), () => {
// Hit processing
return position
})

position.assign(position.add(direction.mul(distance)))
totalDistance.assign(totalDistance.add(distance))
})

return position
})

WebGL/WebGPU Support

Automatic Conversion

The same code automatically converts to both WebGL (GLSL) and WebGPU (WGSL).

fn main() -> vec4f {
let uv: vec2f = position.xy / iResolution;
let color: vec3f = vec3f(uv, sin(iTime) * 0.5 + 0.5);
return vec4f(color, 1.0);
}

Browser Compatibility

Automatically falls back to WebGL/GLSL for browsers without WebGPU support, while using WGSL for WebGPU-capable browsers.

Application Examples

Procedural Textures

const noiseTexture = Fn(() => {
const uv = position.xy.div(iResolution).toVar()

const noise1 = sin(uv.x.mul(10)).mul(sin(uv.y.mul(10)))
const noise2 = sin(uv.x.mul(20).add(iTime)).mul(0.5)

const finalNoise = noise1.add(noise2).mul(0.5).add(0.5)

return vec4(finalNoise, finalNoise, finalNoise, 1)
})

Fractal Generation

const mandelbrot = Fn(() => {
const uv = position.xy.div(iResolution).mul(4).sub(vec2(2))

const c = uv.toVar()
const z = vec2(0).toVar()
const iterations = int(0).toVar()

Loop(int(100), ({ i }) => {
If(length(z).greaterThan(float(2)), () => {
// Divergence test
return
})

z.assign(vec2(z.x.mul(z.x).sub(z.y.mul(z.y)).add(c.x), z.x.mul(z.y).mul(2).add(c.y)))
iterations.assign(i)
})

const color = iterations.div(float(100))
return vec4(color, color, color, 1)
})