ZFXEngine开发笔记之Shadow Volume

作者:i_dovelemon

来源:CSDN

日期:2014 / 10 / 20

主题: Shadow Volume

引言

游戏中,往往有很多的光影效果。想要营造出很好的光影效果,物体在光源照射下的阴影就必不可少。本节内容就向大家讲述如何构建阴影。

Shadow Volume

构建阴影的方法有很多,常用的两种方法是Shadow Volume和Shadow mapping。本片博文将向大家讲述使用Shadow Volume来构建阴影的方法。

Shadow Volume的算法是由Frank Crow在1977年提出的。一个Shadow Volume将一个3D场景划分成两个部分,分别是阴影区域和非阴影区域。这种算法,自从Doom 3yi依赖,就变的非常的出名。John Carmack在前人的基础上,提出了一种改进的方法,使得Shadow Volume方法在游戏业十分的出名。总的来说,就是通过Stencil buffer来标记Shadow volume所划分的阴影和非阴影区域。从而在当前的Color
Buffer中进行绘制,在Stencil Buffer中标记了某块区域是阴影,那么我们就在Color Buffer的对应区域绘制成黑色阴影。

说了这么多的Shadow Volume,那么Shadow Volume到底是什么了?它的具象表现又是怎么样的?为了理解Shadow Volume的概念,我们先来了解下,为什么物体会有阴影。(额。。。,这个很明显吧!!!)

我们知道,光线是直线传播的。如果光线遇到了不可穿越的阻挡物,那么光线无法将被物体遮挡的部分进行光照。在没有光照的情况下,自然这块区域就变成了黑色,从而形成了阴影。

我们来看一张图:

从上图中我们就可以看出,Shadow Volume是值由光源发出的线与被照射物体的边缘顶点连线,然后截取一段,这样的一段几何体,我们就称之为Shadow Volume。物体本身正对着光源的那一个面,我们称之为Front Cap,而背面截取的那一段,被称为Back Cap。这样,就唯一的定义了一个Shadow Volume了。

Z-PASS算法

我们假设,我们已经根据物体构建出来了这样的一个Shadow Volume。有了这个Shadow Volume之后该怎么做了?

想想我们之所有提出这个Shadow Volume的原因,是想要将我们的场景划分称为两个部分:阴影区域和非阴影区域。也就是说,由这个Shadow Volume所构建出来的空间,应该就是不能够被照亮的,也就是说,它就是阴影部分。所以,我们能不能把这个阴影部分通过某种方法标记出来了?读者可能想到,好吧,既然Shadow Volume就是阴影部分,那么我们直接将这个Shadow Volume当做一个模型,并且给它赋予黑色,然后直接画出来,如何?的确,通过这样的方法,阴影的确画出来了。但是,同样的,空间中没有任何物体存在的部分,由于你将Shadow
Volume当做一个物体模型,使得这块部分也被画出来了。读者啊,请仔细的观察下现实世界中的阴影,阴影只会在物体上显示出来,空间本身并不会显示阴影。所以这样的方法,肯定是不行的。但是它提出了一个想法,通过绘制Shadow Volume,来对图像进行标记,标记出那些存在物体,并且在Shadow Volume中的阴影像素部分。好了,这个就是Shadow Volume方法能够实现的原理所在。

那么,通过什么样的方式来进行标记阴影像素部分了?这里就需要用到3D图形库所带有的特性了。

DirectX支持使用Stencil buffer。这里的算法就要借助于Stencil Buffer来标记阴影部分。

下面是使用Z-Pass算法绘制阴影的步骤:

1.正常的使用环境光和发射光来绘制当前场景,从而获取到场景的Depth信息。

2.开启Stencil Buffer,关闭Depth buffer和Color buffer绘制所有的Shadow Volume(我们不是在场景中显示Shadow Volume,只是用来标记,所以关闭掉Color Buffer和Depth Buffer)。绘制Shadow Volume的时候,先绘制正对视点的那些面,并且设置,当Depth Test通过的时候,对应的Stencil Buffer值加1.然后绘制Shadow
Volume的背面,并且设置当Depth Test通过的时候,对应的Stencil Buffer的值减1。

3.使用屏幕渲染(2D渲染,就是对屏幕直接画一张黑色的图)的方法,对屏幕使用黑色渲染,并且只对那些Stencil buffer中的值大于等于1的像素进行渲染。

