WebGL&中的改进区域照明ThreeJS [英] Improved Area Lighting in WebGL & ThreeJS

查看:135
本文介绍了WebGL&中的改进区域照明ThreeJS的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have been working on an area lighting implementation in WebGL similar to this demo:

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

Three.js中的上述实现是从ArKano22的工作移植到gamedev.net上的:

The above implementation in three.js was ported from the work of ArKano22 over on gamedev.net:

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

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

Though these solutions are very impressive, they both have a few limitations. The primary issue with ArKano22's original implementation is that the calculation of the diffuse term does not account for surface normals.

我已经将这种解决方案扩展了几个星期,并与redPlant一起进行了改进以解决此问题.目前,我已将正常计算合并到解决方案中,但结果也有缺陷.

I have been augmenting this solution for some weeks now, working with the improvements by redPlant to address this problem. Currently I have normal calculations incorporated into the solution, BUT the result is also flawed.

这是我当前实现的简要预览:

Here is a sneak preview of my current implementation:

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

The steps for calculating the diffuse term for each fragment is as follows:

  1. 将顶点投影到区域光所在的平面上,以使投影的矢量与光的法线/方向重合.
  2. 通过将投影矢量与光线的法线进行比较,检查顶点是否在区域光平面的正确一侧.
  3. 计算该投影点在平面上相对于光源中心/位置的2D偏移量.
  4. 钳位此2D偏移矢量,使其位于光源的区域内(由其宽度和高度定义).
  5. 推导投影和夹紧的2D点的3D世界位置.这是到达顶点的区域光上的最近点.
  6. 通过获取顶点到最近点向量(规范化)和顶点法线之间的点积,执行对点光源的常规漫射计算.
  1. Project the vertex onto the plane that the area light sits on, so that the projected vector is coincident with the light's normal/direction.
  2. Check that the vertex is on the correct side of the area light plane by comparing the projection vector with the light's normal.
  3. Calculate the 2D offset of this projected point on the plane from the light's center/position.
  4. Clamp this 2D offset vector so that it sits inside the light's area (defined by its width and height).
  5. Derive the 3D world position of the projected and clamped 2D point. This is the nearest point on the area light to the vertex.
  6. Perform the usual diffuse calculations that you would for a point light by taking the dot product between the the vertex-to-nearest-point vector (normalised) and the vertex normal.

问题

此解决方案的问题在于,照明计算是从最近点进行的,并且不考虑照明表面上可能照亮碎片的其他点.让我尝试解释原因...

Problem

The issue with this solution is that the lighting calculations are done from the nearest point and do not account for other points on the lights surface that could be illuminating the fragment even more so. Let me try and explain why…

考虑下图:

区域光既垂直于表面又与表面相交.表面上的每个片段将始终在表面和该光相交的区域光上返回一个最近点.由于表面法线和顶点到光向量始终是垂直的,因此它们之间的点积为零.随后,尽管表面上隐约可见大量光线,但散射贡献的计算仍为零.

The area light is both perpendicular to the surface and intersects it. Each of the fragments on the surface will always return a nearest point on the area light where the surface and the light intersect. Since the surface normal and the vertex-to-light vectors are always perpendicular, the dot product between them is zero. Subsequently, the calculation of the diffuse contribution is zero despite there being a large area of light looming over the surface.

我建议不要从区域光上的最近点计算光,而是从区域光上在顶点与光之间产生最大点积的点进行计算.向量(规范化)和顶点法线.在上图中,这将是紫色的点,而不是蓝色的点.

I propose that rather than calculate the light from the nearest point on the area light, we calculate it from a point on the area light that yields the greatest dot product between the vertex-to-light vector (normalised) and the vertex normal. In the diagram above, this would be the purple dot, rather than the blue dot.

因此,这是我需要您帮助的地方.在我的脑海中,我对如何得出这一点非常了解,但没有数学能力得出解决方案.

And so, this is where I need your help. In my head, I have a pretty good idea of how this point can be derived, but don't have the mathematical competence to arrive at the solution.

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

