参考链接: Java中静态函数的阴影(方法隐藏)

转载原创:ZJU_fish1996   http://blog.csdn.net/zju_fish1996/article/details/51932954

source:原文地址

code:点击可以直接下载源代码

1978年,Lance Williams在其发表的论文《Casting curved shadows on curved surfaces》中提出了Shadow mapping算法,从那以后,该算法在离线渲染和实时渲染两个领域都得到了广泛的应用。皮尔斯动画工作室的Renderman渲染器、以及一些知名电影如《玩具总动员》都使用了shadow mapping技术。

在众多图形应用的阴影技术中,shadow mapping只是产生阴影的一种方法,它有着自身的优缺点:

优点:

1.不需要了解场景中的物体,因为shadow mapping是图像空间的技术,它会自动地随着GPU上物体的创建和改变发挥效应。

2.对于每个灯光而言,只需要一张纹理来维护阴影信息,而不需要使用模板缓冲区。

3.避免了shadow volumes算法的高填充率的缺陷

缺点:

1.易走样,尤其在使用小的阴影图的情况下。

2.在每个灯光下,场景中的物体必须都进行一次绘制,以产生点光源的阴影贴图,而对于全向点光源,绘制的次数会更多。

该教程只实现了一个点光源的基本阴影映射,也有很多人发表了如何发展改进这一技术的论文。

理论

考虑一个硬阴影效果下由点光源照亮的简单场景,在给定场景中一个点的情况下,我们如何知道它是被照亮的部分,还是在阴影中?简单的说,如果场景中的一个点和光源的连线中间没有其它遮挡物的话,那么这个点就是被照亮的。理解shadow mapping的关键步骤就是,理解这些点就是以光源为视点下的可见点。         我们已经掌握了在给定视点下判断可见性的技术,并且几乎在任何使用3d硬件绘制场景时都会用到这一技术,这个技术就是z-buffer消隐算法。所以,以光源为视点绘制场景的情况下,可以通过深度测试的点就是那些不在阴影中的点。

如果我们以光源为视点绘制场景,我们可以先保存深度缓冲区的值,然后,我们再以摄像机为视角绘制场景,我们将保存的深度缓冲区的值从光源位置投影为纹理。对于给定的一个点而言,我们比较比较投影到该点的阴影以及该点到光源的距离,来计算该点是否在阴影中。

假设深度纹理中存储的值为D,点到光源的距离为R:

R = D光源与点的连线中没有物体遮挡,该点不在阴影中R > D光源与点的连线中有物体遮挡,该点在阴影中

应用

我们如何使用OpenGL来实现上述过程?

这个技术要求我们至少绘制两遍场景,为了保证每次绘制更简单,我们绘制了三次。

首先,我们以光源为视点绘制场景。这个可以通过在gluLookAt函数中,设置从光源位置望向场景中心。场景如常绘制,并且读取深度缓存。

所有的阴影计算都是在深度缓存的精度下进行的。使用相等来测试不在阴影中的点,很可能会因为精度误差而产生不正确的结果,这和我们不使用”==“来比较浮点数是一个道理。所以,当我们从光源视角绘制场景时,我们要求OpenGL剔除前向面,因此被写入阴影映射的是物体的背面。这种情况下,存储在阴影映射中的深度值会比光源照射到正面的深度值要大一些。我们使用D>=R来检测不在阴影中的点,所有光源下表面可见的点都不在阴影中。这样我们就利用背面(在光源视角下)把这个问题转化为精确的问题,因为它们仍然符合阴影的定义,所以不会对比较结果产生影响。

这个技术只针对封闭物体,如果场景中存在开放的物体,我们需要使用多边形位移技术来增加深度缓冲区的值。

为了简化这一问题,我们在第一次绘制的时候使用标准的后缓存,这意味着我们的窗口大到足以放下整个阴影纹理,并且不被其他窗体遮挡。这个限制可以通过使用离线缓存来产生纹理以避免。

另外两次绘制都是从相机视角绘制的。首先,我们用一个比较昏暗的灯光绘制整个场景,来表达阴影中显示的效果。这一次仅用环境光来绘制。然而,为了保证阴影中的曲面表面不产生不自然的平坦,我们使用了较暗的漫反射光源。

