59

我一直在研究 WebGL 中的区域照明实现,类似于这个演示:

http://threejs.org/examples/webgldeferred_arealights.html

three.js 中的上述实现是从 ArKano22 在 gamedev.net 上的工作中移植的:

http://www.gamedev.net/topic/552315-glsl-area-light-implementation/

尽管这些解决方案令人印象深刻,但它们都有一些局限性。ArKano22 最初实现的主要问题是漫反射项的计算没有考虑表面法线。

几个星期以来,我一直在扩充这个解决方案,使用 redPlant 的改进来解决这个问题。目前我已将正常计算纳入解决方案,但结果也存在缺陷。

这是我当前实现的预览:

区域照明预告片

介绍

计算每个片段的扩散项的步骤如下:

  1. 将顶点投影到区域灯光所在的平面上,以便投影矢量与灯光的法线/方向重合。
  2. 通过将投影矢量与光的法线进行比较,检查顶点是否位于区域光平面的正确一侧。
  3. 计算平面上此投影点与灯光中心/位置的 2D 偏移。
  4. 夹住这个 2D 偏移矢量,使其位于灯光的区域内(由其宽度和高度定义)。
  5. 导出投影和夹紧的 2D 点的 3D 世界位置。这是区域光上离顶点最近的点
  6. 通过获取顶点到最近点向量(归一化)和顶点法线之间的点积,执行通常的点光源漫射计算。

问题

这个解决方案的问题是光照计算是从最近的点开始的,并且没有考虑灯光表面上可能更照亮片段的其他点。让我试着解释一下为什么……</p>

考虑下图:

问题区域照明情况

区域光既垂直于表面又与表面相交。表面上的每个片段将始终返回表面和光相交的区域光上最近的点。由于表面法线和顶点到光的向量总是垂直的,所以它们之间的点积为零。随后,尽管表面上有大面积的光隐约可见,但漫反射贡献的计算为零。

潜在解决方案

我建议,我们不是从区域光上最近的点计算光,而是从区域光上的一个点计算它,该点在顶点到光向量(归一化)和顶点法线之间产生最大的点积。在上图中,这将是紫点,而不是蓝点。

帮助!

所以,这就是我需要你帮助的地方。在我的脑海中,我对如何得出这一点有一个很好的想法,但没有数学能力来得出解决方案。

目前,我的片段着色器中有以下可用信息:

  • 顶点位置
  • 顶点法线(单位向量)
  • 灯光位置、宽度和高度
  • 光法线(单位向量)
  • 光右(单位向量)
  • 点亮(单位向量)
  • 从顶点投影到灯光平面上的点 (3D)
  • 投影点偏离灯光中心 (2D)
  • 夹紧偏移 (2D)
  • 此钳位偏移的世界位置 -最近点(3D)

为了将所有这些信息放入可视化上下文中,我创建了这个图表(希望它有所帮助):

可用的照明信息

为了测试我的建议,我需要区域光上的投射点——用红点表示,这样我就可以在顶点到投射点(标准化)和顶点法线之间进行点积。同样,这应该产生最大可能的贡献值。

更新!!!

我在 CodePen 上创建了一个交互式草图,可视化了我目前已经实现的数学:

http://codepen.io/wagerfield/pen/ywqCp

密码笔

您应该关注的相关代码是第318行。

castingPoint.location是一个实例,THREE.Vector3并且是拼图的缺失部分。您还应该注意到草图的左下角有 2 个值——这些值是动态更新的,以显示相关向量之间的点积。

我想该解决方案将需要另一个与顶点法线方向对齐并且垂直于光平面的伪平面,但我可能错了!

4

5 回答 5

41

好消息是有一个解决方案;但首先是坏消息。

您使用最大化点积的点的方法从根本上是有缺陷的,并且在物理上不合理。

在上面的第一个插图中,假设您的区域光仅由左半部分组成。

“紫色”点——最大化左半部分点积的点——与最大化两半点积组合的点相同。

因此,如果使用您提出的解决方案,人们会得出结论,区域光的左半部分发出与整个光相同的辐射。显然,这是不可能的。

计算区域光投射到给定点上的总光量的解决方案相当复杂,但作为参考,您可以在 1994 年的论文The Irradiance Jacobian for Partially Occluded Polyhedral Sources here中找到解释。

我建议您看一下图 1和第 1.2 节的几段——然后停下来。:-)

为了简单起见,我编写了一个非常简单的着色器,它使用three.js 实现解决方案WebGLRenderer——而不是延迟的。

编辑:这是一个更新的小提琴:http: //jsfiddle.net/hh74z2ft/1/

在此处输入图像描述

片段着色器的核心非常简单

// direction vectors from point to area light corners

for( int i = 0; i < NVERTS; i ++ ) {

    lPosition[ i ] = viewMatrix * lightMatrixWorld * vec4( lightverts[ i ], 1.0 ); // in camera space

    lVector[ i ] = normalize( lPosition[ i ].xyz + vViewPosition.xyz ); // dir from vertex to areaLight

}

// vector irradiance at point

vec3 lightVec = vec3( 0.0 );

