« Shadow Volume 阴影锥技术之探Ⅴ再弹OpenGL矩阵——到另一个空间去 »

Shadow Volume 阴影锥技术之探Ⅵ

你好,这里是ZwqXin关于Shadow volume阴影锥技术实践记录的第六辑。这里我要给出此系列的第三个DEMO,Shadow Volume的shader实现版本。实现过程中比起第2个DEMO,所经历的波折也不少呢~。——ZwqXin.com  前文见:

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/opengl/shadow-volume-6.html

实现这个shader版本委实还花费了我不少脑细胞。也是是我本来就够笨吧~不过,现在确实做出来一个实现了。它不是完美的——比起之前的Z-FAIL算法不见得效率体现得能多多少(可能是我的问题吧~),而且还依赖于模型(最经典的茶壶模型渲染出错~见DEMO)但是,至少在本系列中我完成了这个之前一直想尝试的shader版本。Shadow Volume最早的GPU加速也就是这种了:通过vertex shader改变顶点来画出一个阴影锥。虽然传统,可是我尝试在网路上搜集一下,所见之处都是:思路,思路,思路,没发现一个可供参考的demo(怀念NEHE 27#啊~),幸好算法不难,自己完全可按“思路”搞得来,但这让调试辛苦不少,哪里错了要自己判断。好,废话少说,跟以前一样,先谈原理,后谈实现。

这里我沿用了Z-FAIL算法,但是画阴影锥不再沿用旧法子了。还记得旧法子吗(详见Shadow Volume 阴影锥技术之探Ⅱ )?先找邻面,作FaceToPlane转换,并根据它们判断“朝光面”后,找出逐帧变化的那个“受光边缘”,画阴影锥侧面(silhouette edges)~~~这里用vertex shader作转换就完全不用以上步骤了。新的步骤是这样的:

1.计算各面的面法向量
2.遍历各个面的各条边(用两顶点标示),给予一个标号(生成新的重合点)
3.给标号的时候如果发现该点已经有标号,就给予新标号后弹出这两个标号,它们能构成一个零大小的矩形
4.弹出的同时,给予每个顶点其各自所属面(相邻的)的面法向量,作为它们的顶点法向量

5.渲染时,在vertex里做判断:
   1)如果该矩形两法向量一者朝光一者背光,拓展这个矩形成为阴影锥侧面(silhouette edges)
   2)如果两法向量都朝光,让它们成为“上封口”
   3)如果两法向量都背光,让它们成为“下封口”

好了,我都说我讨厌做算法总结嘛,上面这些简直不堪入目。所以我通常宁愿多费墨水写写算法详解。把上面的当大纲吧。

首先,你能理解这个抽象形容吗:灯光照射到一个模型上,必然把模型表面分成两部分对吧(一部分受到光照,另一部分不受光),那么这两部分之间必然有分界线对吧(也就是之前提过的模型受光“轮廓线”),假如这圈分界线不是“线”,而是一个以该圈分界线的形状为边沿形状的柱体又如何?这个柱体的高是可伸缩的,受光直射时其高为H(H>0),没有光直射的时候高为0。锥体跟柱体一样的,点光源照射时一方面向后一方面向四周而已。怎么模拟呢?我们在每个相邻面片的重合边之间插入一个矩形(degenerated guad,退化矩形),以此重合边为对边,另两边长度为平时0。当这条面片的重合边在某帧恰好作为该帧的“受光面外轮廓”的一部分时,矩形的“零长边”立刻沿光线到来方向由0开始延伸——这就是阴影锥侧面(silhouette edges)嘛。下一帧到来,若该重合边离开“受光面外轮廓”则此时,此矩形立刻极速弹回去。

怎么判断该重合边在某帧是否处于“受光面外轮廓”上呢?以前不是通过邻边性和朝光性判断吗?这里是shader版本自然有shader的加速方法。方法是制造degenerated guad(退化矩形)的时候给该两条重合边每条一个向量,该向量正是包含该边的那个三角面片的法向量。因为一边由两点组成,所以把该法向量当成这两个点的顶点向量得了。这样的话两个不共面的三角面片相邻,其重合边里那个隐藏的矩形就相当于由两个点坐标和两条法向量描述了。之后在shader中,就可以由传入的光源坐标和两个点坐标计算出光到点的方向向量light-direction,以及同样传入顶点法向量gl_Normal作点乘——这跟light-direction与面法向量点乘是一样的,结果的正负间接判断了哪些面朝光哪些背光。也因此有了上面第5个步骤的判断。判断明解后,怎么延伸这个degenerated guad呢?vertex shader可以直接修改顶点,所以把矩形的顶点交给它就得了。

当然了,原理说得简单,实现起来不是容易的事情。初始化时,计算各面的面法向量是简单的,因为CLoad3DS类本身有过计算(只不过只把结果存在一本地向量里——啥用呢),只要移植一下就得了(注意叉乘向量顺序)。标号是对每个面片做遍历,对每条边,首先把两顶点压入一个容器里,再遍历之前遍历过的那些面片,找到与这条边重合的边,把该边的两顶点也压入该容器。这样处理下来后该容器连续4个点总能组成一个degenerated guad。(注意比较索引是不靠谱的,比较顶点坐标!)法线也作同样处理,存入另一容器得了。另外再用另两个容器保存第一个遍历中每个面片的顶点和法向量(相当与直接保存模型的点和点法向量信息了)。渲染时,两部分容器里的东西分别拿出来画DegeneratedQuads和画封口,代替原来的画阴影锥代码。

shader怎么改变点呢?判断之后,对法向量朝光的那些顶点什么都不做;对法向量背光的那些顶点有两个选择:一是象以前一样延伸一段距离;二是直接延伸到“无限远”。后者是一般文献建议的做法,方法是把齐次坐标变0。但我发现这是走不通的,因为外裁切面的问题(见Shadow Volume 阴影锥技术之探Ⅳ )。所以我尝试按某网路上所说解决方法写了个“远裁切面无限远”的函数代替gluPerspective生成投影矩阵,结果很是古怪,阴影大小不变,况且视觉太容易变“鱼眼”了- -,所以我最后还是选择了前者(反正阴影锥无限长也不好嘛,同见 )。好了,大概这样。见截图:

shadow volume demo3
这个是曾困惑我很久的错误,后面发现是封口代码那里写错了
shadow volume demo3
很好,这里成功了

shadow volume demo3
但是茶壶失败了。猜想是因为模型有好几层(我觉得此时是很难调和的),看下面阴影锥远端,太奇怪了——结论是,我的这个实现对复杂模型不成功(还是用回普通Z-FAIL吧 唉)
shadow volume demo3

本DEMO使用了shader,需要下载glew库到指定目录,下载见此:OpenGL常用的库
放上本DEMO:ShadowVolumeDemo3byZwqxin.rar
按键:
    →   ←   ↑   ↓  PageUp  移动光源
   鼠标左键下按不放并移动鼠标:旋转视角;鼠标滚轮移动
   鼠标右键:自动旋转开关
   空格:转变查看模式:正常模式/辅助可视阴影锥/光源射线模式

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/opengl/shadow-volume-6.html

  • quote 1.admin
  • 博主有没有用OGRE 实现过?
    zwqxin 于 2015-6-13 19:14:00 回复
    没有.
  • 2015-6-12 21:51:24 回复该留言

发表评论:

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

IE下本页面显示有问题?

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

日历

Search

网站分类

最新评论及回复

最近发表

Powered By Z-Blog 1.8 Walle Build 100427

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