有没有办法在片段着色器中使用任意数量的灯光?


19

有没有办法为片段着色器传递任意数量的光照位置(和颜色),并在着色器中循环遍历它们?

如果不是,那么应该如何模拟多个灯光?例如,对于漫射定向照明,您不能只传递明暗器的光权重之和。


我没有使用过WebGL,但是在OpenGL中,您最多可以使用8个光源。我认为,如果您要传递的不止于此,则必须使用例如统一变量。
zacharmarz 2011年

旧方法是始终通过所有灯光,未使用的灯光设置为0亮度,因此不会影响场景。可能不再使用了;-)
Patrick Hughes

7
当您使用Google之类的工具时,请勿使用“ WebGL”一词-即使解决这些问题,该技术对于人们来说还太年轻。以该搜索为例,“我感到很幸运”会奏效。请记住,WebGL问题应该很好地转换为完全相同的OpenGL问题。
乔纳森·迪金森

对于前向渲染中的8盏以上的灯光,我通常使用多遍着色器,并通过添加混合为每个通道分配一组不同的8盏灯光进行处理。
ChrisC

Answers:


29

通常有两种方法可以解决此问题。如今,它们被称为正向渲染和延迟渲染。我将在下面讨论这两种情况的一种变化。

正向渲染

对影响对象的每个光源渲染一次。这包括环境光。您使用加法混合模式(glBlendFunc(GL_ONE, GL_ONE)),因此每个光源的贡献会相互添加。由于不同光线的贡献是累加的,因此帧缓冲区最终获得了价值

您可以通过渲染到浮点帧缓冲区来获得HDR。然后,您在场景上进行最后遍历,以将HDR照明值缩小到可见范围。这也是实现布隆和其他后效应的地方。

此技术(如果场景中有很多对象)的一种常见性能增强方法是使用“预通过”,即在渲染所有对象时不向彩色帧缓冲区​​绘制任何内容(用于glColorMask关闭彩色写入)。这只是填充深度缓冲区。这样,如果渲染的对象位于另一个对象之后,GPU可以快速跳过这些片段。它仍然必须运行顶点着色器,但是它可以跳过通常更昂贵的片段着色器计算。

这更易于编写代码,也更易于可视化。在某些硬件(主要是移动GPU和嵌入式GPU)上,它可能比其他方法更有效。但是在高端硬件上,通常在灯光较多的场景中胜出。

延迟渲染

延迟渲染有点复杂。

用于计算曲面上某个点的光线的光照方程式使用以下曲面参数:

  • 表面位置
  • 表面法线
  • 表面漫反射色
  • 表面镜面颜色
  • 表面镜面光泽
  • 可能还有其他表面参数(取决于照明方程的复杂程度)

在正向渲染中,这些参数可以通过直接从顶点着色器传递,从纹理中拉出(通常通过从顶点着色器传递的纹理坐标)或从片段着色器中的整块布料生成而达到片段着色器的照明功能。其他参数。漫射颜色可以通过将每个顶点的颜色与纹理组合,组合多个纹理来计算。

在延迟渲染中,我们将这一切明确化。在第一遍中,我们渲染所有对象。但是我们不渲染颜色。相反,我们渲染表面参数。因此,屏幕上的每个像素都有一组表面参数。这是通过渲染到屏幕外的纹理来完成的。一种纹理将漫反射颜色存储为其RGB,可能将镜面反射光泽存储为alpha。另一种纹理将存储镜面反射颜色。三分之一将存储正常值。等等。

该职位通常不被存储。取而代之的是,它在第​​二遍通过数学方法进行了重构,因为它太复杂了,无法深入到这里。可以说,我们使用深度缓冲区和屏幕空间片段位置作为输入来计算出该点在曲面上的相机空间位置。

因此,既然这些纹理基本上包含了场景中每个可见像素的所有表面信息,我们就开始渲染全屏四边形。每个灯光都会获得全屏四边形渲染。我们从表面参数纹理采样(并重新构造位置),然后仅使用它们来计算该光线的贡献。再次glBlendFunc(GL_ONE, GL_ONE)将其添加到图像中。我们一直这样做,直到用尽所有灯光。

HDR再次是后处理步骤。

延迟渲染的最大缺点是抗锯齿。需要进行更多的工作才能正确消除锯齿。

如果您的GPU有很多内存带宽,那么最大的好处就是性能。我们只渲染一次实际的几何图形(如果正在做阴影映射,则每个具有阴影的光为1 + 1)。在此之后,我们绝不会将时间花在看不见的隐藏像素或几何上。所有的照明时间都花费在实际可见的物体上。

如果您的GPU没有足够的内存带宽,那么光通过确实会开始受到伤害。每个屏幕像素从3-5个纹理中提取图像并不有趣。

轻通

这是延迟渲染的一种变体,具有一些折衷方案。

与延迟渲染一样,您可以将曲面参数渲染到一组缓冲区中。但是,您已经缩写了表面数据。这次您唯一关心的表面数据是深度缓冲区值(用于重构位置),法线和镜面光泽度。

然后,对于每个灯光,您仅计算照明结果。没有与表面颜色相乘,什么也没有。仅点(N,L)和镜面反射项,完全没有表面颜色。高光和扩散项应保留在单独的缓冲区中。每个光的镜面反射项和漫射项在两个缓冲区中求和。

然后,使用总的镜面反射光和漫反射光计算重新渲染几何图形,以与表面颜色进行最终组合,从而产生整体反射率。

