« 自剖一下自己用的NEHE OpenGL框架(下篇)忆我的第一次OpenGL »

Shadow Volume 阴影锥技术之探Ⅰ

尝试了一下Shadow Volume(阴影锥)技术.现在是最简单的一步:懂得Shadow Volume形成的算法基础.采用非模型的简单三角形为光源遮挡物,采用基于OPENGL固定渲染管线的Z-PASS算法.接下来将会继续探讨对复杂模型的阴影生成,Z-FAIL算法和shader实现版本等等,同时修正BUG。——ZwqXin.com

shadow volume demo1
图中橙色边面包围的那部分就是Shadow Volume,知道什么地方该产生阴影了吗?

shadow volume demo1
采用Z-PASS算法,最后渲染出三角形投影到地面的阴影
shadow volume demo1
某视觉下的效果图

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

在进行实现之前,有必要弄清楚Shadow Volume的算法原理。事实上为了看懂原理(表面上貌似简单),在上学期末两科考试之间那段时间真的在图书馆鏖战了很久(当然不只问本文这种简单的实现)。假期尝试由浅入深,在实现上真正弄明白。这里推荐当时看的一篇文章:阴影锥(shadow volume)原理与展望---真实的游戏效果的实现

虽然这次的实现,我觉得是好简单,结果却真的耗了我差不多一天,晕!在一些小而关键问题上翻了跟斗。这里必要提醒一下要注意的地方:

1. 一个平面的绘制方式(正时针逆时针绘制问题)是很重要的,知道重要还不够,画的时候要小心!这也是我对接下来针对复杂模型阴影的担心(虽然实在不行可以请教NEHE27课)。

2.一帧渲染完后重画,单单把蒙板(stencil)清0是不行的(0只是整数蒙板值的其中普通的一个而已),一定要重设蒙板glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 我的 NEHE框架中默认是glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 所以后面那里要自己添加,否则久把上一帧的结果带到下一帧了(另外框架中也要在设子设置像素格式那里开启蒙板缓存(见此),这个不用说的了是必要的一步)。

部分代码:

  1. void CMainFrame::ShadowCast()//生成阴影函数
  2. {
  3.     glPushAttrib(GL_ALL_ATTRIB_BITS);//先保存目前设置
  4.  
  5.     glEnable(GL_CULL_FACE);//打开可见面判断特性,接下来会看到它的用途
  6.     glDisable( GL_LIGHTING );// 关闭灯光,光照计算对阴影锥来说是多余的
  7.     // 关闭颜色缓存可写性,因为shadow volume不需要画出来
  8.     glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); 
  9.  
  10.     //关闭深度可写性,这样就保存了当前屏幕各个像素的深度了
  11.    glDepthMask(GL_FALSE);
  12.  
  13.    //开启深度测试,这样就可以比较新像素和旧像素的远近了!
  14.    glEnable(GL_DEPTH_TEST); 
  15.  
  16.    //开启蒙板测试,每个像素都有一个独立的蒙板值,拿来干嘛随你,这里拿来判断屏幕中拿些像素处于阴影中
  17.    glEnable(GL_STENCIL_TEST);
  18.                              
  19.    //全窗口屏幕各像素的蒙板缓存清0,好,准备好了!
  20.     glClearStencil(0);
  21.     /////准备工作结束,开始画shadow volume并设置蒙板/////
  22.  
  23.    //首先,设置深度的比较函数。因为关闭DEPTH TEST了,所以无论现在在图上画什么都不会影响像素原来的深度值 
  24.    //只有小于等于原像素深度值的那部分才画出来.
  25.    //(这个其实是本框架默认的,一般OPENGL渲染就该让前面的东西遮盖后面东西时,直接不画被遮盖的那部分,又叫画家算法)
  26.    glDepthFunc(GL_LESS); 
  27.  
  28.   //
  29.  
  30.    //上面是针对人眼的(人换个位置看东西状况就不同了)。下面是针对阴影的(只有光和景物相对位置变动才会改变)
  31.    glStencilFunc(GL_ALWAYS,1, 0xFFFFFFF);
  32.    glStencilOp(GL_KEEP,GL_KEEP,GL_INCR);
  33.  
  34.    //这两个函数堆在一起,表示接下来画出来的东西会让它所占用的那些像素(这里干脆叫"新占像素"啦)的蒙板值增加1,稍微解释一下啦:
  35.    //1.glStencilFunc前两个参数用来作比较,譬如 GL_GREATER, 1的话就是"新占像素的蒙板值大于1才画出来",这里是GL_ALWAYS,1,前者就表示新像素总能通过测试而被画出了,所以后面那个参数其实没什么用途;
  36.    //最后那个参数,明眼人一看就知道是个16进制表示的二进制数啦,其实跟IP掩码一样的,但它掩的是像素值。化为二进制数后位为1的那部分才比较,这里既然全F(111……)就是说全比较啦。所以也是没用途的(都GL_ALWAYS了)
  37.    
  38.    //2.glStencilOp表示处理结果,参数分别指明了:没通过深度测试和蒙板测试的时候怎样;通过了深度测试却没通过蒙板测试时怎样;都通过了时又怎样.
  39.    //上面都让它们全通过了,前两个参数能奈何什么,呵呵!所以根据最后那个参数,"新占像素"蒙板值都加1了.
  40.  
  41.    //说了那么多,究竟让什么的蒙板加1啊?根据Z-PASS算法,这里是让ShadowVolume面朝我们的那些面加1;怎么判断哪些面朝我们呢?阴影体画出来后你看得见的那些面咯(只渲染你所见的所有逆时针画出来的面)
  42.    glFrontFace(GL_CCW);//GL_CCW表示逆时针,只渲染看上去逆时针画的面 
  43.    ShadowVolume(false);//小函数卖面不卖线~
  44.  
  45.  
  46.    //按照Z-PASS算法,接下来是:正面背对逆的面蒙板减1.这个类似的.
  47.    //glStencilFunc(GL_ALWAYS,1, 0xFFFFFFF);//这句一样的就不要写了,OPENGL会直接继承上面的设置的
  48.    glStencilOp(GL_KEEP,GL_KEEP,GL_DECR);//接下来画的"新占像素"全部无条件通过,蒙板减1
  49.    glFrontFace(GL_CW);//变为:看上去顺时针的才画出来 
  50.    ShadowVolume(false);//再渲染一次shadow volume
  51.  
  52.    //然后就可以画阴影了,在全屏幕蒙板值不为0的地方画阴影(看到上面有点红有点黄的部分吗?蒙板测试结果,只有这里蒙板非0)
  53.    glStencilFunc(GL_NOTEQUAL,0, 0xFFFFFFF);
  54.    glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP);//让蒙板非0的像素通过测试,接受接下来对它的XX
  55.  
  56.    //画阴影也许有更好方法,这里沿用传统那种粗暴方法:直接途黑掉它算了!
  57.  
  58.      //1.关掉裁减,当你不确定自己接下来是否一定会按逆时针为正的规则作图
  59.        glDisable(GL_CULL_FACE);
  60.       //2.打开颜色缓存,因为“阴影”要画出来
  61.        glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); 
  62.        //3.开混合,让阴影颜色和阴影所再物体的本来颜色混合一下
  63.        glEnable(GL_BLEND);
  64.        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
  65.  
  66.        //4.转到正视图,这样画出来的矩形能覆盖全屏幕
  67.         OthoView();//自定义,和接下来的PerpectiveView()配合
  68.         glColor4f(0.0,0.0,0.0,0.9);
  69.         glRectf(0.0, 0.0, VB_WIDTH, VB_HEIGHT);
  70.         PerpectiveView();//转回透视视图
  71.  
  72.        //把刚才修改过的设置都设回原值
  73.        glDisable(GL_BLEND);
  74.        glDisable(GL_CULL_FACE);
  75.        glEnable( GL_LIGHTING );
  76.        glDepthFunc(GL_LEQUAL);
  77.        glDepthMask(GL_TRUE);
  78.        glDisable(GL_STENCIL_TEST);
  79.        glPopAttrib();
  80.  
  81. }