通过上面的方式,那些被标记了的像素位置的Stencil Buffer的值都是大于等于1的,然后在屏幕渲染之后,就会变成了黑色部分,从而形成了阴影部分。

读者,看到这里可能会和我一样的去想,为什么这样做了以后就标记成功了?原理是如何的了?

我们来讨论下这种算法的依据是什么。

首先,存在这样的一个事实。当我们从视点,也就是眼睛的部位发射一条射线到一个物体上去。如果这条射线,穿过了Shadow Volume的前面(Front face),并且也穿越了背面(Back Face)。那么这个物体自然就是在Shadow Volume外面的。如果值仅仅的穿越过Front Face,而没有穿越过Back face,那么也就是说这个物体就在Shadow Volume中,即这个物体是需要接受阴影的,我们就来标记它一下。这里讨论的是一个Shadow
Volume的情况,当多个Shadow Volume同时出现的时候,这个事实依然是正确的。如果读者对文字不清楚,那么来看下下图:

(本图来自于网络)

从上图中,我们可以看到,只要穿越了Shadow Volume的前面我们就+1,穿越了后面就-1。而在Shadow Volume里面的值都是大于等于1的。在外面的值刚好是0。很容易理解了吧!!!

基于这个事实,我们就能够想到我们首先将正常的场景绘制出来,那么整个场景的Depth map就有了。每一个像素对应的Depth Value我们都知道了。为了模拟出来上面讨论的那种射线穿越过前面就+1,穿越后面就-1的特性,我们并不是真的发出射线来进行判断(虽然理论上的确可以做到,但是那要消耗太大了,套用一个网友的话来说就是,仅仅是一个效果而已,开销太大的算法完全没有使用的必要),而是通过一种很有技巧性的方法来。

我们现在已经有了Depth map的值,那么我们先绘制所有Shadow Volume的front face。在进行绘制的时候,自然就会进行Depth Test(Z-Test,如果读者对这些概念都不懂,那么本篇文章不适合读者。),如果Depth Test通过了,那么就表示当视点的当前位置,Shadow volume的front face中通过Depth Test的位置是在物体的前方的,也就是说,你发射射线到这个物体的话,一定会穿越我,所以,我就+1,表示射线穿越过我了。这种行为就是上面的”绘制front
face的时候,一旦Depth Test通过了,Stencil Buffer的值就+1“。同样的,对于Shadow Volume的背面,如果它也通过Depth Test,那么就表示这个点也是在物体前面的,射线也会穿越它,所以就-1。这种行为就是上面的”绘制shadow volume的back face的时候,一旦Depth Test通过了,stencil buffer的值-1“。好了,通过这样的方法,我们就能够将最终显示的图像的Stencil buffer全部计数完毕。那么,当stencil buffer的值大于等于1的时候,也就是说该像素点是阴影。如果stencil
buffer的值为0,就表示该点不是阴影或者不存在物体,我们不做任何处理。

哈!多么高技巧的手法啊!真佩服想出这种算法的人,通过这样的技巧,大大的减少了发射射线来的计算开销。果然,算法无止境,只是想到与想不到的区别。

Oops!

上面的方法,已经能够实现大部分的阴影了(wow,大部分???还有没有办法实现的情况???)。但是,读者发现没有,上图中,都是假设视点在Shadow Volume之外,如果视点在Shadow Volume中了(这种情况常常出现,躲在Shadow里的人们啊!!)?读者自己试一下,会发现在绘制Shadow Volume的时候,由于裁剪会把Shadow Volume的前面给裁剪掉,这就造成了,没有绘制这些面,那么stencil
buffer中的值自然就缺少了计数。所以会出现不正常的阴影。

(来源与网络)

从上图,读者就可以在Shadow volume里面的物体,它对应的stencil buffer的值,并不是大于等于1而是0。所以这就导致错误了。

Z-FAIL算法

前面提到,John Carmack(3D游戏鼻祖,读者可自行搜索)在Doom3中,使用了一种新的方法来改进了这样的缺陷。我们称之为Z-FAIL算法。这个算法的整体思路还是和上面的算法一致。只是在对那种行为的模拟上进行了改进。上面讨论说,如果视点在shadow volume里面的话,从视点发射射线遇到物体的话,会出现少计数的情况。那么,也就是说视点如果总是在shadow volume外面的话,那就能保证,这个算法是正确的,对吗?

