For those also starting out trying to properly understand GLSL rather than just reuse online code, here's what I learned over the past few days. Note the below points refer to GLSL for GL ES 2.0.
Two key points were:
Get into the GPU mindset. GPU's have 100's or 1000's or cores all working in parallel, and a shader is basically designed to process lots of data via a Vertex Buffer Object (VBO) and process them in parallel.
uniform
's are limited in number. I think I read somewhere that the Intel chip sets have a 16kb limit, whereas AMD/Nvidia have a 64kb limit, and I generally get an error if I try to use more than 1024 uniform
's.
I managed to solve my original problem by passing in a VBO of vertexId
's via attribute float a_vertexId
. I could then get the spriteId
using a_vertixId / 6
and the vertexId
using a_vertexId - spriteId * 6
. This enabled me to index into a uniform
of canonical square vertices, which meant I didn't have to upload sprite vertices any more.
I was also able to address texture uv
coordinates in this way too. I just sent up a texture ID per vertex via attribute float a_texId
, then used the spriteId
, vertexId
, and a_texId
to index into a uniform
texture coordinates array. Even given the limits on the number of uniform
's allowed this is able to cope with around 1000 different textures per sprite atlas.
For the transformations, I just sent up the transfer parameters (per-vertex), i.e. (tx, ty, angle, scalar)
and applied the following matrix inside the vertex shader:
$$\left(
\begin{array}{cccc}
\sigma\cos(\theta) & -\sigma\sin(\theta) & 0 & t_x \\
\sigma\sin(\theta) & \sigma\cos(\theta) & 0 & t_y \\
0 & 0 & 1 & 0 \\
0 & 0 & 0 & 1
\end{array}
\right).$$
Ideally, it would be best to upload a per-sprite array of transformation matrices as a uniform
, and use spriteId
to index into that for the per-vertex processing. However, as mentioned above, given that we could have 1000's of sprites means that this just isn't possible. I have heard of Shader Storage Buffer Objects (SSBO's) in ES 3.0, but from what I've read, they're not always available in the vertex shader.
In general, I realised performance will differ between systems, and there will be a trade-off between data upload, and CPU vs GPU processing, so ultimately I'm going to create a few different sprite batch shaders and do some tests to see which ways work best.
Anyway, hope this helps someone.
UPDATE
In the end I used two shader programs, (i) a GL_TRIANGLES
batcher for "dynamic" sprites that required rotation, scaling, translation, with transforms performed on the GPU as described above, and (ii) a GL_POINTS
batcher (i.e. Point Sprites) for "static" sprites (such as background tiles) that required only scaling and translation (but not rotation); then again, it is possible to program the fragment shader to perform rotations on point sprites, but this is slow and comes with it's own limitations, e.g. it's the point sprite texture uv
coordinates that are rotated and not the vertices so you have to be careful how you set up your sprite textures to avoid local boundary clipping.