延迟着色究竟如何在LWJGL中起作用?

时间:2013-05-20 15:16:21

标签: java opengl glsl deferred-rendering

我想用GLSL,Java和&amp ;;开始一个延迟着色项目。 OPENGL

1。延迟渲染管道如何工作,是否为每个图像渲染场景? 例如,当我想创建一个镜面反射,模糊和阴影纹理时,是否需要为每个纹理渲染场景。

我见过一些代码片段,那里没有多个渲染循环。

2。什么是几何缓冲区,它有什么作用?它是否类似于场景数据的存储,我可以绘制到纹理而不再渲染?

3 个答案:

答案 0 :(得分:13)

添加更具体的内容,以便您可以开始使用。您需要具有多个附件的FBO以及着色器可以写入多个FBO附件的方法。 Google glDrawBuffers。您的FBO附件也需要是纹理,因此可以将信息传递给着色器。 FBO附件的大小应与要渲染的屏幕大小相同。有很多方法可以解决这个问题。这是一个例子。

您需要两个FBO

几何缓冲区

1. Diffuse (GL_RGBA)
2. Normal Buffer (GL_RGB16F)
3. Position Buffer (GL_RGB32F)
4. Depth Buffer

注意3)是一个巨大的浪费,因为我们可以使用深度缓冲和投影来重建位置。这很多便宜。开始使用位置缓冲区至少是一个好的开始。一次攻击一个问题。

2)正常缓冲区也可以进行更多压缩。

光累积缓冲液

1. Light Buffer (GL_RGBA)
2. Depth Buffer

此FBO 中的深度缓冲区附件应与几何缓冲区中的附件相同。我们可能不会在此示例中使用此深度缓冲区信息,但您迟早会需要它。它将始终包含第一阶段的深度信息。

我们如何呈现这些内容?

我们首先使用非常简单的着色器渲染场景。这些的目的主要是填充几何缓冲区。我们只需使用一个非常简单的着色器填充几何缓冲区来绘制所有几何体。为简单起见,我使用120个着色器并且没有纹理映射(尽管这是非常简单的添加)。

顶点着色器:

#version 120

varying vec3 normal;
varying vec4 position;

void main( void )
{
    normal = normalize(gl_NormalMatrix * gl_Normal);
    position = gl_ModelViewMatrix * gl_Vertex;
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

Fragment Shader:

#version 120

uniform vec4 objectColor; // Color of the object you are drawing
varying vec3 normal;
varying vec4 position;

void main( void )
{
    // Use glDrawBuffers to configure multiple render targets
    gl_FragData[0] = objectColor; // Diffuse
    gl_FragData[1] = vec4(normalize(normals.xyz), 0.0); // normals
    gl_FragData[2] = vec4(position.xyz, 0.0); // Position
}

我们现在已经例如将20个对象绘制成具有不同颜色的几何缓冲区。如果我们看一下漫反射缓冲区,它是一个非常暗淡的图像,带有纯色(或没有光照的普通纹理),但我们仍然有每个单个片段的视图位置,正常和深度。在进行照明时,这将是下一阶段的宝贵信息。

光累积

现在我们切换到我们的光累积缓冲区,现在是时候做一些光魔法了。对于每个单独的光,我们将绘制到我们的光累积缓冲区,并启用了添加混合。只要你覆盖受光影响的所有碎片,你如何做到这一点并不重要。您最初可以通过绘制全屏四边形来完成此操作,但这非常昂贵。我们只会覆盖点光源,但这足以涵盖简单的照明原理(简单的点光源非常简单)。一种简单的方法是在由光半径缩放的光位置处绘制立方体或低多边形球(光量)。这使得渲染大量的小灯更有效..但现在不要担心性能。 Fullscreen quad可以很好地完成这个技巧。

现在,简单的原则是:

  • 每个片段都有一个存储的x,y,z位置,我们只需使用纹理提取
  • 我们传递光明的位置
  • 我们传入光的半径
  • 我们只需测量距离位置缓冲区中的值与光线位置的距离即可知道碎片是否受光影响
  • 从那里进行非常标准的光线计算

片段着色器: (这个着色器适用于任何东西。轻量级,全屏四边形..无论如何)     #version 120

uniform sampler2D diffuseBuffer;
uniform sampler2D positionBuffer;
uniform sampler2D normalBuffer;

uniform float lightRadius; // Radius of our point light
uniform vec3 lightPos; // Position of our point light
uniform vec4 lightColor; // Color of our light
uniform vec2 screensize; // screen resolution

void main()
{
    // VU for the current fragment
    vec2 uv = vec2(gl_FragCoord.x / screensize.x, gl_FragCoord.y / screensize.y);
    // Read data from our gbuffer (sent in as textures)
    vec4 diffuse_g = texture2D(diffuseBuffer, uv);
    vec4 position_g = texture2D(positionBuffer, uv);
    vec4 gnormal_g = texture2D(normalBuffer, uv);

    // Distance from the light center and the current pixel
    float distance = length(lightPos - position_g.xyz);

    // If the fragment is NOT affecter by the light we discard it!
    // PS : Don't kill me for using discard. This is for simplicity.
    if(distance > lightRadius) discard;

    // Calculate the intensity value this light will affect the fragment (Standard light stuff!)
    ... Use lightPos and position_g to calculate the light normal ..
    ... Do standard dot product of light normal and normal_g ...
    ... Just standard light stuff ...

    // Super simple attenuation placeholder
    float attenuation = 1.0 - (distance / lightRadius);

    gl_FragColor = diffuse_g * lightColor * attenuation * <multiplier from light calculation>;
}

我们为每一盏灯重复这个。灯光渲染的顺序并不重要,因为添加混合的结果总是相同的。通过仅累积光强度,您也可以更简单地做到这一点。理论上,您应该已经在光累积缓冲区中获得了最终的点亮结果,但您可能希望进行其他调整。

<强>联合

你可能想调整一些事情。周围?色彩校正?多雾路段?其他后期处理的东西。您可以将光累积缓冲区和漫反射缓冲区组合在一起进行一些调整。我们已经在光照阶段就已经做到了,但是如果你只保存光强度,那么你必须在这里做一个简单的diffuse * light组合。

通常只是一个全屏四边形,可以将最终结果呈现给屏幕。

更多资料

  • 如前所述,我们希望摆脱位置缓冲区。在投影中使用深度缓冲区来重建位置。
  • 您不需要来使用轻量级。有些人更喜欢简单地渲染一个足够大的四边形以覆盖屏幕上的区域。
  • 上面的示例不包括如何为每个对象定义唯一材料等问题。 gbuffer格式有很多资源和变种。有些人喜欢在alpha通道中保存材质索引(在漫反射缓冲区中),然后在纹理中查找一行以获取材质属性。
  • 通过将全屏四边形渲染到光线累积缓冲区中,可以轻松处理影响整个场景的方向光和其他光线类型
  • 聚光灯也很好用,也很容易实现
  • 我们可能想要更多的光属性
  • 我们可能想要一些方法来衡量漫反射和光缓冲结合如何组合以支持环境和发射
  • 有许多方法可以更紧凑的方式存储法线。例如,您可以使用球面坐标删除一个值。有很多关于延迟照明和gbuffer格式的文章。查看人们使用的格式可以为您提供一些想法。只要确保你的gbuffer不要太胖。
  • 使用线性化深度值重建视图位置并且您的投影并不那么难。您需要使用投影常量构造矢量。将其与深度值(0到1之间)相乘以获得视图位置。那里有几篇文章。它只有两行代码。

在这篇文章中可能会有很多内容,但希望它能说明一般原则。没有人编写着色器。这只是通过内存从3.3转换为1.2。

有几种光累积方法。您可能希望减少绘制调用的次数,使VBO具有1000个立方体和锥体来批量绘制所有内容。使用更现代的GL版本,您还可以使用几何着色器来计算覆盖每个灯光的亮区域的四边形。可能最好的方法是使用计算着色器,但这需要GL 4.3。这里的优点是您可以迭代所有光信息并进行一次写入。还有伪计算方法是将屏幕划分为粗略网格并为每个单元格指定灯光列表。这只能通过片段着色器完成,但需要您在CPU上构建灯光列表,并通过UBO将数据发送到着色器。

计算着色器方法是迄今为止最简单的方法。它消除了旧方法中的许多复杂性,以便跟踪和组织所有内容。只需迭代灯光,然后对帧缓冲区进行一次写入。

答案 1 :(得分:1)

1)延迟着色涉及将场景的几何图形和基本上所有其他图形分离渲染为单独的图形。

  

