LYGIA Shader Library
repository·main·Indexed 25 days ago
https://github.com/patriciogonzalezvivo/lygiaA granular, cross-platform, and multi-language shader library providing a vast collection of reusable functions. It supports GLSL, HLSL, WGSL, Metal, and CUDA, and integrates with various environments via #include directives or the WESL (WebGPU Shading Language) superset. The library is organized into categories such as math, color, generative, SDF, and lighting, and can be installed via Git, NPM, or Cargo.
What's inside LYGIA
- LYGIA is a large-scale, cross-platform, and multi-language shader library composed of reusable functions. It is designed to be granular, flexible, and efficient, allowing developers to prototype and port shader projects quickly. The library supports multiple shading languages and integrates with a wide variety of environments, engines, and frameworks.
Overview of WESL (WebGPU Shading Language)
mainWESL is a superset of WGSL designed to improve shader development. Key features include:
importstatements: Allows splitting shader code across files and loading libraries (npm or cargo).@if,@else,@elseifdirectives: Enables conditional compilation to assemble specialized shaders at build time or runtime.
Lygia provides its WebGPU-compatible shaders using the
.weslextension. While older.wgslversions exist, the.weslversions are more complete and recommended for new users.Understand Lygia shader design patterns
mainLygia follows specific design patterns for shader functions to ensure modularity and multi-language support:
- Granularity: Each function typically resides in its own file (e.g.,
myFunc.glslcontainsmyFunc()). - Multi-language: Functions are provided across multiple shading languages including GLSL (
*.glsl), HLSL (*.hlsl), WGSL (*.wgsl), Metal (*.msl), and CUDA (*.cuh). - Self-documentation: Files contain a YAML-structured comment at the top detailing
contributors,description,use(function signature),notes, andoptions(#defineflags). - Collision Prevention: Functions are wrapped in
#ifndef FNC_NAMEguards to prevent name collisions. - Templating: Reusability is achieved via
#defineoptions. Common patterns include templating the return type (e.g.,MYFUNC_TYPE) or the sampling function (e.g.,MYFUNC_SAMPLER_FNC). - Argument Ordering: Optional arguments are placed at the end. Arguments should be sorted by memory footprint:
SAMPLER_TYPE,mat4,mat3,mat2,vec4,vec3,vec2,float,ivec4,ivec3,ivec2,int,bool.
- Granularity: Each function typically resides in its own file (e.g.,
Add a shader to Lygia WESL
mainTo contribute a new shader to the Lygia WESL library, follow these steps:
- Create a
.weslfile: Place the file alongside existing shader files. Follow the Lygia convention of having one user-facing function per file to optimize application bundle sizes. You may include multiple type variants of the same function (e.g., one forf32and one forvec3f) within a single file. - Add tests: Create corresponding tests in the
test/wesldirectory using the appropriate testing method:- Use
testCompute()for pure math functions. - Use
testFragment()for derivative functions (likefwidth,dpdx,dpdy) or texture sampling. - Use
toMatchImage()for visual regression tests such as filters, generative patterns, or complex rendering.
- Use
- Create a
Resolve LYGIA dependencies with build tools (Vite, esbuild, Webpack)
mainIf you are working with local
.glslfiles in a modern build pipeline, you can use existing plugins to handle#includedirectives automatically:- Vite: Use
vite-plugin-glslto import local dependencies or load inline shaders. - esbuild: Use
esbuild-plugin-glsl-includeto import local.glsldependencies. - Webpack: Use
webpack-glsl-loaderto import local dependencies.
- Vite: Use
Implement WGSL function naming conventions
mainBecause WGSL does not support function overloading, you must use unique function names that encode the size of the return type and the parameter types using suffixes.
Naming Rules:
- Consistent scalar types: No suffix needed (e.g.,
fn random(p: f32) -> f32). - Consistent return type, varying parameter size: Suffix the parameter size (e.g.,
random2forvec2fparameters,random3forvec3f). - Inconsistent return and parameter types: Use two suffixes:
[Return Size][Parameter Size].
Suffix Mapping:
vec2<T>orf2$\rightarrow$2vec3<T>orf3$\rightarrow$3vec4<T>orf4$\rightarrow$4- Scalar $\rightarrow$
1
Example Suffix Logic:
random21: Returnsvec2, acceptsf32(scalar).random32: Returnsvec3, acceptsvec2.random44: Returnsvec4, acceptsvec4.
// Consistent scalar fn random(p: f32) -> f32 { ... } // Return type consistent, parameter size varies fn random2(p: vec2f) -> f32 { ... } fn random3(p: vec3f) -> f32 { ... } // Both return and parameter types vary (ReturnSize + ParamSize) fn random21(p: f32) -> vec2f { ... } fn random22(p: vec2f) -> vec2f { ... } fn random32(p: vec2f) -> vec3f { ... } fn random43(p: vec3f) -> vec4f { ... }- Consistent scalar types: No suffix needed (e.g.,
Install and use Lygia in Rust
mainTo use Lygia in a Rust project, add the
lygiacrate. WESL tools are also available for Rust developers.Installation
cargo add lygiaLinking at build time
Add
weslto your build dependencies and use abuild.rsfile to generate artifacts.cargo add --build wesl/// build.rs fn main() { wesl::Wesl::new("src/shaders").build_artifact("main.wesl", "my_shader"); }Linking at run-time
Add
weslto your dependencies to compile shaders during application execution.cargo add wesllet shader_string = Wesl::new("src/shaders") .compile("main.wesl") .inspect_err(|e| eprintln!("WESL error: {e}")) .unwrap() .to_string();Using the Rust CLI tool
Install the CLI to compile shaders directly from the terminal.
cargo install wesl-cli wesl compile <path/to/shader.wesl>Configure Lygia with a JavaScript/TypeScript Bundler
mainFor applications using
vite,webpack, orrollup, installweslandwesl-plugin. You can choose between runtime or static linking:Runtime Linking
Use the
?linksuffix on your import. This allows you to use WESL's conditional compilation features (@ifdirectives) to adapt shaders dynamically at runtime based on GPU capabilities or user settings. This requires thewesllinker in your runtime bundle.Static Linking
Use the
?staticsuffix. This bundles shaders into a single transpiled WGSL string at build time. This is more efficient (~15KB smaller bundle) but prevents dynamic adaptation via@ifdirectives.// Runtime Linking import appWesl from "../shaders/app.wesl?link"; import { link } from "wesl"; const linked = await link(appWesl); linked.createShaderModule(gpuDevice); // Static Linking import appWgsl from "../shaders/app.wesl?static";Resolve LYGIA dependencies via LYGIA server
mainFor cloud platforms (like CodePen or Observable) where you cannot host local files, use the LYGIA server to resolve
#includedependencies online.Include the resolver script in your HTML:
- JavaScript:
<script src="https://lygia.xyz/resolve.js"></script> - ES6 Module:
<script type="module">import resolveLygia from "https://lygia.xyz/resolve.esm.js"</script>
- JavaScript:
Call the resolver in your JavaScript code to transform your shader source code strings.
Install LYGIA locally
mainYou can install LYGIA locally to bundle it with your project. Choose one of the following methods:
- Git Clone:
git clone https://github.com/patriciogonzalezvivo/lygia.git - Git Submodule:
git submodule add https://github.com/patriciogonzalezvivo/lygia.git - Degit (No history):
npx degit https://github.com/patriciogonzalezvivo/lygia.git lygia - NPM: Use the
@lygia/lygiapackage.
If you want to reduce the library size by only keeping the files for your specific language (e.g., GLSL), use the
prune.pyscript.- Git Clone:
Resolve LYGIA shader dependencies using JavaScript
mainTo use LYGIA functions in a web environment, you must resolve the
#include "path/to/file.glsl"dependencies into a single shader string. You can use theresolveLygia()orresolveLygiaAsync()functions provided by theresolve-lygianpm module or the vanilla JS resolver atlygia.xyz/resolve.js.These functions take a
stringorstring[](representing your shader code or a list of shader files) and parse it, solving all#includedependencies. To support specific versions of LYGIA, use the path patternlygia/vX.X.X/...within your dependency paths.Use LYGIA functions in shaders via #include
mainTo use LYGIA in your shaders, use the
#includedirective to import specific functions. Ensure your environment is configured to resolve these include paths. Each function is typically contained in its own file.#include "lygia/space/ratio.glsl" #include "lygia/math/decimate.glsl" #include "lygia/draw/circle.glsl" void main(void) { // ... use functions like ratio(), decimate(), or circle() }