改进了WebGL中的区域照明和ThreeJS

时间:2013-06-10 10:00:21

标签: math 3d three.js webgl lighting

我一直致力于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的改进来解决这个问题。目前我将正常的计算结合到解决方案中,但结果也存在缺陷。

以下是我当前实施的预览:

area lighting teaser

简介

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

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

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

    考虑下图:

    problematic area lighting situation

    区域光线垂直于表面并与之相交。表面上的每个碎片将始终在表面和光线相交的区域光线上返回最近点。由于曲面法线和顶点到光线矢量总是垂直的,因此它们之间的点积为零。随后,尽管表面上有大面积的光,但漫反射贡献的计算结果为零。

    潜在解决方案

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

    帮助!

    所以,这是我需要你帮助的地方。在我的脑海中,我非常清楚如何推导出这一点,但没有数学能力来达到解决方案。

    目前,我的片段着色器中提供了以下信息:

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

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

    available lighting information

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

    UPDATE !!!

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

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

    codepen

    您应该关注的相关代码是 318

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

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

5 个答案:

答案 0 :(得分:40)

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

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

在上面的第一张插图中,假设您的区域光线仅包含左半部分。

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

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

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

我建议您查看图1 ,以及第1.2节的几段 - 然后停止。 : - )

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

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

enter image description here

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

// 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. 理论上,这种方法应该适用于任意多边形,而不仅仅是矩形多边形。
  4. 警告:

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

答案 1 :(得分:2)

嗯。奇怪的问题!看起来你从一个非常具体的近似开始,现在正在向后找到正确的解决方案。

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

对我来说,将半球想象成你要点亮的点,总是有帮助的。你需要所有的光线进入。有些不太重要,有些则更重要。这就是你的惯例。在生产光线跟踪器中,您可以只抽取几千个点并进行猜测。实时你必须更快地猜测。这就是你的图书馆代码所做的:一个好的(但有缺陷的)猜测的快速选择。

这就是我认为你倒退的地方:你意识到他们正在猜测,而且它有时很糟糕(这就是猜测的本质)。现在,不要试图解决他们的猜测,而是想出一个更好的猜测!也许试着理解为什么他们选择那个猜测。一个很好的近似不是关于擅长角落情况,而是擅长降级。这就是我对我的看法。 (再次,抱歉,我现在懒得阅读three.js代码。)

所以回答你的问题:

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

希望这会有所帮助。我可能在这里完全错了,只是在寻找一些快速数学的人,所以我道歉。

答案 2 :(得分:1)

让我们同意,施法点始终处于优势地位。

让我们说“点亮部分”是空间的一部分,由挤压光的四边形沿其法线表示。

如果表面点位于被点亮的部分,则需要计算保持该点的平面,它是法向量和光线的法线。该平面与光线之间的交点会给你两个点作为选项(只有两个,因为投射点始终在边缘)。所以测试那两个,看看哪一个贡献更多。

如果该点不在被点亮的部分,则可以计算四个平面,每个平面具有表面点,其法线和光的四边形的顶点之一。对于每个光四边形顶点,您将有两个点(顶点+一个交叉点)来测试哪个贡献最多。

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

答案 3 :(得分:1)

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

如果我理解正确:

定义L“Light for point 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&gt; 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 取决于环境

答案 4 :(得分:1)

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

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