例如,当我想创建高光,模糊和阴影纹理时,是否需要为每个纹理渲染场景。

对于阴影纹理,可能(如果您使用阴影贴图,则无法避免)。但对于其他一切:

不,这就是延迟着色可能如此有用的原因。在延迟管道中,您只需渲染几何体并保存每个像素的颜色,法线和3d位置(几何缓冲区)。这可以通过几种不同的方式实现,但最常见的是使用具有多个渲染目标(MRT)的帧缓冲对象(FBOs)。当使用FBO进行延迟着色时,除了绑定FBO,在片段着色器中使用多个输出(每个渲染目标一个),并且不计算任何光照时,您将以与正常渲染相同的方式渲染地理。您可以在OpenGL网站上或通过快速谷歌搜索阅读有关FBO和MRT的更多信息。然后,为了点亮您的场景,您将在着色器中读取此数据并使用它来计算照明,就像您通常一样。最简单的方法(但不是最好的方法)是渲染全屏四边形并为场景渲染颜色,法线和位​​置纹理。

2)几何缓冲区是将在场景上完成的光照和其他着色所需的所有数据。它是在几何体过程中创建的(唯一需要渲染几何体的时间),通常是一组纹理。渲染几何体时,每个纹理都用作渲染目标(请参见上文关于FBO和MRT)。您通常有一个颜色纹理,一个用于法线,一个用于3d位置。如有必要,它还可以包含更多数据(如照明参数)。这为您提供了在照明过程中每个像素点亮所需的所有数据。

伪代码可能如下所示:

   for all geometry {
      render to FBO
   }
   for all lights {
      read FBO and do lighting
   }
   //... here you can read the FBO and use it for anything!

答案 2 :(得分:0)

延迟渲染的基本思想是将网格几何体转换为目标帧缓冲区中的位置,并将目标帧缓冲区的像素赋予其最终颜色。

第一步是以某种方式渲染几何体,使帧缓冲区的每个像素接收有关原始几何体的信息,即世界或眼睛空间中的位置(优选眼睛空间),变换的切线空间(正常, tangent,binormal)和其他属性取决于以后需要的内容。这是“几何缓冲区”(也回答你的2.问题)。

使用几何缓冲区,预先计算的几何→像素映射可以重复用于几个类似的处理步骤。例如,如果要渲染50个光源,则必须仅处理几何图形50次(相当于渲染100个三角形,这是现代GPU的子图形),其中每次迭代使用其他参数(光线位置,方向,影子缓冲等)。这与常规多通道渲染形成对比,在每次迭代中,需要重新处理整个几何体。

当然,每次传球都可以用来渲染不同类型的阴影处理(发光,模糊,散景,光晕等)。

然后,对于每次迭代传递,结果将合并为一个合成图像。