This operation looks at the introduction in order to reduce the cost of forward rendering (that is, rendering the next model after one model has finished)
introduce
Forward Rendering or Forward Shading. In a scene, we illuminate an object according to all the light sources, then render the next object, and so on. It is very easy to understand and implement, but it also has a great impact on the performance of the program, because for every object to be rendered, the program has to iterate every light source and every segment to be rendered, which is a lot.
The delayed shading method is based on the idea that most of the computational complexity of our Defer or Postpone rendering (such as illumination) will be very large until later processing. It consists of two processing stages: in the first Geometry Pass, we render the scene once, then get the geometric information of the object, and store it in a series of textures called G-buffer; think of position vector, color vector, Normal Vector and/or Specular Value. The geometric information stored in the G buffer in the scene will be used for (more complex) illumination calculation later.
There will be a G-Buffer to store position normal albedo specular first.
The second operation is a bit like the latter.
GLuint gBuffer; glGenFramebuffers(1, &gBuffer); glBindFramebuffer(GL_FRAMEBUFFER, gBuffer); GLuint gPosition, gNormal, gAlbedoSpec; // - Position color buffer glGenTextures(1, &gPosition); glBindTexture(GL_TEXTURE_2D, gPosition); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0); // - Normal color buffer glGenTextures(1, &gNormal); glBindTexture(GL_TEXTURE_2D, gNormal); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0); // - Color + Specular color buffer glGenTextures(1, &gAlbedoSpec); glBindTexture(GL_TEXTURE_2D, gAlbedoSpec); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0); // - Tell OpenGL which color attachments we'll use (of this framebuffer) for rendering GLuint attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }; glDrawBuffers(3, attachments); // - Create and attach depth buffer (renderbuffer) GLuint rboDepth; glGenRenderbuffers(1, &rboDepth); glBindRenderbuffer(GL_RENDERBUFFER, rboDepth); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, SCR_WIDTH, SCR_HEIGHT); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth); // - Finally check if framebuffer is complete if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) std::cout << "Framebuffer not complete!" << std::endl; glBindFramebuffer(GL_FRAMEBUFFER, 0); glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
The first time I saw this glDrawBuffers, I just wanted to draw the three attachment s in terms of semantics.
//vertex #version 330 core layout (location = 0) in vec3 position; layout (location = 1) in vec3 normal; layout (location = 2) in vec2 texCoords; out vec3 FragPos; out vec2 TexCoords; out vec3 Normal; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { vec4 worldPos = model * vec4(position, 1.0f); FragPos = worldPos.xyz; gl_Position = projection * view * worldPos; TexCoords = texCoords; mat3 normalMatrix = transpose(inverse(mat3(model))); Normal = normalMatrix * normal; } //fragment #version 330 core layout (location = 0) out vec3 gPosition; layout (location = 1) out vec3 gNormal; layout (location = 2) out vec4 gAlbedoSpec; in vec2 TexCoords; in vec3 FragPos; in vec3 Normal; uniform sampler2D texture_diffuse1; uniform sampler2D texture_specular1; void main() { // Store the fragment position vector in the first gbuffer texture gPosition = FragPos; // Also store the per-fragment normals into the gbuffer gNormal = normalize(Normal); // And the diffuse per-fragment color gAlbedoSpec.rgb = texture(texture_diffuse1, TexCoords).rgb; // Store specular intensity in gAlbedoSpec's alpha component gAlbedoSpec.a = texture(texture_specular1, TexCoords).r; }
In this way, the position normal albedospec stores the information rbodepth after correcting the position and normal.
Then we draw quad
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); shaderLightingPass.Use(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, gPosition); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, gNormal); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, gAlbedoSpec); // Also send light relevant uniforms for (GLuint i = 0; i < lightPositions.size(); i++) { glUniform3fv(glGetUniformLocation(shaderLightingPass.Program, ("lights[" + std::to_string(i) + "].Position").c_str()), 1, &lightPositions[i][0]); glUniform3fv(glGetUniformLocation(shaderLightingPass.Program, ("lights[" + std::to_string(i) + "].Color").c_str()), 1, &lightColors[i][0]); // Update attenuation parameters and calculate radius const GLfloat constant = 1.0; // Note that we don't send this to the shader, we assume it is always 1.0 (in our case) const GLfloat linear = 0.7; const GLfloat quadratic = 1.8; glUniform1f(glGetUniformLocation(shaderLightingPass.Program, ("lights[" + std::to_string(i) + "].Linear").c_str()), linear); glUniform1f(glGetUniformLocation(shaderLightingPass.Program, ("lights[" + std::to_string(i) + "].Quadratic").c_str()), quadratic); } glUniform3fv(glGetUniformLocation(shaderLightingPass.Program, "viewPos"), 1, &camera.Position[0]); // Finally render quad RenderQuad();
The data used for calculation is already available in the rgba of textures, vec4 value=texture2D(uv,texture).
//vertex #version 330 core layout (location = 0) in vec3 position; layout (location = 1) in vec2 texCoords; out vec2 TexCoords; void main() { gl_Position = vec4(position, 1.0f); TexCoords = texCoords; } //fragment #version 330 core out vec4 FragColor; in vec2 TexCoords; uniform sampler2D gPosition; uniform sampler2D gNormal; uniform sampler2D gAlbedoSpec; struct Light { vec3 Position; vec3 Color; float Linear; float Quadratic; }; const int NR_LIGHTS = 32; uniform Light lights[NR_LIGHTS]; uniform vec3 viewPos; void main() { // Retrieve data from gbuffer vec3 FragPos = texture(gPosition, TexCoords).rgb; vec3 Normal = texture(gNormal, TexCoords).rgb; vec3 Diffuse = texture(gAlbedoSpec, TexCoords).rgb; float Specular = texture(gAlbedoSpec, TexCoords).a; // Then calculate lighting as usual vec3 lighting = Diffuse * 0.1; // hard-coded ambient component vec3 viewDir = normalize(viewPos - FragPos); for(int i = 0; i < NR_LIGHTS; ++i) { // Diffuse vec3 lightDir = normalize(lights[i].Position - FragPos); vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Diffuse * lights[i].Color; // Specular vec3 halfwayDir = normalize(lightDir + viewDir); float spec = pow(max(dot(Normal, halfwayDir), 0.0), 16.0); vec3 specular = lights[i].Color * spec * Specular; // Attenuation float distance = length(lights[i].Position - FragPos); float attenuation = 1.0 / (1.0 + lights[i].Linear * distance + lights[i].Quadratic * distance * distance); diffuse *= attenuation; specular *= attenuation; lighting += diffuse + specular; } FragColor = vec4(lighting, 1.0); }
Those data are extracted from the front, and phong illumination is calculated from the back.
Here's the end of pure delayed rendering, but there's also forward and delayed mixing.
Remember there's a rboDepth ahead. Depth of storage is still unnecessary.
RenderQuad();//after // 2.5. Copy content of geometry's depth buffer to default framebuffer's depth buffer glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // Write to default framebuffer // blit to default framebuffer. Note that this may or may not work as the internal formats of both the FBO and default framebuffer have to match. // the internal formats are implementation defined. This works on all of my systems, but if it doesn't on yours you'll likely have to write to the // depth buffer in another shader stage (or somehow see to match the default framebuffer's internal format with the FBO's internal format). glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_FRAMEBUFFER, 0); // 3. Render lights on top of scene, by blitting shaderLightBox.Use(); glUniformMatrix4fv(glGetUniformLocation(shaderLightBox.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); glUniformMatrix4fv(glGetUniformLocation(shaderLightBox.Program, "view"), 1, GL_FALSE, glm::value_ptr(view)); for (GLuint i = 0; i < lightPositions.size(); i++) { model = glm::mat4(); model = glm::translate(model, lightPositions[i]); model = glm::scale(model, glm::vec3(0.25f)); glUniformMatrix4fv(glGetUniformLocation(shaderLightBox.Program, "model"), 1, GL_FALSE, glm::value_ptr(model)); glUniform3fv(glGetUniformLocation(shaderLightBox.Program, "lightColor"), 1, &lightColors[i][0]); RenderCube(); }
The glBlitFrameBuffer step is to move the depth to the rendering screen so that the depth will not be a problem and the object can be happily rendered in the end.
This is phong-based.
This is learnopengl's learning record.
Original text https://learnopengl-cn.github.io/05%20Advanced%20Lighting/08%20Deferred%20Shading/