Mastrem
Mastrem

Reputation: 225

Can you modify a uniform from within the shader? If so. how?

So I wanted to store all my meshes in one large VBO. The problem is, how do you do have just one draw call, but let every mesh have its own model to world matrix?

My idea was to submit an array of matrices to a uniform before drawing. In the VBO I would make the color of every first vertex of a mesh negative (So I'd be using the signing bit to check whether a vertex was the first of a mesh).

Okay, so I can detect when a new mesh has started and I have an array of matrices ready and probably a uniform called 'index'. But how do I increase this index by one every time I encounter a new mesh?

Can you modify a uniform from within the shader? If so, how?

Upvotes: 4

Views: 5498

Answers (2)

Yevgeniy Logachev
Yevgeniy Logachev

Reputation: 761

There are two approaches to minimize draw calls - instancing and batching. The first (instancing) allows you to draw multiple copies of same meshes in one draw call, but it depends on the API (is available from OpenGL 3.1). Batching is similar to instancing but allows you to draw different meshes. Both of these approaches have restrictions - meshes should be with the same materials and shaders.

If you would to draw different meshes in one VBO then instancing is not an option. So, batching requires keeping all meshes in 'big' VBO with applied world transform. It not a problem with static meshes, but have some discomfort with animated. I give you some pseudocode with batching implementation

struct SGeometry
{
    uint64_t offsetVB;
    uint64_t offsetIB;
    uint64_t sizeVB;
    uint64_t sizeIB;

    glm::mat4 oldTransform;
    glm::mat4 transform;
}
std::vector<SGeometry> cachedGeometries;

...


void CommitInstances()
{
    uint64_t vertexOffset = 0;
    uint64_t indexOffset = 0;

    for (auto instance in allInstances)
    {
        Copy(instance->Vertexes(), VBO);

        for (uint64_t i = 0; i < instances->Indices().size(); ++i)
        {
            auto index = instances->Indices()[i];
            index += indexOffset;
            IBO[i] = index;
        }

        cachedGeometries.push_back({vertexOffset, indexOffset});

        vertexOffset += instance->Vertexes().size();
        indexOffset += instance->Indices().size();
    }

    Commit(VBO);
    Commit(IBO);
}

void ApplyTransform(glm::mat4 modelMatrix, uint64_t instanceId)
{
    const SGeometry& geom = cachedGeometries[i];

    glm::mat4 inverseOldTransform = glm::inverse(geom.oldTransform);

    VertexStream& stream = VBO->GetStream(Position, geom.offsetVB);
    for (uint64_t i = 0; i < geom.sizeVB; ++i)
    {
        glm::vec3 pos = stream->Get(i);
        // We need to revert absolute transformation before applying new
        pos = glm::vec3(inverseOldNormalTransform * glm::vec4(pos, 1.0f));
        pos = glm::vec3(normalTransform * glm::vec4(pos, 1.0f));
        stream->Set(i);
    }

    // .. Apply normal transformation
}

GPU Gems 2 has a good article about geometry instancing http://www.amazon.com/GPU-Gems-Programming-High-Performance-General-Purpose/dp/0321335597

Upvotes: 0

Nicol Bolas
Nicol Bolas

Reputation: 473447

Can you modify a uniform from within the shader?

If you could, it wouldn't be uniform anymore, would it?

Furthermore, what you're wanting to do cannot be done even with Image Load/Store or SSBOs, both of which allow shaders to write data. It won't work because vertex shader invocations are not required to be executed sequentially. Many happen at the same time, and there's no way for any shader invocation to know that it will happen "after" the "first vertex" in a mesh.

The simplest way to deal with this is the obvious solution. Render each mesh individually, but set the uniforms for each mesh before each draw call. Without changing buffers between draws, of course. Uniform changes, while not exactly cheap, aren't the most expensive state changes that exist.

There are more complicated drawing methods that could allow you more performance. But that form is adequate for most needs. You've already done the hard part: you removed the need for any state change (textures, buffers, vertex formats, etc) except uniform state.

Upvotes: 7

Related Questions