Skip to content

Commit

Permalink
Spritesheet Block, Tint Block, and Fireworks updates (#52)
Browse files Browse the repository at this point in the history
Co-authored-by: AmoebaChant <[email protected]>
  • Loading branch information
alexchuber and AmoebaChant authored Sep 18, 2024
1 parent 362dc35 commit 3d4f4ad
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 7 deletions.
12 changes: 12 additions & 0 deletions packages/demo/src/configuration/blockDeserializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,18 @@ export function getBlockDeserializers(): Map<string, DeserializeBlockV1> {
return new NeonHeartBlock(smartFilter, serializedBlock.name);
});

deserializers.set(BlockNames.spritesheet, async (smartFilter: SmartFilter, serializedBlock: ISerializedBlockV1) => {
const { SpritesheetBlock } = await import(
/* webpackChunkName: "spritesheetBlock" */ "./blocks/effects/spritesheetBlock"
);
return new SpritesheetBlock(smartFilter, serializedBlock.name);
});

deserializers.set(BlockNames.tint, async (smartFilter: SmartFilter, serializedBlock: ISerializedBlockV1) => {
const { TintBlock } = await import(/* webpackChunkName: "tintBlock" */ "./blocks/effects/tintBlock");
return new TintBlock(smartFilter, serializedBlock.name);
});

// Non-trivial deserializers begin.