第三次绘制的就是我们前面提到的阴影比较。这个比较是shadow mapping中至关重要的一点,它事实上可以直接利用硬件来逐像素的进行比较,使用ARB提供的扩展,ARB_shadow。我们设置纹理单位,这样比较就能影响到alpha值以及颜色成分了。所有的片段如果不能通过比较,将会得到alpha为0的值,而通过比较的则会得到alpha为1的值。利用alpha测试,我们可以丢弃那些本该是阴影的片段。现在使用更亮的管线来允许我们绘制场景中被光照到的部分。

使用深度纹理的线性滤波,可以过滤纹理比较后产生的值,这叫做PCF,它能够得到边缘的软阴影。如果我们允许更小的alpha值通过alpha测试,那么被照亮的片段将和阴影混合,可能会比帧缓存中的像素更暗,这样就产生了阴影边界的黑色边框。所以,在这个样例中,alpha测试被用于丢弃所有不被完全照射的区域。暗边界的产生将会使用另一种不同的、更为复杂的方法来实现这两次绘制。在我们的shadow mapping工程里,使用了max混合来合并结果。为了保证简单,我们没有使用PCF技术。

投影纹理

我们如何将光源下的深度缓存,保存在阴影中,并且在摄影机视角渲染到场景物体中呢?

首先,我们来看一下我们使用的坐标系和矩阵:

这个阴影映射是光源视角的一个快照,是光源裁剪空间的二维投影。为了实现纹理投影,我们使用opengl中的 EYE_LINEAR纹理坐标生成,来产生视点位置下顶点的坐标。我们需要使用纹理矩阵,将纹理坐标映射为一个适合于访问阴影图的坐标。因此纹理矩阵需要完成上述绿色箭头标出操作。

实现这一过程的最好方式是:

T纹理矩阵Pl光源投影矩阵Vl光源视点矩阵Vc相机视点矩阵

OpenGL将一个矩阵M以MT的方式应用于一个矩阵坐标T,这将通过世界空间和光源视点空间,把相机坐标空间转换到光源裁剪空间。这避免了物体空间以及任何模型矩阵的使用,并且因此不再需要对我们绘制的每个模型重复运算。

当纹理坐标被转换到光源裁剪空间后,我们还要执行一步操作。在透视除法后,裁剪后的x,y,z坐标的范围落在[-1,1]内,而纹理映射的坐标范围在[0,1]中,而深度值也在[0,1]范围内。我们需要产生一个简单的矩阵,来把[-1,1]映射到[0,1]上。对于每个X,Y,Z坐标,我们都要把我们的纹理矩阵上左乘这个矩阵。

在映射的过程中,我们可以避免使用纹理矩阵。这个可以通过在我们打开EYE_LINEAR时,指定一个矩阵来实现。典型的允许一个坐标的纹理坐标生成的代码如下:

[cpp]

view plain

copy

glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);   glTexGenfv(GL_S, GL_EYE_PLANE, VECTOR4D(1.0f, 0.0f, 0.0f, 0.0f));   glEnable(GL_TEXTURE_GEN_S);

如果我们同时考虑这四个纹理坐标的观察屏幕,它们形成了4 x 4的单位矩阵。纹理矩阵将这些”texgen“矩阵的基础上产生,然后将使用纹理矩阵来操作。我们可以通过忽略纹理矩阵,而把我们将使用到的纹理矩阵使用的数据直接放置到观察平面上,来实现一个小的加速。

最终,大多数设置投影的复杂运算就是计算Vc的逆矩阵了。OpenGL会为我们完成这一操作。当观察平面被确定后,GL将会自动地将其与当前模型矩阵相乘。我们所需要做的只是保证在这个时候,模型矩阵包括了摄像机视角矩阵,其逆将与我们的texgen矩阵相乘。

所以,最终设置纹理投影,包括优化的代码如下:

[cpp]

view plain

copy

