首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >WebGL和ThreeJS中改进的区域照明

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

Stack Overflow用户
提问于 2013-06-10 10:00:21
回答 5查看 10.9K关注 0票数 59

我一直在WebGL中开发类似于本演示的区域照明实现:

arealights.html

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

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

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

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

下面是我当前实现的预览:

引言

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

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

问题

这个解决方案的问题是,照明计算是从最近点进行的,没有考虑到灯光表面的其他点,这些点可能会更多地照亮碎片。让我来解释一下为什么…

请考虑以下图表:

区域光既垂直于表面,又与表面相交。表面上的每一个碎片都会返回一个最近的点,在表面与光相交的区域光上。由于曲面法向量和顶点到光矢量总是垂直的,它们之间的点积为零。随后,尽管表面有很大的光面积,但漫射贡献的计算为零。

势解

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

帮助!

所以这就是我需要你帮忙的地方。在我的头脑中,我很清楚这一点是如何推导出来的,但我没有数学能力来得出这个解。

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

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

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

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

更新!

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

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

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

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

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

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2013-06-18 03:35:42

注意: three.js现在支持THREE.RectAreaLight。这个答案与three.js的旧版本有关。

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

在上面的第一张插图中,假设你的区域光只有左半部分。

“紫色”点--使左半部分的点积最大化的点--和两个半部的点乘积最大化的点一样。

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

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

我建议您查看图1,以及Section1.2的几段--然后停止。:-)

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

编辑:删除过时的小提琴

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

代码语言:javascript
复制
// 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号

票数 41
EN

Stack Overflow用户

发布于 2013-06-16 06:46:11

嗯。奇怪的问题!这似乎是你从一个非常具体的近似开始,现在你的工作回到正确的解决方案。

如果我们只坚持漫射和一个平面(只有一个法线),什么是传入的漫射光?即使我们坚持每一个入射光都有一个方向和强度,而我们只是取allin =积分(发光)((普通))*光,这是很难的。所以整个问题就是解决这个积分。有了点光,你就会作弊,把它做个和,然后把灯拔出来。这对于没有阴影的点灯很好,现在你真正想要做的就是解决这个积分。这就是你可以用一些光探针,球形谐波或许多其他技术来做的。或者用一些技巧来估计矩形的光量。

对我来说,想到你想要点亮的那个半球总是有帮助的。你需要所有的光都进来。有些不那么重要,有些则更重要。这就是你正常的目的。在一个生产射线追踪器,你可以只是样本几千点,并有一个很好的猜测。在实时情况下,你必须猜得更快。这就是你的库代码所做的:一个好的(但有缺陷的)猜测的快速选择。

这就是我认为你在倒退的地方:你意识到他们在猜测,有时候很糟糕(这就是猜测的本质)。现在,不要试图纠正他们的猜测,而是想出一个更好的!也许试着去理解他们为什么选择这种猜测。一个好的近似不是指善于处理拐角处的案件,而是表现得很好。在我看来就是这样的。(再次,对不起,我现在太懒得读three.js代码了)。

因此,要回答你的问题:

  • 我想你是走错路了。您正在从一个高度优化的想法开始,并试图修复这个问题。最好从问题开始。
  • 一次解决一件事。你的截图有很多镜片,这与你的问题无关,但非常直观,可能对设计模型的人有很大的影响。
  • 你在正确的轨道上,对渲染有一个比大多数人更好的想法。对你有利也对你不利。阅读一些现代游戏引擎和他们的照明模式。你总是会发现一个迷人的结合,黑客和深刻的理解。深刻的理解是什么驱使选择正确的黑客:)

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

票数 2
EN

Stack Overflow用户

发布于 2013-06-10 13:09:40

让我们一致认为,铸造点总是在边缘。

让我们说,“发光部分”是空间的一部分,它代表的是沿其正常方向的挤压光的四角体。

如果表面点位于发光部分,那么你需要计算出保持这个点的平面,它是法线矢量,光是法线。平面和光线之间的交点会给你两个点作为选择(只有两个,因为浇铸点总是在边缘)。所以,测试这两个人,看看谁的贡献更大。

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

这应该能起作用。如果你遇到任何反例,请给我反馈。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/17021264

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档