而视点不可能总是在shadow volume外面,所以我们来虚拟一个视点,这个视点总是存在所有的shadow volume的深度最深处。如果不理解的话,来看看下面的图片:

这样,我们在通过这个virtual eye来进行上面的算法,是不是就总是成功的了,对吧!而这样进行的话,就必然要与原来的运算相反。也就是说,对于virtual eye的front face实际上就是shadow volume的back face,而对于virtual eye的back face就是shadow view相对于真实eye的front face。同样的对于virtual eye来说的depth
test通过,对真真的eye来说实际上是depth test失败。好了,这样就能得到下面关于Z-FAIL的算法了:

1.使用环境光和发射光绘制正常的场景,获取depth test的值。

2.开启stencil buffer,关闭depth write, color buffer,先绘制shadow volume的背面,并且设置当depth test失败的时候,stencil buffer的值+1。然后再绘制shadow volume的前面,并且设置depth test成功的时候,stencil buffer的值-1。

3.使用屏幕渲染,渲染屏幕,绘制阴影。

好了,聪明吧!!!使用了一个等价代换的思想就能够将原本不能成功情况,变的成功了。这种算法,无论视点在何处,都能够成功的绘制出来。

读者啊,请注意,这里说明的virtual eye的思想,只是我对它这个算法的理解,John Carmack是如何想出Z-FAIL算法的,我并不知道,这里仅仅提供给大家参考,能够通过这种方法理解,最好不过了。

Ghost Shadow

在作者自己试验编写这个Demo的时候,总是会出现一个莫名其妙的Shadow,如下图所示:

上面的阴影是正常的,但是下面不知道怎么回事又出现了一个阴影。作者苦苦调试,改代码,弄了一整天,最后才在ShaderX系列文章中,关于Shadow Volume的一章中找到的原因所在。

原来构建Shadow volume的时候,你的Shadow volume需要无限延伸,也就是说,在绘制shadow volume的时候,它的背面肯定有一些被远裁剪面给裁剪掉了,这就导致了一部分的stencil buffer没有进行计数,从而出现这种情况。在ShaderX的文章中,提出了一种改进的策略,读者可以自行去了解。这里作者就偷下懒,没有将shadow volume构建的无限延伸,而是在深度范围以内,这也就没有问题了。

读者啊,这些方法都是通过计算机模拟出来的,并不是真实世界阴影形成的方式。他们只能够在一些条件情况下可以使用,可能在另外的环境,比如ShaderX中提到的Terrian地形上的阴影,就又会出现问题。所以,读者慢慢的挣扎吧!遇到这些情况,慢慢想办法来解决满足它。总有一天,我们完全可以利用计算进行真实情况下的阴影模拟。(作者傻帽了,现在好像就可以了,使用Ray tracing的方法,就能够模拟出来真实的情况了,可惜的是,这种算法计算量太大,现在的计算机硬件技术难以事实的计算出来,如果哪一天出现了能够实时的支持Ray
tracing, Photo ,Radiace这些图形学算法的时候,游戏的画面将会发生质的改变啊!!!期待ing。。。。)

好了,下面是最终的截图:

这篇文章只是介绍算法的思想,至于实现,就需要作者自己去慢慢的探索了。文章也没有讲述如何构建Shadow volume,感兴趣的读者可以看ShaderX中的文章来了解。

相关链接:

http://blog.163.com/wmk_2000_ren/blog/static/138846192201019114117466/

ShaderX2

时间: 2024-10-12 18:57:03

ZFXEngine开发笔记之Shadow Volume的相关文章

ZFXEngine开发笔记-SSE版本的ZFXVector

SSE介绍 在学习3D游戏编程大师技巧的时候,就了解到,可是使用一种称之为"单指令,多数据(SIMD)"的技术来编写3D数学库.通过这样的方法,可以将我们经常使用的诸如向量计算,矩阵变换等操作加快很多倍.这次,在学习3D引擎开发的时候,也用到了这个技术.SIMD是一种技术的名称,而并不是具体的工具.实现这种技术,不同的CPU厂商推出了不同的技术,像MMX, 3DNow!, SSE, SSE2, SSE3....由于我的计算机上使用的是Intel的处理器,它支持MMX,SSE,SSE2,

ZFXEngine开发笔记之3D模型格式支持(1)

