Dev Guide: Particle Render Batches
As outlined before, the RendererParticles
class manages one RendererParticles_RenderBatch
instance for each occurring combination of particle color and number of pins per side.
The render batch class itself is then responsible for rendering all particles with a specific color and number of pins.
For this purpose, it stores a list of all registered particles matching these properties as well as a number of TRS matrices for each particle.
The matrices are stored in lists of arrays of size 1023 so that each array can be submitted to a single instanced draw call.
New arrays are added to the lists if necessary.
The class stores 12 of these lists because multiple components have to be rendered for the different view types.
Particle Components
Rendering a single particle involves drawing several components, some of which are only shown in certain view modes. The base component that is always shown is the particle's body. In the hexagonal view modes, the body consists of a solid black outline, pins distributed on the particle's edges, and the colored internal area. In graph view mode, it consists of a solid gray circle with a colored ring around it.
If the particle is expanded, the shader for the hexagonal view modes automatically computes the connection between the particle's head and its tail. In graph view mode, this connection is not created by the shader:
That is why the second component is the connector of expanded, expanding or contracting particles in the graph view mode. It is simply a black rectangle that is drawn on a Z layer beneath the particle body:

The third and final component is the pin overlay, which is only required in the hexagonal view modes and when the circuit visualization is enabled. It is the same as the particle body, except that it only contains the pins, not the particle outline and not the colored internal area. Its purpose is to be rendered on a Z layer on top of the circuits (which are rendered on top of the particle body by a different rendering class) so that the pins remain visible on top of the lines.
These images show how the particle body, circuits and the pins are layered to produce the desired effect:
They also show the beep highlights that are drawn on top of the circuit lines and the partition set handles.
Rendering
Each component is rendered using a specific material on a simple quad mesh.
For the particle connector in graph view, the material has a solid color and the mesh is shaped to exactly match the connector.
For the particle body and the pin overlay, the meshes are rectangles that are large enough to fit an expanded particle.
This way, the material has enough space to draw a contracted or expanded particle and the animated transitions between the expansion states.
The meshes are created by the MeshCreator_CircularView
and MeshCreator_HexagonalView
classes, using some of the static constants defined in the RenderSystem
.
The materials are created by the TextureCreator
class.
This class creates copies of existing materials and modifies their parameters to achieve the desired appearance.
For the particle body and the pin overlay in the hexagonal view modes, copies of Resources/Materials/HexagonalView/HexagonCombinedMat
are used.
This material is explained in detail on the shader example page.
It basically takes some textures that describe the particle outline and the connecting piece as well as a fill color as parameters and uses them to render a particle that is contracted, expanded, or anywhere inbetween.
The texture describing the particle's outline must already contain the pins, so the TextureCreator
creates a copy of the base hexagon texture (which does not have any pins) and draws the required pins directly into the texture.
For the pin overlay, it draws the pins into a transparent texture in the same manner and replaces the connector texture by a transparent one.
If the circuit visualization is disabled, the RendererParticles_RenderBatch
requests a new body material from the TextureCreator
which does not contain the pins, and it stops rendering the pin overlay altogether.
The TextureCreator
caches the textures and materials it creates to avoid generating the same things multiple times.
For the graph view, the Resources/Materials/CircularView/ConnectorMat
and Resources/Materials/CircularView/ParticleMat
materials are used to render the connector and the particle body, respectively.
Just like the hexagonal material, these materials have parameters specifying the current expansion percentage to control the animation.
The material for the connector is special in that it uses the shader's vertex output to shift the quad mesh's vertices for the animation.
The vertices that should be moving are marked in UV channel 1 by the MeshCreator_CircularView
(the shader example page explains this in more detail).
Matrices
As mentioned above, the RendererParticles_RenderBatch
stores 12 TRS matrices per particle.
This is because there are 3 components (body, pin overlay, graph view connector) and each component has 4 versions, one for each possible movement phase (contracted, expanded, contracting and expanding).
Each of the used materials has parameters controlling its animation state: The start time of the animation, the duration, and the expansion percentages at the start and the end of the animation.
The shader will interpolate between the start and end percentages within the given time frame.
Setting the start and end percentage to the same value disables the animation, i.e., setting both to 0 represents the contracted state and setting both to 1 represents the expanded state.
Recall that a RendererParticles_RenderBatch
instance renders all particles with the same color and number of pins.
Within this set of particles, there may be some of every movement state.
These particles have to be rendered in separate draw calls because their materials use different animation parameters.
However, because we want to draw as many objects as possible with little overhead, and since the movement state of a particle may change very frequently, collecting particles with the same movement state into separate batches is not an option.
Instead, we render each particle 4 times, once for each movement state, but only one of the states gets the correct TRS matrix.
Whenever a particle's visualization state is updated, which usually happens in each frame, its matrices are reset to a position that is not visible (because its Z layer is behind the camera) and only the matrices matching the particle's movement state are updated to the appropriate position.
This way, we can submit all matrices in the minimum number of instanced draw calls, and only the correct version of each particle will be visible.
Each of the groups of matrices requires an appropriate property block that lets the material display the corresponding movement phase/expansion state.
For this purpose, the MaterialPropertyBlockData_Particles
class is used.
This class is a wrapper around a single Unity MaterialPropertyBlock
which contains material parameters specific to the particle materials.
It allows changing values easily without having to know the exact names of the parameters in the materials.