//Calculate texture matrix for projection   //This matrix takes us from eye space to the light's clip space   //It is postmultiplied by the inverse of the current view matrix when specifying texgen      static MATRIX4X4 biasMatrix   (0.5f, 0.0f, 0.0f, 0.0f,   0.0f, 0.5f, 0.0f, 0.0f,   0.0f, 0.0f, 0.5f, 0.0f,   0.5f, 0.5f, 0.5f, 1.0f);      MATRIX4X4 textureMatrix=biasMatrix*lightProjectionMatrix*lightViewMatrix;      //Set up texture coordinate generation.   glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);   glTexGenfv(GL_S, GL_EYE_PLANE, textureMatrix.GetRow(0));   glEnable(GL_TEXTURE_GEN_S);      glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);   glTexGenfv(GL_T, GL_EYE_PLANE, textureMatrix.GetRow(1));   glEnable(GL_TEXTURE_GEN_T);      glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);   glTexGenfv(GL_R, GL_EYE_PLANE, textureMatrix.GetRow(2));   glEnable(GL_TEXTURE_GEN_R);      glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);   glTexGenfv(GL_Q, GL_EYE_PLANE, textureMatrix.GetRow(3));   glEnable(GL_TEXTURE_GEN_Q);

拓展使用

在这个项目中,我们只使用了两个拓展, ARB_depth_texure和ARG_shadow.

代码

这个实例使用的代码量非常小,主要原因如下:

1.我们不需要手动的生成任何特定几何体,因为shadow mapping算法不需要提取轮廓线,也不需要其它附加的顶点性质,如切向量。

所有的几何体都是使用glutSolidSphere和一些简单的指令绘制的。

2.主要的工作都是由硬件完成的。

shadow mapping比较只需要几行代码来允许硬件操作,然后它就会被自动执行。

首先我们创建3个无符号整数来维护显示列表的编号。一个显示列表用于绘制场景的一部分,由于变量被定义为静态的,我们可以在使用前获得变量的值。

[cpp]

view plain

copy

//Display lists for objects   static GLuint spheresList=0, torusList=0,baseList=0;

如果变量sphereList为0,我们使用glGenLists,在spheresList中保存新的显示列表编号。新的显示列表编号不为0,因此代码只会被执行一次。这样就将OpenGL产生4个球体的指令存储在了显示列表中。

[cpp]

view plain

copy

//Create spheres list ifnecessary    if(!spheresList)    {        spheresList=glGenLists(1);        glNewList(spheresList,GL_COMPILE);        {            glColor3f(0.0f,1.0f, 0.0f);            glPushMatrix();               glTranslatef(0.45f,1.0f, 0.45f);            glutSolidSphere(0.2,24, 24);               glTranslatef(-0.9f,0.0f, 0.0f);            glutSolidSphere(0.2,24, 24);               glTranslatef(0.0f,0.0f,-0.9f);            glutSolidSphere(0.2,24, 24);               glTranslatef(0.9f,0.0f, 0.0f);            glutSolidSphere(0.2,24, 24);               glPopMatrix();        }        glEndList();    }

类似的,我们产生平面和圆环的显示列表:

[cpp]

view plain

copy

//Create torus if necessary   if(!torusList)   {       torusList=glGenLists(1);       glNewList(torusList,GL_COMPILE);       {           glColor3f(1.0f,0.0f, 0.0f);           glPushMatrix();              glTranslatef(0.0f,0.5f, 0.0f);           glRotatef(90.0f,1.0f, 0.0f, 0.0f);           glutSolidTorus(0.2,0.5, 24, 48);              glPopMatrix();       }       glEndList();   }      //Create base if necessary   if(!baseList)   {       baseList=glGenLists(1);       glNewList(baseList,GL_COMPILE);       {           glColor3f(0.0f,0.0f, 1.0f);           glPushMatrix();              glScalef(1.0f,0.05f, 1.0f);           glutSolidCube(3.0f);              glPopMatrix();       }       glEndList();   }

现在,我们使用显示列表来绘制随着angles变化而旋转的球,每一次这个函数被调用后,除了第一次调用,这是唯一被执行的部分:

[cpp]

view plain

copy

//Draw objects   glCallList(baseList);   glCallList(torusList);      glPushMatrix();   glRotatef(angle, 0.0f, 1.0f, 0.0f);   glCallList(spheresList);   glPopMatrix();

现在,我们来看看主要的代码文件,这里面包含了所有有趣的代码。

首先我们需要包含必要的头文件,包括glee.h,一个opengl的扩展库。

[cpp]

view plain

copy

#define WIN32_LEAN_AND_MEAN   #include <windows.h>   #include <stdio.h>   #include "GLee/GLee.h" //GL header file, including extensions   #include <GL/glut.h>   #include "Maths/Maths.h"   #include "TIMER.h"   #include "FPS_COUNTER.h"   #include "scene.h"   #include "main.h"

