下面以最快的速度简单谈谈阴影生成技术,目前普遍采用的一般有三种: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)生成算法的更多相关文章

  1. OpenGL阴影,Shadow Volumes(附源程序,使用 VCGlib )

    实验平台:Win7,VS2010 先上结果截图:    本文是我前一篇博客:OpenGL阴影,Shadow Mapping(附源程序)的下篇,描述两个最常用的阴影技术中的第二个,Shadow Volu ...

  2. OpenGL 阴影之Shadow Mapping和Shadow Volumes

    先说下开发环境.VS2013,C++空项目,引用glut,glew.glut包含基本窗口操作,免去我们自己新建win32窗口一些操作.glew使我们能使用最新opengl的API,因winodw本身只 ...

  3. 一个UUID生成算法的C语言实现 --- WIN32版本 .

    一个UUID生成算法的C语言实现——WIN32版本   cheungmine 2007-9-16   根据定义,UUID(Universally Unique IDentifier,也称GUID)在时 ...

  4. 分布式全局不重复ID生成算法

    分布式全局不重复ID生成算法 算法全局id唯一id  在分布式系统中经常会使用到生成全局唯一不重复ID的情况.本篇博客介绍生成的一些方法. 常见的一些方式: 1.通过DB做全局自增操作 优点:简单.高 ...

  5. C++ 基于凸包的Delaunay三角网生成算法

    Delaunay三角网,写了用半天,调试BUG用了2天……醉了. 基本思路比较简单,但效率并不是很快. 1. 先生成一个凸包: 2. 只考虑凸包上的点,将凸包环切,生成一个三角网,暂时不考虑Delau ...

  6. C++ 凸包生成算法

    由于我的极差记忆力,我打算把这个破玩意先记下来.因为以后会有改动(Delaunay三角网生成算法),我不想把一个好的东西改坏了... 好吧-- 凸包生成算法,: 1.先在指定的宽(width)高(he ...

  7. RocketMQ msgId生成算法

    当我们用RocketMQ发送信息的时候通常都会返回如下信息: SendResult [sendStatus=SEND_OK, msgId=0A42333A0DC818B4AAC246C290FD000 ...

  8. 分布式系统的唯一id生成算法你了解吗?

    在分库分表之后你必然要面对的一个问题,就是id咋生成? 因为要是一个表分成多个表之后,每个表的id都是从1开始累加自增长,那肯定不对啊. 举个例子,你的订单表拆分为了1024张订单表,每个表的id都从 ...

  9. STL_算法_04_算术和生成算法

    ◆ 常用的算术和生成算法: 1.1.求和( accumulate 是求和的意思)(对指定范围内的元素求和,然后结果再加上一个由val指定的初始值.) T accumulate(iteratorBegi ...

随机推荐

  1. Doxygen简单经验谈。。。

    Doxygen,大名鼎鼎的文档生成工具,被Boost.OpenCasCade等诸多项目作为文档生成的不二人选.人说,才华横溢往往是高深莫测,这句话放在 Doxygen这里显然是不适用的.十八般武艺样样 ...

  2. JavaScript-undefined与null区别

    JavaScript中的null在其他编程语言中也很常见,但是JavaScript在设计的过程中null自动转换为0,为了更好表示空,这个时候undefined出现了,null通过typeof结果是“ ...

  3. 免费桌面视频录像工具OBS的简单操作介绍

    本帖最后由 felix0911 于 2014-5-21 09:32 编辑 0起点,傻瓜操作,为什么不尝试录制一个自己的游戏视频,来展现自己牛逼风骚的操作呢?(本教学不包括后期制作,特效背景音乐等部分) ...

  4. Sequence在Oracle中的使用

    Oracle中,当需要建立一个自增字段时,需要用到sequence.sequence也可以在mysql中使用,但是有些差别,日后再补充,先把oracle中sequence的基本使用总结一下,方便日后查 ...

  5. 初学 Delphi 嵌入汇编[1] - 汇编语言与机器语言

    非科班出身, 现在才接触汇编, 惭愧呀, 好好学! 主选课本是清华大学王爽老师的<汇编语言>. 推荐 王爽老师的汇编网 汇编语言之前是机器语言. 机器语言是机器指令的集合, 机器指令是一系 ...

  6. 求通俗讲解下tensorflow的embedding_lookup接口的意思

    https://www.zhihu.com/question/48107602 作者:王凯链接:https://www.zhihu.com/question/48107602/answer/15980 ...

  7. SqlDateTime 溢出。必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之间。

    出现的错误:SqlDateTime 溢出.必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之间. 错误的原因:.NET Framework dat ...

  8. OpenGL ES 3.0之Uniform详解

    Uniform是变量类型的一种修饰符,是OpenGL ES  中被着色器中的常量值,使用存储各种着色器需要的数据,例如:转换矩阵.光照参数或者颜色. uniform 的空间被顶点着色器和片段着色器分享 ...

  9. (C++)字符串分割

    题目: 如何对C++中输入的字符串进行分割呢?如“I am a student”,去除空格后分割成为“I”,“am”, “a”, “student”四个单词 思路: 直接参考代码 代码: void s ...

  10. MySQL数据库设置远程访问权限方法小结

    http://www.jb51.net/article/42441.htm MySQL基础知识第一期,如何远程访问MySQL数据库设置权限方法总结,讨论访问单个数据库,全部数据库,指定用户访问,设置访 ...