WebGL和ThreeJS中改进的区域照明

我一直在研究与WebGL相似的区域照明实现:

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

three.js中的上述实现已经从gamedev.net上的ArKano22的工作中移出:

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

虽然这些解决scheme非常令人印象深刻,但它们都有一些限制。 ArKano22最初实现的主要问题是漫射项的计算不考虑曲面法线。

我几个星期以来一直在扩充这个解决scheme,通过redPlant的改进来解决这个问题。 目前我有正常的计算纳入解决scheme,但结果也有缺陷。

这是我目前实现的一个预览:

区域照明传情

介绍

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

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

问题

这个解决scheme的问题在于,照明计算是从最近的点完成的,并且不考虑灯表面上的其他点可能更多地照亮片段。 让我试着解释为什么…

考虑下图:

有问题的地区照明情况

面光既垂直于表面又与其相交。 表面上的每个碎片将始终返回表面和光线相交处的区域光线上的最近点 。 由于曲面法线和顶点 – 光线vector总是垂直的,它们之间的点积是零。 随后,尽pipe在表面上有大面积的光线,漫射贡献的计算是零。

潜在的解决scheme

我build议,不要从面积光线上的最近点计算光线,而是从面积光点上计算顶点到光线vector(归一化)和顶点法线之间的最大点积。 在上图中,这将是紫色点,而不是蓝色点。

帮帮我!

所以,这是我需要你的帮助。 在我的脑海里,我对这个观点是如何得出的有一个很好的想法,但是没有达到解决的math能力。

目前我在片段着色器中有以下信息:

  • 顶点位置
  • 顶点法线(单位vector)
  • 光的位置,宽度和高度
  • 光正常(单位vector)
  • 轻右(单位vector)
  • 点亮(单位vector)
  • 投影点从顶点到灯光平面(3D)
  • 距离灯光中心的投影点偏移量(2D)
  • 夹持偏移(2D)
  • 这个夹持偏移的世界位置 – 最近点 (3D)

为了将所有这些信息放到一个可视的上下文中,我创build了这个图(希望它有帮助):

可用的照明信息

为了testing我的build议,我需要在由红点表示的区域灯光上的投射点,以便在顶点到投射点(标准化)和顶点法线之间执行点积。 再次,这应该产生最大可能的贡献值。

UPDATE!

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

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

codepen

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

castingPoint.locationcastingPoint.location的一个实例,是拼图的缺失部分。 您还应该注意到草图左下angular有两个值 – 这些值是dynamic更新的,以显示相关向量之间的点积。

我想,解决scheme将需要另一个伪平面,与顶点法线的方向alignment,并与光平面垂直,但我可能是错的!

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

你使用点积最大化的方法是根本上有缺陷的,而不是物理上可信的。

在你上面的第一个例子中,假设你的区域灯只由左半部分组成。

“紫色”点 – 最大化左半点点积的点 – 与两半点的点积最大化的点相同。

因此,如果要使用您提出的解决scheme,则可以得出结论,左半区域的光线发射的辐射与整个光线相同。 显然,这是不可能的。

计算区域光照射到一个给定点上的总光量的解决scheme是相当复杂的,但是作为参考,您可以在1994年的文章“部分遮挡的多面体源的辐照雅可比matrix”中find解释。

我build议你看看图1第1.2节的几个段落,然后停下来。 🙂

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

编辑:这是一个更新的小提琴: 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. 我只实现了diffuse组件,因为这是你的问题所在。
  2. 您将不得不使用合理的启发式实现镜面reflection组件 – 类似于您已经编码的内容,我预计。
  3. 这个简单的例子并不处理区域光线“部分低于地平线”的情况 – 即并非所有的4个顶点都高于面的平面。
  4. 由于WebGLRenderer不支持区域指示灯,因此您无法“将灯光添加到场景”并期望其工作。 这就是为什么我将所有必要的数据传递到自定义着色器。 ( WebGLDeferredRenderer当然支持区域灯光。)
  5. 阴影不被支持。

三.js r.73

嗯。 奇怪的问题! 看起来你是从一个非常具体的近似开始的,现在正朝着正确的解决scheme迈进。

如果我们坚持只漫reflection和一个平坦的表面(只有一个法线)什么是传入的漫射光? 即使我们坚持每一个入射光都有一个方向和强度,而我们只取allin = integral(lightin)((lightin)。(normal))*这个光线很难。 所以整个问题就是解决这个问题。 用点亮的灯光,把它作为一个总和,把光线拉出来。 这对于没有阴影的点光源来说工作得很好。现在你真正想要做的就是解决这个积分问题。 这就是你可以用某种光探头,球谐函数或许多其他技术做的事情。 或者一些技巧来估计矩形的光量。

对我来说,想要点亮上方的半球总是有帮助的。 你需要所有的光线,有些不那么重要,还有一些更重要。 这就是你的正常状态。 在生产光线追踪器中,你可以抽取几千点,并有一个很好的猜测。 实时你必须更快地猜测。 这就是你的库代码所做的:一个好的(但有缺陷)猜测的快速select。

这就是我认为你正在倒退的地方:你意识到他们正在猜测,而且有时候这很糟糕(这就是猜测的本质)。 现在,不要试图解决他们的猜测,而是想出一个更好的猜测! 也许试着了解他们为什么select这个猜测。 一个好的近似值不是关于擅长angular落的情况,而是好的。 这就是这个对我来说。 (再次,对不起,我现在懒懒地读了three.js代码)。

所以要回答你的问题:

  • 我想你是错误的。 你从一个高度优化的想法开始,并试图解决这个问题。 最好从问题开始。
  • 一次解决一件事。 你的屏幕截图有很多高光,与你的问题无关,但非常直观,可能对devise模型的人有很大的影响。
  • 你在正确的轨道上,比大多数人有更好的想法。 这可以为你工作和反对你。 阅读一些现代游戏引擎及其灯光模型。 你总会发现一个迷人的黑客和深刻的理解组合。 深刻的理解是什么驱动挑选正确的黑客:)

希望这可以帮助。 我可能在这里完全错了,只是在寻找一些快速的math,在这种情况下,我道歉。

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

假设“点亮的部分”是沿着其正常方向由挤压光的四边形表示的空间的一部分。

如果表面点坐在点亮的部分,那么你需要计算保持该点的平面,这是法向量和光线的正常值。 这个飞机和光线之间的交叉会给你两个点作为选项(只有两个,因为铸造点总是在边缘上)。 所以testing这两个看哪一个贡献更多。

如果点不在点亮部分,那么你可以计算出四个平面,每个平面都有表面点,它的法线和光线的四个顶点之一。 对于每个灯四angular顶点,您将有两个点(顶点+一个交点)来testing哪个贡献最大。

这应该做的伎俩。 如果遇到任何反例,请给我反馈。

http://s3.hostingkartinok.com/uploadshttp://img.dovov.com2013/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 …无穷

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/uploadshttp://img.dovov.com2013/06/6dbb7b6d3babc092d3daf18bb3c6e6d5.png

回答:

L = k * Pi /(2 * x0)

k取决于环境

已经有一段时间了,但是有一篇文章在使用“最重要的点”而不是“最近点”来近似区域光的照度积分:

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