现在创建我们的全局对象,计时器和fps。

[cpp]

view plain

copy

//Timer used for frame rate independent movement   TIMER timer;      //Frames per second counter   FPS_COUNTER fpsCounter;

之后,我们创建一些全局变量,相机和灯光的位置是在这里给出的固定值。我们同时把shadow map的大小固定设为512x512,并且存储创建shadow map纹理编号的空间。另外,我们还创建空间来维护相机以及光源视角下的投影和视区矩阵。

[cpp]

view plain

copy

//Camera & light positions   VECTOR3D cameraPosition(-2.5f, 3.5f,-2.5f);   VECTOR3D lightPosition(2.0f, 3.0f,-2.0f);      //Size of shadow map   const int shadowMapSize=512;      //Textures   GLuint shadowMapTexture;      //window size   int windowWidth, windowHeight;      //Matrices   MATRIX4X4 lightProjectionMatrix, lightViewMatrix;   MATRIX4X4 cameraProjectionMatrix, cameraViewMatrix;

初始化函数也会在代码的开始被调用:

[cpp]

view plain

copy

//Called for initiation   bool Init(void)   {

首先我们使用glee库来检查ARB_depth_texture和ARB_shadow扩展是否被支持

[cpp]

view plain

copy

//Check for necessaryextensions   if(!GLEE_ARB_depth_texture || !GLEE_ARB_shadow)   {       printf("I requireARB_depth_texture and ARB_shadow extensionsn\n");       return false;   }

现在我们设置模型视区矩阵、着色、深度测试的初始状态,我们同时允许背面剔除来获得加速。因为我们使用到了glScale来绘制场景,所以我们需要开启GL_NORMALIZE。

[cpp]

view plain

copy

//Load identity modelview   glMatrixMode(GL_MODELVIEW);   glLoadIdentity();      //Shading states   glShadeModel(GL_SMOOTH);   glClearColor(0.0f, 0.0f, 0.0f, 0.0f);   glColor4f(1.0f, 1.0f, 1.0f, 1.0f);   glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);      //Depth states   glClearDepth(1.0f);   glDepthFunc(GL_LEQUAL);   glEnable(GL_DEPTH_TEST);      glEnable(GL_CULL_FACE);      //We use glScale when drawing the scene   glEnable(GL_NORMALIZE);

下一步就是创建阴影映射纹理。这是一张尺寸为shadowMap大小的方形纹理,并且格式为DEPTH_COMPONENT,我们不希望用任何东西来初始化这个阴影数据,所以我们把它的像素指针指向NULL。

[cpp]

view plain

copy

//Create the shadow maptexture   glGenTextures(1, &shadowMapTexture);   glBindTexture(GL_TEXTURE_2D, shadowMapTexture);   glTexImage2D( GL_TEXTURE_2D, 0,GL_DEPTH_COMPONENT, shadowMapSize, shadowMapSize, 0,       GL_DEPTH_COMPONENT,GL_UNSIGNED_BYTE, NULL);   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST);   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST);   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_CLAMP);   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP);

我们想要一个改变场景中物体的漫反射和环境反射材质属性的更简单的方法,所以我们使用了glColorMaterial,所以颜色的改变也会影响材质。

我们设置所有表面的镜面反射颜色为白色,镜面反射指数为16.

[cpp]

view plain

copy

//Use the color as theambient and diffuse material   glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE);   glEnable(GL_COLOR_MATERIAL);      //White specular material color, shininess 16   glMaterialfv(GL_FRONT, GL_SPECULAR, white);   glMaterialf(GL_FRONT, GL_SHININESS, 16.0f);

相机和光源的矩阵在这里被设置,并被保存到全局变量中。

首先,我们保存当前的模型视图矩阵。然后,对于每个我们想要设置的矩阵,我们首先将当前矩阵初始化为单位矩阵,然后调用相关的opengl函数,在模型视图堆栈上创建相关的矩阵。这些矩阵随后被读入全局矩阵变量中。最终, 我们还原模型视图矩阵。

注意到我们创建了所有的矩阵,包括在模型视图堆栈上创建投影矩阵。这就是为什么GetFloatv总是读取模型视图矩阵。

