0
\$\begingroup\$

I currently transform (translate, rotate, scale) a bunch of vertices in my own Java code, then populate an mPositions array and an mTextureCoordinates array, which draws a bunch of different textured sprites to the screen in one GL draw command. Works great.

However, I now wish to move the transformation process away from native Java code and over to the vertex shader, and so I will need to pass into the shader transformation matrices which encode the translation, rotation, and scaling operations for each sprite.

Given my current approach the naive and obvious choice is to introduce another array mTransformations, passed into the shader via a GLES20.glVertexAttribPointer command, which contains a matrix for each and every vertex.

But this way seems a little wasteful for two reasons:

  1. I will have to add the same transformation matrix 6 times per sprite to the mTransformations array since each square sprite is made of 2 triangles (3+3 vertices)

  2. Since I'm now going to use the shader to perform the transformations, the 6 canonical coordinates of each square sprite (two triangles) will be the same for every sprite. In effect I'd have to populate mPositions with the same coordinates over and over for each sprite.

Is there a more efficient way to do achieve what I want?

\$\endgroup\$
2
  • \$\begingroup\$ Why not just use uniforms? \$\endgroup\$
    – Bálint
    Aug 2, 2019 at 14:42
  • 1
    \$\begingroup\$ Downvoter please explain. \$\endgroup\$
    – user24493
    Aug 2, 2019 at 16:08

1 Answer 1

0
\$\begingroup\$

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:

  1. 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.

  2. 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.

\$\endgroup\$

You must log in to answer this question.