这里的好处是您可以进行多重采样(至少比延迟采样更容易)。您进行的每对象渲染比前向渲染少。但是,这样做的主要延迟是为不同表面具有不同照明方程式的时间更容易。

使用延迟渲染时,通常每个光源都使用相同的着色器绘制整个场景。因此,每个对象都必须使用相同的材​​质参数。使用光照预传递,可以为每个对象赋予不同的着色器,以便它可以自己完成最后的光照步骤。

这没有提供正向渲染情况那样的自由。但是,如果您有可用的纹理带宽,它仍然会更快。


-1:未提及LPP / PPL。-1延迟:渲染是任何DX9.0硬件(即使在我的“商务”笔记本电脑上)的立竿见影的效果-这是大约2009年的基本要求。除非您的目标是DX8.0(不能执行Deferred / LPP)延迟/ LPP是默认设置。最后,“很多内存带宽”是疯狂的-我们通常甚至还没有使PCI-X x4饱和,此外,LPP大大降低了内存带宽。最后,您的评论为-1;这样的循环好吗?您知道这些循环每帧发生2073600次,对吗?即使有显卡的并行性,也很糟糕。
乔纳森·迪金森

1
@JonathanDickinson我认为他的观点是,延迟/光照预通过的内存带宽通常比正向渲染的带宽大几倍。这不会使延迟的方法无效。这只是选择它时要考虑的事情。顺便说一句:延迟的缓冲区应位于视频内存中,因此PCI-X带宽无关。重要的是GPU的内部带宽。长像素着色器(例如,具有展开的循环)如果正在做有用的工作,那就没什么好奇怪的。而且,z缓冲区预通过技巧没有错;它工作正常。
内森·里德

3
@JonathanDickinson:这是在谈论WebGL,因此对“着色器模型”的任何讨论都是无关紧要的。而使用哪种类型的渲染并不是“宗教性的话题”:这仅仅是您在哪种硬件上运行的问题。嵌入式GPU的“视频内存”只是常规的CPU RAM,如果延迟渲染,效果会非常差。在基于移动图块的渲染器上,情况甚至更糟。无论硬件如何,延迟渲染都不是“即时赢”。就像其他硬件一样,它也需要权衡取舍。
Nicol Bolas

2
@JonathanDickinson:“此外,通过z缓冲区预传递技巧,您将难以消除应该绘制的对象与z的冲突。” 那真是胡说八道。您将使用相同的变换矩阵和相同的顶点着色器渲染相同的对象。多遍渲染是在Voodoo中进行的1天;这是一个已解决的问题。积累照明并不能改变它。
Nicol Bolas

8
@JonathanDickinson:但是我们不是在谈论渲染线框,是吗?我们正在谈论渲染与以前相同的三角形。OpenGL 保证了要渲染的同一对象的不变性(当然,只要您使用相同的顶点着色器,即使在那时,也有invariant关键字可以保证在其他情况下可以保证不变)。
Nicol Bolas

4

您需要使用延迟渲染或预通过照明。一些较旧的固定功能管线(阅读:无着色器)最多支持16或24个光源- 就是这样。延迟渲染消除了光照限制;但要以更复杂的渲染系统为代价。

显然,WebGL支持MRT,这对于任何形式的延迟渲染都是绝对必需的-因此它可能是可行的。我只是不确定它是否合理。

或者,您可以研究Unity 5-它已将开箱即用的渲染推迟了。

解决此问题的另一种简单方法是简单地确定灯光的优先级(也许基于与玩家的距离以及它们是否在相机视锥中),并仅启用前8位。许多AAA标题设法做到了这一点却没有太大影响有关输出质量的信息(例如,“孤岛惊魂1”)。

您还可以查看预先计算的光照贴图。诸如《雷神之锤1》这样的游戏从中获得了很多收益-它们可以很小(双线性过滤可以很好地柔化拉伸的光照贴图)。不幸的是,预先计算不包括100%动态灯光的概念,但它确实看起来很棒。您可以将其与您的8个灯光限制相结合,例如,只有火箭等具有真实的灯光-但是墙上或此类的灯光将是光照贴图。

旁注:您是否不想在着色器中循环播放它们?与您的表现说再见。GPU 不是 CPU,并且不能像JavaScript那样以相同的方式工作。请记住,渲染的每个像素(即使它被覆盖)也必须执行循环-因此,如果您以1920x1080的分辨率运行并且运行16次的简单循环,则该循环中的所有内容都有效地运行了33177600次。您的图形卡将并行运行很多这些片段,但是这些循环仍然会消耗旧的硬件。


-1:“您需要使用延迟渲染”这根本不是真的。延迟渲染当然是一种方法,但这不是唯一的方法。同样,循环在性能方面还不错,特别是如果它们基于统一的值(即:每个片段没有不同的循环长度)。
Nicol Bolas

1
请阅读第4段。
乔纳森·迪金森

2

您可以使用支持n盏灯的像素着色器(其中n是较小的数字,如4或8),并多次重绘场景,每次传递新的一批灯光,并使用加性混合将它们全部组合在一起。

这是基本思想。当然,要使速度足够快以适合合理大小的场景,还需要进行大量优化。不要只绘制可见光(视锥和遮挡剔除);不要绘制所有光。实际上,并不是每次绘制都重新绘制整个场景,而只是重新绘制该光照范围内的对象;着色器具有多个版本,它们支持不同数量的光源(1、2、3,...),因此您不会浪费时间评估过多的光源。

当您有许多小光源时,另一个答案中提到的延迟渲染是一个不错的选择,但这不是唯一的方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.