I've got an extensive world terrain that uses vec3 for the vertex position attribute. That's good, because the terrain has endless gradations due to the use of floating point. But I'm thinking about how to reduce the amount of data uploaded to the GPU. For my terrain, which uses discrete / grid-based vertex positions in x and z, it's pretty clear that I can replace my vec3s (floats, really) with shorts, halving the per-vertex position attribute cost from 12 bytes each to 6 bytes. Considering I've got little enough other vertex data, and an enormous amount of terrain data to push into the world, it's a major gain.

Currently in my code, one unit in GLSL shaders is equal to 1m in the world. I like that scale. If I move over to using shorts, though, I won't be able to use the same scale, as I would then have a very blocky world where every step in height is an entire metre.

So I see these potential solutions to scale the positional data correctly once it arrives at the vertex shader stage:

  • Use 10:1 scaling, i.e. 1 short unit = 1 decimetre in CPU-side code. Do a division by 10 in the vertex shader to scale incoming decimetre values back to metres. Arbirary (non-PoT) divisions tend to be slow, however.

  • Use (some-power-of-two):1 scaling (eg. 8:1), which enables the use of a bitshift (eg. val >> 3) to do the division... not sure how performant this is in shaders, though. Not as intuitive to read values, but possibly quite a bit faster than div by a non-PoT value.

  • Use a texture as lookup table to convert the value from decimetres to metres. I've heard that this is really fast.

Or whatever solutions others can offer to achieve the same results -- minimal vertex data with sensible scaling.

  • \$\begingroup\$ You're already multiplying your vertex positions by a matrix to transform them to clip space, yes? Just incorporate the scaling into the matrix. \$\endgroup\$ – Nathan Reed Nov 30 '12 at 19:24
  • \$\begingroup\$ @NathanReed Hmm, Sam suggested same. Thing is, it wouldn't make any difference to processing cost, would it? (as opposed to doing it at a later stage of the pipeline) Because in the ordinary case, the values would be multiplied by the identity matrix which is one and thus probably gets factored out of the calculation by the shader code compiler(?). In other words, one way or another, it amounts to extra calculations, to my mind. \$\endgroup\$ – Engineer Nov 30 '12 at 19:26
  • 1
    \$\begingroup\$ @NickWigill In a regular vertex shader you'd already be multiplying by a matrix, even if you weren't scaling your terrain. I mean, you have to get your verts from world space to clip space somehow, right? So just bake the scaling into that matrix when you compute it on the CPU once per frame. No extra vertex shader code needed. \$\endgroup\$ – Nathan Reed Nov 30 '12 at 20:15
  • \$\begingroup\$ @NathanReed Totally with you there. However what I'm suggesting is that ordinarily, the values representing scale in the matrix are all 1f; certainly they are in my terrain chunks, at present. Because they are one, I'm speculating that they may well be optimised out of the calculation, and thus don't contribute to cost when no scaling is present, this sort of thing being quite common nowadays. Whereas, as soon as they become non-1, they do begin to contribute -- each component as an extra multiply. In fairness though, this may be a minor quibble, but just replying to what you were suggesting. \$\endgroup\$ – Engineer Nov 30 '12 at 20:26
  • 2
    \$\begingroup\$ Two points: (1) No, they're not optimized out of the computation. The shader compiler can't know what values you're going to put in the matrix at runtime, so it has to assume they could be any float values at all. It doesn't recompile when you change the matrix. (2) The same components of the matrix are used for both rotation and scaling, so if you're doing any rotation (including camera rotation), those components are non-identity already. \$\endgroup\$ – Nathan Reed Nov 30 '12 at 21:54

It seems to me that you only need to push 4 bytes to the GPU and retain your full precision: only send the X and Z components once, since they will never change. It is also possible to not send any X or Z data by using gl_VertexID (I can provide you with some examples but this is slightly beyond the scope of the question).

Then you're only left with the Y data to send. That's 4 bytes per vertex to send. But your suggestion of using shorts is just as valid; and you will never have to do a division, because what you do is multiply by (1.0 / 8.0) instead. And if you really worry about the vertex shader performance, that multiplication can be hidden in your world matrix, but honestly I would not try to optimise that until I knew it really is a bottleneck (and I doubt it is).

Also, I suggest having a look at the GL_NV_half_float extension, which lets you send 2-byte floating point numbers to the GPU.

| improve this answer | |
  • \$\begingroup\$ How did the multiply-by-reciprocal skip my mind? That's how you know you have a lot on your mind :) Still, a good answer, it's true that although I will frequently be re-uploading terrain meshes, it will not be all that frequent in the larger scheme of things. 2 byte floating point sounds fun, I was just wondering if something of the sort exists... but not in extensions. Pity. \$\endgroup\$ – Engineer Nov 30 '12 at 17:05
  • 2
    \$\begingroup\$ @NickWiggill Maybe I haven't been clear enough. The idea behind gl_VertexID is that in the case of a heightmap based on a rectangular grid you do not send vertex data at all. You build the X and Z coordinates by applying modulus and division to the value of gl_VertexID, which is a built-in variable that costs nothing in terms of data bandwidth. \$\endgroup\$ – sam hocevar Dec 1 '12 at 20:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.