PDA

View Full Version : Multi-layered/Deferred Shading and FBO operations



CaptainSnugglebottom
05-16-2016, 11:04 PM
Hello,

I am trying to understand how the entire deferred shading would work with the gl* functions that we have. The FBO's are attached and several things are rendered to them (colors, normals, depth, etc.), but the only way to do anything with this FBO was the glBlitFramebuffer function, that copies things from the COLOR_ATTACHMENT0 object (renderbuffer in my case, that's what I use) to whatever FBO is attached to GL_DRAW_FRAMEBUFFER point.

So at what point would I actually use the generated maps to shade them based on my light data? Also, how would I tell the Blit function to use COLOR_ATTACHMENT(n) instead of just 0, in case I want to copy to screen something else?

The only way I can think of is to write a completely new draw() function that uses the textures (replace my renderbuffer with textures, I don't know if renderbuffer would work) to draw a screen-sized rectangle, and while I draw another shader uses the textures to apply the shading to the rectangle. What is the way to assign FBO's textures (or renderbuffers) for being used in rendering of something else?

Am I close? Drawing a rectangle to act like a screen feels amateur-ish, but it's the only way to use the generated maps I can come up with.

GClements
05-16-2016, 11:33 PM
The only way I can think of is to write a completely new draw() function that uses the textures (replace my renderbuffer with textures, I don't know if renderbuffer would work) to draw a screen-sized rectangle, and while I draw another shader uses the textures to apply the shading to the rectangle.

Which is essentially how deferred shading works, at least in the most basic case.

In the more typical case, rather than rendering a single screen-sized rectangle, you'd render smaller regions corresponding to the illumination range of individual lights and accumulate the results. Or you'd split the screen into tiles, and the rendering of each tile would only consider the lights which are relevant to that tile. This way, you're not performing lighting calculations for each light for the entire scene, only the portions where the light is relevant.

The fragment shader would still have the same structure, i.e. to calculate the colour for a given pixel, you'd use the corresponding pixel from the position, normal, reflectance etc maps rendered in the first pass.

Even if you render a screen-sized quad, it can still reduce the lighting calculations because you're only performing them for visible fragments, not fragments which are overdrawn by closer geometry (although simply rendering from front to back can also avoid that via early-depth optimisation).



What is the way to assign FBO's textures (or renderbuffers) for being used in rendering of something else?

glBindTexture(). Once you've rendered into them, they're just textures.

Multi-pass rendering requires the use of textures rather than renderbuffers so that the shader can read from them. With renderbuffers, the only way to get the data out is via framebuffer reads (glReadPixels(), glBlitFrameBuffer(), glCopyTex[Sub]Image2D(), etc).

CaptainSnugglebottom
05-17-2016, 10:13 PM
Which is essentially how deferred shading works, at least in the most basic case.

In the more typical case, rather than rendering a single screen-sized rectangle, you'd render smaller regions corresponding to the illumination range of individual lights and accumulate the results. Or you'd split the screen into tiles, and the rendering of each tile would only consider the lights which are relevant to that tile. This way, you're not performing lighting calculations for each light for the entire scene, only the portions where the light is relevant.

The fragment shader would still have the same structure, i.e. to calculate the colour for a given pixel, you'd use the corresponding pixel from the position, normal, reflectance etc maps rendered in the first pass.

Even if you render a screen-sized quad, it can still reduce the lighting calculations because you're only performing them for visible fragments, not fragments which are overdrawn by closer geometry (although simply rendering from front to back can also avoid that via early-depth optimisation).


glBindTexture(). Once you've rendered into them, they're just textures.

Multi-pass rendering requires the use of textures rather than renderbuffers so that the shader can read from them. With renderbuffers, the only way to get the data out is via framebuffer reads (glReadPixels(), glBlitFrameBuffer(), glCopyTex[Sub]Image2D(), etc).

Thank you very much!

Is there a way in GLSL to define an array of textures. So lets say one scene is using 3 textures, whereas another one is using 6.... or will it require another shader, or something like a finite number of defined textures that are used only when told to (via uniform control variables). And in the latter case, if I don't use a texture variable in the shader, would it be to leave it unattached to anything as long as I don't access it?

Also, what is the more convenient way for defining textures, besides getting their attachment points via their in-shader name, and then binding them to that point?

If render things to several textures, and then try to use those textures in another shader for deferred shading, will they still be bound to the FBO or will they be reattached? Could using same FBO cause issues ? (ex: i render to color attachments 0-2, then send textures to another shader but still render to the same FBO at color attachment 0)

GClements
05-17-2016, 11:22 PM
Is there a way in GLSL to define an array of textures.

You can have a uniform array of samplers. However, any expression used to index the array must be dynamically uniform, i.e. the value must be the same for all invocations of a shader which may run concurrently. For a vertex shader, this means that the expression must be constant for each draw call (i.e. depend only upon uniforms), for a fragment shader it must be constant for a single primitive (i.e. depend only upon uniforms and flat-qualified inputs).

The other option is to use an array texture (e.g. GL_TEXTURE_2D_ARRAY). The expression used to select the layer doesn't need to be dynamically uniform (i.e. it can vary on a per-vertex or per-fragment basis), but array textures are less flexible, as all of the layers have to have the same format and dimensions, and sampler parameters (filters, repeat mode, etc) are set for the entire texture rather than individual layers.

An array texture only requires a single texture unit, an array of samplers requires a separate texture unit for each texture.



Also, what is the more convenient way for defining textures, besides getting their attachment points via their in-shader name, and then binding them to that point?

I'm not sure that I understand the question. Are you referring to samplers or to framebuffer attachments?



If render things to several textures, and then try to use those textures in another shader for deferred shading, will they still be bound to the FBO or will they be reattached? Could using same FBO cause issues ? (ex: i render to color attachments 0-2, then send textures to another shader but still render to the same FBO at color attachment 0)

The current FBO, FBO attachments and bindings of textures to texture units won't change unless you change them.

For multi-pass rendering, you need to explicitly change the texture bindings and the FBO or its attachments between passes. In particular, you can't use a texture (more precisely, a mipmap level) as both an input and an output in the same draw call (if you need to to that, use image load/store instead). You can use a mipmap level as a FBO attachment while reading from other mipmap levels of the same texture provided that you set GL_TEXTURE_BASE_LEVEL and/or GL_TEXTURE_MAX_LEVEL such that the level attached to the FBO is outside of that range (and thus cannot be read from).

CaptainSnugglebottom
06-23-2016, 02:32 PM
Okay, just got back after a vacation and ready to continue. I have fixed some conceptual issues I had before, and now with newly added Blend Map I carry on. However, with my new implementation a similar issue I had before remains. I only see a single top level texture rendering, the rest draws blank, and I wonder if there's something I am not following.

I implemented the following code (going top to bottom):

void graphicsWindow::drawScene(void)
{
// DRAWING LAYERS

if (sceneDebugFlag) { std::cout << "GENERATING FINAL IMAGE..." << std::endl; }
bufferWindow->scene->drawLayers();

// DRAWING FINAL IMAGE

if (sceneDebugFlag) { std::cout << "DISPLAYING FINAL IMAGE..." << std::endl; }
glBindFramebuffer(GL_FRAMEBUFFER, 0); // bind main window buffer
glClearColor(1.0f, 0.0f, 0.0f, 1.0f); // set clearing color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // wipe the drawing surface clear

glUseProgram(bufferWindow->windowSceneRenderShader);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, bufferWindow->scene->sceneRenderedImageTextureID);
glUniform1i(glGetUniformLocation(this->windowSceneRenderShader, "finalImageTexture"), 0);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, bufferWindow->scene->sceneRenderedControlTextureID);
glUniform1i(glGetUniformLocation(this->windowSceneRenderShader, "finalControlTexture"), 0);

glBindVertexArray(this->windowVAO);
glBindBuffer(GL_ARRAY_BUFFER, this->windowVBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, (3 + 2) * sizeof(GLfloat), NULL);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, (3 + 2) * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glDrawArrays(GL_TRIANGLES, 0, 30);
}


