Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/excaliburjs/Excalibur. 🎮 Your friendly TypeScript 2D game engine for the web 🗡️ excaliburjs.com
excalibur excaliburjs game-development game-engine game-framework gamedev games html5-canvas typescript
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267import { Color } from '../../color';import type { ExcaliburGraphicsContext } from './excalibur-graphics-context';import { ExcaliburGraphicsContextWebGL } from './excalibur-graphics-context-webgl';import type { Shader, UniformDictionary } from './shader';import { Logger } from '../../util/log';import type { ImageSource } from '../image-source';import type { ImageFiltering } from '../filtering';
export interface MaterialOptions { /** * Name the material for debugging */ name?: string;
/** * Excalibur graphics context to create the material (only WebGL is supported at the moment) */ graphicsContext?: ExcaliburGraphicsContext;
/** * Optionally specify a vertex shader * * If none supplied the default will be used * * ``` * #version 300 es * // vertex position in local space * in vec2 a_position; * in vec2 a_uv; * out vec2 v_uv; * // orthographic projection matrix * uniform mat4 u_matrix; * // world space transform matrix * uniform mat4 u_transform; * void main() { * // Set the vertex position using the ortho & transform matrix * gl_Position = u_matrix * u_transform * vec4(a_position, 0.0, 1.0); * // Pass through the UV coord to the fragment shader * v_uv = a_uv; * } * ``` */ vertexSource?: string;
/** * Add custom fragment shader * * *Note: Excalibur image alpha's are pre-multiplied * * Pre-built varyings: * * * `in vec2 v_uv` - UV coordinate * * `in vec2 v_screenuv` - UV coordinate * * Pre-built uniforms: * * * `uniform sampler2D u_graphic` - The current graphic displayed by the GraphicsComponent * * `uniform vec2 u_resolution` - The current resolution of the screen * * `uniform vec2 u_size;` - The current size of the graphic * * `uniform vec4 u_color` - The current color of the material * * `uniform float u_opacity` - The current opacity of the graphics context * */ fragmentSource: string;
/** * Add custom color, by default ex.Color.Transparent */ color?: Color;
/** * Add additional images to the material, you are limited by the GPU's maximum texture slots * * Specify a dictionary of uniform sampler names to ImageSource */ images?: Record<string, ImageSource>;
/** * Optionally set starting uniforms on a shader */ uniforms?: UniformDictionary;}
const defaultVertexSource = `#version 300 esin vec2 a_position;
in vec2 a_uv;out vec2 v_uv;
in vec2 a_screenuv;out vec2 v_screenuv;
uniform mat4 u_matrix;uniform mat4 u_transform;
void main() { // Set the vertex position using the ortho & transform matrix gl_Position = u_matrix * u_transform * vec4(a_position, 0.0, 1.0);
// Pass through the UV coord to the fragment shader v_uv = a_uv; v_screenuv = a_screenuv;}`;
export interface MaterialImageOptions { filtering?: ImageFiltering;}
export class Material { static BuiltInUniforms = [ 'u_time_ms', 'u_opacity', 'u_resolution', 'u_graphic_resolution', 'u_size', 'u_matrix', 'u_transform', 'u_graphic', 'u_screen_texture' ]; private _logger = Logger.getInstance(); private _name: string; private _shader!: Shader; private _color: Color = Color.Transparent; private _initialized = false; private _fragmentSource: string; private _vertexSource: string;
private _images: Record<string, ImageSource> = {}; private _uniforms: UniformDictionary = {};
constructor(options: MaterialOptions) { const { color, name, vertexSource, fragmentSource, graphicsContext, images, uniforms } = options;
this._name = name ?? 'anonymous material'; this._vertexSource = vertexSource ?? defaultVertexSource; this._fragmentSource = fragmentSource; this._color = color ?? this._color; this._uniforms = uniforms ?? this._uniforms; this._images = images ?? this._images;
if (!graphicsContext) { throw Error(`Material ${name} must be provided an excalibur webgl graphics context`); }
if (graphicsContext instanceof ExcaliburGraphicsContextWebGL) { this._initialize(graphicsContext); } else { this._logger.warn(`Material ${name} was created in 2D Canvas mode, currently only WebGL is supported`); }
if (process.env.NODE_ENV === 'development') { if (this.images.u_graphic) { this._logger.warn( `Material named "${this.name}" is overriding built in image u_graphic, is this on purpose? If so ignore this warning.` ); }
if (this.images.u_screen_texture) { this._logger.warn( `Material named "${this.name}" is overriding built in image u_screen_texture, is this on purpose? If so ignore this warning.` ); }
for (const uniform of Object.keys(this._uniforms)) { if (Material.BuiltInUniforms.includes(uniform)) { this._logger.warn( `Material named "${this.name}" is overriding built in uniform ${uniform}, is this on purpose? If so ignore this warning.` ); } } } }
private _initialize(graphicsContextWebGL: ExcaliburGraphicsContextWebGL) { if (this._initialized) { return; }
this._shader = graphicsContextWebGL.createShader({ name: this._name, vertexSource: this._vertexSource, fragmentSource: this._fragmentSource, uniforms: this._uniforms, images: this._images, // max texture slots // - 2 for the graphic texture and screen texture // - 1 if just graphic startingTextureSlot: this.isUsingScreenTexture ? 2 : 1 }); this._initialized = true; }
public get uniforms(): UniformDictionary { return this._shader.uniforms; }
public get images(): Record<string, ImageSource> { return this._shader.images; }
get color(): Color { return this._color; }
set color(c: Color) { this._color = c; }
get name() { return this._name; }
get isUsingScreenTexture() { return this._fragmentSource.includes('u_screen_texture'); }
get isOverridingGraphic() { return !!this._images.u_graphic; }
update(callback: (shader: Shader) => any) { if (this._shader) { this._shader.use(); callback(this._shader); } }
getShader(): Shader | null { return this._shader; }
addImageSource(samplerName: string, image: ImageSource) { this._shader.addImageSource(samplerName, image);
if (process.env.NODE_ENV === 'development') { if (this.images.u_graphic) { this._logger.warn( `Material named "${this.name}" is overriding built in image u_graphic, is this on purpose? If so ignore this warning.` ); }
if (this.images.u_screen_texture) { this._logger.warn( `Material named "${this.name}" is overriding built in image u_screen_texture, is this on purpose? If so ignore this warning.` ); } } }
removeImageSource(samplerName: string) { this._shader.removeImageSource(samplerName); }
use() { if (this._initialized) { // bind the shader this._shader.use(); // Apply standard uniforms this._shader.trySetUniformFloatColor('u_color', this._color); } else { throw Error(`Material ${this.name} not yet initialized, use the ExcaliburGraphicsContext.createMaterial() to work around this.`); } }}