Skip to content

geoffreytools/vite-plugin-glslify-inject

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

vite-plugin-glslify-inject

Why | How to use | Output | How to set up

  • Write GLSL in distinct files with the glslify module system;
  • Import them as ES6 modules;
  • Inject contants at runtime;
  • Auto-generate TS types for your modules, constants and uniforms;
  • Compatible with linters such as GLSL Lint.

Why

Organising code in distinct files and dedicated file types is a matter of preference.

This plugin enables a setup where you keep GLSL seperate from your JS while having all the advantages of glslify module resolution and runtime code injection, without having to deal with string interpolation, potentially loosing GLSL linting in the process, and in what I consider a cleaner format.

Getting TS types for your uniforms at the TS/GLSL interface is also nice to have. You probably already do it for other APIs.

How to use

In this example we create a material that takes a grayscales texture and styles it with false colors.

main.ts

import passThrough from '@shaders/passThrough.vert';
import { falseColorsWith, FalseColorsUniforms } from '@shaders/falseColors.frag';

const gradient = ["#00178f", "#006172", "#47dd00"] as const;
const steps = gradient.length;

const material = new THREE.RawShaderMaterial({
    uniforms: parseUniforms<FalseColorsUniforms>()({ gradient, myTexture }),
    vertexShader: passThrough,
    fragmentShader: falseColorsWith({ steps })
});

I don't detail parseUniforms but even with a library like threejs you probably do some processing on your uniforms before passing them to your material.

flaseColors.frag

We import the interColor util with a glslify pragma.

We define uniform vec3 gradient[steps] where steps can be overwritten at runtime.

#pragma glslify: interColor = require(./lib/interColor.glsl)

precision highp float;
precision highp int;

varying vec2 vUv;

const int steps = 2; // overwritten at runtime

uniform vec3 gradient[steps];
uniform sampler2D myTexture;

void main() {
    float alpha = texture2D(myTexture, vUv).r;

    vec3 rgb = interColor(gradient, alpha);

    gl_FragColor = vec4(rgb, 1);
}

lib/interColor.glsl

#pragma glslify: export(interColor)

precision highp float;

vec3 interColor (vec3 gradient[2], float t) {
    return mix(gradient[0], gradient[1], t);
}

vec3 interColor (vec3 gradient[3], float t) {
    vec3 color;
    if(t <= 0.5) {
        color = mix(gradient[0], gradient[1], t * 2.0);
    }  else {
        color = mix(gradient[1], gradient[2], (t - 0.5) * 2.0);
    }
    return color;
}

