阿里云服务器网站建设,荆州大气网站建设价格,字形分析网站,接私活做预算的网站延迟渲染学习笔记 一、基本概念二、G-BufferMRT 三、Lighting Pass四、结合延迟渲染和前向渲染五、更多光源 我们之前使用的一直是 前向渲染#xff08;正向渲染 Forward Rendering#xff09;#xff0c;指的是在场景中根据所有光源照亮一个物体#xff0c;之后再渲染下一… 延迟渲染学习笔记 一、基本概念二、G-BufferMRT 三、Lighting Pass四、结合延迟渲染和前向渲染五、更多光源 我们之前使用的一直是 前向渲染正向渲染 Forward Rendering指的是在场景中根据所有光源照亮一个物体之后再渲染下一个物体。对程序性能影响很大因为对于每一个需要渲染的物体程序都要对每一个光源每一个需要渲染的片段进行迭代。 而且还有一部分被渲染的片段会被覆盖遮挡造成浪费
一、基本概念
延迟渲染(Deferred Rendering) 包含两个Pass
第一个是几何处理Pass先渲染场景一次之后获取对象的各种几何信息比如顶点位置、颜色、法线、高光信息等并存储在G-Buffer中 然后第二个Pass是用于计算光照信息——Lighting Pass我们将会渲染一个满屏的方片并根据G-Buffer中的几何数据信息来为每个片元进行光照计算在G-Buffer中每个像素进行迭代。
与前向渲染不同延迟渲染并不是将每个对象进行顶点着色器到片元着色器的计算而是将片元着色器移动到后期处理。这样保证了对于在光照处理阶段中处理的每一个像素都只处理一次所以我们能够省下很多无用的渲染调用。
缺陷
显存消耗会较大G-Buffer需要我们存储较大的几何数据不能使用MSAA因为我们通过只有几何数据的G-Buffer来进行着色
二、G-Buffer
G-Buffer是一个用来存储光照计算所需数据的纹理的总称。
3D位置向量来计算片段位置变量 lightDirviewDir3D法向量normalRGB漫反射颜色向量Albedo镜面强度高光反射强度光源的位置向量、颜色向量观察者的位置向量
在前向渲染中我们每个物体的光照计算都是根据特定实时数据的所以如何给Lighting Pass传递正确的光照数据是很重要的。延迟渲染G-Buffer已经把几何数据渲染到一张2D纹理中纹理允许我们存储各种各样的数据类型所以纹理上每个片元都有正确的几何数据供光照计算
while(...) // render loop
{// 1. geometry pass: render all geometric/color data to g-buffer glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);glClearColor(0.0, 0.0, 0.0, 1.0); // keep it black so it doesnt leak into g-bufferglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);gBufferShader.use();for(Object obj : Objects){ConfigureShaderTransformsAndUniforms();obj.Draw();} // 2. lighting pass: use g-buffer to calculate the scenes lightingglBindFramebuffer(GL_FRAMEBUFFER, 0);lightingPassShader.use();BindAllGBufferTextures();SetLightingUniforms();RenderQuad();
}MRT
在几何处理阶段我们需要渲染场景中所有物体并且存储这些几何数据在G-Buffer中。我们可以使用 MRT 来在一个Pass中渲染多个颜色缓冲。
我们需要初始化一个帧缓冲gBuffer这个gBuffer会有多个颜色缓冲附件以及一个深度渲染缓冲对象。对于位置和法向量的纹理我们希望使用高精度的纹理(每分量16或32位的浮点数)而对于反照率和镜面值使用默认的纹理(每分量8位浮点数)就够了。
GLuint gBuffer;
glGenFramebuffer(1, gBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
GLuint gPosition, gNormal, gColorSpec;
//位置颜色缓冲
glGenTextures(1, gPosition);
glBindTexture(GL_TEXTURE_2D, gPosition);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCRHEIGHT, 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);//法线颜色缓冲
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);//颜色镜面颜色缓冲
glGenTextures(1, gAlbedoSpec);
glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, 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_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0);//告诉OpenGL我们将用哪个颜色附件来渲染
GLuint attatchments[3] {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2};
glDrawBuffers(3, attachments);// 之后同样添加渲染缓冲对象(Render Buffer Object)为深度缓冲(Depth Buffer)并检查完整性
[...]接下来就是将数据渲染到G-Buffer中我们将使用一下片元着色器
#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()
{ // 存储第一个G缓冲纹理中的片段位置向量gPosition FragPos;// 同样存储对每个逐片段法线到G缓冲中gNormal normalize(Normal);// 和漫反射对每个逐片段颜色gAlbedoSpec.rgb texture(texture_diffuse1, TexCoords).rgb;// 存储镜面强度到gAlbedoSpec的alpha分量gAlbedoSpec.a texture(texture_specular1, TexCoords).r;
} 因为有光照计算所以要将所有坐标转换到一个坐标系下此处我们是将所有坐标转换到世界空间下
三、Lighting Pass
我们通过对G-Buffer进行逐像素的遍历将其数据作为光照计算的输入来计算场景最终的光照颜色。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shaderLightingPass();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gPosition);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, gNormal);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);//发送光照相关的uniform
SendAllLightUniformsToShader(shaderLightingPass);
glUniform3fv(glGetUniformLocation(shaderLightingPass.Program, viewPos), 1, camera.Position[0]);
RenderQuad(); 在片元着色器中我们将会在G-Buffer中直接采样
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D gAlbedoSpec;struct Light {vec3 Position;vec3 Color;
};
const int NR_LIGHTS 32;
uniform Light lights[NR_LIGHTS];
uniform vec3 viewPos;void main()
{ // 从G缓冲中获取数据vec3 FragPos texture(gPosition, TexCoords).rgb;vec3 Normal texture(gNormal, TexCoords).rgb;vec3 Albedo texture(gAlbedoSpec, TexCoords).rgb;float Specular texture(gAlbedoSpec, TexCoords).a;// 然后和往常一样地计算光照vec3 lighting Albedo * 0.1; // 硬编码环境光照分量vec3 viewDir normalize(viewPos - FragPos);for(int i 0; i NR_LIGHTS; i){// 漫反射vec3 lightDir normalize(lights[i].Position - FragPos);vec3 diffuse max(dot(Normal, lightDir), 0.0) * Albedo * lights[i].Color;lighting diffuse;}FragColor vec4(lighting, 1.0);
} 四、结合延迟渲染和前向渲染
在延迟渲染中光源通常被视为无形的点或方向而不是具有材质和颜色的物体。如果我们想将光源渲染为一个带有光照颜色的立方体就需要额外的几何处理而这超出了延迟渲染的范畴就需要结合前向渲染透明物体、镜面反射、光源模型等。
前向渲染的部分会在延迟渲染操作之后进行。
// 延迟渲染光照渲染阶段
[...]
RenderQuad();// 现在像正常情况一样正向渲染所有光立方体
shaderLightBox.Use();
glUniformMatrix4fv(locProjection, 1, GL_FALSE, glm::value_ptr(projection));
glUniformMatrix4fv(locView, 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(locModel, 1, GL_FALSE, glm::value_ptr(model));glUniform3fv(locLightcolor, 1, lightColors[i][0]);RenderCube();
}不过现在的深度结果并不正确因为除了光源立方体的深度信息都在延迟渲染过程中所以我们需要将延迟渲染中的深度信息提取出来然后再渲染光立方体。
我们可以使用glBlitFramebuffer复制一个帧缓冲的内容到另一个帧缓冲中。我们需要指定一个帧缓冲为读帧缓冲(Read Framebuffer)并且类似地指定一个帧缓冲为写帧缓冲(Write Framebuffer)
glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // 写入到默认帧缓冲
glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST
);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// 现在像之前一样渲染光立方体
[...] 在这里我们复制整个读帧缓冲的深度缓冲信息到默认帧缓冲的深度缓冲对于颜色缓冲和模板缓冲我们也可以这样处理。 五、更多光源
延迟渲染本身并不能支持非常大量的光源但是我们可以为其引入一个优化光体积(Light Volumes)。因为对于场景中的物体有的光是影响非常非常小的所以我们就可以计算光的影响半径。
我们可以通过光源的衰减值来计算光的影响范围我们只需要对在光影响范围内的片段进行光照计算就可以了。