« shader复习与深入:Anisotropic Lighting(各向异性光照)软阴影的实现尝试Ⅰ »

shader复习与深入:Diffraction(衍射)

既然都提到光的波动性了,实时渲染图形学里与之关系最大的衍射效果(Diffraction)就不得不浮上水面了。——ZwqXin.com

与本文相关的光照Shader技术:
[Shader快速复习:Per Pixel Lighting(逐像素光照)]
[shader复习与深入:Anisotropic Lighting(各向异性光照)]
[Shader快速复习:Reflection And Refraction(反射与折射)]
[Shader快速复习:Cube Mapping(立方环境贴图)]

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html

也是在差不多一年前,虚拟物理实验室项目里,我接受的第一个任务就是实现衍射效果,然后后面还实现过色散效果。不过那是圆孔衍射,而且我只需要把参数可控的、颗粒感的泊松斑表现出来,不涉及光学规律的模拟;色散效果同样是只需要一条参数可控的、漂亮的七彩折射光线。但因为需要设置参数,我还是上网温习了不少光学知识 - -,所以也知道这些变换的效果在现实中都可算是衍射的产物。太阳光中每种色光具有不同的波长,导致了区别;光具有波动性,导致了衍射。

《Diffraction Shaders》是Jos Stam提出这种在实时渲染中应用光波动性的SIGGRAPH文章。里面颇算艰深的数学推理之后,提出了Diffraction shader这种“算法模型”,然后,在Nvidia的Gems 1中,提出了该模型的简化版本。

如果一个表面有整齐的微小沟槽,光照情况会变成如何呢?在高三我们或许学过光的二性:粒子性与波动性。基本Phong光照模型中,光被认为是持续的直线,能发生反射折射等,是因为把光抽象成了不断发射的粒子流,物质无外力非静止的话自然就是直线传播了。但光也有其波动性的一面(或者说,直线传播只是整体概念,局部放大它就是如波般运动了)。它们分别对应宏观现象(反射折射etc)和微观现象(干涉衍射),所以当研究到微观现象的时候,如Phong光照模型等就不合适了,要用一种“认识到”光的波动性的模型。什么时候会遭到“微观”呢?譬如衍射,当光传播过程中遇到的障碍物的线性很小(比光的波长还小或相差不大的时候),就会绕行……

我在上篇[shader复习与深入:Anisotropic Lighting(各向异性光照)]中提到了该情况下光路的变化,形成扩散的高光(用的是the most significant light-reflect来模拟,但实际是光的四散),并没有说四散的光之间的相互影响。实际上,反射光线(更准确地说是反射的球面光波)之间因交叠而会互相影响(干涉),波之间彼此加合或抵消形成亮纹暗纹(与相位、光程相关),同时因为各色光的光波长不一样(影响光程),造成更复杂的交杂情况。如果这里也要挑选一种the most significant的情况的话,应该是相位相同的光之波。因为相位相反的光波会抵消,相当于都消失了;有一定相位差的则会按其程度增加波之间抵消的程度——会逐渐衰弱下去。所以可以假设惟有相位相同的波有可能进入我们的眼睛,然后考虑它们的光程:


(from NVidia)

如果还稍微记得高中所学的几何和光学知识(光程应该是其波长的整数倍才能最终有“合体效果”,导致光程变化的是反射时刻),就明白这个条件:(sin q 1 - sin q 2)*d = N* lambda(N为任意正整数,d是波在沟槽表面的间距)就是我们想要的。我们要找出所有满足这个条件的波长lambda,相同波长的波的振幅总和就是最终进入我们眼睛的光的亮度。这就跑出了几个问题:

1. 七彩的表面

这种Diffraction最具体的表现样例就是CD背面:其表面呈现各种色泽且某位置的具体颜色随光线、视线变化而变化。不同的颜色对应不同的波长,我们看到该位置上的该颜色是因为该颜色对应的波长满足上述条件,没有消失且很可能亮度得到加强并进入我们的眼睛。因为sin q 1 - sin q 2是随视线向量和光源向量变化的,所以当这些向量变化时满足条件的波长就会变化(即该位置反射回来的绿色光可能在稍倾斜CD前能加强并到达我们的眼睛,倾斜CD后其波长可能就不满足要求而消失或在到达我们眼睛前衰弱到不能被察觉的情况,这时我们看到该位置上是红色是紫色反正就没有绿色的成分了[对吗])。反正我们能看到的都是其lambda通过(sin q 1 - sin q 2)*d = N* lambda考验的光线。

2. lambda与颜色的转换

lambda对应颜色,前者是不可实际感知的量,后者才是而且是计算机图形学里最重要的感知量。可见光的波长lambda大概在400-700多之间,在[图像色彩空间与HSL/HSV] 里提过真实颜色的范围,但没有什么确定的范围(这里不能直接用0-255);况且RGB颜色有三通道,怎么进行这种1→3的转换呢?其实最好还是给一张检索纹理,用归一化的波长去检索这个纹理得出检索位置的RGB。确实有这么一个彩虹图:


