体积光原理及WebGL实现

iefreer 发表于 2019-07-16 17:43:36

标签: webgl, volume light, godrays, shader

- +

体积光(或叫上帝之光)在自然界中是十分常见的现象,如太阳光从云隙中透过时产生的云隙光,森林中阳光从树叶中穿过产生的光柱。

如果我们要在网页三维场景中模拟这种光效,需要深入了解大气物理模型和光散射原理。

image.png

大气物理模型

物体与其观察者之间存在着复杂的介质,比如太阳光到达我们眼睛是穿过了厚厚的大气层,大气层里面除了空气分子外,还包含云雨雾霾和灰尘等粒子。如果摄像机在地面上或者是在十分接近地面的位置,可以认为空气具有一个恒定的粒子密度,但准确而言,实际中的大气密度在地球引力的作用下,越靠近地表空气密度越高,越远离地表空气越稀薄。所以,我们假定空气粒子的密度是沿着海拔高度h呈指数递减的。按照经验,可以认为云雨雾霾尘埃等大颗粒粒子更多的存在于近地表的对流层中1200km以下,而在这之上到7994km之间为空气分子介质,在这个高度之外,我们认为近视于真空。

光散射

光在大气这种介质中传播会出现散射,散射按照粒子尺寸和光波长的相对关系如下图所示:

image.png

1. Rayleigh散射:

由空气中远小于波长的微粒(如空气分子)引起的散射称作瑞利散射。Rayleigh散射强度与光线波长的四次方成反比,这意味着白光中波长较短的颜色光(蓝色)会比波长较长的光(红色)有更强的散射强度,导致天空在白天偏向蓝色,而在黄昏偏向橙红色。 当日出或日落的时候,由于太阳的位置接近地平线,阳光斜射入大气,会在大气层中穿过很长的距离。在这个过程中,太阳光中的蓝色光几乎都会被散射殆尽无法抵达人眼,只剩下了波长较长的红色光,所以在太阳及其周围的天空都会呈现橘红色。

2. Mie散射:

在空气中直径与波长相当的微粒(如尘埃、雾滴等)所导致的散射现象称作Mie散射。与Rayleigh散射不同,Mie散射与波长无关,散射方向表现出明显的各向异性,光线会被粒子更多的向后方散射。而当阴雨天气时,空气中存在大量的水滴颗粒,Mie散射导致天空呈现灰白色。现今经常出现的雾霆天气,同样是因为空气中悬浮的大颗粒过多而导致的Mie散射现象。

结合上述的大气模型,我们可以认为Mie散射主要存在于1200km以下,而Rayleigh散射存在于7994km之下。

3. 内散射和外散射:

太阳光在大气中传输的时候会与空气中的微粒产生交互作用。有两种重要的交互方式:散射,它改变了光线的方向;吸收,它将光能吸收并转变为其它形态的能量(如热能)。而散射效果对场景中物体的影响又分为两个方面:一方面是一部分由物体反射的光被散射到视线之外,并不能到达摄像机,因而被衰减,称作外散射;另一方面是一部分太阳光被空气中的粒子散射正对向摄像机,这些正朝向视线的散射被称作内散射。

20190106184629851.png

最后抵达视点被人眼所观察到的光线可分为两部分:衰减后的物体反射辐射度、被内散射的大气散射辐照度。

image.png[方程1]

其中Lviewer为最终抵达摄像机的总光强,Lobject为物体的反射光(当视线不与物体相交时则为0),Linscatter为从O到C点路径上所有内散射光线的总和,这里暂时忽略太阳直射。

image.png


上面讲了大气物理模型和散射的物理原理,接下来看看我们具体该如何计算它。

为了计算每个像素的照明度,我们必须考虑光源到该像素的散射以及是否存在遮挡。

以阳光为例,我们从日光散射的分析模型开始,如下方程2:

equ277-01.jpg[方程2]

