Anisotropic Lighting(各向异性光照)是一种模拟有大量细小齐整沟槽(grooves)的表面的光照情况的图形学技术。在这种表面,光产生的效果是普通的图形学光照模型所无法模拟的。——ZwqXin.com
普通的图形学光照模型:[Shader快速复习:Per Pixel Lighting(逐像素光照)]
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/shaderglsl/review-anisotropic-lighting.html
大概是一年前了,虚拟物理实验室项目工作中,为了模拟透镜表面那种粘性的高光效果,尝试用GLSL shader实现Anisotropic Lighting,参考资料是这篇文章(Anisotropic Lighting )以及NVIDIA SDK的一个HLSL DEMO。
其实最实在的样例还是CD背面(为什么?google!)。在现实生活中,镜面高光的产生自然跟接触面“是个镜面”有关,事实上,把任何一个“光-物体”接触处“微观”到一定程度,都可以是一个平面,接触点只要恰好在这个平面上就好了,就有镜面光了(即使那是多么微不足道)。关键在于,对于有微小沟槽的表面,一旦光“陷入”了沟槽,就会被这微小沟槽的“无数法线”搞晕:该沿那条法线(作为中线)反射出去呢(能不能反射得出去还是另一个问题)?光它自己又有粒子性又有波动性,故增加了它必须“考虑”的烦恼。其实我们外人来想就觉得这样很自然:光被叭喇叭喇地拆碎,然后沿着不同的法线四散出去。(至于另一问题:反弹来反弹去始终是要出去的,除非那接触物很爱吸光。)这样的结果是一束光进、好多束光出,形成扩散的高光(然后根据能量首恒可知,扩散的高光不会有一进一出时那么亮,但照亮物体的范围变大了)。当然,出来光的还有diffuse成分嘛。
当然了,以上只是我当时看完那篇文章后对模型的YY,实际发生了什么最好请示学光电的人。但大概情况就是这样,而该文章给出了更强的YY(其实是抽象假设喇):从微小沟槽内的法线形成的法线平面上,选择一个方向作为the most significant Normal(最具代表性/最重要的法线?),然后按镜面反射公式反射出来的光线方向R作为the most significant light reflection 。首先怎么选那个法线?文章说是光源向量在法线平面的投影喔。这样麻烦了点,于是文章又直接抛出以下公式让我们计算出NdotL和VdotR,还记得吗,它们就是Phong模型里头diffuse和specular成分的原材料(VdotR == NdotH)[Shader快速复习:Per Pixel Lighting(逐像素光照)] 。具体怎么根据上面the most significant light reflection的理论推导出来的,有兴趣的同学找[Banks94]和[Stalling97]来看吧(我是不知道怎么找了~顺带一提这是某AMD文章Per-Pixel Strand Based Anisotropic Lighting里提及的)。
上式中只需要计算LdotT和VdotT,L、V是光源向量和视向量,T则麻烦,它是沟槽的方向向量。有人把这两个公式“变成”纹理检索过程:LdotT和VdotT分别作为UV检索的S和T坐标,出来的是NdotL和VdotR。(这方法好,很好很强大 - -)注意我们只要一张RGB或RGBA格式的图就够了,毕竟我们只需要两个通道。对第一图只需要一个T方向检索量(LdotT)就够了,第二图则两个一起上,而且S、T可互换(式子[==纹理]很对称嘛)。
(from Per-Pixel Strand Based Anisotropic Lighting)
在说一次:T很麻烦,它是沟槽的方向向量。对CD那种圆切线还好说,很多物件的切线很难得到的啊(这也是NormalMap的难处[shader复习与深入:Normal Map(法线贴图)Ⅰ] )。前面提到NVidia有个SDK sample,弄的也是Anisotropic Lighting,不过就很简洁:不要用T了,用LdotN和HdotN来检索纹理吧。它明显没有阐述这里头的数学原理,也就给了一张新的Texture:
(from NV SDK Anisotropic Lighting)
注意这与上图是颇有区别的,但我不好推测这是代表个啥公式,但它就能方便地用LdotN(T)和HdotN(S)来检索。其中N是顶点法向量(不是那个the most significant哦),H是半向量。然后我也不知道它出来的是不是就是NdotL和VdotR(这个N和R都是the most significant),文章讲得很模糊。但sample的效果很有Anisotropic Lighting的feel。我上面提及去年做的那个透镜表面粘光效果就是直接用这个弄成的。今天重新修整了这个shader:
- //www.ZwqXin.com Anisotropic lighting
- //vertex shader
- uniform vec4 lightpos;
- uniform vec4 eyepos;
- varying vec2 texCoord0;
- void main(void)
- {
- vec3 norm = normalize(gl_NormalMatrix * gl_Normal);
- vec4 pos = gl_ModelViewMatrix * gl_Vertex;
- vec3 vlightpos = (gl_ModelViewMatrix * lightpos).xyz;
- vec3 veyepos = (gl_ModelViewMatrix * eyepos).xyz;
- vec3 vlightdir = normalize(vlightpos - pos.xyz);
- vec3 veyedir = normalize(veyepos - pos.xyz);
- //h = normalize(l + e)
- vec3 halfVec = normalize(veyedir + vlightdir);
- texCoord0.x = 2.0 * dot(halfVec, norm) - 1.0;//每个顶点的值随视线变化而变化
- texCoord0.y = 2.0 * dot(vlightdir, norm) - 1.0;//每个顶点拥有固定值
- gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
- }
- //fragment shader:
- uniform sampler2D tLookup;
- uniform float intensity;
- varying vec2 texCoord0;
- void main(void)
- {
- vec4 col = intensity * texture2D(tLookup, texCoord0.xy);
- //gl_FragColor = col; //1
- gl_FragColor = vec4(col.rgb * (col.a), 1.0); //2
- }
fragment shader中,1是不应用镜面光分量的结果,2是应用镜面光分量(在alpha通道)的结果(见下面两图)。其实用vec3(col.r)或vec3(col.g)代替col.rgb也可,后者的r分量比gb小点所以结果呈现青色。其实这些无什么所谓,要弄颜色的话取某单通道再乘个颜色量就好。另外建议纹理的S_WRAP选择GL_MIRRORED_REPEAT,看SPEC就知道其作用了,保险点好。
最后说一下另一个Anisotropic lighting的shader实现。在《Gems 1》里看到的,在《GLSL Shading Language》里Diffraction一节里也用到了。它没有用特制纹理,而是直接拿tangent切线计算,而且算法貌似是完全不一样的。其中还有TdotH,真不知道是怎么搞的,据介绍是WARD92里提出的,在Jos Stam的Diffraction开山论文《Diffraction Shaders》里也提到,貌似跟BRDF算法有关,这个……这个就不要轻易理解了。
- //Anisotropic Lighting 2 www.ZwqXin.com
- //vertex shader
- uniform vec4 baseColor;
- uniform float controlR;
- uniform vec4 lightpos;
- uniform vec4 eyepos;
- attribute vec3 rm_tangent;
- void main(void)
- {
- vec3 norm = normalize(gl_NormalMatrix * gl_Normal);
- vec3 tang = normalize(gl_NormalMatrix * rm_tangent);
- vec4 pos = gl_ModelViewMatrix * gl_Vertex;
- vec3 vlightpos = (gl_ModelViewMatrix * lightpos).xyz;
- vec3 veyepos = (gl_ModelViewMatrix * eyepos).xyz;
- vec3 vlightdir = normalize(vlightpos - pos.xyz);
- vec3 veyedir = normalize(veyepos - pos.xyz);
- //h = normalize(l + e)
- vec3 halfVec = normalize(veyedir + vlightdir);
- //0.3是光的波长,干脆就只用后面的调节系数controlR 调节好了
- float u = dot(tang, halfVec)*0.3;
- float w = dot(norm, halfVec);
- float e = controlR * u / w;
- float c = exp(-e * e);
- vec4 aniColor = baseColor * c;
- gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
- gl_FrontColor = aniColor;
- }
- //fragment shader
- void main(void)
- {
- gl_FragColor = gl_Color;
- }
以上两种shader都是在顶点级处理的,搬到fragment shader处理也就那么简单,边界柔和点而已。给出两种shader的结果(但我并没有作比较的意思,以下两图光照条件和角度都8一样):
最后再佩服一下造出前一种方法中那张纹理的人。这也就是所谓的预处理的一种吧,把函数关系式用纹理表达;这也是GPGPU中纹理概念[Vertex Texture Fetch 顶点纹理拾取] 的又一延伸吧,还是有种神奇的感觉……
该纹理下载:Aniso2.rar
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/shaderglsl/review-anisotropic-lighting.html