下一篇见:Shadow Volume 阴影锥技术之探Ⅱ(模型的影子)
放上本DEMO:shadowvolumeDemobyZwqxin.rar

按键:
           PageUp  移动光源
鼠标左键下按不放并移动鼠标:旋转视角;鼠标滚轮移动
 空格:转变查看模式:辅助可视阴影锥/光源射线模式/正常模式

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

  • quote 1.流浪地球
  • 你好,请问你这个DEMO中的场景盒子是怎样构造的?顶点的顺序按逆时针排列,6个面各是从哪个方向上看上去呢?和面法线的方向有关吗?我看NEHE27中是以法线方向按右手法则排列的,可您这里又不是这样。请问到底该如何做呢?GL_QUADS和GL_TRIANGLE_STRIP有区别吗?请指教
    zwqxin 于 2011-3-23 22:35:22 回复
    太遥远了点不太记得了,一般我都是按右手定则的(应该是视点在盒子内侧)。GL_QUADS和GL_TRIANGLE_STRIP的区别请网上找资料参考一下,用在这里除了数据外没什么区别。
  • 2011-3-22 22:52:37 回复该留言
  • quote 2.流浪地球
  • 你好,请问你的相机类中:
    m_fAngleYaw += m_fRotationSpeed*(oldx-x)*PI/360;
    m_fAnglePitch -= m_fRotationSpeed*(oldy-y)*PI/360;
    上面为什么是除以360,PI不是对应180吗?

    下面这段变换,是怎样的原理呢?能推荐我一些关于这些变换的资料吗?非常感谢~!
    m_fDir[0] = sin(m_fAngleYaw)*cos(m_fAnglePitch);
    m_fDir[1] = sin(m_fAnglePitch);
    m_fDir[2] = -cos(m_fAngleYaw)*cos(m_fAnglePitch);

    m_fUp[0] = -sin(m_fAngleYaw)*sin(m_fAnglePitch);
    m_fUp[1] = cos(m_fAnglePitch);
    m_fUp[2] = sin(m_fAnglePitch)*cos(m_fAngleYaw);
    zwqxin 于 2011-3-26 22:54:01 回复
    那个只是Yaw和Pitch的“速度”,数值无须太在意,你除多少度都可。后者,你可以在网上搜索一下“欧拉角”、“球坐标”之类的。
  • 2011-3-25 21:10:06 回复该留言

发表评论:

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

IE下本页面显示有问题?

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

日历

Search

网站分类

最新评论及回复

最近发表

Powered By Z-Blog 1.8 Walle Build 100427

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