[ad_1]
I am learning how to make an OpenGL renderer / game engine. I created a C++ class Light
, which looks roughly like this:
class Light {
vec3 position;
vec3 color;
// additional padding to make Light compatible with std140 layout
};
The Scene class holds a list of Lights
. During the rendering pass, the Renderer
class receives a const std::vector<Light>&
and uploads it to an UBO / SSBO. The fragment shader can then access this UBO/SSBO and iterate over all the lights.
Problem:
Consider implementing shadow mapping. The fragment shader must know which shadow map is attached to each light (via int shadowMapIndex
which indexes a samplerCubeArray
with the depth cubemaps). Some lights may not be shadow-casting (shadow casting may be turned on/off for each light), there may be additional data the renderer needs to set up for each light etc.
As a result, the Light
class needs to become:
class Light {
vec3 position;
vec3 color;
bool castShadow;
int shadowMapIndex;
// ... additional data the renderer may need to set up
};
The Renderer
class sets up the shadow maps, so during the rendering pass it must assign to each Light
object the index of the associated shadow map, hence, modify the Light
scene object. I want to avoid this – I want to keep the Light
class high-level and not have the renderer modify the state of the scene during the rendering. I would also like the Light
class itself not to have to conform to any specific layout (std140) .
The idea (?) for a solution:
Introduce a RendererLight
class which contains all the data held by the Light
object in addition to any information the renderer sets up itself and passes to the shaders. A RendererLight
object is created during the rendering pass for each Light
object.
In pseudo-code this approach looks like:
class Light {
vec3 position;
vec3 color;
// no padding etc. - doesn't need to be std140 compatible;
};
class RendererLight {
vec3 position;
vec3 color;
bool castShadow;
int shadowMapIndex;
// padding to ensure std140-compatible layout etc.;
RendererLight(const Light& light){
// construct the RendererLight object by essentially copying the data
// from the Light object
position = light.position;
// ...
}
};
void Renderer::render(const Scene& scene){
const std::vector<Light>& lights = scene.getLights();
const std::vector<RendererLight> rendererLights;
for (const auto& light : lights){
// Create a RendererLight object from a Light object
RendererLight& rendererLight = rendererLights.emplace_back(light);
// assign the shadow map information and other render-specific information
rendererLight.shadowMapIndex = getShadowMapIndex(...);
rendererLight.otherBitOfInfoTheShaderNeeds = ...;
}
// Upload the RendererLight objects to the GPU
uploadToUBO(rendererLights);
}
Questions:
- Is this the right approach, or should I simply allow the
Renderer
class to modify the state of theLight
objects directly? - Is it an anti-pattern / wrong to build what is essentially a copy of the
Light
during the rendering pass (to avoid modifying the original object)? - Is there any better approach to managing lights?
[ad_2]