Currently I have the following information available in my fragment shader:

  • 顶点位置
  • 顶点法线(单位向量)
  • 灯光位置,宽度和高度
  • 法线(单位向量)
  • 右轻(单位向量)
  • 点亮(单位矢量)
  • 从顶点到光平面(3D)的投影点
  • 投影点距灯光中心(2D)的偏移量
  • 夹紧偏移(2D)
  • 此固定偏移量的世界位置– 最近点(3D)
  • vertex position
  • vertex normal (unit vector)
  • light position, width and height
  • light normal (unit vector)
  • light right (unit vector)
  • light up (unit vector)
  • projected point from the vertex onto the lights plane (3D)
  • projected point offset from the lights center (2D)
  • clamped offset (2D)
  • world position of this clamped offset – the nearest point (3D)

为了将所有这些信息置于可视的上下文中,我创建了以下图表(希望有帮助):

To put all this information into a visual context, I created this diagram (hope it helps):

要测试我的建议,我需要在区域光上用红色点表示的浇铸点,以便在顶点到浇铸点(归一化)之间进行点积计算. )和顶点法线.同样,这应该产生最大可能的贡献值.

To test my proposal, I need the casting point on the area light – represented by the red dots, so that I can perform the dot product between the vertex-to-casting-point (normalised) and the vertex normal. Again, this should yield the maximum possible contribution value.

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

I have created an interactive sketch over on CodePen that visualises the mathematics that I currently have implemented:

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

The relavent code that you should focus on is line 318.

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

castingPoint.location is an instance of THREE.Vector3 and is the missing piece of the puzzle. You should also notice that there are 2 values at the lower left of the sketch – these are dynamically updated to display the dot product between the relevant vectors.

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

I imagine that the solution would require another pseudo plane that aligns with the direction of the vertex normal AND is perpendicular to the light's plane, but I could be wrong!

推荐答案

好消息是,有解决方案.但首先是坏消息.

The good news is there is a solution; but first the bad news.

您使用使点积最大化的点的方法从根本上是有缺陷的,而不是从物理上看似合理的.

Your approach of using the point that maximizes the dot product is fundamentally flawed, and not physically plausible.

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

In your first illustration above, suppose that your area light consisted of only the left half.

紫色"点-使左半部分的点积最大化的点-与使两个半部组合的点积最大化的点相同.

The "purple" point -- the one that maximizes the dot-product for the left half -- is the same as the point that maximizes the dot-product for both halves combined.

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

Therefore, if one were to use your proposed solution, one would conclude that the left half of the area light emits the same radiation as the entire light. Obviously, that is impossible.

用于计算在给定点上区域光投射的总光量的解决方案非常复杂,但是作为参考,您可以在1994年的论文部分遮挡的多面体光源的辐照度雅可比式"中找到解释. /em> 此处.

The solution for computing the total amount of light that the area light casts on a given point is rather complicated, but for reference, you can find an explanation in the 1994 paper The Irradiance Jacobian for Partially Occluded Polyhedral Sources here.

我建议您查看图1 1.2节的几段内容-然后停下来. :-)

I suggest you look at Figure 1, and a few paragraphs of Section 1.2 -- and then stop. :-)

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

To make it easy, I have coded a very simple shader that implements the solution using the three.js WebGLRenderer -- not the deferred one.

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

片段着色器的核心很简单

The core of the fragment shader is quite simple

// 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. 您将必须使用合理的启发式方法来实现镜面反射组件-我希望您已经编写了类似的代码.
  3. 这个简单的示例无法处理区域光部分低于地平线"的情况-即,并非所有4个顶点都位于脸部平面上方.
  4. 由于WebGLRenderer不支持区域照明,因此您不能将照明添加到场景中"并期望它能够工作.这就是为什么我将所有必要的数据传递到自定义着色器的原因. (WebGLDeferredRenderer当然支持区域照明.)
  5. 不支持阴影.
  1. I have only implemented the diffuse component, because that is what your question addresses.
  2. You will have to implement the specular component using a reasonable heuristic -- similar to what you already have coded, I expect.
  3. This simple example does not handle the case where the area light is "partially below the horizon" -- i.e. not all 4 vertices are above the plane of the face.
  4. Since WebGLRenderer does not support area lights, you can't "add the light to the scene" and expect it to work. This is why I pass all necessary data into the custom shader. ( WebGLDeferredRenderer does support area lights, of course. )
  5. Shadows are not supported.

three.js r.73

three.js r.73

这篇关于WebGL&amp;中的改进区域照明ThreeJS的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