光源和相机有不同的投影矩阵。

为了尽可能提升精度,光源的远近平面被放置得尽可能接近。并且,光源使用的宽高比为1,所以视线体是一个被截断的四棱锥。

[cpp]

view plain

copy

//Calculate & savematrices   glPushMatrix();      glLoadIdentity();   gluPerspective(45.0f,(float)windowWidth/windowHeight, 1.0f, 100.0f);   glGetFloatv(GL_MODELVIEW_MATRIX,cameraProjectionMatrix);      glLoadIdentity();   gluLookAt(cameraPosition.x, cameraPosition.y,cameraPosition.z,   0.0f, 0.0f, 0.0f,   0.0f, 1.0f, 0.0f);   glGetFloatv(GL_MODELVIEW_MATRIX,cameraViewMatrix);      glLoadIdentity();   gluPerspective(45.0f, 1.0f, 2.0f, 8.0f);   glGetFloatv(GL_MODELVIEW_MATRIX,lightProjectionMatrix);      glLoadIdentity();   gluLookAt( lightPosition.x, lightPosition.y,lightPosition.z,   0.0f, 0.0f, 0.0f,   0.0f, 1.0f, 0.0f);   glGetFloatv(GL_MODELVIEW_MATRIX,lightViewMatrix);      glPopMatrix();

最终,我们重设计时器,并返回真。

[cpp]

view plain

copy

//Reset timer   timer.Reset();      return true;

显示函数在绘制帧的时候被调用。

[cpp]

view plain

copy

//Called to draw scene   void Display(void)   {

首先,我们计算球体的旋转角度。使用计时器,旋转率将与帧刷新频率独立开来。

[cpp]

view plain

copy

//angle of spheres in scene.Calculate from time   float angle=timer.GetTime()/10;

第一次绘制的时候,我们从光源视点来绘制场景。清空颜色和深度缓存,并且设置光源的投影矩阵和模型矩阵。使用和shadow map一样大小的作为窗口大小。

[cpp]

view plain

copy

//First pass - from light'spoint of view   glClear(GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT);      glMatrixMode(GL_PROJECTION);   glLoadMatrixf(lightProjectionMatrix);      glMatrixMode(GL_MODELVIEW);   glLoadMatrixf(lightViewMatrix);      //Use viewport the same size as the shadow map   glViewport(0, 0, shadowMapSize, shadowMapSize);

在这里,我们让opengl为我们剔除正面,所以背面被绘制到shadow map中。这个处理方法在前面已经解释过了。我们同时需要禁用写入颜色缓存,并且使用面片光照。因为我们仅对深度缓存的内容感兴趣。

[cpp]

view plain

copy

//Draw back faces into theshadow map   glCullFace(GL_FRONT);      //Disable color writes, and use flat shading forspeed   glShadeModel(GL_FLAT);   glColorMask(0, 0, 0, 0);

现在,我们可以开始绘制场景了:

[cpp]

view plain

copy

//Draw the scene   DrawScene(angle);

CopyTexSubImage2D用于把当前帧缓存的内容复制到纹理中。我们首先绑定阴影映射纹理,然后再把视区复制到纹理。因为我们绑定了一个DEPTH_COMPONENT的纹理,数据将会自动从深度缓存中读入。

[cpp]

view plain

copy

//Read the depth buffer intothe shadow map texture   glBindTexture(GL_TEXTURE_2D, shadowMapTexture);   glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0,shadowMapSize, shadowMapSize);

第一次绘制的最后,我们恢复原来的状态。

[cpp]

view plain

copy

//restore states   glCullFace(GL_BACK);   glShadeModel(GL_SMOOTH);   glColorMask(1, 1, 1, 1);

第二次绘制时,我们从相机的视角绘制场景,我们把光源设置为阴影区域的亮度。首先,我们清除深度缓存。我们不需要清除颜色缓存,因为我们还没有写任何东西。然后,我们设置相机是叫的矩阵,并且使用包括整个窗体的视区大小。

[cpp]

view plain

copy

//2nd pass - Draw fromcamera's point of view   glClear(GL_DEPTH_BUFFER_BIT);   glMatrixMode(GL_PROJECTION);   glLoadMatrixf(cameraProjectionMatrix);      glMatrixMode(GL_MODELVIEW);   glLoadMatrixf(cameraViewMatrix);      glViewport(0, 0, windowWidth, windowHeight);

