Skip to content

Materials & Shaders

The Material API manages custom shaders and rendering effects. Assign a material to a Sprite or SpineAnimation component via the material field.

Creating a Shader

import { Material } from 'esengine';
const shader = Material.createShader(vertexSrc, fragmentSrc);

Returns a ShaderHandle (number). A return value of 0 indicates failure.

Built-in Vertex Attributes

All shaders share these fixed vertex attribute locations:

LocationNameTypeDescription
0a_positionvec3Vertex position
1a_colorvec4Vertex color
2a_texCoordvec2Texture coordinates

Built-in Shader Sources

ShaderSources provides the default sprite and color shaders as reference:

ConstantDescription
ShaderSources.SPRITE_VERTEXDefault sprite vertex shader
ShaderSources.SPRITE_FRAGMENTDefault sprite fragment shader
ShaderSources.COLOR_VERTEXVertex shader for untextured geometry
ShaderSources.COLOR_FRAGMENTFragment shader for untextured geometry

Creating a Material

import { Material, BlendMode } from 'esengine';
const mat = Material.create({
shader,
uniforms: {
u_time: 0,
u_tint: { r: 1, g: 0, b: 0, a: 1 },
},
blendMode: BlendMode.Additive,
});
sprite.material = mat;

MaterialOptions

FieldTypeDefaultDescription
shaderShaderHandleRequired. Shader to use
uniformsRecord<string, UniformValue>{}Initial uniform values
blendModeBlendModeNormalBlend mode
depthTestbooleanfalseEnable depth testing

Uniforms

Setting and Getting

Material.setUniform(mat, 'u_time', elapsed);
Material.setUniform(mat, 'u_tint', { r: 1, g: 0.5, b: 0, a: 1 });
const time = Material.getUniform(mat, 'u_time');

Uniform Types

TypeExample
number1.5
Vec2{ x: 0.5, y: 1.0 }
Vec3{ x: 1, y: 0, z: 0 }
Color{ r: 1, g: 1, b: 1, a: 1 }
Vec4{ x: 1, y: 1, z: 1, w: 1 }
number[][1, 2, 3, 4]
TextureRefMaterial.tex(textureId, slot?)

Texture Uniforms

Use Material.tex() to create a texture reference for sampler uniforms:

const tex = await assets.loadTexture('assets/noise.png');
Material.setUniform(mat, 'u_noiseTex', Material.tex(tex.handle, 1));

BlendMode

ValueNameDescription
0NormalStandard alpha blending
1AdditiveAdditive blending (glow effects)
2MultiplyMultiply blending (darken)
3ScreenScreen blending (lighten)
4PremultipliedAlphaPremultiplied alpha blending
Material.setBlendMode(mat, BlendMode.Screen);
const mode = Material.getBlendMode(mat);

Material Instances

Create an instance that shares the same shader but has independent uniforms:

const instance = Material.createInstance(sourceMat);
Material.setUniform(instance, 'u_tint', { r: 0, g: 1, b: 0, a: 1 });

Shader File Format (.esshader)

Shader files use #pragma directives to separate vertex and fragment sections:

#pragma vertex
#version 300 es
precision highp float;
layout(location = 0) in vec3 a_position;
layout(location = 1) in vec4 a_color;
layout(location = 2) in vec2 a_texCoord;
uniform mat4 u_projection;
uniform mat4 u_model;
out vec4 v_color;
out vec2 v_texCoord;
void main() {
v_color = a_color;
v_texCoord = a_texCoord;
gl_Position = u_projection * u_model * vec4(a_position, 1.0);
}
#pragma end
#pragma fragment
#version 300 es
precision highp float;
in vec4 v_color;
in vec2 v_texCoord;
uniform sampler2D u_texture;
out vec4 fragColor;
void main() {
fragColor = texture(u_texture, v_texCoord) * v_color;
}
#pragma end

Load shader files via the asset server:

const shader = await assets.loadShader('assets/effects/glow.esshader');

Material File Format (.esmaterial)

Materials can be defined as JSON files and loaded via the asset server:

{
"version": "1.0",
"type": "material",
"shader": "effects/glow.esshader",
"blendMode": 1,
"depthTest": false,
"properties": {
"u_intensity": 1.5,
"u_color": { "x": 1, "y": 0.5, "z": 0, "w": 1 }
}
}
FieldTypeDescription
versionstringFormat version ("1.0")
typestringMust be "material"
shaderstringPath to .esshader file (relative to material or absolute)
blendModenumberBlendMode enum value
depthTestbooleanEnable depth testing
propertiesobjectUniform name/value pairs
const loaded = await assets.loadMaterial('assets/effects/glow.esmaterial');
sprite.material = loaded.handle;

Example: Flashing Effect

Animate a uniform in a system to create a flashing effect:

import { defineSystem, addSystem, Query, Mut, Res } from 'esengine';
import { Sprite, Material, BlendMode, Time } from 'esengine';
const flashShader = Material.createShader(
ShaderSources.SPRITE_VERTEX,
`#version 300 es
precision highp float;
in vec4 v_color;
in vec2 v_texCoord;
uniform sampler2D u_texture;
uniform float u_flash;
out vec4 fragColor;
void main() {
vec4 tex = texture(u_texture, v_texCoord) * v_color;
fragColor = mix(tex, vec4(1.0), u_flash * step(0.5, tex.a));
}`
);
const flashMat = Material.create({
shader: flashShader,
uniforms: { u_flash: 0.0 },
});
addSystem(defineSystem(
[Res(Time)],
(time) => {
const flash = Math.abs(Math.sin(time.elapsed * 5.0));
Material.setUniform(flashMat, 'u_flash', flash);
}
));

Full API Reference

MethodDescription
Material.createShader(vertex, fragment)Create a shader program
Material.releaseShader(shader)Release a shader
Material.create(options)Create a material
Material.createInstance(source)Create a material instance sharing the shader
Material.createFromAsset(data, shader)Create from a parsed .esmaterial file
Material.setUniform(mat, name, value)Set a uniform value
Material.getUniform(mat, name)Get a uniform value
Material.getUniforms(mat)Get all uniforms as a Map
Material.setBlendMode(mat, mode)Set blend mode
Material.getBlendMode(mat)Get blend mode
Material.setDepthTest(mat, enabled)Set depth testing
Material.getShader(mat)Get the shader handle
Material.release(mat)Release a material
Material.isValid(mat)Check if a material exists
Material.toAssetData(mat, shaderPath)Export to serializable format
Material.tex(textureId, slot?)Create a texture uniform reference

Next Steps