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,628
    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:
    12,352
    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.
     
  3. lmbarns

    lmbarns

    Joined:
    Jul 14, 2011
    Posts:
    1,628
    Thank you so much for the detailed response!
     
  4. Neogene

    Neogene

    Joined:
    Dec 29, 2010
    Posts:
    95
    bgolus:

    regarding reading the vertex values vs the texture values for some custom operations / data storage (eg: saving using the color as a degree of intensity for a particular effect or using the color value as a data to use in some algorithm/shader) is the same in your opinion or is in favor of the vertex approach?

     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    I don't really favor one over the other.

    When the choice is between a texture and vertex colors it comes down to the question of if the data is intended to only be per vertex anyway (ie: data related to vertex manipulation), or if using per vertex data is going to give enough density to be useful, then go per vertex. If not, use a texture.

    For something like modifying the intensity of some kind of per vertex wiggle, I use vertex color.

    If it's modifying the color of an entire object, or discrete sections of an object that will be batched, also probably vertex color.

    If it's a gradient across the surface, then it depends on if the mesh has enough tessellation or if the gradients can align with the geometry well enough to hide the vertex interpolation. For fx I'll quite frequently build custom meshes with the explicit goal of using per vertex colors, and spend the time to add geometry or flip edges to get the gradient shapes I want.

    However the mesh needs to be more vertices than make sense for the particular use case, or don't want to / can't modify a mesh, or if it's just a complex shape I'm too lazy to model, I'll use a texture.
     
  6. Beloudest

    Beloudest

    Joined:
    Mar 13, 2015
    Posts:
    247
    Interesting thread
    bglous:
    Are there any methods for storing smoothness and metallic in vertex data, thus achieving PBR while maintaining the status of a single batchable material? I can't seem to locate any PBR vertex shaders that fit the bill. I wonder if I'm not approaching the concept correctly or if it's beyond the limits.
     
  7. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,792
    I store smoothness in vertex alpha. Not sure if you can fit metalness too, maybe it's possible with some weird packing, but then the workflow is probably kinda ruined.

    And I think this is more of a workflow thing. Depending of software used, a swatch texture workflow might be a bit more convenient (the method naturally produces easily selectable color sets), while tweaking vertex colors (depending on tool used) can be somewhat trickier.
     
    Beloudest likes this.
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    There's certainly nothing stopping you from baking smoothness and metallic into vertex data. Could be done with some custom vertex alpha using 7 bits for smoothness and 1 for metallic, and then unpacking the data in the vertex shader before interpolation. Or if you're not using vertex colors for anything else you could use the red and green vertex color channels for smoothness and metallic. Or you could use extra UV channels to store that data.

    Basically all per vertex data is just arbitrary data that can be used however you want. For static batching you have to be mindful that the vertex position, normal, tangent, and second UV set have specific expectations for what kind of data they hold and may be modified by the static batching system. But everything else is fair game to do with as you please.

    But no, you're unlikely to find any pre-existing shaders that do what you want, because setups like this are usually bespoke and require custom content creation pipeline setups.
     
    Beloudest likes this.
  9. Beloudest

    Beloudest

    Joined:
    Mar 13, 2015
    Posts:
    247
    Thanks for the information, when I'm not busy with deadlines I will have to properly try out.