转:体积阴影(Shadow Volumes)生成算法
下面以最快的速度简单谈谈阴影生成技术,目前普遍采用的一般有三种:Planar Shadow、Shadow Mapping和Shadow Volume,前者类似投影,计算最简单,缺点只能绘制抛射在平面上的阴影;Shadow mapping利用站在光源处所沿光源法线看去所生成的深度图来检测场景中的体象素是否处于阴影中,缺点是光源与物体位置相对固定、且在极端情况下计算精度差,不太适合精确到象素的动态光阴场合;Shadow Volume是目前最适合精确表现动态光阴场景的技术,适用性最广,其典型的适用范例便是Doom 3,不足在于阴影体积引入了额外的顶点和面,加大了存储和处理强度,同时渲染出的阴影比较硬,如果要实现软阴影,仍需其他技术配合。
这里我们快速往前跳,Perspective projection、Depth test、Stencil buffer等概念就不多谈了。Shadow Volume的一般步骤为:生成阴影体积(Mesh)和阴影渲染,阴影体积生成算法又分两种,一种是D3D SDK sample中所采用的方法,先分离/插补物体面,然后用Vertex Shader加速,对复制所得的未拉伸的阴影体积进行沿光线方向的Extrusion;另一种则是多数tutorial中常用的Software Renderer方式,首先拣选出所有向光面,将每一个向光面的所有边均加入一个list,每加入一个边时,如果list中已经存在相同的边了,则不加入,同时将list中相同的边删除,这样处理完所有的向光面后,list中便只会剩下物体向光面轮廓的边信息,将该轮廓沿光矢量方向进行延伸,然后完成侧面Quad插补和前后封口(如果使用的是Z-fail)即可。而对于阴影渲染,则一般用4个pass,具体有Z-Pass/Z-Fail两种做法:
Z-pass算法
1. 先关闭光源,将整个scence渲染一遍,此时一片漆黑,但获得了深度值
2. 关闭深度写,渲染阴影体的正面,深度测试通过则模板值加1
3. 然后渲染阴影体的背面,深度测试通过则模板值减1
4. 最后模板值不为0的面就在阴影体中,开启深度写
5. 用模板手法重新渲染一次加光的scence即可,让阴影部分为黑色
6. 其致命缺点是当视点在阴影中时,会导致模板计数错误
7. 同时,有可能因为Z-near clip plane过近而导致模板计数错误
Z-fail算法(John Carmack's Reverse)
1. 先关闭光源,将整个scence渲染一遍,获得深度值
2. 关闭深度写,渲染阴影体的背面,深度测试失败则模板值加1
3. 渲染阴影体的正面,深度测试失败则模板值减1
4. 最后模板值不为0的面便处于阴影体中,开启深度写
5. 用模板手法重新渲染一次加光的scence即可,阴影部分不渲染色度
6. 注意,该算法要求阴影体积是闭合的,即需要前后封口
7. 该方法不是没有缺陷的,有可能因为Z-far clip plane过近而导致模板计数错误
值得注意的方面
1. Z-pass由于不用封口,因此速度比Z-fail快,但存在处理不了的情况
2. Quake 3貌似使用的是z-pass shadow volume和planar shadow
3. 为保证足够robust,必须确保z-near/z-far中至少一个不出问题,Nvidia的论文推荐采用z-fail,用w=0来实现无穷远的z-far平面
4. 记住使用Z-fail一定要封口,而且阴影体积的每个面的法线必须正确地指向物体之外,包括front cap和back cap
5. 上面给出的是简单过程,阴影很硬,可以稍微变通一下:先以环境光渲染一遍,然后计算模板,再用模板渲染打光的scene,最后以alpha blend加深阴影
下面开始大面积地贴图,首先来看个Z-fail的具体例子(z-pass简单,就不举例了):从外面看一个面向光源的单面的情况。图中,虚线框起来的为阴影体积,圆形为点光源,其中,面ADFB和面DEF是back faces,而面ABC、ACED和CBFE则是三个front faces(注意这里顶点顺序用的是左手系),为说明问题(主要是为了说明在计算阴影体积时如何避免z-fighting),我故意把面ABC画在了黄色三角形下面一点点的位置,而且其中的灰色阴影是与平面共面的,而面DEF则处于平面下面一点点的地方。

首先进行2个back faces的depth test:粉红色的是stencil buffer中因Z- fail而加1的区域;然后进行3个front faces的depth test:亮绿色的是stencil buffer中因Z- fail而减1的区域

最后红绿区域正负相抵,stencil buffer中只剩下平面上的灰色三角区域中的值不为0,即原图阴影所在的位置。上面是个从单面外看的例子,值得注意的是,单面与两个背靠背双面的情况是不一样的,那么后者的计算结果是不是也是正常的呢?继续看图:

上图的红、绿细线分别表示两个背靠背单面,而粗线则构成了体积阴影,红粗线、绿粗线分别表示红面、绿面沿光线方向的的投影,一般而言,back cap处于无穷远处,而front cap则值得注意,为避免z-fighting,它的位置应该是在红面之后、绿面之前,也许你会说:“hey, 这里的front cap完全可以直接等于红面嘛”,是的,当在eye看来红面是背面时的确可以这么做,可当eye位置变化、红面不再是背面时,简单的以红面为front cap便会导致z-fighting。从图上可看出,当eye处于阴影内部时,模板计数也是正确的。下面再来看看仅有一个单面、且处于阴影内部的情况:

只要front cap注意了z-fighting的情况,其计算结果也是正确的,有意思的是,实际上当eye处于阴影内部时,在back culling的时候,三角形的背面便已经被去除了,因此在实际渲染时,在三角面的背面处并不会存在其深度信息,但即便如此,z-fail的模板计数仍是正确的。

最后,再来看一个计算方块的阴影的例子,可以看出,当front cap为背面时,阴影体积的计算很简单,无需考虑其z-fighting的情况,直接用方块的向光面作为front cap即可;但当front cap为正面时,还是面临着位置需要微调的问题。
总结一下,用z-fail方式来计算没有厚度的面的阴影和具有厚度的物体的阴影还是有点微妙区别的;在计算阴影体积时,要注意避免front cap处z-fighting的情况,稍微调整一下front cap的位置,即将front cap沿光线方向稍微向后挪那么一点点;或者是.... 等等,还有一种更简洁优雅的办法,即将Z-fail算法中的"Depth Test失败"理解成象素深度">="所在位置的深度而不仅仅只是">",将ZBufferFunction设为Campare.Less而不是Campare.LessEqual,这样的话便完全避开了z-fighting的问题,微调什么的都可以免了,无论是面还是物体,在任何情况下都可以直接用物体向光面作为front cap....faint...快写完了才考虑清楚...白废话了那么多....无比郁闷-_-b....收工.
转:体积阴影(Shadow Volumes)生成算法的更多相关文章
- OpenGL阴影,Shadow Volumes(附源程序,使用 VCGlib )
实验平台:Win7,VS2010 先上结果截图: 本文是我前一篇博客:OpenGL阴影,Shadow Mapping(附源程序)的下篇,描述两个最常用的阴影技术中的第二个,Shadow Volu ...
- OpenGL 阴影之Shadow Mapping和Shadow Volumes
先说下开发环境.VS2013,C++空项目,引用glut,glew.glut包含基本窗口操作,免去我们自己新建win32窗口一些操作.glew使我们能使用最新opengl的API,因winodw本身只 ...
- 一个UUID生成算法的C语言实现 --- WIN32版本 .
一个UUID生成算法的C语言实现——WIN32版本 cheungmine 2007-9-16 根据定义,UUID(Universally Unique IDentifier,也称GUID)在时 ...
- 分布式全局不重复ID生成算法
分布式全局不重复ID生成算法 算法全局id唯一id 在分布式系统中经常会使用到生成全局唯一不重复ID的情况.本篇博客介绍生成的一些方法. 常见的一些方式: 1.通过DB做全局自增操作 优点:简单.高 ...
- C++ 基于凸包的Delaunay三角网生成算法
Delaunay三角网,写了用半天,调试BUG用了2天……醉了. 基本思路比较简单,但效率并不是很快. 1. 先生成一个凸包: 2. 只考虑凸包上的点,将凸包环切,生成一个三角网,暂时不考虑Delau ...
- C++ 凸包生成算法
由于我的极差记忆力,我打算把这个破玩意先记下来.因为以后会有改动(Delaunay三角网生成算法),我不想把一个好的东西改坏了... 好吧-- 凸包生成算法,: 1.先在指定的宽(width)高(he ...
- RocketMQ msgId生成算法
当我们用RocketMQ发送信息的时候通常都会返回如下信息: SendResult [sendStatus=SEND_OK, msgId=0A42333A0DC818B4AAC246C290FD000 ...
- 分布式系统的唯一id生成算法你了解吗?
在分库分表之后你必然要面对的一个问题,就是id咋生成? 因为要是一个表分成多个表之后,每个表的id都是从1开始累加自增长,那肯定不对啊. 举个例子,你的订单表拆分为了1024张订单表,每个表的id都从 ...
- STL_算法_04_算术和生成算法
◆ 常用的算术和生成算法: 1.1.求和( accumulate 是求和的意思)(对指定范围内的元素求和,然后结果再加上一个由val指定的初始值.) T accumulate(iteratorBegi ...
随机推荐
- HTTP协议学习【转】
面试过程中又一个常见的问题,http协议,因为做服务器开发如果用http协议的话,现在各种开源软件都封装好了,python中只需要简单的继承定义好的类,重写get或者post等方法,几行代码就可以搭建 ...
- 浅谈 Boost.Asio 的多线程模型
Boost.Asio 有两种支持多线程的方式,第一种方式比较简单:在多线程的场景下,每个线程都持有一个io_service,并且每个线程都调用各自的io_service的run()方法. 另一种支持多 ...
- 使用Spring Cloud Security OAuth2搭建授权服务
阅读数:84139 前言: 本文意在抛砖引玉,帮大家将基本的环境搭起来,具体实战方案还要根据自己的业务需求进行制定.我们最终没有使用Spring Security OAuth2来搭建授权服务,而是完全 ...
- Android -- 获取视频第一帧缩略图
干货 从API 8开始,新增了一个类: android.media.ThumbnailUtils这个类提供了3个静态方法一个用来获取视频第一帧得到的Bitmap,2个对图片进行缩略处理. public ...
- PDO 增删改查封装的类
Selecting Data 你在mysql_*中是这样做的 <?php $result = mysql_query('SELECT * from table') or die(mysql_er ...
- [Algorithm] Inorder Successor in a binary search tree
For the given tree, in order traverse is: visit left side root visit right side // 6,8,10,11,12,15,1 ...
- Android 自定义 ListView 上下拉动刷新最新和加载更多
本文内容 开发环境 演示上下拉动刷新最新和加载更多 ListView 参考资料 本文演示上下拉动,刷新最新和加载更多,这个效果很常见,比如,新闻资讯类 APP,当向下拉动时,加载最新的资讯:向上拉动时 ...
- 加快AS的Gradle Build速度
随着项目的代码以及依赖库的日益增多,构建的速度却是越来越慢,Android Studio2.0 之后提供了Instant Run貌似福利来了,但经过一段时间的使用发现不尽如人意,最大的吐槽点是首次编译 ...
- Android开发点滴 - 实现层级式导航(API 16+)
在Jelly Bean(API 16)以前,为了实现顶部的导航菜单,程序员们不得不手工写代码, 神马在OnCreate啊,神马onOptionsItemSelected啊,但是,现在一切都是浮云了. ...
- jQuery 1
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="utf-8& ...