上述方程式是方程1的具体化,s是光线穿过介质的距离,θ是视线和太阳光线之间的夹角。Esun是太阳源光,βex是由光吸收和外散射特性组成的消光常数,βsc是由瑞利和米氏散射特性组成的角散射项。该方程第一项计算从发射点到视点吸收到的光量,第二项计算由于光散射到视点射线路径而产生的附加量。由于阻塞物质(如云、建筑物和其他物体)而产生的影响在这里被简单地模拟为光照的衰减如方程3:

image.png[方程3]

上述方程中,D(Φ)是太阳光线到视线位置之间不透明遮挡物的合成衰减率。

这样做引入了确定图像中每个点光源遮挡的复杂性。在屏幕上,我们没有完整的体积信息来决定遮挡。不过,我们可以通过在图像空间中,通过把从像素到光源的射线上的样本进行相加,估算每个像素的遮挡可能性。打到发射区域上的样本与打到遮挡物上的样本之间的比例,就是我们想要的遮挡百分比:D(Φ)。在发射区域比遮挡物亮度高的情况下,用该方法估算效果最好。如果我们把照明样本除以样本数目n,后处理(post-process)过程则可以化简为对图像取样进行求和,如方程式4:

image.png[方程4]

更进一步,我们引入衰减系数来对求和结果进行参数化控制:

image.png[方程5]

这里exposure控制后处理中的总体强度,weight控制每个样本的权重,decayi(介于[0, 1]之间)控制每个样本的衰减。这种指数式衰减因子实际上让每道光都能从光源处平滑的洒落下来。exposureweight是简单的计量因子。增加它们其中任何一个都会在整体上增强计算出来的亮度。样本的weight通过微粒度(fine-grain)控制进行调整,exposure通过粗粒度(coarse-grain)控制进行调整。因为样本都是来自原图,不需要额外处理半透明物体。多光源的处理可以通过连续叠加屏间(screen-space)通道。虽然这个例子中,我们用的是日光分析模型,但实际上其它图像资源也适用。对于位于a点的太阳,以及每一个屏幕空间图像点φ,我们从原图开始,沿着射线矢量,按规定间隔,Δ(φ) = (φ-a)/n(density),连续地取样本然后求和。这里我们用密度(density)来控制样本间隔,这样可以在必要时减少样本迭代次数。当我们提高密度因子值时,样本间距相应减小,结果是光束更亮了,覆盖范围变短了。在下图中,来自φ1样本是没有被遮蔽的,结果就是正规评估L(s,θ)得到最大的散射照明。在φ2, 一部分样本沿途中碰到了建筑物,所以计算到的散射照明就少了。通过为图像中每一个像素进行射线求和,我们得出了包含遮挡物的光散射体结构。

image.png

有了以上这些公式,我们就可以编写相应的后处理着色器代码来进行光照计算了。

给定初始图像后,样本坐标沿着射线的方向,从像素点位置延伸到屏幕空间的光源位置上。屏幕空间中的光源位置是通过标准的world-view-project转换计算出来的,计量和偏移都在坐标[1-,1]的范围内。方程式5求和出来的连续样本L(s, θ, Φ ),通过weight常数和指数式衰减的衰减系数进行计量,目的是为了将控制效果的方法参数化。样本密度可以被调整以作为最终的控制因子,合成后的颜色值可以通过常量衰减系数exposure来缩放。

float4 main(float2 texCoord : TEXCOORD0) : COLOR0
{
  // Calculate vector from pixel to light source in screen space.
   half2 deltaTexCoord = (texCoord - ScreenLightPos.xy);
  // Divide by number of samples and scale by control factor.
  deltaTexCoord *= 1.0f / NUM_SAMPLES * Density;
  // Store initial sample.
   half3 color = tex2D(frameSampler, texCoord);
  // Set up illumination decay factor.
   half illuminationDecay = 1.0f;
  // Evaluate summation from Equation 3 NUM_SAMPLES iterations.
   for (int i = 0; i < NUM_SAMPLES; i++)
  {
    // Step sample location along ray.
    texCoord -= deltaTexCoord;
    // Retrieve sample at new location.
   half3 sample = tex2D(frameSampler, texCoord);
    // Apply sample attenuation scale/decay factors.
    sample *= illuminationDecay * Weight;
    // Accumulate combined color.
    color += sample;
    // Update exponential decay factor.
    illuminationDecay *= Decay;
  }
  // Output final color with a further scale control factor.
   return float4( color * Exposure, 1);
}

