延迟着色在LWJGL中究竟是如何工作的



我想用GLSL, Java &openGl

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

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

2。什么是几何缓冲,它做什么?它是否类似于场景数据的存储,我可以绘制到纹理而无需再次渲染?

添加一些更具体的东西,以便您可以开始。你需要带有多个附件的FBO和一种让你的着色器写入多个FBO附件的方法。谷歌glDrawBuffers。你的FBO附件也需要是纹理,这样信息就可以传递给着色器。FBO附件应该与呈现到的屏幕大小相同。有很多方法可以做到这一点。下面是一个例子。

你需要两个fbo

几何缓冲区

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

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

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个对象到我们的几何缓冲区。如果我们看一下漫射缓冲,它是一个非常暗淡的图像,只有纯色(或没有照明的纯纹理),但我们仍然有每个片段的视图位置,法线和深度。这将是下一阶段做灯光时很有价值的信息。

光积累

现在我们切换到光积累缓冲,是时候做一些光魔法了。对于每一个单独的光,我们将绘制到我们的光积累缓冲区,并启用添加混合。只要你覆盖了所有受光影响的碎片,你怎么做对结果并不那么重要。你可以先画一个全屏四边形,但这是非常昂贵的。我们将只涉及点灯,但这足以涵盖简单的照明原理(简单的点灯是非常琐碎的制作)。一个简单的方法是绘制一个立方体或低多边形球体(光体积)在光的位置按光半径缩放。这使得渲染成吨的小灯更有效率。但是现在不用担心性能。全屏四边形也可以。

现在,简单的原则是:
  • 每个片段都有一个存储的x,y,z位置,我们只需通过纹理获取
  • 我们通过光的位置
  • 我们通过光的半径
  • 我们可以知道片段是否受到光的影响,只需测量位置缓冲区中的值与光位置的距离
  • 从那里开始,这是非常标准的光计算

Fragment shader:(这个着色器适用于任何东西。轻体积,全屏四边形…不管)# 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将数据发送到着色器。

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

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

例如,当我想创建一个镜面,模糊和阴影纹理,我需要渲染场景为每一个这些纹理。

对于阴影纹理,可能(如果你使用阴影映射,这是不可避免的)。但是对于其他内容:

不,这就是延迟阴影如此有用的原因。在延迟管道中,您只渲染一次几何图形,并保存每个像素的颜色、法线和3d位置(几何缓冲区)。这可以通过几种不同的方式实现,但最常见的是将帧缓冲对象(fbo)与多个渲染目标(mrt)一起使用。当使用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!

延迟渲染的基本思想是将网格的几何形状转换为目标framebuffer上的位置,并为目标framebuffer的像素提供最终颜色的过程分开。

第一步是以一种方式渲染几何图形,使framebuffer的每个像素接收有关原始几何图形的信息,即在世界或眼睛空间中的位置(首选眼睛空间),转换的切线空间(法线,切线,双法线)和其他属性,这取决于稍后需要的内容。这是"几何缓冲"(回答你的2。

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

当然,每个通道可以用来渲染不同类型的阴影处理(发光,模糊,散景,光晕等)

然后,对于每个迭代通过的结果合并在一起,形成一个合成图像。

相关内容

  • 没有找到相关文章

最新更新