void graphicsScene::drawLayers(void)
{
// DRAWING LAYERS
//-------------------------------------------------------------------------------------------------------------------------

if (sceneDebugFlag) { std::cout << " GENERATING LAYERS..." << std::endl; }

int i = 0;
for (i = 0; i < this->listOfSubScenes.size(); i++) {

this->listOfSubScenes.front()->drawObjects();

this->listOfSubScenes.push(this->listOfSubScenes.front());
this->listOfSubScenes.pop();
}

// RENDER LAYERS
//-------------------------------------------------------------------------------------------------------------------------

if (sceneDebugFlag) { std::cout << " DISPLAYING LAYERS..." << std::endl; }

glBindFramebuffer(GL_FRAMEBUFFER, sceneRenderFBO); // bind main window buffer

glDrawBuffers(2, this->sceneDrawBuffers);
glViewport(0, 0, this->ownerWindow->width, this->ownerWindow->height); // redundant?
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glUseProgram(this->sceneLayerRenderShader);

GLuint uniformIDBuff;
uniformIDBuff = glGetUniformLocation(this->sceneLayerRenderShader, "layerAspectRatio");
glUniform1i(uniformIDBuff, this->ownerWindow->aspectRatio);

for (i = 0; i < this->listOfSubScenes.size(); i++) {

uniformIDBuff = glGetUniformLocation(this->sceneLayerRenderShader, "layerOffset");
glUniform1i(uniformIDBuff, this->listOfSubScenes.front()->layerOffset);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, this->listOfSubScenes.front()->layerRenderedImageTextureID);
glUniform1i(glGetUniformLocation(this->sceneLayerRenderShader, "layerImageTexture"), 0);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, this->listOfSubScenes.front()->layerRenderedControlTextureID);
glUniform1i(glGetUniformLocation(this->sceneLayerRenderShader, "layerControlTexture"), 1);