事实上,屏幕空间采样并非只是遮挡采样,由于表面纹理的不同,会导致不理想的条纹出现。通常我们会采用遮挡预通道(pre-pass)和遮挡模板(stencil)来处理这些效果瑕疵。这里不做进一步的介绍。

possitive(2) negative(0) views319 comments1
私信 收藏 分享
分享到

发送私信

最新评论

iefreer 2019-07-18 13:47:34

reference: https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch13.html


请先 登录 再评论.
相关文章
  • WebGL Roadmap

    Unity 5.0 shipped with a working preview of our WebGL technology in March this year. Since then, Google has disabled (by default) NPAPI support in the...

  • 使用CSS3 box-decoration-break特性实现多行文本样式

    当文章中的长文本被自动断行为多行文本时,其样式可能会出乎我们的设计。本文介绍如何使用CSS3中的box-decoration-break特性来处理多行元素样式。
    按照规范...

  • Web界面编程状态变化和JS开发框架(React/Angular/Ember)

    UI编程中的一个关键课题就是界面组件化(可复用)以及组件状态管理。稍早一些的windows程序员可能接触过MFC,其界面编程中有一个DDX(DoDataExchange)的机制,...

  • 通过实例深入理解HTML5/CSS3/SVG/WebGL的技术本质

    常常听到人们对于HTML5的讨论,看了页面头部这个那个就是HTML5,误认为HTML5只是新增些标签“而已”,学完了

  • HTML5、Hybrid APP、Native APP对比和技术选型

    HTML5和Native APP都很容易理解。为了获得HTML5的移植性和移动本地应用的高性能,搞出来一些混合APP的解决方案。比如Apache的Cordova(也就是以前的PhoneGap),...

  • 三维向量的简单运算和实用意义

    在WebGL的实际应用中我们广泛使用向量的几何运算来计算角度、距离,判断点线、点面之间的关系,比如物体之间的碰撞检测。本文简要介绍三维计算机图形学中常用的...

  • Three.js入门教程6 - 创建全景图和纹理

    全景图非常酷。使用Three.js做一个属于自己的全景图并不是那么困难。要做一个全景图,你需要一个软件用来做一张全景图片。我使用了iPhone上的Microsoft Photosyn...

  • Three.js入门教程4 - 创建粒子系统动画

    嗨,又见面了。这么说我们已经开始学习Three.js了,如果你还没有看过之前三篇教程,建议你先读完。如果你已经读完前面的教程了,你可能会想做一些关于粒子的东西。让我们直面这个话题吧,每个人都爱粒子效果。不管你是否知道,你可以很轻易地创建它们。

  • 如何使用WebGL创建一个逼真的下雨动画

    之前写过文章来分别讲解如何使用CSS3和Canvas2D实现过雨滴和下雨动画。通过背景处理看起来也有视觉上的3D效果,但并非真正的3D场景,如果要加入用户交互,进行36...

  • 使用Canvas绘制完美的不完美圆形

    真实世界是不完美的,当我们需要模拟真实世界时,经常需要引入不完美/不规则的形状。比如陨石、雨滴、行星、树叶、绵延的海岸线、云朵等。本文介绍如何基于Canva...

  • Three.js 开发基础知识 - 绘制3D对象

    Three.js是一个用来简化WebGL开发的JavaScript库,比如绘制一个三维立方体,使用WebGL需要100多行,那Three.js只要10几行就能够完成。本文通过创建一个立方体来...

  • Processing.js和P5.js的功能简介和区别

    什么是ProcessingProcessing是关于数字艺术的编程语言,支持跨平台,语言本身是一个类Java语言,程序文件的后缀为.pde。
    什么是Processing.js为了能让Proce...

  • 更多...