deserializers.set(BlockNames.blur, async (smartFilter: SmartFilter, serializedBlock: ISerializedBlockV1) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/demo/src/configuration/blockSerializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export const blocksUsingDefaultSerialization: string[] = [
BlockNames.particle,
BlockNames.hearts,
BlockNames.neonHeart,
BlockNames.spritesheet,
BlockNames.tint,
];

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/demo/src/configuration/blocks/blockNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ export const BlockNames = {
particle: "ParticleBlock",
hearts: "HeartsBlock",
neonHeart: "NeonHeartBlock",
spritesheet: "SpritesheetBlock",
tint: "TintBlock",
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
uniform sampler2D input; // main
uniform float time;
uniform float rows;
uniform float cols;
uniform float frames;

vec4 mainImage(vec2 vUV) { // main
float invRows = 1.0 / rows;
float invCols = 1.0 / cols;

// Get offset of frame
float frame = mod(floor(time), frames);
float row = (rows - 1.0) - floor(frame * invCols); // Reverse row direction b/c UVs start from bottom
float col = mod(frame, cols);

// Add offset, then scale UV down to frame size
vUV = vec2(
(vUV.x + col) * invCols,
(vUV.y + row) * invRows
);

return texture2D(input, vUV);
}
123 changes: 123 additions & 0 deletions packages/demo/src/configuration/blocks/effects/spritesheetBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import type { Effect } from "@babylonjs/core/Materials/effect";
import { type SmartFilter, type IDisableableBlock, type RuntimeData, createStrongRef } from "@babylonjs/smart-filters";
import { ShaderBlock, ConnectionPointType, ShaderBinding } from "@babylonjs/smart-filters";
import { BlockNames } from "../blockNames";
import { shaderProgram, uniforms } from "./spritesheetBlock.shader";

/**
* The shader bindings for the Spritesheet block.
*/
export class SpritesheetShaderBinding extends ShaderBinding {
private readonly _inputTexture: RuntimeData<ConnectionPointType.Texture>;
private readonly _time: RuntimeData<ConnectionPointType.Float>;
private readonly _rows: RuntimeData<ConnectionPointType.Float>;
private readonly _cols: RuntimeData<ConnectionPointType.Float>;
private readonly _frames: RuntimeData<ConnectionPointType.Float>;

/**
* Creates a new shader binding instance for the SpriteSheet block.
* @param parentBlock - The parent block
* @param inputTexture - The input texture
* @param time - The time passed since the start of the effect
* @param rows - The number of rows in the sprite sheet
* @param cols - The number of columns in the sprite sheet
* @param frames - The number of frames to show
*/
constructor(
parentBlock: IDisableableBlock,
inputTexture: RuntimeData<ConnectionPointType.Texture>,
time: RuntimeData<ConnectionPointType.Float>,
rows: RuntimeData<ConnectionPointType.Float>,
cols: RuntimeData<ConnectionPointType.Float>,
frames: RuntimeData<ConnectionPointType.Float>
) {
super(parentBlock);
this._inputTexture = inputTexture;
this._time = time;
this._rows = rows;
this._cols = cols;
this._frames = frames;
}

/**
* Binds all the required data to the shader when rendering.
* @param effect - defines the effect to bind the data to
*/
public override bind(effect: Effect): void {
super.bind(effect);
effect.setTexture(this.getRemappedName(uniforms.input), this._inputTexture.value);
effect.setFloat(this.getRemappedName(uniforms.time), this._time.value);
effect.setFloat(this.getRemappedName(uniforms.rows), this._rows.value);
effect.setFloat(this.getRemappedName(uniforms.cols), this._cols.value);

// Apply default value for frame count if it was not provided
effect.setFloat(
this.getRemappedName(uniforms.frames),
this._frames.value > 0 ? this._frames.value : this._rows.value * this._cols.value
);
}
}

/**
* A block that animates a sprite sheet texture.
*/
export class SpritesheetBlock extends ShaderBlock {
/**
* The class name of the block.
*/
public static override ClassName = BlockNames.spritesheet;

/**
* The input texture connection point
*/
public readonly input = this._registerInput("input", ConnectionPointType.Texture);

/**
* The time connection point to animate the effect.
*/
public readonly time = this._registerOptionalInput("time", ConnectionPointType.Float, createStrongRef(0.0));

/**
* The number of rows in the sprite sheet, as a connection point.
*/
public readonly rows = this._registerOptionalInput("rows", ConnectionPointType.Float, createStrongRef(1.0));

/**
* The number of columns in the sprite sheet, as a connection point.
*/
public readonly columns = this._registerOptionalInput("columns", ConnectionPointType.Float, createStrongRef(1.0));

/**
* The number of frames to animate from the beginning, as a connection point.
* Defaults to rows * columns at runtime.
*/
public readonly frames = this._registerOptionalInput("frames", ConnectionPointType.Float, createStrongRef(0.0));

/**
* The shader program (vertex and fragment code) to use to render the block
*/
public static override ShaderCode = shaderProgram;

/**
* Instantiates a new Block.
* @param smartFilter - The smart filter this block belongs to
* @param name - The friendly name of the block
*/
constructor(smartFilter: SmartFilter, name: string) {
super(smartFilter, name);
}

/**
* Get the class instance that binds all the required data to the shader (effect) when rendering.
* @returns The class instance that binds the data to the effect
*/
public getShaderBinding(): ShaderBinding {
const input = this._confirmRuntimeDataSupplied(this.input);
const rows = this.rows.runtimeData;
const columns = this.columns.runtimeData;
const time = this.time.runtimeData;
const frames = this.frames.runtimeData;

return new SpritesheetShaderBinding(this, input, time, rows, columns, frames);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
uniform sampler2D input; // main
uniform vec3 tint;
uniform float amount;

vec4 mainImage(vec2 vUV) { // main
vec4 color = texture2D(input, vUV);
vec3 tinted = mix(color.rgb, tint, amount);
return vec4(tinted, color.a);
}
100 changes: 100 additions & 0 deletions packages/demo/src/configuration/blocks/effects/tintBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import type { Effect } from "@babylonjs/core/Materials/effect";
import type { SmartFilter, IDisableableBlock, RuntimeData } from "@babylonjs/smart-filters";
import { ShaderBlock, ConnectionPointType, ShaderBinding, createStrongRef } from "@babylonjs/smart-filters";
import { BlockNames } from "../blockNames";
import { uniforms, shaderProgram } from "./tintBlock.shader";
import { Color3 } from "@babylonjs/core/Maths/math.color";

/**
* The shader bindings for the Tint block.
*/
export class TintShaderBinding extends ShaderBinding {
private readonly _inputTexture: RuntimeData<ConnectionPointType.Texture>;
private readonly _tint: RuntimeData<ConnectionPointType.Color3>;
private readonly _amount: RuntimeData<ConnectionPointType.Float>;

/**
* Creates a new shader binding instance for the Tint block.
* @param parentBlock - The parent block
* @param inputTexture - the input texture
* @param tint - the tint to apply
* @param amount - the amount of tint to apply
*/
constructor(
parentBlock: IDisableableBlock,
inputTexture: RuntimeData<ConnectionPointType.Texture>,
tint: RuntimeData<ConnectionPointType.Color3>,
amount: RuntimeData<ConnectionPointType.Float>
) {
super(parentBlock);
this._inputTexture = inputTexture;
this._tint = tint;
this._amount = amount;
}

/**
* Binds all the required data to the shader when rendering.
* @param effect - defines the effect to bind the data to
*/
public override bind(effect: Effect): void {
super.bind(effect);
effect.setTexture(this.getRemappedName(uniforms.input), this._inputTexture.value);
effect.setColor3(this.getRemappedName(uniforms.tint), this._tint.value);
effect.setFloat(this.getRemappedName(uniforms.amount), this._amount.value);
}
}

/**
* A simple block to apply a tint to a texture
*/
export class TintBlock extends ShaderBlock {
/**
* The class name of the block.
*/
public static override ClassName = BlockNames.tint;

/**
* The input texture connection point.
*/
public readonly input = this._registerInput("input", ConnectionPointType.Texture);

/**
* The tint color connection point.
*/
public readonly tint = this._registerOptionalInput(
"tint",
ConnectionPointType.Color3,
createStrongRef(new Color3(1, 0, 0))
);

/**
* The strength of the tint, as a connection point.
*/
public readonly amount = this._registerOptionalInput("amount", ConnectionPointType.Float, createStrongRef(0.25));

/**
* The shader program (vertex and fragment code) to use to render the block
*/
public static override ShaderCode = shaderProgram;

/**
* Instantiates a new Block.
* @param smartFilter - The smart filter this block belongs to
* @param name - The friendly name of the block
*/
constructor(smartFilter: SmartFilter, name: string) {
super(smartFilter, name);
}

/**
* Get the class instance that binds all the required data to the shader (effect) when rendering.
* @returns The class instance that binds the data to the effect
*/
public getShaderBinding(): ShaderBinding {
const input = this._confirmRuntimeDataSupplied(this.input);
const tint = this.tint.runtimeData;
const amount = this.amount.runtimeData;

return new TintShaderBinding(this, input, tint, amount);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
uniform sampler2D input; // main
uniform float time;
uniform float aspectRatio;
uniform float fireworks;
uniform float fireworkSparks;

const float PI = 3.141592653589793;
const float EXPLOSION_COUNT = 8.;
const float SPARKS_PER_EXPLOSION = 128.;
const float EXPLOSION_DURATION = 20.;
const float EXPLOSION_SPEED = 5.;
const float EXPLOSION_RADIUS_THESHOLD = .06;
Expand All @@ -29,14 +29,14 @@ vec4 mainImage(vec2 vUV) { // main
vec2 origin = vec2(0.);
vUV.x *= aspectRatio;

for (float j = 0.; j < EXPLOSION_COUNT; ++j)
for (float j = 0.; j < fireworks; ++j)
{
vec3 oh = hash31((j + 1234.1939) * 641.6974);
vec3 oh = hash31((j + 800.) * 641.6974);
origin = vec2(oh.x, oh.y) * .6 + .2; // .2 - .8 to avoid boundaries
origin.x *= aspectRatio;
// Change t value to randomize the spawning of explosions
t += (j + 1.) * 9.6491 * oh.z;
for (float i = 0.; i < SPARKS_PER_EXPLOSION; ++i)
for (float i = 0.; i < fireworkSparks; ++i)
{
vec3 h = hash31(j * 963.31 + i + 497.8943);
// random angle (0 - 2*PI)
Expand Down
Loading

0 comments on commit 3d4f4ad

Please sign in to comment.