Ok, I admit it. So far, I cheated. I only used a single light, with provision but without implementation for multiple lights, especially shadow-casting ones.
Screenshot first, talk later.
Lights and shadows are much more complex than they look, for the same reason: There are no cookie - cutter solutions, you need to at least pick and choose between several available options, or quite probably, create your own version suited to your own needs.
Multiple lights don't play very nice, because you have no completely obvious way to implement them, especially shadow-casting lights. If you go for forward-rendering, as I am doing up till now (not recommended for really complex high-end high performance applications in general), you can expect an algorithmic complexity of O(m*n), where m is the number of objects and n the number of lights.
In a nutshell, whatever the actual implementation, you have to somehow accumulate the results of each light on each pixel, be it with a loop in the shader, with blending and multiple passes, with deferred rendering, or some other way.
I will admit that I was prepared for looping in the shader, ultimately scrapped it - I had no obvious way to support an arbitrary number of shadow-casting lights in the shader, as I need to render a shadow map for each anyway.
So, for the moment at least, I chose the second option : I modified the rendering pipeline by adding an integer pass parameter to the render() function that configures and executes the shaders. This way, the C++ side of the shaders can keep track and manage its state depending on the pass: in that case, each pass is a light. The first pass is without blending in order to render the objects correctly on the background, and the rest of the passes are executed with additive blending to accumulate the results.
Keep in mind, this is not necessarily the best performing way, but it is arguably the most flexible for forward-rendering, as you can have as many shadow-casting lights as your application and graphics cards can take, playing nice with each other.
I will venture a guess that it is also a good logical preparation for the later-to-be-implemented deferred-rendering.
I love the result...
PS: Enable v-sync when prototyping, I bet that the unhealthy whistling sounds that come from my speakers when my card tries to push out 3500 fps are not a good signal for my card's health and longevity...
As the code snippet, a treat: How xlslModularProgram's render() actually looks like...
This part of the execution runs after the shadow map for light #pass is created.
You could do a lot to optimize here, especially if you use deferred rendering, for example creating and using a few shadowmaps at the same time.
//If you need more than 255 passes, you should rethink your design. These calls are not for free!
bool render(unsigned char pass) override {
this->makeCurrent(); //binds the shaders to the pipeline
if (pass >0) {
static const float factors[4] = {1, 1, 1, 1};
context.d3d_devcon().OMSetBlendState(blend_state_add, factors, 0xFFFFFF); //Enable additive blending to accumulate each light's effect for the second and subsequent passes. We don't want blending for the first pass, because we want to clear the background, not blend with it.
}
plugin_render_starting(pass); //instruct plugins to run their per-pass logic
for (unsigned int i = 0; i < entityManager->objects.size(); ++i) {
grg::TriangleMesh& obj = entityManager->objects[i];
obj.beginGpuBufferRender<VboStaticType>(globalObjectVboAttachmentPoint); // This all important line actually tells the object to upload to the GPU, IF CHANGED, the buffer that this shader has attached to the object
plugin_render_object_starting(obj); //instruct plugins to run their per-object logic
//All the accumulated changes for matrices, materials and other constants will be now uploaded to the GPU, AS NEEDED. The call is practically free if no changes have occured
constantBufferManager->onRender();
// draw the vertex buffer to the render target
////////////// TA - DAH //////////////
if (obj.getFacesCount()) { context.d3d_devcon().DrawIndexed(entityManager->objects[i].getFacesCount() * 3, 0, 0); }
else { context.d3d_devcon().Draw(entityManager->objects[i].getVerticesCount(), 0); }
////////////// TA - DAH //////////////
plugin_render_object_finished(entityManager->objects[i]); //per-object plugin cleanup logic
entityManager->objects[i].endGpuBufferRender<VboStaticType>(globalObjectVboAttachmentPoint);
}
plugin_render_finished();
this->unbind(); //Remove the shader from the pipeline
context.d3d_devcon().OMSetBlendState(NULL, NULL, 0xFFFFFF);//Reset blending to default
return pass < (int)(entityManager->lights.size() - 1);//The return code for this function instructs the shader manager if it should continue running subsequent passes. This shader does one pass per light, so stops when all lights are processed.
}
No comments:
Post a Comment