我一直致力于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的改进来解决这个问题。目前我将正常的计算结合到解决方案中,但结果也存在缺陷。
以下是我当前实施的预览:
计算每个片段的扩散项的步骤如下:
此解决方案的问题在于,光照计算是从最近点完成的,并且不考虑光源表面上可能照亮片段的其他点。让我试着解释一下为什么......
考虑下图:
区域光线垂直于表面并与之相交。表面上的每个碎片将始终在表面和光线相交的区域光线上返回最近点。由于曲面法线和顶点到光线矢量总是垂直的,因此它们之间的点积为零。随后,尽管表面上有大面积的光,但漫反射贡献的计算结果为零。
我建议不要计算区域光线上最近点的光线,而是从区域光线上的一个点计算它,从而产生顶点到光线之间的最大点积矢量(标准化)和顶点法线。在上图中,这将是紫色点,而不是蓝点。
所以,这是我需要你帮助的地方。在我的脑海中,我非常清楚如何推导出这一点,但没有数学能力来达到解决方案。
目前,我的片段着色器中提供了以下信息:
为了将所有这些信息放入可视化上下文中,我创建了这个图表(希望它有所帮助):
为了测试我的建议,我需要在区域光源上使用投射点 - 由红点表示,这样我就可以在顶点到投射点之间执行点积(标准化)和顶点法线。同样,这应该产生最大可能的贡献值。
我在CodePen上创建了一个交互式草图,可视化我目前已经实现的数学:
您应该关注的相关代码是 318 。
castingPoint.location
是THREE.Vector3
的一个实例,是拼图的缺失部分。您还应该注意到草图左下方有2个值 - 这些值会动态更新,以显示相关矢量之间的点积。
我想解决方案需要另一个与顶点法线方向对齐的伪平面并垂直于光线的平面,但我可能错了!
答案 0 :(得分:40)
好消息是有一个解决方案;但首先是坏消息。
使用最大化点积的点的方法从根本上是有缺陷的,而且在物理上是不合理的。
在上面的第一张插图中,假设您的区域光线仅包含左半部分。
“紫色”点 - 最大化左半部分点积的点 - 与最大化两半点积的点相同。
因此,如果要使用您提出的解决方案,可以得出结论,区域光的左半部分发出与整个光相同的辐射。显然,这是不可能的。
计算区域光在给定点上投射的总光量的解决方案相当复杂,但作为参考,您可以在1994年的论文 The Irradiance Jacobian for Partially Occluded Polyhedral Sources < / em> 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 );
更多好消息:
警告:
WebGLRenderer
不支持区域灯光,因此您无法“将灯光添加到场景中”并期望它能够正常工作。这就是我将所有必要的数据传递到自定义着色器的原因。 (WebGLDeferredRenderer
当然支持区域灯。)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