Search Unity

Performance implications between vertex colors vs textures

Discussion in 'General Graphics' started by lmbarns, May 3, 2019.

  1. lmbarns

    lmbarns

    Joined:
    Jul 14, 2011
    Posts:
    1,471
    I have models being created and am at the stage we either UV map them or go with solid colors, the artists want to do solid colors because it's faster, however:

    This is on android
    This is in VR (Oculus Go)
    There are dozens of individual buildings and a lot already going on.
    The map is dynamically generated, cannot lightmap or bake lighting or occlusion (I want to fake it in the texture)

    I'm inclined to go with small textures, in atlases.

    From my understanding, using solid colors on something like a building with is going to create a ton of individual objects with individual materials, at a minimum, a building will have 5 or more unique colors. These are also hitting the CPU, far more draw calls, but Unity has gotten a lot better with many draw calls in modern versions.

    Compared to using a texture, which is stored in memory and accessed by the GPU without touching the CPU? I also feel like the appearance of having occlusion and hopefully some lighting data baked into the texture will be much better, visually. You also get benefits of mip maps?

    Can anyone provide insight into the better direction if you had the option of which route to go?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    7,137
    If the comparison is between "vertex colors alone" and "a single texture alone", vertex colors are usually going to be the winner.

    For vertex colors, you artist can apply the vertex color to the models directly in their DCC tools, or it can modified in the editor or at runtime from script. The later can be done by modifying the mesh data, or using AdditonalVertexStreams to override existing color data. If you're using vertex colors, this means all objects can use the same "vertex color only" material which leads to better chances for batching, and overall reduction in render state changes (the most expensive part of what people often refer to as "draw calls").

    Vertex colors are stored on meshes and uploaded to the GPU as 4 bytes (0-255) per vertex, meaning 32 bits per vertex. In the shader, this data needs to be read by the vertex shader and passed on to the fragment shader. Reading per vertex data into the vertex shader, and passing data between the vertex and the fragment shaders both have a cost! Minimizing both can have a significant impact on performance. On desktop, each byte will be expanded to a full 32 bit float and passed on as 128 bits of data per vertex, but on mobile using a fixed4 means it'll only be 40 (10 bpc), 44 (11 bpc), or 64 (16 bpc) bits of data. I believe both fixed and half on Adreno 5 are 16 bit floating point values, so it'll be the last option. The interpolated color is then used as is, adding no additional costs.* If each triangle is truly a solid color, then you could use the nointerpolation modifier and it'll be even cheaper.

    * Switching between different floating point precisions within a shader often has a cost associated with it on mobile GPUs, sometimes a significant one, but as I think fixed and half are the same internally on Adreno 5 GPUs, this shouldn't a problem. Just don't use float!

    Lets compared that to a single texture atlas. Textures need UVs on the mesh. By default that's stored as two 32 bit floats per vertex, so 64 bits per vertex. Like vertex colors your artists can assign these in their tools, or they can be modified in editor or at runtime from script using the same options as vertex colors. They also need to be passed from the vertex to the fragment shader, which is again 64 bits (two 32 bit values) per vertex. So at this point, it's a slightly more expensive vertex shader due to reading more data, per vertex, but a wash when it comes to transferring that data between the stages. You may even be able to get a bit of a win here by using 16 bit UVs (half2) and thus only passing 32 bits per vertex. But then you still have to sample the texture in the fragment shader.

    If your game is unlit this means the vertex color option wins hands down 100% of the time. With lighting some of the time it takes to sample the texture can be hidden by the lighting calculations. GPUs will usually try to sample all of the textures it can at the start of the fragment shader execution and then continue on with the rest of the shader to hide the time it takes for the sampler units to return a value. In this case the vertex color vs texture may not be as obvious a win for the vertex color. The texture is still putting some additional pressure on the memory bandwidth, so overall the vertex color will likely come out on top.


    The real question is "by how much"? I would suggest making some dummy test assets and compare the two options on the device. If you're looking to bake AO into the mesh vertex colors or textures, that may require high vertex counts for the models to do using vertex colors depending on your wanted look and mean you'll want to use textures anyway. In fact the real answer in the end may end up being "do both"; use vertex colors for the color, and use a texture atlas for AO only, either baked uniquely per mesh, or hand authored fakery using a set of gradient shapes.



    However the absolute worst option is to have multiple solid colored materials that you apply to surfaces. That you have correct. Don't do that. You're better off with a texture atlas or color swatch texture and having your artists use that one texture for everything, or at least as much as possible. Dino Frontier used a single uncompressed 128x128 swatch texture (of which we only used the top 1/5th or so) with color swatches and simple gradients, and a second 512x512 "detail texture" to add additional details as an overlay. Gave the artists slightly more control than vertex coloring alone.
     
    Giometric and lmbarns like this.
  3. lmbarns

    lmbarns

    Joined:
    Jul 14, 2011
    Posts:
    1,471
    Thank you so much for the detailed response!