(From Nvidia)

那么里面蕴涵的公式是什么呢?在下面shader的lambdatoRGB函数给出了端倪:

  1. //
  2. vec3 lambdatoRGB(float lambda, float contrlW)
  3. {
  4.  //光的波长大概在400um - 700um
  5.  //把传入的lambda在此区间normalize
  6.  
  7.  float vLambda = lambda - 400.0 / 300.0;
  8.  
  9.  vec3 bumpvec = vec3(vLambda) - vec3(0.75, 0.5, 0.25);
  10.  
  11.  bumpvec = contrlW * bumpvec * bumpvec;
  12.  
  13.  return max(vec3(1.0) - bumpvec, vec3(0.0)); 
  14. }

对应上面光谱分明的彩虹图,归一化到(0,1)的波长lambda与结果并非线性关系:result = 1 - C * lambda` * lambda`。其中C是调节系数,lambda` = vec3(lambda) - vec3(0.75,0.5,0.25)。但这表明可见光中红色光波长最大,紫色最小。这个函数是Opengl Shading Language(橙书)的Diffraction一章里取的,实际上直接用彩虹纹理检索可能更好。

3. sin q 1 - sin q 2怎么算

这个我是一头雾水,它与视线向量、光源向量相关(或者说,它与半向量有关),但是直接算出来很麻烦。在GEMS 1和橙书中,TdotH == sin q 1 - sin q 2,直观上我很难想明白,无奈对BRDF的理论不熟悉,《Diffraction Shaders》里也没找着这样做的根据(其实是看不懂啊哈),但是就结果来看的确TdotH 可代替 sin q 1 - sin q 2啊。但是又要顶点切线数据了,这可真够头疼恩。

  1. // Diffraction shader  www.ZwqXin.com
  2.  
  3. // vertex shader
  4. uniform vec4 lightpos;
  5. uniform vec4 eyepos;
  6.  
  7. attribute vec3 rm_tangent;
  8.  
  9. varying vec3 tangent;
  10. varying vec3 norm;
  11. varying vec3 halfVec;
  12.  
  13. void main(void)
  14. {
  15.    norm = normalize(gl_NormalMatrix * gl_Normal);
  16.    tangent = normalize(gl_NormalMatrix * rm_tangent); 
  17.    
  18.    vec4 pos = gl_ModelViewMatrix * gl_Vertex;
  19.    vec3 vlightpos = (gl_ModelViewMatrix * lightpos).xyz;  
  20.    vec3 veyepos = (gl_ModelViewMatrix * eyepos).xyz;
  21.    
  22.     vec3 vlightdir = normalize(vlightpos - pos.xyz);
  23.     vec3 veyedir = normalize(veyepos - pos.xyz);
  24.     
  25.     //h = normalize(l + e)
  26.     halfVec = (veyedir + vlightdir);
  27.          
  28.    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
  29. }
  30.  
  31. //fragment shader
  32. uniform vec4 baseColor;
  33. uniform float GroovesSpacing;
  34. uniform float ControlBumpWidth;
  35.  
  36. varying vec3 tangent;
  37. varying vec3 norm;
  38. varying vec3 halfVec;
  39.  
  40. void main(void)
  41. {
  42.    vec3 vnorm = normalize(norm);
  43.    vec3 vtangent = normalize(tangent);
  44.    vec3 vhalf = halfVec;   //传说不要normalize
  45.    
  46.     float lambda = 0.0;
  47.     vec3 spectrumColor = vec3(0.0);
  48.     
  49.     float TdotH = abs(dot(vtangent, vhalf));
  50.     
  51.     for(int i = 1; i <= 7; ++i)
  52.     {
  53.       lambda = GroovesSpacing * TdotH / float(i);
  54.     
  55.       spectrumColor += lambdatoRGB(lambda, ControlBumpWidth);        
  56.     }
  57.  
  58.   gl_FragColor =  baseColor * vec4(spectrumColor,1.0) ;   
  59. }

我把算法弄到fragment shader里了,这样看上去更和谐点,N取1-7其实是少了点,但多了也无谓。

Diffraction  http://www.zwqxin.com
Diffraction  http://www.zwqxin.com

对于物体上完全无光线能反射进我们眼睛的位置是黑色的,为了让结果漂亮点,自己又多贴了层CubeMAP[Shader快速复习:Cube Mapping(立方环境贴图)] 。如果完全表现Diffraction的效果,高光部分也不要忽略,于是又加多一层Ansotropic lighting[shader复习与深入:Anisotropic Lighting(各向异性光照)] :

Diffraction  http://www.zwqxin.com

效果还不错吧?

参考:GPU GEMS1 & OpenGL Shading Language

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/shaderglsl/review-diffraction.html

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

IE下本页面显示有问题?

→点击地址栏右侧【兼容视图】←

日历

Search

网站分类

最新评论及回复

最近发表

Powered By Z-Blog 1.8 Walle Build 100427

Copyright 2008-2013 ZwqXin. All Rights Reserved. Theme edited from ipati.