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

SpriteBatch With Custom Effects and Per-Sprite Data #8295

Open
Tommy-Hu opened this issue May 3, 2024 · 2 comments
Open

SpriteBatch With Custom Effects and Per-Sprite Data #8295

Tommy-Hu opened this issue May 3, 2024 · 2 comments

Comments

@Tommy-Hu
Copy link

Tommy-Hu commented May 3, 2024

Intent

Allow per-sprite data (that are passed with each Draw() call) to be easily passed into effects -- without beginning new batches or needing to use DrawUserIndexedPrimitive(). The user will be able to access the per-sprite data in the Vertex Shader of the effect, and can therefore easily perform different sub-effects per-sprite. Having extra data per vertex will increase batch item size, but the user will be able to choose whether or not they need to use this new functionality (by using an old overload or setting the new overload's parameter to its default value).

Possible Solution

Create an overload SpriteBatch.Draw(OTHER_OLD_PARAMS, params float[] customFloats). The framework throws an error if customFloats.Length exceeds some threshold (so that the user cannot put in a float[] of length 100000 for fun) or if the given customFloats array is null.

A possible implementation of the new SpriteBatch.Draw overload might be one of the following (I only dug around the MonoGame code a little bit, so these may be impossible):

  1. There would be a few new MonoGame internal structs created, each representing a custom vertex of a set number of floats (e.g., a VertexPositionColorTextureF1, VertexPositionColorTextureF2, VertexPositionColorTextureF3,... that supports 1, 2, and 3 extra floating-point data on vertices respectively). The new structs would extend IVertexType. The new Draw overload adds one of these into the batch depending on the length of customFloats.

  2. A new MonoGame internal struct VertexPositionColorTextureSingles : IVertexType can be created that dynamically changes its VertexDeclaration size based on the number of extra floating parameters to the Draw overload.

  3. If the SpriteBatch must send batches of vertices of the same struct size, then the batcher could either send the batch of vertices based on the maximum Length of customFloats stored in the batch or instead, add a new optional parameter to SpriteBatch.Begin() that specifies the number of extra floats to carry per vertex/Draw call.

Either way, this solution would achieve the goal of easily sending custom data to each effect per sprite, essentially allowing different effect parameters in a single batch.

Motivation

2D games that need to use multiple effects may find beginning a new sprite batch for each effect very slow (this is what happened in my game). A way to achieve multiple effects with fewer sprite batches is to write a master effect that is a collection of all effects in one single effect file. A custom vertex (that extends IVertexType) can then be used to send an extra per-vertex floating-point identifier variable to the vertex shader, which can then choose to apply one or more of the sub-effects of the master effect based on the received identifier. Right now, there is no way that I know of that allows custom vertices in SpriteBatch. To achieve the sub-effect selection described above, GraphicsDevice.DrawUserIndexedPrimitives() would be used, meaning that the user would need to remake the SpriteBatch's sorting functionality, matrix calculations, etc.

The user may also wish to send other per-sprite data to the vertex shader. Beginning a new SpriteBatch to set uniform shader variables for each sprite that has a different shader data or manually drawing the object vertices with DrawUserIndexedPrimitives() are both unattractive options, as mentioned above.

@stromkos
Copy link
Contributor

Set a uniform variable in the shader, modify the value between draw calls.


2D games that need to use multiple effects may find beginning a new sprite batch for each effect very slow

Not if they are batched properly. Group all Draw calls by Effect.

@Tommy-Hu
Copy link
Author

Tommy-Hu commented May 28, 2024

Hey @stromkos, thanks for the suggestion :) However, the problem with uniform and grouping Draw calls by effect is that doing so will disrupt the ordering of the sprites.

For example, in a top-down game, the dev may want to have sprites with different effects for objects, but those objects must be grouped by the Y component instead of by effect. In particular, let's say the dev wants all the enemies to use a particular shader, and every other object including the player, vegetation, buildings etc. uses some other shader. Now, since the game is top-down, the sort order must be by the Y component so that objects behind other objects are properly rendered.


Here is a more concrete example. Please bear with my terrible handwriting.

image
This image shows the intended behavior.

If the player/animals walk behind a building, the building sprite will be drawn after the player/animals so that the player/animals don't appear above the building. Of course, the opposite is true if the player/animals walk in front of the building, where the building sprite must be rendered before the player/animal sprites. A way to properly order the Draw calls is to sort these sprites by their Y component, where the higher Y will be rendered before the lower Y sprites.

In this case, if the dev wants to add a glow effect for the player and the animals, but no effect for the building, then the player cannot be grouped with the glowing animals. Specifically, if the player is behind the building but the animals are in front of the building, grouping sprites by glow effect causes the animals and the player to be rendered together, either before or after the building sprite renders. The user will then see either nothing because the building renders after the glowing sprites, or see the player sprite on top of the building because the building renders before the glowing sprites.

Of course, as I suggested before, a way to solve this is to pass per-sprite data into the vertex shader of a master effect to select whether a sprite uses the glow effect or not at runtime.

TLDR

The only way I know to render a top-down game for an arbitrary number of sprites is to sort them by their Y. This conflicts with sorting sprites by Effect. Now, even if all sprites use the same effect, every sprite may have different effect parameters. For instance, different glowing sprites have different glowAmount and glowColor values. As far as I know, this cannot be done with uniform variables unless I begin a new batch for each sprite.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants