当前位置: 首页 > news >正文

OGL(教程35)——延迟渲染1

原文地址:http://ogldev.atspace.co.uk/www/tutorial35/tutorial35.html

从17节开始,到现在对灯光的处理就是所谓的向前渲染或者向前着色。这是最直接的方式,我们在顶点着色器中对物体的各个顶点做变换(对法线进行变化,把位置变换到裁剪空间),然后在FS中对像素进行光照计算。对于每个对象的每个像素只能调用一次FS,我们需要对FS提供所有的光源信息,然后在计算这个像素的光照的时候全部考虑进去。这种方式简单,但是也有自身的缺点。如果场景高度复杂(比如当今流行的游戏中),它有大量的物体,还有大量的深度复杂性(屏幕上的一个像素会被多个物体覆盖),这样对此像素的计算会浪费很多CPU。比如,如果深度复杂度为4,那就意味着对3个像素的计算是浪费掉的,因为只有最上面的一个像素才最终有效。我们可以对物体进行排序,但是对复杂的物体,其效果不是很理想。

向前渲染的另外一个问题是,在很多灯的情况下也会遇到问题。在这种情况下,光源对于很小的区域有效果,否则会承受不住压力。但是我们的FS计算,是针对所有灯的,即使它离像素很远。你可以尝试计算像素和光源的位置,但是会额外增加计算量,还会增加FS的分支。向前渲染的FS部分,对多盏灯不能胜任。

延迟渲染是针对上面的问题,被当今很多游戏所采用的流行方案。延迟渲染的一个核心是,把几何计算(位置和法线计算)和光照计算解耦。并不是把所有的物体都一股脑的处理,从顶点缓冲到最终的帧缓冲,我们把它分为两个主要的阶段。第一个阶段,我们运行VS,但是我们不把处理的属性传给FS以备光照计算使用,我们把它传入到G-Buffer中。GBuffer从逻辑上讲是一组2D贴图,每个贴图对应一个顶点属性。我们分离属性,然后利用OpenGL所谓的MRT(多个渲染目标技术)一次绘制所有的属性到多张贴图。由于我们在FS中写入属性,那么在GBuffer中的最终结果是被光栅化插值之后的顶点属性。我们把这个阶段叫做几何阶段。每个对象都在这个阶段处理。由于深度测试,当几何阶段完成之后,出现在GBuffer中的,都是离摄像机最近的的点的差值之后的属性。这就意味着所有无关的像素深度测试失败,然后被丢弃,只有留在GBuffer中的像素才是最终会被计算光照的像素。下面是典型的GBuffer的一帧图:
在这里插入图片描述

在第二个阶段,称之为光照阶段,我们遍历GBuffer中的像素,我们从不同的贴图采样像素的属性,然后像之前我们处理光照的方式计算光照。由于留下的像素都是离摄像机最近的点,所以我们对一个像素只会进行一次光照计算。

我们怎么遍历GBuffer中的像素呢?最简单的方式是渲染一个和屏幕大小相同的四边形。但是还有更好的方法。之前说过,由于光源的影响范围有限,我们假设很多像素与它无关。当一个光源的对像素的影响足够小,最好忽略这个光源对其的影响,这也是从性能考虑。在向前渲染中没有有效的方式处理。但是在延迟渲染中,我们可以计算光源影响的一个球体(对于点光源来说是这样,对于聚光灯,我们使用cone角度)。球体代表了光源影响的区域,在球体之外,我们忽略光源的影响。我们可以用一个粗略的球体模型,它只有很少的几个面,来表现那个点的光源所影响的范围。VS只做位置的变换,把位置变换到裁剪空间。FS只会对相关的像素进行处理,并对相关的像素做光照计算。有些人会计算出最小的包含球体的四边形,这个计算是从光源视角计算。渲染这个四边形不耗时,因为只有两个三角形而已。这个方法可有效限制计算像素的个数。

我们将分为三步介绍延迟渲染,分为三个章节进行。
1、本节我们将会使用MRT技术填充GBuffer。我们会输出到GBuffer到屏幕。
2、下一节,我们将会增加光照通道,在延迟渲染中看看光照如何计算。
3、最后一节,我们将会学习模板缓冲,来阻止距离较远的点光源。

代码注释:

Source walkthru
(gbuffer.h:28)

class GBuffer
{
public:

    enum GBUFFER_TEXTURE_TYPE {
        GBUFFER_TEXTURE_TYPE_POSITION,
        GBUFFER_TEXTURE_TYPE_DIFFUSE,
        GBUFFER_TEXTURE_TYPE_NORMAL,
        GBUFFER_TEXTURE_TYPE_TEXCOORD,
        GBUFFER_NUM_TEXTURES
    };

    GBuffer();

    ~GBuffer();

    bool Init(unsigned int WindowWidth, unsigned int WindowHeight);

    void BindForWriting();

    void BindForReading();

private:

    GLuint m_fbo;
    GLuint m_textures[GBUFFER_NUM_TEXTURES];
    GLuint m_depthTexture;
};

GBuffer包含所有延迟渲染阶段需要的贴图。我们有针对针对顶点属性的贴图,也有称当深度缓冲的贴图。我们之所以需要深度缓冲贴图,因为我们将会在FBO中封装所有的贴图,所以默认的深度缓冲没有用。FBO已经在23节讲述,这里不再提及。

GBuffer类有两个方法,将会在运行时反复调用。BindForWriting()绑定贴图到目标,这个发生在几何阶段。BindForReading()绑定FBO作为输入,所以它的结果可以输出到屏幕。

(gbuffer.cpp:48)

bool GBuffer::Init(unsigned int WindowWidth, unsigned int WindowHeight)
{
    // Create the FBO
    glGenFramebuffers(1, &m_fbo); 
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);

    // Create the gbuffer textures
    glGenTextures(ARRAY_SIZE_IN_ELEMENTS(m_textures), m_textures);
    glGenTextures(1, &m_depthTexture);

    for (unsigned int i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(m_textures) ; i++) {
       glBindTexture(GL_TEXTURE_2D, m_textures[i]);
       glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, WindowWidth, WindowHeight, 0, GL_RGB, GL_FLOAT, NULL);
       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, m_textures[i], 0);
    }

    // depth
    glBindTexture(GL_TEXTURE_2D, m_depthTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, WindowWidth, WindowHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT,
                  NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_depthTexture, 0);

    GLenum DrawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 }; 
    glDrawBuffers(ARRAY_SIZE_IN_ELEMENTS(DrawBuffers), DrawBuffers);

    GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

    if (Status != GL_FRAMEBUFFER_COMPLETE) {
        printf("FB error, status: 0x%x\n", Status);
        return false;
    }

    // restore default FBO
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

    return true;
}

这个就是我们如何初始化GBuffer的。我们首先创建了FBO对象、还有各种贴图,还有深度缓冲。顶点属性贴图在循环中进行初始化,循环体中做的是:
1、创建贴图的存储区域(没有初始化它)
2、绑定贴图到FBO作为目标
初始化深度贴图被显示调用,因为他有不同的格式,它被绑定到不同的FBO对象。

为了做MRT,我们需要写入所有的贴图。我们通过提供一个数组的附着地点给glDrawBuffers()函数。这个数组允许我们有一些灵活性,如果我们把GL_COLOR_ATTACHMENT6 作为第一个索引,FS写入的第一个输出变量,此变量会绑定到GL_COLOR_ATTACHMENT6。本节我们不考虑效率,只是一个接一个的绑定。

最终,我们检测FBO的状态,确保所有的东西都是正确的额,然后重置默认的FBO(未来的变换不会影响到GBuffer)。此时GBuffer已经可以被使用了。

(tutorial35.cpp:105)

virtual void RenderSceneCB()
{ 
    CalcFPS();

    m_scale += 0.05f;

    m_pGameCamera->OnRender();

    DSGeometryPass();
    DSLightPass();

    RenderFPS();

    glutSwapBuffers();
}

我们现在从上往下看执行过程。上面的函数主循环函数。它做的并不多。它处理一些全局的事务,比如帧率计算,还有展示,摄像机的更新等等。主要的工作室执行几何阶段,接着是光照阶段。如之前提到的,本节只处理几何阶段。

(tutorial35.cpp:122)

void DSGeometryPass()
{
    m_DSGeomPassTech.Enable();

    m_gbuffer.BindForWriting();

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    Pipeline p;
    p.Scale(0.1f, 0.1f, 0.1f);
    p.Rotate(0.0f, m_scale, 0.0f);
    p.WorldPos(-0.8f, -1.0f, 12.0f);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(m_persProjInfo);
    m_DSGeomPassTech.SetWVP(p.GetWVPTrans()); 
    m_DSGeomPassTech.SetWorldMatrix(p.GetWorldTrans());
    m_mesh.Render();

}

我们开始几何阶段,是通过开启合适的计算,然后设置GBuffer对象以备写入。这样之后,我们清除GBuffer。现在所有的东西都准备好了,我们开始变换,然后是渲染网格。在真实的游戏中,我们会一个接一个渲染多个网格。最终我们会得到顶点的各个属性。

(tutorial35.cpp:141)

void DSLightPass()
{
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_gbuffer.BindForReading();

    GLsizei HalfWidth = (GLsizei)(WINDOW_WIDTH / 2.0f);
    GLsizei HalfHeight = (GLsizei)(WINDOW_HEIGHT / 2.0f);

    m_gbuffer.SetReadBuffer(GBuffer::GBUFFER_TEXTURE_TYPE_POSITION);
    glBlitFramebuffer(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT,
                    0, 0, HalfWidth, HalfHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);

    m_gbuffer.SetReadBuffer(GBuffer::GBUFFER_TEXTURE_TYPE_DIFFUSE);
    glBlitFramebuffer(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 
                    0, HalfHeight, HalfWidth, WINDOW_HEIGHT, GL_COLOR_BUFFER_BIT, GL_LINEAR);

    m_gbuffer.SetReadBuffer(GBuffer::GBUFFER_TEXTURE_TYPE_NORMAL);
    glBlitFramebuffer(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 
                    HalfWidth, HalfHeight, WINDOW_WIDTH, WINDOW_HEIGHT, GL_COLOR_BUFFER_BIT, GL_LINEAR);

    m_gbuffer.SetReadBuffer(GBuffer::GBUFFER_TEXTURE_TYPE_TEXCOORD);
    glBlitFramebuffer(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 
                    HalfWidth, 0, WINDOW_WIDTH, HalfHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR); 
}
(geometry_pass.vs)

#version 330 

layout (location = 0) in vec3 Position; 
layout (location = 1) in vec2 TexCoord; 
layout (location = 2) in vec3 Normal; 

uniform mat4 gWVP;
uniform mat4 gWorld;

out vec2 TexCoord0; 
out vec3 Normal0; 
out vec3 WorldPos0; 

void main()
{ 
    gl_Position = gWVP * vec4(Position, 1.0);
    TexCoord0 = TexCoord; 
    Normal0 = (gWorld * vec4(Normal, 0.0)).xyz; 
    WorldPos0 = (gWorld * vec4(Position, 1.0)).xyz;
}
#version 330

in vec2 TexCoord0; 
in vec3 Normal0; 
in vec3 WorldPos0; 

layout (location = 0) out vec3 WorldPosOut; 
layout (location = 1) out vec3 DiffuseOut; 
layout (location = 2) out vec3 NormalOut; 
layout (location = 3) out vec3 TexCoordOut; 

uniform sampler2D gColorMap; 

void main() 
{ 
    WorldPosOut = WorldPos0; 
    DiffuseOut = texture(gColorMap, TexCoord0).xyz; 
    NormalOut = normalize(Normal0); 
    TexCoordOut = vec3(TexCoord0, 0.0); 
}

相关文章:

  • Spring源码分析 之浅谈设计模式
  • OGL(教程36)——延迟渲染2
  • .NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划
  • OGL(教程37)——延迟渲染3
  • ImageMagic的配置
  • assimp编译及使用(1)
  • unity中使用fmod音频插件3
  • js禁止微信浏览器下拉显示黑底查看网址
  • assimp编译及使用(2)
  • python - 函数
  • OGL(教程38)——骨骼动画
  • ARM汇编1
  • OGL(教程40)——Stencil Shadow Volume
  • OGL(教程41)——物体运动模糊
  • Django rest_framework 总结
  • Android开发 - 掌握ConstraintLayout(四)创建基本约束
  • canvas绘制圆角头像
  • Elasticsearch 参考指南(升级前重新索引)
  • Phpstorm怎样批量删除空行?
  • Python利用正则抓取网页内容保存到本地
  • Python学习笔记 字符串拼接
  • select2 取值 遍历 设置默认值
  • Theano - 导数
  • uni-app项目数字滚动
  • vue 配置sass、scss全局变量
  • XForms - 更强大的Form
  • 聊聊flink的BlobWriter
  • 前端 CSS : 5# 纯 CSS 实现24小时超市
  • 实习面试笔记
  • 消息队列系列二(IOT中消息队列的应用)
  • 写给高年级小学生看的《Bash 指南》
  • 以太坊客户端Geth命令参数详解
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • #、%和$符号在OGNL表达式中经常出现
  • #经典论文 异质山坡的物理模型 2 有效导水率
  • (+4)2.2UML建模图
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (33)STM32——485实验笔记
  • (HAL库版)freeRTOS移植STMF103
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (二)丶RabbitMQ的六大核心
  • (附源码)springboot社区居家养老互助服务管理平台 毕业设计 062027
  • (附源码)springboot学生选课系统 毕业设计 612555
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (附源码)ssm经济信息门户网站 毕业设计 141634
  • (附源码)计算机毕业设计SSM基于健身房管理系统
  • (官网安装) 基于CentOS 7安装MangoDB和MangoDB Shell
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (实战)静默dbca安装创建数据库 --参数说明+举例
  • (一) springboot详细介绍
  • (转)为C# Windows服务添加安装程序
  • .halo勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET Core 将实体类转换为 SQL(ORM 映射)
  • .Net Core/.Net6/.Net8 ,启动配置/Program.cs 配置
  • .NET Core中Emit的使用