水腐蚀的实时渲染

In this article, I present an attempt for generalizing caustics computation in real-time using WebGL and ThreeJS. The fact that it is an attempt is important, finding a solution that works well in all cases and runs at 60fps is difficult, if not impossible. But you will see that we can get pretty decent results using this technique.

在本文中,我提出了使用WebGL和ThreeJS实时概括焦散计算的尝试。 尝试是很重要的事实,要找到一种在所有情况下都能正常工作并以60fps运行的解决方案是困难的,即使不是不可能的。 但是您会看到,使用这种技术可以得到相当不错的结果。

什么是苛性碱? (What are caustics?)

Caustics are patterns of light that occur when light is refracted and reflected from a surface, in our case an air/water interface.

焦散是光从表面(在我们的情况下是空气/水界面)折射和反射时出现的光的模式。

Due to the reflection and refraction occurring on water waves, water acts as a dynamic magnifying glass which creates those light patterns.

由于在水波上发生反射和折射,水起着动态放大镜的作用,形成了这些光的图案。

In this post we focus on caustics due to the light refraction, so mainly what happens underwater.

在这篇文章中,我们主要讨论由于光折射引起的焦散,因此主要是水下发生的事情。

In order to get stable 60fps, we need to compute them on the graphics card (GPU), so we will compute them entirely using shaders written in GLSL.

为了获得稳定的60fps,我们需要在图形卡(GPU)上对其进行计算,因此我们将完全使用GLSL编写的着色器来计算它们。

To compute them, we need to:

要计算它们,我们需要:

  • compute the refracted rays at the water surface (which is straightforward in GLSL as a built-in function is provided for that)

    计算水面的折射光线(在GLSL中很简单,因为为此提供了内置函数 )

  • compute where those rays are hitting the environment with an intersection algorithm

    用相交算法计算那些射线照射环境的位置
  • compute the caustics intensity by checking where rays are converging

    通过检查光线会聚的位置来计算焦散强度
Image for post

著名的WebGL-water演示 (The well-known WebGL-water demo)

I was always amazed by this demo by Evan Wallace, showing visually convincing water caustics using WebGL: madebyevan.com/webgl-water

埃文·华莱士(Evan Wallace)的这个演示让我始终感到惊讶,它使用WebGL在视觉上令人信服地显示了水的腐蚀性: madebyevan.com/webgl-water

Image for post

I really recommend reading his Medium article which explains how to compute them in real-time using a light front mesh and the partial derivative GLSL functions. His implementation is blazingly fast and super good looking, but it has some drawbacks: It only works with a cubic pool, and a sphere ball in the pool. You cannot put a shark underwater and expect the demo to work, simply because it is hard-coded in the shaders that it is a sphere ball underwater.

我真的建议阅读他的Medium文章 ,其中解释了如何使用较浅的前网格和偏导数GLSL函数实时计算它们。 他的实现非常快且外观超级好,但是它也有一些缺点: 它仅适用于 立方池和池中的球形球 。 您不能将鲨鱼放在水下,也不能期望该演示能够正常工作,仅因为它在着色器中被硬编码为它是水下球体。

The reason for putting a sphere underwater is that computing the intersection between a refracted light ray and a sphere was straightforward, and it involves very simple math.

将球体置于水下的原因是,计算折射光线与球体之间的交点非常简单,并且涉及非常简单的数学运算。

All of this is fine for a demo, but I wanted a more general solution for caustics computation, so that any kind of unstructured meshes could lay in the pool, like a shark.

所有这些对于演示来说都很好,但是我想要一种更通用的焦散计算解决方案,以便任何类型的非结构化网格都可以像鲨鱼一样躺在池中。

Image for post

Now, let's get to our approach. In this article, I will expect you already know the basics of 3D rendering using rasterization, and how the vertex shader and fragment shader work together to draw primitives (triangles) on the screen.

现在,让我们开始我们的方法。 在本文中,我希望您已经了解使用光栅化进行3D渲染的基础知识,以及顶点着色器片段着色器如何协同工作以在屏幕上绘制图元(三角形)。

处理GLSL限制 (Working with GLSL limitations)

In shaders, written in GLSL (OpenGL Shading Language), you can only access a limited amount of information about the scene like:

在以GLSL(OpenGL阴影语言)编写的着色器中,您只能访问有关场景的有限信息,例如:

  • Attributes of the vertex you are currently drawing (position: 3D vector, normal: 3D vector, etc.). You can pass your own attributes to the GPU, but it needs to have a GLSL built-in type.

    当前正在绘制的顶点的属性(位置:3D矢量,法线:3D矢量等)。 您可以将自己的属性传递给GPU,但是它需要具有GLSL内置类型。
  • Uniforms, which are constant for the entire mesh you are currently drawing, at the current frame. It can be a texture, the camera projection matrix, a light direction etc. It has to have a built-in type: int, float, sampler2D for textures, vec2, vec3, vec4, mat3, mat4.

    在当前帧,U niform对于当前正在绘制的整个网格是恒定的。 它可以是纹理,相机投影矩阵,光照方向等。它必须具有内置类型:int,float,用于纹理的sampler2D,vec2,vec3,vec4,mat3,mat4。

But there is no mean for accessing to meshes that are present in the scene.

但是没有访问场景中存在的网格的手段。

This is the exact reason why the webgl-water demo could only be made with a simple 3D scene. It was easier to compute the intersection between the refracted ray and a very simple shape that can be represented using uniforms. In the case of a sphere, it can be defined by a position (3D vector) and a radius (float) so this information can be passed to the shaders using uniforms, and the intersection calculation involves very simple math that can easily and quickly be performed in a shader.

这就是为什么只能使用简单的3D场景制作webgl-water演示的确切原因。 计算折射射线与可以使用制服表示的非常简单的形状之间的交点更加容易。 在球体的情况下,可以通过位置(3D矢量)和半径(浮点数)定义球体,因此可以使用统一将这些信息传递给着色器,并且相交计算涉及非常简单的数学运算,可以轻松,快速地将其求和。在着色器中执行。

Some ray-tracing techniques performed in shaders pass meshes through textures, but this is out of scope for real-time rendering using WebGL in 2020. We have to keep in mind that we want to compute 60 images per second, using a good amount of rays in order to get a decent result. If we compute the caustics using 256x256=65536 rays, it means running an important amount of intersection calculations each second (which also depends on the number of meshes in the scene).

在着色器中执行的某些光线跟踪技术会通过纹理传递网格,但这超出了2020年使用WebGL进行实时渲染的范围。我们必须牢记,我们想每秒计算60张图像,但要使用大量为了获得体面的结果。 如果我们使用256x256 = 65536射线计算焦散,则意味着每秒运行大量的相交计算(这也取决于场景中的网格数量)。

We need to find a way to represent the sub-water environment as uniforms and compute the intersection, while keeping decent performances.

我们需要找到一种方法来将水下环境表示为制服,并计算交集,同时保持良好的性能。

创建环境图 (Creating an environment map)

When it comes to dynamic shadows computation, a well known technique is shadow mapping. It is commonly used in video games, it looks good and it’s fast.

当涉及动态阴影计算时,一种众所周知的技术是阴影映射 。 它通常在视频游戏中使用,它看起来不错并且速度很快。

Shadow mapping is a technique which is performed in two passes:

阴影映射是一项通过两步执行的技术:

  • The 3D scene, seen from the light point of view, is first rendered in a texture. This texture, instead of containing the fragments color, will contain all the fragments depth (distance between the light source and the fragment). This texture is called the shadow map.

    从光的角度看,3D场景首先在纹理中渲染。 该纹理将包含所有片段的深度(光源和片段之间的距离),而不是包含片段的颜色。 此纹理称为阴影贴图。
  • The shadow map is then used when rendering the 3D scene. When drawing a fragment on the screen, we can know from the shadow map if another fragment is between the light source and our current fragment. If that is the case we know our fragment is in the shadow and we should draw it a bit darker.

    然后在渲染3D场景时使用阴影贴图。 在屏幕上绘制片段时,我们可以从阴影图中知道光源和当前片段之间是否还有另一个片段。 如果是这种情况,我们知道片段在阴影中,应该将其绘制得更暗一些。

You can read a bit more about shadow mapping and find nice illustrations in this excellent OpenGL tutorial: www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping.

您可以阅读更多有关阴影映射的内容,并在此出色的OpenGL教程中找到漂亮的插图: www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping

You can also find a live example using ThreeJS (press “t” to display the shadow map on the bottom left corner) here: threejs.org/examples/?q=shadowm#webgl_shadowmap.

您也可以在此处使用ThreeJS(按“ t”在左下角显示阴影图)找到一个实时示例: threejs.org/examples/?q=shadowm#webgl_shadowmap

