Tagged template literal for authoring Excalibur Material fragment shaders.
It handles the boilerplate and, most importantly, the alpha space bookkeeping.
Automatic alpha premultiplication
Excalibur's WebGL pipeline is premultiplied end to end. Textures are uploaded with
UNPACK_PREMULTIPLY_ALPHA_WEBGL, so a raw texture(u_graphic, uv) hands back a premultiplied
value, and the blend function is (ONE, ONE_MINUS_SRC_ALPHA), so the fragment output must be
premultiplied too. Doing your own alpha math in the middle of that is a reliable source of
washed out blending and dark halos on antialiased edges.
Shaders written with this tag instead get a straight (un-premultiplied) alpha authoring
space. Sampling calls are un-premultiplied on the way in and your color output is
premultiplied on the way out, so ordinary intuitive alpha math works in between:
const material = game.graphicsContext.createMaterial({
name: 'fade',
fragmentSource: glsl`
in vec2 v_uv;
uniform sampler2D u_graphic;
out vec4 fragColor;
void main() {
vec4 color = texture(u_graphic, v_uv);
color.a *= 0.5; // just works, no manual premultiply needed
fragColor = color;
}`
});
To sample a texture that is not color data (a lookup table, a noise or data texture) use
ex_texture_raw(tex, uv), which is passed through untouched. To turn the whole transform off
for a shader, add #pragma excalibur premultiply(off) to its source.
Other conveniences
Adds #version 300 es and a precision declaration if you did not write them
Injects the pixel_texture(sampler2D, vec2) pixel art filter if your source references it
Injects uniform vec2 u_graphic_resolution; alongside it if you have not declared it
Exposes ex_premultiply(vec4) / ex_unpremultiply(vec4) if you need to convert by hand
Tagged template literal for authoring Excalibur Material fragment shaders.
It handles the boilerplate and, most importantly, the alpha space bookkeeping.
Automatic alpha premultiplication
Excalibur's WebGL pipeline is premultiplied end to end. Textures are uploaded with
UNPACK_PREMULTIPLY_ALPHA_WEBGL, so a rawtexture(u_graphic, uv)hands back a premultiplied value, and the blend function is(ONE, ONE_MINUS_SRC_ALPHA), so the fragment output must be premultiplied too. Doing your own alpha math in the middle of that is a reliable source of washed out blending and dark halos on antialiased edges.Shaders written with this tag instead get a straight (un-premultiplied) alpha authoring space. Sampling calls are un-premultiplied on the way in and your color output is premultiplied on the way out, so ordinary intuitive alpha math works in between:
const material = game.graphicsContext.createMaterial({ name: 'fade', fragmentSource: glsl` in vec2 v_uv; uniform sampler2D u_graphic; out vec4 fragColor; void main() { vec4 color = texture(u_graphic, v_uv); color.a *= 0.5; // just works, no manual premultiply needed fragColor = color; }` });Filtering still happens in premultiplied space where it belongs, only the filtered result is converted. See https://www.realtimerendering.com/blog/gpus-prefer-premultiplication/
To sample a texture that is not color data (a lookup table, a noise or data texture) use
ex_texture_raw(tex, uv), which is passed through untouched. To turn the whole transform off for a shader, add#pragma excalibur premultiply(off)to its source.Other conveniences
#version 300 esand aprecisiondeclaration if you did not write thempixel_texture(sampler2D, vec2)pixel art filter if your source references ituniform vec2 u_graphic_resolution;alongside it if you have not declared itex_premultiply(vec4)/ex_unpremultiply(vec4)if you need to convert by hand