for( int i = 0; i < NVERTS; i ++ ) {

    vec3 v0 = lVector[ i ];
    vec3 v1 = lVector[ int( mod( float( i + 1 ), float( NVERTS ) ) ) ]; // ugh...

    lightVec += acos( dot( v0, v1 ) ) * normalize( cross( v0, v1 ) );

}

// irradiance factor at point

float factor = max( dot( lightVec, normal ), 0.0 ) / ( 2.0 * 3.14159265 );

更多好消息:

  1. 这种方法在物理上是正确的。
  2. 衰减是自动处理的。(请注意,较小的灯光将需要较大的强度值。)
  3. 理论上,这种方法应该适用于任意多边形,而不仅仅是矩形。

注意事项:

  1. 我只实现了漫反射组件,因为这就是您的问题所要解决的问题。
  2. 您将不得不使用合理的启发式方法来实现镜面反射组件——我希望类似于您已经编码的内容。
  3. 这个简单的例子没有处理区域光“部分低于地平线”的情况——即并非所有 4 个顶点都在面的平面之上。
  4. 由于WebGLRenderer不支持区域灯光,因此您不能“将灯光添加到场景中”并期望它能够正常工作。这就是为什么我将所有必要的数据传递到自定义着色器中。(WebGLDeferredRenderer当然支持区域灯。)
  5. 不支持阴影。

三.js r.73

于 2013-06-18T03:35:42.613 回答
2

嗯。奇怪的问题!似乎您从一个非常具体的近似值开始,现在正在努力寻找正确的解决方案。

如果我们只坚持漫反射和平坦的表面(只有一条法线),入射漫射光是什么?即使我们坚持每一个入射光都有一个方向和强度,我们只是把 allin =integral(lightin) ((lightin).(normal))*light 这很难。所以整个问题是解决这个积分。使用点光,您可以通过将其计算为总和并将光拉出来作弊。这适用于没有阴影的点光源等。现在你真正想要做的是解决这个积分。这就是你可以用某种光探头、球谐函数或许多其他技术来做的事情。或一些技巧来估计矩形的光量。

对我来说,想想你想要照亮的点上方的半球总是有帮助的。你需要所有的光进来。有些不那么重要,有些更重要。这就是你的常态。在生产光线追踪器中,您可以只对几千个点进行采样并做出很好的猜测。实时你必须猜得更快。这就是你的库代码所做的:一个好的(但有缺陷的)猜测的快速选择。

这就是我认为你在倒退的地方:你意识到他们在猜测,而且有时很糟糕(这就是猜测的本质)。现在,不要试图修正他们的猜测,而是想出一个更好的猜测!也许试着理解他们为什么选择这个猜测。一个好的近似不是擅长极端情况,而是擅长退化。这就是我的样子。(再次,对不起,我现在懒得阅读three.js 代码)。

所以回答你的问题:

  • 我认为你的做法是错误的。你从一个高度优化的想法开始,并试图解决这个问题。最好从问题开始。
  • 一次解决一件事。您的屏幕截图有很多镜面反射,这与您的问题无关,但非常直观,可能对设计模型的人产生了很大影响。
  • 您走在正确的轨道上,并且比大多数人对渲染有更好的想法。这对你有利也可能对你不利。阅读一些现代游戏引擎及其照明模型。你总能找到技巧和深刻理解的迷人组合。深刻的理解是选择正确黑客的动力:)

希望这可以帮助。我在这里可能完全错了,并且对那些只是在寻找一些快速数学的人漫不经心,在这种情况下,我道歉。

于 2013-06-16T06:46:11.857 回答
1

让我们同意铸造点总是在边缘。

假设“光照部分”是由挤出光的四边形沿其法线表示的空间部分。

如果表面点位于光照部分,那么您需要计算持有该点的平面,它是法线向量和光线的法线。该平面和灯光之间的交点将为您提供两个点作为选项(只有两个,因为投射点始终在边缘上)。所以测试这两个,看看哪个贡献更多。

如果该点不在光照部分,那么您可以计算四个平面,每个平面都有表面点、它的法线和灯光四边形的顶点之一。对于每个 light-quad 顶点,您将有两个点(顶点 + 一个交叉点)来测试哪个贡献最大。

这应该可以解决问题。如果您遇到任何反例,请给我反馈。

于 2013-06-10T13:09:40.810 回答
1

已经有一段时间了,但是gpu gems 5中有一篇文章使用“最重要的点”而不是“最近的点”来近似区域灯的照明积分:

http://gpupro.blogspot.com/2014/03/gpu-pro-5-physically-based-area-lights.html

于 2014-07-17T16:13:35.913 回答
1

http://s3.hostingkartinok.com/uploads/images/2013/06/9bc396b71e64b635ea97725be8719e79.png

如果我理解正确:

定义 L“点 x0 的光”

L ~ K/S^2

S = sqrt(y^2+x0^2)

L = sum(k/(sqrt(y^2+x0^2))^2), y=0..infinity

L = sum(k/(y^2+x0^2)), y=0..infinity, x > 0, y > 0

L = 积分(k/(y^2+x0^2)), y=0..infinity = k*Pi/(2*x0)

http://s5.hostingkartinok.com/uploads/images/2013/06/6dbb7b6d3babc092d3daf18bb3c6e6d5.png

回答:

L = k*Pi/(2*x0)

k取决于环境

于 2013-06-10T13:33:08.370 回答