glBindVertexArray(this->sceneVAO);
glBindBuffer(GL_ARRAY_BUFFER, this->sceneVBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, (3 + 2) * sizeof(GLfloat), NULL);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, (3 + 2) * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glDrawArrays(GL_TRIANGLES, 0, 30);

this->listOfSubScenes.push(this->listOfSubScenes.front());
this->listOfSubScenes.pop();
}
}

void graphicsSubScene::drawObjects(void)
{
// PERFORM RENDERING OF OBJECTS
//------------------------------------------------------------------------------------------------------------------------------


if (subSceneDebugFlag) { std::cout << " GENERATING OBJECTS..." << std::endl; }

glBindFramebuffer(GL_FRAMEBUFFER, objectRenderFBO); // bind main window buffer

glViewport(0, 0, this->ownerScene->ownerWindow->width, this->ownerScene->ownerWindow->height); // redundant?
glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glUseProgram(this->subSceneObjectRenderShader);

int i = 0;
for (i = 0; i < this->listOfObjects.size(); i++) {

//this->listOfObjects.front()->drawObject();

this->listOfObjects.push(this->listOfObjects.front());
this->listOfObjects.pop();
}


// PERFORM SHADING OF LAYERS
//------------------------------------------------------------------------------------------------------------------------------

if (subSceneDebugFlag) { std::cout << " SHADING LAYERS..." << std::endl; }

glBindFramebuffer(GL_FRAMEBUFFER, layerShadingFBO); // bind main window buffer

glViewport(0, 0, this->ownerScene->ownerWindow->width, this->ownerScene->ownerWindow->height); // redundant?
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glUseProgram(this->subSceneLayerShadingShader);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, this->objectRenderBlendMapID);
glUniform1i(glGetUniformLocation(this->subSceneLayerShadingShader, "blendMap"), 0);

glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, this->objectRenderControlMapID);
glUniform1i(glGetUniformLocation(this->subSceneLayerShadingShader, "controlMap"), 1);

glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, this->objectRenderColorMapID);
glUniform1i(glGetUniformLocation(this->subSceneLayerShadingShader, "colorMap"), 2);

glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, this->objectRenderNormalMapID);
glUniform1i(glGetUniformLocation(this->subSceneLayerShadingShader, "normalMap"), 3);

glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D, this->objectRenderSpecularMapID);
glUniform1i(glGetUniformLocation(this->subSceneLayerShadingShader, "specularMap"), 4);

glBindVertexArray(this->layerShadingVAO);
glBindBuffer(GL_ARRAY_BUFFER, this->layerShadingVBO);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, (3 + 2) * sizeof(GLfloat), NULL);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, (3 + 2) * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glDrawArrays(GL_TRIANGLES, 0, 30);
}

It's relatively straight forward. My window draws the scene, then extracts 1 output texture from it and draws onto a square. The background is set to be red for testing, and it works.

The scene draws the layers, then renders each layer onto a square. The output is saved into a texture that is used by the window. The background is set as green, for testing, again it works.

However right now, I have a red space with a green square in it. The single layer that is attached to the scene, doesn't draw any objects, but at the deferred shading stage it cleans the background into a blue color. But it's not being drawn on the screen. This is a bit of a pickle since I am not sure which portion doesn't work. Whether I don't see any blue squares because it's the scene that doesn't draw the layer, or the layer that isn't initiated properly and is transparent. The textures and geometries are initiated the same way on all levels. So I wonder if I'm missing a step required to do render to texture again before buffer swap.

Update: I initiated the layer output textures using a test texture I had around, and commented out any renderings to it. In theory I would see a texture inside a green square inside a red square, but I still get just the green square, meaning the layer's output texture is not being rendered into scene's quad.


My texture initialization are of the format:
void graphicsSubScene::setUpObjects(void)
{
// OBJECT RENDER VARIABLES

glGenFramebuffers(1, &objectRenderFBO);
glBindFramebuffer(GL_FRAMEBUFFER, objectRenderFBO);
glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH, this->ownerScene->ownerWindow->width);
glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT, this->ownerScene->ownerWindow->height);
glFramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_SAMPLES, 4);
glDrawBuffers(5, this->objectRenderDrawBuffers);

// BLEND MAP SET UP

glGenTextures(1, &(this->objectRenderBlendMapID));
glBindTexture(GL_TEXTURE_2D, this->objectRenderBlendMapID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, this->ownerScene->ownerWindow->width, this->ownerScene->ownerWindow->height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // not needed?
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, objectRenderBlendMapID, 0); // move to loop?
glBindTexture(GL_TEXTURE_2D, 0);


... more textures here

// DEPTHBUFFER SETUP
glGenTextures(1, &(this->objectRenderDepthBufferID));
glBindTexture(GL_TEXTURE_2D, this->objectRenderDepthBufferID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, this->ownerScene->ownerWindow->width, this->ownerScene->ownerWindow->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // not needed?
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, objectRenderDepthBufferID, 0); // move to loop?
glBindTexture(GL_TEXTURE_2D, 0);
}