Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Multiple materials at once #2845

Merged
merged 4 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const hits = engine.currentScene.physics.rayCast(

### Fixed

- Fixed issue where rendering multiple materials at once would crash the renderer
- Fixed issue where raycasting with more complex collision groups was not working as expected

### Updates
Expand Down
Binary file added sandbox/tests/material/heart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 79 additions & 5 deletions sandbox/tests/material/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/// <reference path="../../lib/excalibur.d.ts" />

// identity tagged template literal lights up glsl-literal vscode plugin
var glsl = x => x;

var game = new ex.Engine({
canvasElementId: 'game',
width: 512,
Expand All @@ -10,11 +13,54 @@ var game = new ex.Engine({
});

var tex = new ex.ImageSource('https://cdn.rawgit.com/excaliburjs/Excalibur/7dd48128/assets/sword.png', false, ex.ImageFiltering.Pixel);
var heartImage = new ex.ImageSource('./heart.png', false, ex.ImageFiltering.Pixel);
var background = new ex.ImageSource('./stars.png', false, ex.ImageFiltering.Blended);

var loader = new ex.Loader([tex, background]);
var loader = new ex.Loader([tex, heartImage, background]);

var outline = glsl`#version 300 es
precision mediump float;

uniform float iTime;

uniform sampler2D u_graphic;

in vec2 v_uv;

out vec4 fragColor;

vec3 hsv2rgb(vec3 c){
vec4 K=vec4(1.,2./3.,1./3.,3.);
return c.z*mix(K.xxx,clamp(abs(fract(c.x+K.xyz)*6.-K.w)-K.x, 0., 1.),c.y);
}

void main() {
const float TAU = 6.28318530;
const float steps = 4.0; // up/down/left/right pixels

float radius = 2.0;
vec3 outlineColorHSL = vec3(sin(iTime/2.0) * 1., 1., 1.);

vec2 aspect = 1.0 / vec2(textureSize(u_graphic, 0));

for (float i = 0.0; i < TAU; i += TAU / steps) {
// Sample image in a circular pattern
vec2 offset = vec2(sin(i), cos(i)) * aspect * radius;
vec4 col = texture(u_graphic, v_uv + offset);

// Mix outline with background
float alpha = smoothstep(0.5, 0.7, col.a);
fragColor = mix(fragColor, vec4(hsv2rgb(outlineColorHSL), 1.0), alpha); // apply outline
}

// Overlay original texture
vec4 mat = texture(u_graphic, v_uv);
float factor = smoothstep(0.5, 0.7, mat.a);
fragColor = mix(fragColor, mat, factor);
}
`

var fragmentSource = `#version 300 es
var fragmentSource = glsl`#version 300 es
precision mediump float;

// UV coord
Expand Down Expand Up @@ -65,6 +111,11 @@ var swirlMaterial = game.graphicsContext.createMaterial({
fragmentSource
});

var outlineMaterial = game.graphicsContext.createMaterial({
name: 'outline',
fragmentSource: outline
})

var click = ex.vec(0, 0);

game.input.pointers.primary.on('down', evt => {
Expand All @@ -81,16 +132,39 @@ actor.onInitialize = () => {
}
});
actor.graphics.add(sprite);
actor.graphics.material = outlineMaterial;
};

actor.onPostUpdate = (_, delta) => {
time += (delta / 1000);
outlineMaterial.getShader().use();
outlineMaterial.getShader().trySetUniformFloat('iTime', time);
}

var heartActor = new ex.Actor({x: 200, y: 200});
heartActor.onInitialize = () => {
var sprite = heartImage.toSprite();
sprite.scale = ex.vec(4, 4);
heartActor.graphics.add(sprite);
heartActor.graphics.material = outlineMaterial;
}

game.add(heartActor);

game.input.pointers.primary.on('move', evt => {
heartActor.pos = evt.worldPos;
swirlMaterial.getShader().use();
swirlMaterial.getShader().trySetUniformFloatVector('iMouse', evt.worldPos);
});


var backgroundActor = new ex.ScreenElement({x: 0, y: 0, width: 512, height: 512, z: -1});
var time = 0;
backgroundActor.onPostUpdate = (_, delta) => {
time += (delta / 1000);
swirlMaterial.getShader().use();
swirlMaterial.getShader().trySetUniformFloat('iTime', time);
swirlMaterial.getShader().trySetUniformFloatVector('iMouse', click);
// swirlMaterial.getShader().use();
// swirlMaterial.getShader().trySetUniformFloat('iTime', time);
// swirlMaterial.getShader().trySetUniformFloatVector('iMouse', click);
}
backgroundActor.onInitialize = () => {
backgroundActor.graphics.add(background.toSprite());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { vec } from '../../../Math/vector';
import { ImageFiltering } from '../../Filtering';
import { GraphicsDiagnostics } from '../../GraphicsDiagnostics';
import { HTMLImageSource } from '../ExcaliburGraphicsContext';
import { ExcaliburGraphicsContextWebGL } from '../ExcaliburGraphicsContextWebGL';
import { QuadIndexBuffer } from '../quad-index-buffer';
Expand Down Expand Up @@ -140,6 +141,9 @@ export class MaterialRenderer implements RendererPlugin {
// apply resolution
shader.trySetUniformFloatVector('u_resolution', vec(this._context.width, this._context.height));

// apply graphic resolution
shader.trySetUniformFloatVector('u_graphic_resolution', vec(imageWidth, imageHeight));

// apply size
shader.trySetUniformFloatVector('u_size', vec(sw, sh));

Expand All @@ -159,6 +163,9 @@ export class MaterialRenderer implements RendererPlugin {

// Draw a single quad
gl.drawElements(gl.TRIANGLES, 6, this._quads.bufferGlType, 0);

GraphicsDiagnostics.DrawnImagesCount++;
GraphicsDiagnostics.DrawCallCount++;
}

private _addImageAsTexture(image: HTMLImageSource) {
Expand Down
3 changes: 3 additions & 0 deletions src/engine/Graphics/Context/shader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,9 @@ export class Shader {
}

private _processSourceForError(source: string, errorInfo: string) {
if (!source) {
return errorInfo;
}
const lines = source.split('\n');
const errorLineStart = errorInfo.search(/\d:\d/);
const errorLineEnd = errorInfo.indexOf(' ', errorLineStart);
Expand Down
1 change: 1 addition & 0 deletions src/engine/Graphics/Context/vertex-layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export class VertexLayout {
if (!this._shader.compiled) {
throw Error('Shader not compiled, shader must be compiled before defining a vertex layout');
}
this._vertexTotalSizeBytes = 0;
this._layout.length = 0;
const shaderAttributes = this._shader.attributes;
for (const attribute of this._attributes) {
Expand Down
68 changes: 68 additions & 0 deletions src/spec/MaterialRendererSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,72 @@ describe('A Material', () => {
await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas))
.toEqualImage('src/spec/images/MaterialRendererSpec/material-component.png');
});

it('can be draw multiple materials', async () => {
const material1 = new ex.Material({
name: 'material1',
color: ex.Color.Red,
fragmentSource: `#version 300 es
precision mediump float;
uniform vec4 u_color;
out vec4 fragColor;
void main() {
fragColor = u_color;
}`
});

const material2 = new ex.Material({
name: 'material2',
color: ex.Color.Blue,
fragmentSource: `#version 300 es
precision mediump float;
uniform vec4 u_color;
out vec4 fragColor;
void main() {
fragColor = u_color;
}`
});

const engine = TestUtils.engine({
width: 100,
height: 100,
antialiasing: false,
snapToPixel: true
});
const context = engine.graphicsContext as ex.ExcaliburGraphicsContextWebGL;

const tex = new ex.ImageSource('src/spec/images/MaterialRendererSpec/sword.png');

const loader = new ex.Loader([tex]);

await TestUtils.runToReady(engine, loader);

const actor1 = new ex.Actor({
x: 0,
y: 0,
width: 100,
height: 100
});
actor1.graphics.use(tex.toSprite());
actor1.graphics.material = material1;

const actor2 = new ex.Actor({
x: 100,
y: 100,
width: 100,
height: 100
});
actor2.graphics.use(tex.toSprite());
actor2.graphics.material = material2;

context.clear();
engine.currentScene.add(actor1);
engine.currentScene.add(actor2);
engine.currentScene.draw(context, 100);
context.flush();

expect(context.material).toBe(null);
await expectAsync(TestUtils.flushWebGLCanvasTo2D(engine.canvas))
.toEqualImage('src/spec/images/MaterialRendererSpec/multi-mat.png');
});
});
Binary file added src/spec/images/MaterialRendererSpec/multi-mat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.