接下来设置灯光,我们使用一个较暗的漫反射光以及为0的镜面反射亮度。

[cpp]

view plain

copy

//Use dim light to representshadowed areas   glLightfv(GL_LIGHT1, GL_POSITION,VECTOR4D(lightPosition));   glLightfv(GL_LIGHT1, GL_AMBIENT, white*0.2f);   glLightfv(GL_LIGHT1, GL_DIFFUSE, white*0.2f);   glLightfv(GL_LIGHT1, GL_SPECULAR, black);   glEnable(GL_LIGHT1);   glEnable(GL_LIGHTING);      Scene(angle);

第三次绘制是实际的阴影计算。如果一个片段通过了阴影测试(说明它不是阴影部分的)那么我们希望它在亮光下被绘制,覆盖前一次暗光绘制的效果。所以,我们允许亮光,并且使用所有的镜面光亮度。

[cpp]

view plain

copy

//3rd pass   //Draw with bright light   glLightfv(GL_LIGHT1, GL_DIFFUSE, white);   glLightfv(GL_LIGHT1, GL_SPECULAR, white);

接下来,我们计算texgen矩阵,它将用于把阴影映射投影到场景上,并且允许纹理坐标生成。

[cpp]

view plain

copy

//Calculate texture matrixfor projection   //This matrix takes us from eye space to thelight's clip space   //It is postmultiplied by the inverse of thecurrent view matrix when specifying texgen   static MATRIX4X4 biasMatrix(0.5f, 0.0f, 0.0f,0.0f,   0.0f, 0.5f, 0.0f, 0.0f,   0.0f, 0.0f, 0.5f, 0.0f,   0.5f, 0.5f, 0.5f, 1.0f); //bias from [-1, 1] to[0, 1]   MATRIX4X4textureMatrix=biasMatrix*lightProjectionMatrix*lightViewMatrix;      //Set up texture coordinate generation.   glTexGeni(GL_S, GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR);   glTexGenfv(GL_S, GL_EYE_PLANE,textureMatrix.GetRow(0));   glEnable(GL_TEXTURE_GEN_S);      glTexGeni(GL_T, GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR);   glTexGenfv(GL_T, GL_EYE_PLANE, textureMatrix.GetRow(1));   glEnable(GL_TEXTURE_GEN_T);      glTexGeni(GL_R, GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR);   glTexGenfv(GL_R, GL_EYE_PLANE,textureMatrix.GetRow(2));   glEnable(GL_TEXTURE_GEN_R);      glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR);   glTexGenfv(GL_Q, GL_EYE_PLANE,textureMatrix.GetRow(3));   glEnable(GL_TEXTURE_GEN_Q);

现在,我们绑定并且允许阴影映射纹理,并且设置自动纹理比较。首先我们允许比较,并且告诉GL在r小于等于纹理中的值时,通过测试。阴影比较将对于每个片段产生0或1的结果。我们让GL把这些都替换成4个颜色通道(一个复制到4个中,即产生一个灰度值)

[cpp]

view plain

copy

//Bind & enable shadowmap texture   glBindTexture(GL_TEXTURE_2D, shadowMapTexture);   glEnable(GL_TEXTURE_2D);      //Enable shadow comparison   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);      //Shadow comparison should be true (ie not inshadow) if r<=texture   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);      //Shadow comparison should generate an INTENSITYresult   glTexParameteri(GL_TEXTURE_2D,GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY);

如果阴影比较通过了,将会产生为1的alpha值。所以,我们使用alpha测试来剔除那些小于0.99的片段。用这种方法,没有通过阴影测试的片段将不被绘制,所以这就允许了前一次绘制的暗场景效果被显示在屏幕上。

[cpp]

view plain

copy

//Set alpha test to discardfalse comparisons   glAlphaFunc(GL_GEQUAL, 0.99f);   glEnable(GL_ALPHA_TEST);

之后,是场景的第三次绘制,也是最后一次绘制,然后所有的状态都被重设了。

[cpp]

view plain

copy

DrawScene(angle);      //Disable textures and texgen   glDisable(GL_TEXTURE_2D);      glDisable(GL_TEXTURE_GEN_S);   glDisable(GL_TEXTURE_GEN_T);   glDisable(GL_TEXTURE_GEN_R);   glDisable(GL_TEXTURE_GEN_Q);      //Restore other states   glDisable(GL_LIGHTING);   glDisable(GL_ALPHA_TEST);

