体积光原理及WebGL实现

techbrood 发表于 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(14) views8370 comments1

发送私信

最新评论

iefreer 2019-07-18 13:47:34

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


请先 登录 再评论.
相关文章
  • 2019年NodeJS框架Koa和Express选型比较

    Koa和Express都是NodeJS的主流应用开发框架。
    Express是一个完整的nodejs应用框架。Koa是由Express团队开发的,但是它有不同的关注点。Koa致力于核心中间件...

  • WebGL场景中多相机拍摄的原理和意义

    一般而言,3D场景的渲染只需要一个相机,不过借助多相机可以获取一些单相机无法达到的特效。比如突显特定对象并模糊背景。
    3D相机渲染的基本原理是依靠颜色...

  • 如何使用BabylonJS加载OBJ或STL模型

    BabylonJS(也就是babylon.js,这是一个和three.js类似的WebGL开发框架),更多的用在游戏领域。
    本文说明和演示如何使用babylon.js来加载一个标准3d模型文...

  • CSS3人行走动作图解和动画实现

    对于人类而言,行走是一种很自然的想要前进并防止跌倒的一组动作重复。大部分人1岁就学会了走路,但至此以后的几十年间,或许我们从来没留意过自己行走姿势。当...

  • 常见面试题JavaScript闭包(ES5语法)

    JavaScript闭包(Closure)是常见的JS面试题,是否理解闭包是一个简单的区分JS初级和高级程序员的判例。几乎每个JS程序员都在使用闭包,有意或无意间。比如编写一个jQuery鼠标点击处理函数:$(function()

  • 深入理解CSS3滤镜(filter)功能和实例详解

    CSS3滤镜功能源自SVG滤镜规范,SVG滤镜最早用来给矢量图添加类似PS中像素图的一些特效。
    把这个滤镜功能引入到普通HTML元素中可以带来很有趣的效果(模糊、...

  • 深入理解JS和CSS3动画性能问题和技术选择

    本文对比了JS及其框架和CSS3的动画性能,并深入剖析了其内在原因。技术结论大致如下:1. jQuery出于设计原因,在动画性能上表现最差2. CSS3由于把动画逻辑推给了...

  • 使用HTML5 Canvas实现的界面元素截屏功能

    如果网站出现问题,常常需要截图来提交反馈,这个功能很实用。使用HTML5的Canvas可以实现这个目标。我们首先引入

  • HTTP/2背景和新特性简介

    在前面的一篇文章中已经介绍了

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

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

  • 深度贴图(depth map)概念简介和生成流程

    Depth map 深度图是一张2D图片,每个像素都记录了从视点(viewpoint)到遮挡物表面(遮挡物就是阴影生成物体)的距离,这些像素对应的顶点对于观察者而言是“可...

  • WebGL入门教程5 - 详解纹理滤镜(Texture Filter)

    WebGL中使用纹理贴图来实现细腻的物体表面观感,其中一个重要的参数是纹理滤镜(Texture Filter)。
    这个参数用来处理当对象出现缩放时,纹理如何处理中间...

  • 更多...