vec3 interColor (vec3 gradient[4], float t) {

// [...]

Output

Runtime values

At current, precision specifiers are not deduplicated and there is no compile-time check to see if they are consistent.

precision highp float;

vec3 interColor (vec3 gradient[2], float t) {
    return mix(gradient[0], gradient[1], t);
}

vec3 interColor (vec3 gradient[3], float t) {
    vec3 color;
    if(t <= 0.5) {
        color = mix(gradient[0], gradient[1], t * 2.0);
    }  else {
        color = mix(gradient[1], gradient[2], (t - 0.5) * 2.0);
    }
    return color;
}

vec3 interColor (vec3 gradient[4], float t) {

// [...]

precision highp float;
precision highp int;
#define GLSLIFY 1

varying vec2 vUv;

const int steps = 2; // overwritten at runtime

uniform vec3 gradient[steps];
uniform sampler2D myTexture;

void main() {
    float alpha = texture2D(myTexture, vUv).r;

    vec3 rgb = interColor(gradient, alpha);

    gl_FragColor = vec4(rgb, 1);
}

Types

The following types are auto generated next to their source and updated live while the dev server is running. A variety of exports are available to enable different import patterns.

declare module '@shaders/passThrough.vert' {
    const passThrough: string;

    export { passThrough as default, passThrough as glsl, passThrough };
}
declare module '#shaders/falseColors.frag' {
    namespace THREE {
        export type Vector3 = { x: number, y: number, z: number, isVector3: true };
        export type Color = { r: number, g: number, b: number, isColor: true };
        export type Texture = { image: unknown, isTexture: true, isCubeTexture?: never };
    }

    const falseColors: string;

    const inject: (map: { steps?: number }) => string;

    type Uniforms = {
        gradient: Array<number> | Float32Array | Array<[number, number, number]> | Array<THREE.Vector3> | Array<THREE.Color>,
        myTexture: WebGLTexture | THREE.Texture
    };

    export {
        falseColors as default,
        falseColors as glsl,
        falseColors,
        inject,
        inject as falseColorsWith,
        Uniforms,
        Uniforms as FalseColorsUniforms
    };
}

Because gradient is an array which length is defined by a constant, and because constants can be injected at runtime, Uniform.gradient lists array types instead of tuples.

See How to set up to control this output.

How to set up

npm install --save-dev vite-plugin-glslify-inject

The vite.config.js corresponding to the above example:

  • we invoke the plugin;
  • we pass in your include/exclude patterns;
  • we pass a path alias pointing to the location of your shaders for .d.ts modules to be generated;
  • we pass a library preset to generate threejs types (in addition to native types) for uniforms.
import { defineConfig } from 'vite'
import glsl from 'vite-plugin-glslify-inject'

export default defineConfig({
    plugins: [
        glsl({
            include: './src/shaders/**/*.(vert|frag)',
            exclude: 'node_modules/**',
            types: { alias: '@shaders', library: 'threejs' }
        })
    ],
    resolve: {
        alias: {
          '@shaders': "/src/shaders/",
        }
    }
})

Uniforms

You can disable the generation of type definitions for uniforms if you don't need them. There is no need to specify a library if you do so:

glsl({
    include: './src/shaders/**/*.(vert|frag)',
    exclude: 'node_modules/**',
    types: { alias: '@shaders', uniforms: false }
})

Libraries

You are likely using a library which bundles your uniforms for you and enables higher level data structures than flat/typed arrays, hence the library field.

In the example above we pass in the string threejs. I may add types for other libraries, but you can also inject your own types using the types fields in the configuration.

Array types

The plugin can handle the nesting of types in arrays for you, but your library definition needs to opt-in for it. It is the case of the built-in threejs for example.

Here is a minimal example for the trivial shader uniform vec2 foo[2]:

// config
library: { nesting: true }
// output
type Uniforms = {
    foo: [number, number, number, number] | Float32Array |  [[number, number],  [number, number]]
};

Base types

The library field takes a types field which is a record of type definition lists. Each GLSL type accepts an array of TS types (they only need to be specific enough to be safe. Don't copy and paste the whole definitions).

Here are a few ways you could add a vec2 type definition for the trivial shader uniform vec2 foo;. The choice of variant only affects tooltips.

Inline
// config
library: {
    types: {
        vec2: ['{ x: number, y: number, isVector2: true }']
    }
}
// output
type Uniforms = {
    foo: [number, number] | Float32Array | { x: number, y: number, isVector2: true }
};
Alias
// config
library: {
    types: {
        vec2: [{
            alias: 'Vector2',
            type: '{ x: number, y: number, isVector2: true }'
        }]
    }
}
// output
type Vector2 = { x: number, y: number, isVector2: true };
type Uniforms = {
    foo: [number, number] | Float32Array | Vector2
};
Namespaced alias
// config
library: {
    namespace: 'THREE'
    types: {
        vec2: [{
            alias: 'Vector2',
            type: '{ x: number, y: number, isVector2: true }'
        }]
    }
}
// output
namespace THREE {
    export type Vector2 = { x: number, y: number, isVector2: true };
}
type Uniforms = {
    foo: [number, number] | Float32Array | THREE.Vector2
};

About

Handsfree type safe GLSL programming for WebGL with Vite

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages