【问题标题】:Improved Area Lighting in WebGL & ThreeJS改进了 WebGL 和 ThreeJS 中的区域照明
【发布时间】:2013-06-05 22:54:14
【问题描述】:

我一直致力于在 WebGL 中实现类似于此演示的区域照明:

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

three.js 中的上述实现是从 Gamedev.net 上 ArKano22 的工作中移植过来的:

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

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

几周以来,我一直在扩充这个解决方案,并使用 redPlant 的改进来解决这个问题。目前我在解决方案中加入了正常的计算,但结果也有缺陷。

这是我当前实现的预览:

简介

计算每个片段的漫反射项的步骤如下:

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

问题

此解决方案的问题在于,照明计算是从最近点完成的,并且没有考虑灯光表面上可能更照亮片段的其他点。让我试着解释一下为什么……

考虑下图:

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

潜在解决方案

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

救命!

所以,这就是我需要你帮助的地方。在我的脑海中,我很清楚如何得出这一点,但我不具备得出解决方案的数学能力。

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

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

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

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

更新!!!

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

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

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

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

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

【问题讨论】:

  • 为什么说“漫反射项不考虑表面法线”? three.js 实现中的扩散项不是一个,而是两个余弦项。
  • 对不起,我的意思是说 ArKano22 的原始实现没有考虑表面法线。我已经更新了问题以反映这一点。与 three.js 实现将 2 个余弦项相乘的方式非常相似,我也在做同样的事情,但引入了一个衰减因子,该因子使最近点到顶点向量和光法线之间的点积产生偏差。这给出了我在上面的预览中显示的灯光周围的照明区域,但牺牲了正常计算的包含。
  • 由于您提出的找到使点积最大化的点的方法无论如何都是一个近似值,因此请考虑计算来自每个角(或侧面中点)的总光贡献(包括衰减)的替代方法轻,并选择最大值。至少你可以看到它的样子。
  • @WestLangley Paul Lewis 早些时候在 Twitter 上提出了相同的迭代方法,这绝对是我明天想尝试的东西。想了这么久,我的大脑有些发火,但我仍然相信,鉴于现有的大量信息,有一个确切的解决方案?
  • 寻找投射点的伪代码是否可以接受?我需要知道一些事情才能解决它。在链接代码中,请提及 1)表面法线向量 2)光平面边界(四个线段)和 3)光法线。

标签: math 3d three.js webgl lighting


【解决方案1】:

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

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

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

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

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

计算区域光投射在给定点上的总光量的解决方案相当复杂,但作为参考,您可以在 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. 不支持阴影。

three.js r.73

【讨论】:

  • 这太棒了,非常感谢!对于您的努力、参考和示例,我将接受此作为问题的答案,为您提供来之不易的 500 分,但希望您能协助进一步完善解决方案,以省略产生负点的光顶点表面法线的产品。当光线与表面相交时会发生这种情况。当光线下沉一半时,贡献相互抵消:jsfiddle.net/Us54P/1
  • 你不能只是“省略顶点”。您必须构建一个新的多边形,该多边形定义为包含在表面“地平线上方”的半空间中的区域光部分。 (谷歌剪辑多边形平面)然后用新的多边形进行上述计算。
  • @WestLangley 如果可以的话,我自己会给你 500 分。 :)
  • 谢谢 - 正是我想要的。顺便说一句,你可以用for( int i = NVERTS, j = 0; j &lt; NVERTS; i = j++ ),然后vec3 v1 = lVector[j]; 之类的东西来避免“呃”
  • 对不起 - 忽略它。本来应该是for( int i = NVERTS - 1... 还是不行。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

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

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

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

    所以回答你的问题:

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

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

    【讨论】:

    • 感谢您的明智和明智的建议。我几乎完全同意你上面所说的一切。我采用了现有的近似值(由于问题的固有性质,它始终是近似值)并强调了解决方案失败的“边缘”情况。自今年年初以来,我一直在玩弄 3D 编程,并且阅读了尽可能多的材料,尽管很难理解一些更复杂的数学。在我能够测试我的建议之前,我不知道它是更好还是更坏......但通过反复试验,我生活并希望
    【解决方案4】:

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

    如果我理解正确:

    define 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 取决于环境

    【讨论】:

    • 感谢您尝试解决此问题...我很想大声喊哈利路亚,但不幸的是,我无法很好地理解您的答案,无法将其重新实施到我的代码中.我是一个自学成才的程序员,而不是数学家,所以我在解释公式方面遇到了困难。我在 CodePen 上创建了一个交互式草图:codepen.io/wagerfield/pen/ywqCp - 如果您能够采用您的解决方案并实施它(从第 306 行开始),我将非常感激!如果您对此感兴趣,那么这个问题还有 500 分的奖励 :-)
    【解决方案5】:

    让我们同意施法点总是在边缘。

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

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

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

    这应该可以解决问题。遇到反例请给我反馈。

    【讨论】:

    • 感谢您花时间考虑这个问题。首先,被照亮的部分不必位于从灯光四边形突出的立方体体积内——它可以位于空间中的任何地方。但是,如果该点位于该区域内,则它可能会比外面的点接收到更多的光,但不一定。我在 CodePen 上创建了一个交互式草图,它可视化了该计算的所有组件,并输出了最近点和铸造点的点积计算。这是需要解决的施法点。草图:codepen.io/wagerfield/pen/ywqCp
    • 你误会了什么。如果点在挤出体积中,则投射点位于边缘的某处,对于投射,您有两个选择:与光平面相交时获得的光边缘上的点和保持表面点的平面,其法线和光的法线。如果点超出了挤出体积,那么你需要四个平面,每个平面都包含一个光的四边形、表面点及其法线的顶点。
    猜你喜欢
    • 2016-07-12
    • 2012-08-10
    • 1970-01-01
    • 1970-01-01
    • 2014-05-15
    • 2015-03-26
    • 2015-09-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多