This technique works just fine in most cases. It can work with any kind of unstructured meshes in the scene.

在大多数情况下,此技术都可以正常工作。 它可以与场景中任何类型的非结构化网格一起使用。

My first idea was that I could perform a similar approach for the water caustics, which means first rendering the sub-water environment in a texture, and use this texture for computing the intersection between the rays and the environment. Instead of rendering the fragments depth only, I also render the fragments position in the environment map.

我的第一个想法是, 我可以对水腐蚀进行类似的处理,这意味着首先在纹理中渲染水下环境,然后使用该纹理来计算射线与环境之间的交点 。 除了仅渲染片段深度之外,我还渲染了环境图中的片段位置。

This is the environment map result:

这是环境图结果:

Image for post
Env map: the RGB channels store the XYZ position, the alpha channel stores the depth
Env map:RGB通道存储XYZ位置,alpha通道存储深度

如何计算光线/环境的交点 (How to compute ray/environment intersection)

Now that I have the sub-water environment map, I need to compute the intersection between the refracted rays and the environment.

现在我有了水下环境图,我需要计算折射光线与环境之间的交点。

The algorithm works as following:

该算法的工作原理如下:

  • Step 1: Start from the point of intersection between the light ray and the water surface

    步骤1:从光线与水面的交点开始

  • Step 2: Compute refraction using the refract function

    步骤2:使用折射函数计算折射

  • Step 3: Move from the current position in the direction of the refracted ray, by one pixel of the environment map texture.

    步骤3:从当前位置沿折射射线方向移动环境地图纹理的一个像素。

  • Step 4: Compare the registered environment depth (stored in the current environment texture pixel) with your current depth. If the environment depth is bigger than the current depth, it means we need to go further, so we apply again step 3. If the environment depth is smaller than the current depth, it means the ray hit the environment at the position your read from the environment texture, you found the intersection with the environment.

    步骤4:将注册的环境深度(存储在当前环境纹理像素中)与当前深度进行比较。 如果环境深度大于当前深度,则意味着我们需要走得更远,因此我们再次应用步骤3 。 如果环境深度小于当前深度,则意味着光线在您从环境纹理读取的位置处与环境相交,从而将其射向环境。

Image for post
current depth smaller than environment depth: you need to go further
当前深度小于环境深度:您需要走得更远
Image for post
current depth bigger than the environment depth: you found the intersection
当前深度大于环境深度:您发现了相交处

腐蚀性纹理 (Caustics texture)

Once the intersection is found, we can compute the caustics intensity (and a caustics intensity texture) using the technique explained by Evan Wallace in his article. The resulting texture looks like the following:

一旦找到交叉点,就可以使用Evan Wallace在他的文章中解释的技术来计算焦散强度(和焦散强度纹理)。 产生的纹理如下所示:

Image for post
Caustics intensity texture (note that the effect of caustics is less important on the shark, because it’s closer to the water surface, which reduces light convergence)
苛性碱强度纹理(请注意,苛性碱对鲨鱼的影响不太重要,因为它更靠近水面,从而减少了光线的会聚)

This texture contains the light intensity information for each point of the 3D space. We can then read this light intensity from the caustics texture when rendering the final scene, and we get the following result:

该纹理包含3D空间每个点的光强度信息。 然后,当渲染最终场景时,我们可以从焦散纹理中读取此光强度,并得到以下结果:

Image for post
Image for post

You can find the implementation of this technique on the following Github repository: github.com/martinRenou/threejs-caustics. Give it a star if you like it!

您可以在以下Github存储库中找到此技术的实现: github.com/martinRenou/threejs-caustics 。 如果喜欢,给它加星!

You can try this demo if you want to see the result of the caustics computation live: martinrenou.github.io/threejs-caustics.

如果您想实时查看焦散计算的结果,可以尝试此演示: martinrenou.github.io/threejs-caustics

关于此交集算法 (About this intersection algorithm)

This solution depends a lot on the environment texture resolution. the bigger the texture is, the better the precision of the algorithm is, but the longer it takes to find the solution (you have more pixels to read and compare before finding it).

该解决方案在很大程度上取决于环境纹理分辨率 。 纹理越大,算法的精度越好,但是找到解的时间越长(找到之前需要阅读和比较的像素更多)。

Also, reading from a texture in shaders is alright as long as you don’t do it too many times, here we are making a loop that keeps reading new pixels from the texture, that is not recommended.