作者:i_dovelemon 来源:CSDN 日期:2014 / 9 / 17 主题:3D Format, Milk 3D Shape, Chunk Based System, Skeleton Animation (文中以红色中字标示的文字,是整个文章的注意项,请读者留心) 引言 在3D游戏领域,艺术家们通过3D建模软件建立自己的艺术品模型和绚丽的3D场景.当我们想要在自己的游戏中使用这些模型的时候,我们就需要将这种模型变成能够被我们的引擎所识别的文件格式.每一个建模软件都有自己的文件格式,不

ZFXEngine开发笔记之Bump Mapping(2)

作者:i_dovelemon 日期: 2014 / 9 / 13 来源 : CSDN 主题 :Bump Mapping, Tangent Space, Normal Map, Height Map 引言 在上篇文章中,我讲述了如何根据高度图来创建法线图.并且承诺在后面会讲述3D几何学中的重要工具Tangent Space的相关知识.今天,就在这里,向大家讲述如何接下来的工作部分. Tangent Space 我们知道,在前篇文章中,讲述的法线图中的法线是在纹理图的空间中也就是Tangent Sp

ZFXEngine开发笔记之Omni Light

作者:i_dovelemon 日期:2014 / 9 / 5 来源:CSDN博客 主题:Per-pixel lighting, Omni light, early-z, Multi-pass, Assembly Shader 引言 在第一人称射击游戏中,经常需要点光源来完成光照计算.所以,这篇文章中将会讲述如何创建这个点光源,并且如何设计让引擎可以支持多光源渲染. Omni light介绍 所谓的Omni light,就是指点光源.不过在这里,我们进行点光源的光照计算的时候,没有用到顶点的法线来

ZFXEngine开发笔记- GPU硬件架构

作者:i_dovelemon 日期:2014 / 8 / 31 来源: CSDN博客 文章: GPU硬件架构 引言 在3D图形学中,可编程渲染管道的出现,无疑是一种创举.在下面的文章中,会向大家简要的介绍现如今的可编程渲染管道中最重要的Vertex Shader和Pixel Shader的硬件架构以及如何使用汇编语言来编写Shader. Vertex Shader 在硬件上,Vertex Shader中所有的运算都在一个名为vertex arithmetic logic unit(ALU)中进行

ZFXEngine开发笔记之Bump Mapping(1)

作者:i_dovelemon 日期:2014 / 9 / 7 来源:CSDN博客 主题:Bump Mapping, Tangent Space, Normal Map, Height Map 引言 我们知道,在真实世界中,任何物体的表面都不是非常光滑的,这种粗糙的表面效果,在光源的照射下会有明暗的感觉.那么,如何在3D图形中模拟这种效果了?是通过建模的方法,为模型的表面建造出粗糙的表面?这种方法很难做出真实的粗糙感出来,毕竟表面的粗糙程度是很细微的效果,很难用建模的方式建模出来.所以,只能用其他

Android开发笔记(一百零八)智能语音

智能语音技术 如今越来越多的app用到了语音播报功能,例如地图导航.天气预报.文字阅读.口语训练等等.语音技术主要分两块,一块是语音转文字,即语音识别:另一块是文字转语音,即语音合成. 对中文来说,和语音播报相关的一个技术是汉字转拼音,想想看,拼音本身就是音节拼读的标记,每个音节对应一段音频,那么一句的拼音便能用一连串的音频流合成而来.汉字转拼音的说明参见<Android开发笔记(八十三)多语言支持>. 语音合成通常也简称为TTS,即TextToSpeech(从文本到语言).语音合成技术把文字

(转)Shadow Map &amp; Shadow Volume

转自:http://blog.csdn.net/hippig/article/details/7858574 shadow volume 这个术语几乎是随着 DOOM3 的发布而成为FPS 玩家和图形学爱好者谈论的对象的.虽然这个游戏还没有上市,但是凭借 John Carmack 的传奇经历以及 DOOM3发布的一些让人惊讶的预览图片,我们仍然有理由认为它将会是 2004 年最热门的 FPS 游戏之一. id software向来都不吝惜为了达到最好的图像效果而使用最先进的渲染技术,这曾经使得玩

张高兴的 Windows 10 IoT 开发笔记:RTC 时钟模块 DS3231

原文:张高兴的 Windows 10 IoT 开发笔记:RTC 时钟模块 DS3231 GitHub:https://github.com/ZhangGaoxing/windows-iot-demo/tree/master/DS3231 注意:不包含闹钟设置