Friday, January 3, 2020

opengl - What is the best method to update shader uniforms?


What is the most accepted way for keeping a shader's matrices up to date, and why?


For example, at the moment I have a Shader class that stores the handles to the GLSL shader program & uniforms. Every time I move the camera I then have to pass the new view matrix to the shader, then every different world object I must pass it's model matrix to the shader.



This severely limits me as I can't do anything without having access to that shader object.


I thought of creating a singleton ShaderManager class that is responsible for holding all active shaders. I can then access that from anywhere and a world object wouldn't have to know about what shaders are active just that it needs to let the ShaderManager know the desired matrices but I'm not sure this is the best way and there's probably some issues that will arise from taking this approach.



Answer



Use uniform buffers (ie constant buffers in D3D lingo).


So long as all your shaders agree on the layout and binding point of each such buffer then updating becomes a breeze. Models have no need to know anything about shaders at all. They need only update the model-view matrix in their constant buffer and the rendering pipeline will use it automatically.


At a minimum I would argue you should have two such buffers. The first should store your projection matrix, camera matrix, the concatenated camera-projection matrix, viewport information, frustrum details, and matrix inverses. You only need to update this buffer once per scene.


Then give each model another buffer to store its model-view matrix, normal matrix, inverses, and material properties. This is updated once for each model and can be done in a different update pass (if/when appropriate). The material information can/should be moved to a third material-specific buffer if you have the ability to share materials between multiple objects.


In a forward-shading setup it makes some sense to put all the lights in another buffer and in deferred-shading it likewise makes sense to use a per-light buffer for the light pass (in place of a model/material buffer used in the geometry pass).


Note that you need a moderately up-to-date version of GL to use uniform buffers at all (3.1 or an extension; common enough today except on some older but still-in-service laptops) and you need a fairly recent version to be able to bind uniform buffers to specific binding locations from inside the shader code (4.2; still uncommon but getting better) otherwise you have to do more work in your CPU-side code to set things up (your CPU code needs to know the right binding points anyway, so it's more of an API smell than a serious issue). OpenGL|ES didn't add uniform buffers until 3.0 which is still unsupported on most popular mobile platforms, unfortunately.


If buffers aren't an option then you will need some global place to store index locations for the active shader. You can use glGetUniformLocation after loading your shader to find the indices for well-known names (like ModelViewMatrix or the like) then store these indices. Your render can map enum values like MODEL_VIEW passed to a SetUniform wrapper function to look into the bound shader, find the index, and call glUniform properly. It's not a particular huge change in client code usage from buffers other than needing to set each uniform individually if you get it all wrapped up nicely.



See GLSL Inteface Blocks and Uniform Buffer Objects.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...