为了显示这个样例在你的电脑上运行状态如何,我们将会在屏幕上方显示fps大小。为了做到这一点,我们首先调用FPS_COUNTER::Update来计算fps。

[cpp]

view plain

copy

//Update frames per secondcounter   fpsCounter.Update();

sprintf被用于把fps从float转换为string

[cpp]

view plain

copy

//Print fps   static char fpsString[32];   sprintf(fpsString, "%.2f", fpsCounter.GetFps());

然后,投影和模型矩阵被设置为一个简单的正投影,旧的矩阵利用堆栈被保存起来。

[cpp]

view plain

copy

//Set matrices for ortho   glMatrixMode(GL_PROJECTION);   glPushMatrix();   glLoadIdentity();   gluOrtho2D(-1.0f, 1.0f, -1.0f, 1.0f);      glMatrixMode(GL_MODELVIEW);   glPushMatrix();   glLoadIdentity();

现在,我们调用glut库的函数,一次性输出所有文字。

[cpp]

view plain

copy

//Print text   glRasterPos2f(-1.0f, 0.9f);   for(unsigned int i=0; i<strlen(fpsString);++i)       glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,fpsString[i]);

旧的投影和模型矩阵现在从堆栈上被恢复。

[cpp]

view plain

copy

//reset matrices   glMatrixMode(GL_PROJECTION);   glPopMatrix();   glMatrixMode(GL_MODELVIEW);   glPopMatrix();

我们现在完成了帧的绘制,所以调用glFinish,然后告诉glut库交换前后缓冲区,最终我们调用glutPostRedisplay来要求下一帧尽可能快地绘制。

[cpp]

view plain

copy

glFinish();   glutSwapBuffers();   glutPostRedisplay();

reshape函数在窗体大小被改变的时候调用(包括窗口创建时),它首先把当前的窗口大小存储到全局变量中,所以视窗可以在第二次绘制的时候被正确的重建。

[cpp]

view plain

copy

//Called on window resize   void Reshape(int w, int h)   {       //Save new window size       windowWidth=w, windowHeight=h;

相机投影矩阵在窗体大小被改变后,也会发生变化。因为它被存储在全局变量中,并且仅在必要的时候发送给GL,我们按我们设置这个变量的方式,来更新这个变量。我们先保存当前的模型矩阵,再把当前矩阵设置为单位矩阵,然后新的相机投影矩阵被创建,并且被存储。最后,我们恢复原来的模型矩阵。

[cpp]

view plain

copy

//Update the camera'sprojection matrix       glPushMatrix();       glLoadIdentity();       gluPerspective(45.0f, (float)windowWidth/windowHeight,1.0f, 100.0f);       glGetFloatv(GL_MODELVIEW_MATRIX,cameraProjectionMatrix);       glPopMatrix();   }

键盘回调函数在键盘被按下后调用。如果我们按了escape键,程序将退出。如果按了p键,计时器会停止,动画也将暂停。按u键可以重启计时器。.

[cpp]

view plain

copy

//Called when a key is pressed   void Keyboard(unsigned char key, int x, int y)   {       //If escape is pressed, exit       if(key==27)       exit(0);          //Use P to pause the animation and U to unpause       if(key=='P' || key=='p')       timer.Pause();          if(key=='U' || key=='u')       timer.Unpause();   }

我们的主函数初始化了glut和窗体,然后调用了init函数,检查是否出错,如果返回false,代码退出。窗体创建得足够容纳512X512的阴影映射。之后我们设置了glut的回调函数,并且进入了主循环。

[cpp]

view plain

copy

int main(int argc, char** argv)   {       glutInit(&argc, argv);       glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB |GLUT_DEPTH);       glutInitWindowSize(640, 512);       glutCreateWindow("Shadow Mapping");          if(!Init())           return 0;          glutDisplayFunc(Display);       glutReshapeFunc(Reshape);       glutKeyboardFunc(Keyboard);       glutMainLoop();       return 0;   }

