Hi,
I have implemented simple deferred rendering.
Here is the render loop:
for (auto cam : EntitySystem::instance()->_cameras) {
perCameraUpdate(cam); //shadowMaps, frustum update, terrain lod, camera ubo
drawToGBuffer(cam);
//write to lightmap
applyDirectionalLights(cam);
applySpotlights(cam);
applyPointLights(cam);
deferredShading(cam);
}
This is the gBuffer:
_depthComponentTexture = new Texture(_width, _height, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_NEAREST);
_worldNormalSpecPower = new Texture(_width, _height, GL_RGBA16F, GL_RGBA, GL_NEAREST);
_albedoSpecularIntensity = new Texture(_width, _height, GL_RGBA8, GL_RGBA, GL_NEAREST);
_3unusedShaderless = new Texture(_width, _height, GL_RGBA8, GL_RGBA, GL_NEAREST);
I don’t know if gBuffer internal format is correct. Should bytes of internal format add up? GL_RGBA16F = 8 bytes and GL_RGBA8 is 4 bytes.
Position is reconstructed from depth like this:
vec3 worldSpaceFromDepth(in float depth) {
float z = depth * 2.0 - 1.0;
vec4 clipSpacePosition = vec4(uv0 * 2.0 - 1.0, z, 1.0);
vec4 direct = pc.projectionCameraMatInverse * clipSpacePosition;
return direct.xyz / direct.w;
}
If _3unusedShaderless.a is 1 lighting is discarded and its used for debug lines, skybox etc.
The lighting is done in world space. If i do lighting in view space then normal can be encoded with spheremap transform to vec2, it might be worth it.
Point light pass, same for other lights:
void DeferredRenderer::applyPointLights(Camera* cam) {
auto shader = _pointLightShader;
auto pointLightsArray = Scene::instance()->_inFrustum_pointsLights;
shader->bind();
shader->updateUniforms();
shader->setBlend(1);
cam->_fbo->bindFbo();
cam->getGBuffer().bindGBufferReadAt(0);
cam->_fbo2->getTexture().bind(4); //read previous lights and add to current
unsigned i = 0;
while (true) {
if (i == pointLightsArray.size()) break;
//glClear(GL_COLOR_BUFFER_BIT); //it works without clearing fbo1 for some reason
unsigned j = 0;
for (; i < pointLightsArray.size() && j < 50; ++i, ++j) {//batch of 50 shadowless lights
auto pl = pointLightsArray[i];
UniformBufferObjects::instance()->pushPointLightToUBO(pl, j);
}
UniformBufferObjects::instance()->setPointLightsCount(j);
UniformBufferObjects::instance()->updatePointLightUBO();
_ndsQuad->draw();
Graphics::instance()->fboToFbo(cam->_fbo->getFramebufferID(), cam->_fbo2->getFramebufferID()); //fbo blit, copy cur lightmap to fbo2
}
}
Light shader:
#version 330
out vec4 outColor;
void main(){
if(int(texture2D(g_unusedShadeless, uv0).a) == 1) return;
initData();
vec3 result = CalcDirLight();
outColor = vec4(result + texture2D(previousDraw, uv0).xyz * blend, 1.0);
}
Position is reconstructed and if fragment is not in lights range its discarded.
Point and spot lights have no shadow maps for now. Lights are rendered in batches, 4 textures from gbuffer + shadowMap + lightMap = 6 textures -> 5 lights with shadows per batch.
Batching lights is way faster then rendering one by one. Blitting fbos to accumulate light seems like a hack, can this be done any other way?
I cant get transparency for sun to work. It worked it forward rendering, does it have something to do with MRTs?
Skybox is just a cube and sun a plane.
Draw sun:
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Assets::instance()->fetchMesh("NDS_QUAD")->draw();
glDisable(GL_BLEND);
Skybox shader:
#version 330
int main(){
vec4 color = texture(diffuse, uv0);
out_g_worldNormalSpecPower = vec4(0.0, 0.0, 0.0, 0.0);
out_g_albedoSpecIntesity = vec4(color);
out_g_unusedShadeless = vec4(0,0,0,1);
}
Sun shader:
#version 330
int main(){
vec4 difColor = texture2D(diffuseTex, uv0) * sunColor; //sun color = glm::vec4(1, 1, 0.6, 1), diffuseTex = white halo
out_g_worldNormalSpecPower = vec4(0);
out_g_albedoSpecIntesity = vec4(difColor.xyz, 0);
out_g_unusedShadeless = vec4(0,0,0,1);
}
Have yet to implement transparency.
Transparency:
- sort transparent objects by z axis
- draw them in forward rendering
- discard every pixels by comparing them with depth from gBuffer
- calculate all lights in frustum
Is this a good idea?
Thanks for reading!