OpenGL阴影贴图详解

既然模拟出了光照,那么也少不了阴影,阴影的产生是因为距离光线较近的物体遮挡了距离较远的物体,导致被遮挡的物体接受的光照少于遮挡物的,因此阴影的产生与否与物体到光源的位置有关系,静态物体的阴影可以用光照贴图来模拟,而动态阴影要用阴影锥或者阴影贴图实现,阴影锥会引入许多额外的顶点为管线带来负担,目前比较流行的阴影模拟方法是用阴影贴图,它的好处在于只是用纹理存储物体的深度信息而并不会引入额外顶点.

要实现阴影贴图有以下几个步骤:

首先开辟一块纹理缓存以便之后保存世界的深度信息:

	//Create the shadow map texture
	glGenTextures(1, &shadowMapTextureLeft);
	glBindTexture(GL_TEXTURE_2D, shadowMapTextureLeft);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);
	glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowMapSize, shadowMapSize,
			0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
	glBindTexture(GL_TEXTURE_2D, 0);

我这边开了两张深度纹理shadowMapTextureLeft与shadowMapTextureRight.

然后新建渲染目标对象fbo,fbo类似于帧缓冲区,只不过把渲染的片段保存于其他的缓存而不是屏幕上的帧缓冲区:

	glGenFramebuffersEXT(1, &fboId);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
	glDrawBuffer(GL_NONE);
	glReadBuffer(GL_NONE);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

这边的glDrawBuffer(GL_NONE);与glReadBuffer(GL_NONE);是为了屏蔽颜色的输入输出,因为这个fbo用于写入深度信息.

缓冲区准备工作完成.

接着设置光源:

void changeLightPos(float lx,float ly,float lz) {
	lightPos.x=lx;
	lightPos.y=ly;
	lightPos.z=lz;
	lightPos.w=0;
}

然后设置渲染阴影贴图的投影矩阵:

	//Calculate light projection matrix
	float size=128*2;
	lightProjectionMatrixLeft = ortho(-size-size,size-size,-size*2,size*2,-size*2,size*2);

这边我用的是方向光源,所以设置的平行投影.

然后设置渲染阴影贴图的视图矩阵,将视点放在光源位置,将方向定位光向量:

	lightViewMatrixLeft=lookAt(lightPos.x, lightPos.y, lightPos.z,
							cx, cy, cz,
							0.0f, 1.0f, 0.0f);

好了,现在准备工作都完成了,开始渲染阴影贴图:

    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fboId);
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
			GL_DEPTH_ATTACHMENT_EXT,GL_TEXTURE_2D, shadowMapTextureLeft, 0);
	//Use viewport the same size as the shadow map
	glViewport(0, 0, shadowMapSize, shadowMapSize);
	//First pass - from light's point of view
	glMatrixMode(GL_PROJECTION);
	glLoadMatrixf(lightProjectionMatrixLeft);
	glMatrixMode(GL_MODELVIEW);
	glLoadMatrixf(lightViewMatrixLeft);
	//Draw back faces into the shadow map
	glCullFace(GL_FRONT);
	glClear(GL_DEPTH_BUFFER_BIT);

	//Draw the scene
	callback();

给fbo挂上深度纹理,接下来渲染的内容都将保存到深度纹理当中,把遮挡物的正面剔除,因为遮挡物反面也会产生正确的阴影.

然后还原渲染目标,清除一下帧缓冲区内容

    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,0);
	//restore states
	glCullFace(GL_BACK);
	glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);

接着进行一遍普通渲染,重置投影与视图矩阵:

	glViewport(0, 0, windowWidth, windowHeight);
	glMatrixMode(GL_PROJECTION);
	glLoadMatrixf(cameraProjectionMatrix);

	glMatrixMode(GL_MODELVIEW);
	glLoadMatrixf(cameraViewMatrix);

传入深度纹理进行一次普通渲染:

	glActiveTextureARB(GL_TEXTURE1_ARB);
	glBindTexture(GL_TEXTURE_2D, shadowMapTextureLeft);
	glActiveTextureARB(GL_TEXTURE2_ARB);
	glBindTexture(GL_TEXTURE_2D, shadowMapTextureRight);

	callback();

	glActiveTextureARB(GL_TEXTURE1_ARB);
	glBindTexture(GL_TEXTURE_2D, 0);
	glActiveTextureARB(GL_TEXTURE2_ARB);
	glBindTexture(GL_TEXTURE_2D, 0);

普通渲染的时候使用阴影贴图比较着色器,进行阴影渲染.

有一点要说明一下,阴影贴图中的片段深度信息都是在纹理坐标系内的点,那么进行正常渲染的时候我们传入渲染阴影贴图时候用的视图矩阵与投影矩阵,然后进行以下变换:

模型空间->世界空间->阴影视图空间->阴影投影空间->规范化设备空间->阴影贴图纹理空间

那么接下来向着色器传递以下矩阵:

	//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);	//bias from [-1, 1] to [0, 1]
	shadowMatrix=biasMatrix*lightProjectionMatrixLeft*lightViewMatrixLeft;

所以顶点着色器可以这么写:

uniform mat4 viewMatrix,modelMatrix,normalMatrix;
uniform mat4 shadowLeftMatrix,shadowRightMatrix;

varying vec4 shadowVertLeft,shadowVertRight;
......
......

		shadowVertLeft = shadowLeftMatrix * modelMatrix * gl_Vertex;
    	shadowVertRight = shadowRightMatrix * modelMatrix * gl_Vertex;

		viewVertex = vec3(viewMatrix * modelMatrix * gl_Vertex);
    	gl_Position = gl_ProjectionMatrix * viewMatrix * modelMatrix * gl_Vertex;  