[转载] [OpenGL] shadow mapping(实时阴影映射)相关推荐

  1. OpenGL shadow mapping 阴影贴图的实例

    OpenGL shadow mapping 阴影贴图 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> #i ...

  2. OpenGL Shadow Mapping阴影贴图的实例

    OpenGL Shadow Mapping阴影贴图 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include "vapp.h" #inc ...

  3. OpenGL基础53:阴影映射(下)

    接上文:OpenGL基础52:阴影映射(上) 五.阴影失真 按照上文的计算的结果,一个很明显的问题是:对于参与计算深度贴图的物体,其表面可以看到这样的栅格状的阴影,这种常见的错误表现也叫做阴影失真(S ...

  4. [OpenGL] shadow mapping(实时阴影映射)

    source:原文地址 code:点击可以直接下载源代码 1978年,Lance Williams在其发表的论文<Casting curved shadows on curved surface ...

  5. OpenGL基础52:阴影映射(上)

    参考于:https://learnopengl.com/#!Advanced-Lighting/Shadows/Shadow-Mapping 一.游戏中的阴影 阴影是光线被阻挡的结果,当一个光源的光线 ...

  6. QT with OpenGL(Shadow Mapping)(面光源篇)

    面光源与平行光源的生成大多相同,因此这里只说明面光源与平行光源的区别. 关于平行光源的细节看这篇blog 1. 生成深度帧缓存 对于每一个面光源都要生成一个帧缓存 depthMapFBO = new ...

  7. OpenGL Shadow Mapping阴影贴图的实例

    OpenGL 阴影贴图 先上图,再解答. 正常显示 按下2键 按下3键 完整主要的源代码 源代码剖析 先上图,再解答. 正常显示 按下2键 按下3键

  8. Threejs中的Shadow Mapping(阴影贴图)

    简而言之,步骤如下: 1.从灯光位置视点(阴影相机)创建深度图. 2.从相机的位置角度进行屏幕渲染,在每个像素点,比较由阴影相机的MVP矩阵计算的深度值和深度图的值的大小,如果深度图值小的话,则表示该 ...

  9. 【Shading】Shadow Mapping 阴影映射

    课程来源:GAMES101-现代计算机图形学入门-闫令琪 Lecture12 GAMES101 现代计算机图形学入门 主讲老师:闫令琪,UCSB 课程主页:https://sites.cs.ucsb. ...

最新文章

  1. 大数据促健康产业高增长
  2. 【C语言】练习5-8
  3. Spring REST:异常处理卷。 1个
  4. Android实现计算器布局(线性布局)
  5. 如何搭建企业大数据分析平台
  6. Axure8.0汉化包+注册码
  7. 双歧杆菌基因组序列批量下载、基因组注释、antiSMASH合成基因簇挖掘、核心基因的同源性比较。
  8. 对微软winsock PC端开发蓝牙疑问
  9. 万人总结的软件测试面试简历及软件测试面试题
  10. 计算机程序中的自省程序(反射程序)(introspective program)是什么?(introspectable、introspection)
  11. 通过实例理解Go Execution Tracer
  12. C++的字符串输入方式
  13. Visual Haze Removal by a Unified GenerativeAdversarial Network(基于生成式对抗网络的图像去雾IEEE2019)
  14. 【Codeforces 549F】Yura and Developers | 单调栈、启发式合并、二分
  15. Andr oid 多窗 口编程
  16. 网络信息安全管理之资产、脆弱性、威胁、风险
  17. 震动传感器(模拟和数字)
  18. Digimat-MF:微观-宏观(两尺度)方法
  19. java正则开头结尾_以m开头以d结尾的正则表达式-正则表达式以什么开头-正则表达式不以0开头...
  20. JavaScript代码压缩工具UglifyJS和Google Closure Compiler的基本用法

热门文章

  1. Vim进阶2 map映射
  2. android图片浏览远近,快图浏览编辑图片方法介绍_怎么编辑图片_3DM手游
  3. php项目实战流程_一个完整的php流程管理实例代码分享
  4. vf求计算机系统当前日期的年份数,计算机二级VF常用函数列表
  5. MYSQL的集群的安装与配置(mysql-5.1.21)
  6. 【Day41】Python之路——AJAX
  7. sharepoint 2007功能增强解决方案,资料收集
  8. 【js】JavaScript parser实现浅析
  9. 西瓜仿站高手v1.08官方正式版
  10. V 8 nfs+drbd+heartbeat