另外,只要不做太多次,就可以从着色器中读取纹理,这是可以的,在这里,我们正在做一个循环,不断从纹理中读取新像素,不建议这样做。

Furthermore, while-loops are prohibited in WebGL (for a good reason), so we need to make our algorithm a for-loop that can be unrolled by the compiler. This means we need an end-condition for our loop that is known at compilation time, typically a “maximum iteration” value, which enforces us to stop looking for the intersection if we did not find it after a maximum number of attempts. This limitation results in wrong caustics results if the refraction is too important.

此外,由于良好的原因,WebGL中禁止了while循环 ,因此我们需要使我们的算法成为可以由编译器展开的for循环。 这意味着我们需要在编译时就知道循环的结束条件,通常是“最大迭代”值,如果我们在经过最大尝试次数后仍未找到交点,则将强制我们停止寻找交点。 如果折射太重要,此限制将导致错误的苛性结果。

Our method is not as fast as the simplified set up by Evan Wallace, yet it is much more tractable than a full-blown ray tracing approach, and can be used for real-time rendering. However speed remains dependent on some conditions like the light direction, refraction intensity, and environment texture resolution.

我们的方法没有埃文·华莱士(Evan Wallace)简化的方法快,但是它比成熟的光线跟踪方法更易于处理,并且可以用于实时渲染。 但是,速度仍然取决于某些条件,例如光线方向,折射强度和环境纹理分辨率。

完成演示 (Finalizing the demo)

This article is focused on the water caustics computation, but there are other techniques used in this demo.

本文重点讨论水的苛性碱计算,但此演示中还有其他技术。

Concerning the water surface rendering, we used a skybox texture and cube mapping to get some reflection. We also applied refraction on the water surface using a simple screen space refraction (see this article about screen space reflection and refraction), this technique is not physically correct but it’s visually appealing and fast. Furthermore, we added chromatic aberrations for more realism.

关于水面渲染,我们使用了天空盒纹理和立方体贴图进行反射。 我们也折射应用使用简单的屏幕空间折射的水面上(请参阅本文中关于屏幕空间反射和折射),这种技术是不是身体正确的,但它的视觉吸引力和快速。 此外,我们添加了色差以提高真实感。

We still have some ideas for further improvements including:

我们仍然有一些进一步改进的想法,包括:

  • Chromatic aberrations on caustics: we currently apply chromatic aberrations on the water surface, but this effect should also be visible on the underwater caustics.

    苛性碱的色差:我们目前在水面上应用色差,但这种效果在水下苛性碱中也应可见。
  • Light scattering through the water volume.

    光散射通过水量。
  • As suggested by Martin Gérard and Alan Wolfe on Twitter, we can improve performances by using hierarchical environment maps (which would act as quad trees for the intersection searching). They also suggested to render the environment maps from the point of view of the refracted rays (assuming the water is completely flat), this would make the performances independent from the light incidence.

    正如MartinGérard和Alan Wolfe在Twitter上所建议的那样,我们可以通过使用分层环境地图(充当相交搜索的四叉树)来提高性能。 他们还建议从折射光线的角度绘制环境图(假设水是完全平坦的),这将使性能独立于光的入射。

致谢 (Acknowledgments)

This work on real-time and realistic visualization of water is led at QuantStack and founded by ERDC.

实时和水的现实可视化这项工作的领导是QuantStack并成立ERDC

关于作者 (About the author)

Image for post

My name is Martin Renou, I am a Scientific Software Engineer at QuantStack. Before joining QuantStack, I studied at the aerospace engineering school SUPAERO in Toulouse, France. I also worked at Logilab in Paris, France and Enthought in Cambridge, UK. As an open-source developer at QuantStack, I work on a variety of projects, from xtensor and xeus-python in C++ to ipyleaflet and bqplot in Python and Javascript/TypeScript.

我叫Martin Renou ,我是QuantStack的科学软件工程师。 加入QuantStack之前,我曾在法国图卢兹的航空工程学院SUPAERO学习。 我还曾在法国巴黎的Logilab和英国剑桥的Enthought工作。 作为QuantStack的开源开发人员,我从事各种项目,从C ++中的xtensorxeus-python到Python和Javascript / TypeScript中的ipyleafletbqplot

翻译自: https://medium.com/@martinRenou/real-time-rendering-of-water-caustics-59cda1d74aa

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值