然后在片段着色器当中将传入的shadowVertLeft和shadowVertRight都除以它们的w坐标值就能得到正常渲染的片元在阴影贴图坐标系内的坐标值了,之后把这个坐标值的深度(也就是z值)与阴影贴图内的深度值用shadow2D做比较,这边我偷了个懒,用shadow2DProj做,使用proj函数可以实现齐次除法,也就是顶点值除以w.

片段着色器这么写:

uniform sampler2DShadow texShadowLeft,texShadowRight;

varying vec4 shadowVertLeft,shadowVertRight;
......
......
	vec4 texcoordOffset = shadowVertLeft;
    if(texcoordOffset.x >= 0.0 && texcoordOffset.y >= 0.0 &&
       texcoordOffset.x <= 1.0 && texcoordOffset.y <= 1.0 ) {

		texcoordOffset.z+=bias;
    	float depth = shadow2DProj(texShadowLeft, texcoordOffset).z;
    	if(depth<1.0 && factor>=0.2)
			factor-= 0.1;
		if(factor<0.4)
			factor=0.4;
	}

片段的z坐标要加上一个偏移量,原因如图所示:

最后返回的factor值即为阴影值,把它与环境颜色相乘就能够输出片段了.

最终效果如下:

还能够在渲染阴影贴图的那一步使用着色器对深度纹理进行过滤产生半影区,这边我使用了pcf法对阴影进行处理.

时间: 2024-08-01 22:45:42

OpenGL阴影贴图详解的相关文章

(转)CAS (4) —— CAS浏览器SSO访问顺序图详解(CAS Web Flow Diagram by Example)

CAS (4) —— CAS浏览器SSO访问顺序图详解(CAS Web Flow Diagram by Example) tomcat版本: tomcat-8.0.29 jdk版本: jdk1.8.0_65 nginx版本: nginx-1.9.8 cas版本: cas4.1.2cas-client-3.4.1 参考来源: jasig.github.io:CAS protocol CAS (1) —— Mac下配置CAS到Tomcat(服务端) CAS (2) —— Mac下配置CAS到Tomc

SPI总线协议及SPI时序图详解

SPI,是英语Serial Peripheral Interface的缩写,顾名思义就是串行外围设备接口.SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议.SPI是一个环形总线结构,由ss(cs).sck.sdi.sdo构成,其时序其实很简单,主要是在sck的控制下,两个双向移位寄存器进行数据交换. 上升沿发送.下降沿接收.高位先发送.上升沿到来

JavaScript 作用域链图详解

<script type="text/javascript"> /** * 作用域链: */ var a = "a"; function hao947(){ var b = "b"; alert(a);// output a alert(b);// output b // alert(c);// output undefind function hao(){ var c = "c"; alert(a);// out

一张图详解Linux中的top命令

一张图详解Linux中的top命令及每个参数的含义:

openGL点精灵PointSprite详解: 纹理映射,旋转,缩放,移动

第一,什么是点精灵 openGL的图形由顶点构成,以后利用顶点进行纹理的映射.点精灵就是,一个顶点被当作一个精灵来处理.特别之处就是,一个顶点也可进行纹理贴出.例如,原来是个顶点构成的一个矩形,现在一个顶点就可以完成了.瞬间我们就可以想想,粒子效果,那些云雾水流火花什么的用了点精灵,就可以瞬间减少3个顶点的计算,glDrawArrays使用GL_POINT就可以了,完全也不需要什么顶点索引了.这是非常诱人的效率. 第二,点精灵的局限 一个顶点缩放都必须是矩形.并且大小的最大最小值是有范围的,每个

useradd思维导图详解

useradd思维导图详解 本思维导图,用来说明Linux的的用户和群组的详细关系. Xmind文件和预览图: 思维导图文件用Xmind软件打开,下载链接:useradd详解.rar 预览图: Linux笔记分享,如有错误之处,欢迎留言指正,谢谢!

CentOS7安装nagios并配置出图详解

目录 开始之前 系统环境 监控内容 所需软件包 Centos7重要变化 配置开发环境 同步时间 关闭Selinux 使用CRT上传软件包 安装邮件服务 监控主机安装 常用到的命令 安装nagios所需要的运行环境 增加用户 安装nagios 配置权限 安装插件 安装nrpe 远程主机安装 常用到的命令 配置运行环境 安装nagios-plugin 安装nrpe 启动nrpe. 监控主机安装PNP 配置开发环境 安装php4nagios (版本号为0.6) 配置pnp4nagios 图表展示 问题

[转载] 多图详解Spring框架的设计理念与设计模式

转载自http://developer.51cto.com/art/201006/205212_all.htm Spring作为现在最优秀的框架之一,已被广泛的使用,51CTO也曾经针对Spring框架中的JDBC应用做过报道.本文将从另外一个视角试图剖析出Spring框架的作者设计Spring框架的骨骼架构的设计理念. AD: Spring作为现在最优秀的框架之一,已被广泛的使用,51CTO也曾经针对Spring框架中的JDBC应用做过报道.本文将从另外一个视角试图剖析出Spring框架的作者

【Oracle】Oracle Database 12c Release 2安装多图详解

1.1 下载安装包 oracle官网地址请自行百度oracle,51CTO内容限制不让我放!1)打开官方网站,找到下载连接,如图所示.2)选择更多下载.3)选择数据库版本,这里选择的是目前的最新版本4)接收许可协议,选在linux版本进行下载5) 接收许可协议,点击linuxx64_12201_database.zip6)登陆oracle账没有的可以自己创建一个7)然后就能够进行下载 1.2 安装过程详解 注意oracle的安装需要在图形化界面中进行安装(也可选择命令行模式静默安装,非常繁琐不推