求教textureProj+opengl sampler2dDShadow的原理

君,已阅读到文档的结尾了呢~~
广告剩余8秒
文档加载中
Shadow Mapping 原理与实践
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
Shadow Mapping 原理与实践
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='/DocinViewer-.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口GLSL projective texture - cgwolver - 博客园
随笔 - 57, 文章 - 0, 评论 - 89, 引用 - 0
Projective Texture(投影贴图)是一项重要的基础技术;尽管在固定管线时代就可以很容易的实现;然而在可编程管线时代,在shader中灵活的运用Projective Texture,是实现各种高级效果的一项基础操作;当然,它本身的效果也很“cool”;另外,对于已经算不上新鲜技术的“shadow mapping”,Projective Texture是必须的一项操作,就是把光源观察方向所得到的Depth Texture(深度贴图)投射到光源的观察的空间;
在固定管线中实现Projective Texture非常简单:
首先要打开纹理自动生成,并设置相关参数:
static GLfloat sPlane[4] = { 1, 0, 0, 0 };
static GLfloat tPlane[4] = { 0, 1, 0, 0 };
static GLfloat rPlane[4] = { 0, 0, 1, 0 };
static GLfloat qPlane[4] = { 0, 0, 0, 1 };
glTexGenfv(GL_S, GL_EYE_PLANE, sPlane);
glTexGenfv(GL_T, GL_EYE_PLANE, tPlane);
glTexGenfv(GL_R, GL_EYE_PLANE, rPlane);
glTexGenfv(GL_Q, GL_EYE_PLANE, qPlane);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
glEnable(GL_TEXTURE_GEN_Q);
这样就可以把纹理从眼睛的观察方向投射到场景中去,如果要从光源方向投射纹理,就要使用glActiveTextureARB首先把那一层的纹理单元激活,然后载入光源方向的透视观察矩阵,最后别忘了glEnable(GL_TEXTUREn_ARB);
然而,现在的各种特效,不用shader实现,太费劲了;当进入可编程管线时,上面的设置的自动纹理生成将不起作用;需要自己在shader中实现投影的采样;然而,这一切在GLSL中实现非常容易:
以下代码实现了把一个纹理投向一个指定的观察方向,需要预先把要使用的投影观察矩阵载入GL_TEXTURE0_ARB
//Vertex Shader
uniform mat4 camMatI
uniform mat4 camM
varying vec4 ProjTextureC
void main(void)
&&&&&gl_Position = gl_ModelViewProjectionMatrix * gl_V
&&&&&vec4 pos = gl_ModelViewMatrix*gl_V//观察坐标
&&&&&pos = camMatInv*//世界坐标
&&&&&ProjTextureCoord= gl_TextureMatrix[0]*//纹理坐标;
&&&&&ProjTextureCoord.z += 0.99;
//Fragment Shader
varying vec4 ProjTextureC
uniform sampler2D ProjectiveT
void main(void)
&&&&&vec4 test = ProjTextureC
&&&&&test.x = test.x/test.w;
&&&&&test.y = test.y/test.w;
&&&&&vec4 tex_color= texture2DProj( ProjTextureCoord,ProjectiveTexture);
&&&&&//防止纹理扩出边界和映射到后方表面上
&&&&&if(test.x&0.0) tex_color.rgb = 1.0;
&&&&&if(test.x&1.0) tex_color.rgb = 1.0;
&&&&&if(test.y&0.0) tex_color.rgb = 1.0;
&&&&&if(test.y&1.0) tex_color.rgb = 1.0;
&&&&&if(test.z&0.0) tex_color.rgb = 1.0;
&&&&&gl_FragColor = gl_Color*tex_摘抄&GPU Programming And Cg Language Primer 1rd Edition& 中文名&GPU编程与CG语言之阳春白雪下里巴人&&
投影纹理映射( Projective Texture Mapping )最初由 Segal 在文章 &Fast shadows and lighting effects using texture maaping& 中提出,用于映射一个纹理到物体上,就像将幻灯片投影到墙上一样。该方法不需要在应用程序中指定顶点纹理坐标,实际上,投影纹理映射中使用的纹理坐标是在 顶点着色程序中通过视点矩阵和投影矩阵计算得到的,通常也被称作投影纹理坐标 (coordinates in projective space) 。而我们常用的纹理坐标是在建模软件中通过手工调整纹理和 3D 模型的对应关系而产生的。
投影纹理映射的目的是将纹理和三维空间顶点进行对应,这种对应的方法好比 & 将纹理当作一张幻灯片,投影到墙上一样 & 。如 图 35 投影纹理映射 所示。
本章我们针对投影纹理映射的原理和实现方法进行详细的阐述。这一章的地位很高,在一些阴影算法以及体绘制算法中都需要用到投影纹理映射技术。严格的说,只要涉及到 & 纹理实时和空间顶点对应 & ,通常都会用到投影纹理映射技术。
12.1 投影纹理映射的优点
投影纹理映射有两大优点:其一,将纹理与空间顶点进行实时对应,不需要预先在建模软件中生成纹理坐标;其二,使用投影纹理映射,可以有效的避免纹理扭曲现象。
为了说明第一个优点,先举一个简要的例子:很多情况下,我们需要将场景渲染两遍,第一遍是为了获取场景信息,得到的场景信息通常保存为一张纹理(例
如深度图);然后基于&存放场景信息&的纹理进行第二次渲染;第二次渲染结果才是最终显示到屏幕上的效果。为了在第二次渲染中使用到&存放场景信息&的纹
理(无预先设置的纹理坐标),需要时时进行纹理计算,这时就可以使用投影纹理映射技术。实际上,这也是投影纹理映射技术的最广泛的应用了。
& 可能大家对于上一段文字还不能理解得很清楚,不过在第 13 章的阴影贴图算法以及第 15 章的体绘制光线投射算法中,大家会明白其含义。一个算法只有理论加实践,才可能真正的被理解,只会照本宣科的朗诵术语,基本上都是鲁迅先生所说的 & 泥塘 & 似的人。
投影纹理映射的第二个优点是:可以有效的避免纹理扭曲现象。如 图 36
所示,将一张纹理投影到两个三角面片上,它们的顶点纹理坐标相同,但是由于三角面片形状不同,插值出来的内部点的纹理坐标也会产生不同的梯度(
gradient ),最后纹理颜色在两个三角面片上的分布也是不一样的。
图 37 右边所示的是将一张纹理贴到一个正方形上,左边所示的是将同样的纹理贴到一个梯形上,正方形和梯形的顶点纹理坐标相同,但两者的贴图效果是不同的。梯形上的纹理会出现明显的扭曲现象。这是因为几何体的变换,导致插值出来的内部纹理坐标分布不均衡。
12.2 齐次纹理坐标( Homogeneous Texture Coordinates )
齐次纹理坐标( homogeneous texture coordinates
)的概念对大多数人来说比较陌生,纹理坐标一般是二维的,如果是体纹理,其纹理坐标也只是三维的。齐次纹理坐标的出现是为了和三维顶点的齐次坐标相对应,
因为本质上,投影纹理坐标是通过三维顶点的齐次坐标计算得到的。
齐次纹理坐标通常表示为(s,t,r,q ), 以区别于物体位置齐次坐标(x, y, z, w) 。一维纹理常用s 坐标表示,二维纹理常用(s, t) 坐标表示,目前忽略r 坐标,q 坐标的作用与齐次坐标点中的w 坐标非常类似。值一般为1 。
12.3 原理与实现流程
对投影纹理映射,很多教程上都是这么解释的:纹理好比一张幻灯片,灯光好比投影机,然后将纹理投影到一个物体上,类似于投影机将幻灯片投影到墙上。这个比喻没有太大的问题,也找不到更加形象的比喻了。问题是:这个解释刚好颠倒了算法的实现流程。
投影纹理映射真正的流程是 &
根据投影机(视点相机)的位置、投影角度,物体的坐标,求出每个顶点所对应的纹理坐标,然后依据纹理坐标去查询纹理值 &
,也就是说,不是将纹理投影到墙上,而是把墙投影到纹理上。投影纹理坐标的求得,也与纹理本身没有关系,而是由投影机的位置、角度,以及 3D
模型的顶点坐标所决定。所以,我一直觉得 & 投影纹理映射 & 这个术语具有很强的误导性,总让人觉得是把纹理投射出去。
根据顶点坐标获得纹理坐标的本质是将顶点坐标投影到 NDC 平面上,此时投影点的平面坐标即为纹理坐标。如果你将当前视点作为投影机,那么在顶点着色程序中通过 POSTION 语义词输出的顶点投影坐标,就是当前视点下的投影纹理坐标没有被归一化的表达形式。
&Projective texture mapping& 文章中有一幅比较著名的图片,说明计算纹理投影坐标的过程,如 图 38 所示。
左边是正常的顶点坐标空间转换流程,无非是顶点从模型坐标空间转换到世界坐标空间,然后从世界坐标空间转换到视点空间,再从视点空间转换到裁剪空间,然后
投影到视锥近平面,经过这些步骤,一个顶点就确定了在屏幕上的位置。图的右边是将视点当作投影机,根据模型空间的顶点坐标,求得投影纹理坐标的流程。通过
比较,可以发现这两个流程基本一样,唯一的区别在于求取顶点投影坐标后的归一化不一样:计算投影纹理坐标需要将投影顶点坐标归一化到【0 ,1
】空间中,实现这一步,可以在需要左乘矩阵normalMatrix
, 也可以在着色程序中对顶点投影坐标的每个分量先乘以1/2 然后再加上1/2 。
&&&&&&&&&& &&&&&&&&&&&&&&
所以求取投影坐标矩阵的公式为:
求得纹理投影矩阵后,便可以使用该矩阵将顶点坐标转换为纹理投影坐标。
使用投影纹理坐标之前,别忘了将投影纹理坐标除以最后一个分量 q 。到此,你就可以使用所求得的投影纹理坐标的前两个分量去检索纹理图片,从中提取颜色值。还记得Cg 标准函数库中有的纹理映射函数的表达形式为:
tex2DProj(sampler2D tex, float4 szq)
tex2DProj 函数与 tex2D 函数的区别就在于:前者会对齐次纹理坐标除以最后一个分量 q ,然后再进行纹理检索!
注意:上面常被提到的&投影机&只是一种形象化的比喻,本质是视点相机,很多教程上都说&将灯光当作投影机&,这是一种
错误的表达(并非是这些教程的作者不懂,而是语言组织上的错误),他们真正的意思是&在当前灯光所在的位置放置一个相机,相机的观察方向和光线投射方向一
致&,这个相机就作为投影机使用。在一些阴影算法中,根据光源信息设置投影机,并从投影机的角度渲染出场景信息纹理(如,阴影纹理),然后把这个纹理放到
正常的场景渲染相机中使用,这时就需要投影机的矩阵信息来建立投影纹理矩阵了。
附:投影纹理矩阵的计算通常不需要开发人员自己动手,常用的图形API 中都给出了获取各种矩阵(视点矩阵、投影矩阵等)的函数,不过偏移矩阵需要自己设置。在应用程序中获取这些矩阵信息后,再传递到着色程序中使用。
顶点着色程序和片段着色程序如下所示:
代码 16 投影纹理映射顶点着色程序
void main_v(
&&&&&& float4 position&&&&&&&&&&& &&&&&&&&&&&&& : POSITION,
&&&&&& float4 normal&&&&&&&&&&&&& &&&&&&&&&&&&& : NORMAL,
&&&&&& out float4 outPos&&&&&&&&&&&&&&&&&&&& &&&&&& : POSITION,
&&&&&& out float4 outShadowUV &&&&&& : TEXCOORD0,
&&&&&& uniform float4x4 worldMatrix,
&&&&&& uniform float4x4 worldViewProj,
&&&&&& uniform float4x4 texViewProj // 投影纹理矩阵
&&&&&& outPos = mul(worldViewProj, position);
// 计算投影纹理坐标
&&&&&& float4 worldPos = mul(worldMatrix, position);
&&&&&& outShadowUV = mul(texViewProj, worldPos);
代码 17 投影纹理映射片段着色程序
void main_f(
&&&&&& &&&&&&&&&&&&& float4 shadowUV&&&&&&&&&&&&&&&&&&& : TEXCOORD0,
&&&&&& &&&&&&&&&&&&& out float4 result&&&&&&&&& &&&&&& &&&&&& : COLOR,
&&&&&& &&&&&&&&&&&&& uniform sampler2D projectiveMap& // 用于投影的纹理
&&&&&& shadowUV = shadowUV / shadowUV.w;
&&&&&& float4 mapC
&&&&&& // 归一化到 0-1 空间
&&&&&& shadowUV.x = (shadowUV.x +float(1.0))/float(2.0);
&&&&&& shadowUV.y = (shadowUV.y +float(1.0))/float(2.0);
&&&&&& mapColor = tex2D(projectiveMap, shadowUV.xy);&&
&&&&&& result = mapC&
阅读(...) 评论()9953人阅读
图形图像(709)
技术理论(1005)
图形引擎(1545)
算法相关(153)
Direct3D(476)
&这两天勉勉强强把一个shadowmap的demo做出来了。参考资料多,苦头可不少。Shadow Map技术是目前与Shadow Volume技术并行的传统阴影渲染技术,而且在游戏领域可谓占很大优势。本篇是第一辑。——Shadow Map的原理很简单,但是实现起来到处是雷。当然这只是我的体会。恩,不过就是“从光源处看场景,那些看不见的区域全部都该是阴影”。很容易看出,与针对特定模型的Shadow Volume不同,Shadow Map是针对场景的。这就是说,对一个光源应用一次Shadow Map,该光源所“看”到的所有对象——都在作阴影判断的范围内。当然,不针对模型说明了不用再像Shadow Volume那样需要考究和处理模型的形状、几何信息,工作成本下去了,效率上来了,因此在不那么注重阴影准确度的游戏里可是很受欢迎。准确度?恩,是的,有得必有失,这样两者至今才能依然肩并肩。Shadow Map注重对场景信息的采样,采样,也就是说要考究锯齿处理——这也是Shadow Map技术不断求突破的核心。当然还有别的便利,譬如对透明纹理的阴影生成支持,shadow volume前天一直在找可以实现这种效果的方法,但是暂时没找着——譬如一张纯黑背静的树的图片做Billboard,shadow volume处理的是一个矩形,得出的阴影也只是个矩形;但是Shadow Map中可以预先处理这张图片(剔除背景色)再做MAP操作,可以得出树轮廓的阴影——Billboard在Shadow Volume眼里是一个矩形的“模型”(可能有别的技法可以改变它的这种“看法”,知情者可否相告呢?),但在shadow map眼里是“场景”的一部分,它完全可以融合在这个大环境中生成阴影贴图。两步走。首先是获得光源视觉下场景深度,保存在一张纹理上。通常是通过上一篇所说的矩阵变换,到达光源视点下的屏幕投影空间,截屏,把该截屏的深度信息写入一张纹理上(通常用专门的深度纹理,它可以用来作深度比较)。之后一步,回到普通的相机视觉,对相机所看到的每一点,找到刚才深度纹理对应的那个点——把该点像素在纹理图的深度作为判断该点是否应该处于阴影区域的依据。怎样判断?和“光源 - 该像素点”的距离做比较咯。实际上这个比较在做深度图的时候就可以出来了啊~为什么要等到做完深度图再专门拿出来比较呢?因为在光视觉下看到的“点的集合”跟照相机视觉下看到的“点集合”是不一样的。通俗点讲,两种视觉下看到的场景不一样(除非你照相机本来就在光源,视线一致),这么说来,可以“点与点对应”(注意,不是唯一对应)的那部分仅仅是这两个“点集合”的交集而已.......比两者中任一者都要来得小。我们于是只处理这部分可以一一对应起来的点,也只能处理它们——这里就隐含了一个巨大的问题了:那些在相机中可以看到,但是在光源视觉下看不到的场景怎么办?这部分场景的点做不了比较啊!是的,我思考这个问题颇久了,然后我在下篇将会讲到这个问题。比较出来,把距离大于对应深度的那些部分涂黑就可以了。比较部分,老实说,我觉得手工比较很麻烦,毕竟这种对应关系是很难确定的。幸好OPENGL有专门的API给我们来做这事情,呵呵。完成这种比较有几个条件:1.上面提到了深度纹理就是条件之一,它能直接储存深度;2.深度纹理比较命令。它们的位置看起来是这样的:&&GenTex() //一般可在初始化里调用 { .................. glGenTextures(1, &shadowmapid); &&& glBindTexture(GL_TEXTURE_2D, shadowmapid); & glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fbowidth, fboheight, 0,& &&&& GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); &&& 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_MIN_FILTER, GL_LINEAR); &&& glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); ............... } & GenShadow() { glBindTexture(GL_TEXTURE_2D, shadowmapid); 渲染场景到纹理 ..... } & CastShadow { glBindTexture(GL_TEXTURE_2D, shadowmapid); //深度纹理比较的API glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); //glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); ...............//正常地再渲染一次场景 }&GenTex() //一般可在初始化里调用{..................glGenTextures(1, &shadowmapid);&&& glBindTexture(GL_TEXTURE_2D, shadowmapid);& glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fbowidth, fboheight, 0,&&&&& GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL);&&& 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_MIN_FILTER, GL_LINEAR);&&& glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);...............}GenShadow(){glBindTexture(GL_TEXTURE_2D, shadowmapid);渲染场景到纹理.....}CastShadow{glBindTexture(GL_TEXTURE_2D, shadowmapid);//深度纹理比较的APIglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);//glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);...............//正常地再渲染一次场景}同样是设置纹理特性(glTexParameteri),为什么不一起放在生成纹理时就操作呢?因为上面说过那个理由:我们不是比较整张深度图 嘛(当然我不知道它内部是不是一比较就得全部比较的)。涂黑部分,固定管道下见过是依赖于上面注释掉的那句。它能把比较结果X(两种结果,X =1代表距离大于深度,要涂黑;X = 0则相反,代表距离小于等于[GL_LEQUAL]深度,比较“通过”,不用处理)应用到纹理的所有通道(GL_INTENSITY大概表示这意思吧)。接下来用ALPHA测试存到透明通道里的结果,大于0.99则涂黑便可(我所看的那教程上就是这样做的);如果你跟我一样用shader来更灵活地处理像素,则可注释掉上面GL_INTENSITY那句,在我们的像素shader里,用sampler2DShadow采样上面那深度纹理,用shadow2DProj就同样能获取这个结果X了。最后附录一下与GL_INTENSITY相关的几个纹理“像素值储存格式”(参考这里:平民程序):GL_INTENSITY: RGBA = (X, X, X, X) 把结果储存到所有通道& GL_ALPHA: RGBA = (0, 0, 0, X) 仅把结果储存到Alpha通道& GL_LUMINANCE: RGBA = (X, X, X, 1)把结果储存到3个Color通道更多关于本次实现的信息,可参考下篇:Shadow Map阴影贴图技术之探Ⅱ本文来源于ZwqXin
, 转载请注明原文地址: &这里是ZwqXin关于Shadow Map阴影贴图的OpenGL实现记录的第二辑,在上篇中讲述了大概原理,在本篇我想在此基础上讲讲我所认为和理解的细节关键点,并放出demo。上篇请参考 Shadow Map阴影贴图技术之探Ⅰ。——事实上我在这里最想要讲的就是在再弹OpenGL矩阵——到另一个空间去 和 Shadow Map阴影贴图技术之探Ⅰ 中重重地提及但没有展开的两个问题(当然,其实当时是不太了解所以没说下去,但是经过这些天对demo运作效果的调试,我想我应该可以说说了)。其一,怎么变换矩阵到所需的光源视觉下投影空间,怎样转化为可查询的纹理;其二,那些“在相机中可以看到,但是在光源视觉下看不到”的场景,怎么办。1.& 到深度纹理的纹理空间去先讲讲矩阵变换的问题。Shadow Map中的第一步(PASS 1),我们要把光源所看到的东西映射到一张深度纹理中,这涉及的也就上面这张曾经给我莫大启发的图中的变换。找到光源的视界,我们是回到最初,按照右边那条路一路变下来的,矩阵变换也应该按照这条路子思考。明确我们现在(在PASS 2中)要做的是:寻找这么一个矩阵变换,通过变换可以让物体某个坐标(局部坐标)逐步对应到那幅深度图的某个纹理坐标(即点与点对应,见上篇),这跟我以前说过的顶点坐标在不同空间的转换一样的嘛。因为涉及最终目标——纹理空间下的纹理坐标,所以我们叫此变换为:纹理矩阵变换。OpenGL也专门给了一个外号这类型的变换:GL_TEXTURE。和GL_MODELVIEW,GL_PROJECTION一样,是一个标志,glMatrixMode(GL_TEXTURE)表明接下来的变换是“纹理矩阵变换”。(标志有什么作用?它们作用在于通知OPENGL,这样OPENGL就知道对变换归类,知道顶点坐标和矩阵之间应该“怎么乘”了,也能在编程人员喊“给我把XX变换的矩阵叫来!”的时候,准确给出所需要的。典型的是shader中由opengl提供的gl_ModelViewMatrix,gl_ProjectionMatrix....当然还有gl_TextureMatrix[i]。) 好了,开始变换吧!(以下涉及变换实际顺序与代码操作顺序的倒置,不太清楚的朋友可以参考我写的乱弹OpenGL中的矩阵变换系列呵呵[ZwqxXin自卖广告]。)首先,与以前一样的是从模型空间转到世界空间,没问题!然后,啊,有问题啦!从世界坐标到光源版本的视图空间。问题不是这个转换矩阵怎么求(事实上很简单嘛,单独弄个光源的glLookat再获取此命令生成矩阵),而是上面那个从模型空间转到世界空间的模型变换矩阵怎么求!opengl里它是和视图变换矩阵合在一起的啊!直接求的话实在太委屈了,像往常那样如何?PASS1里不是就实施过一下光源视觉嘛,在那时候获得那个modelview怎么样?那你准备在什么时候去取?代码逻辑跟变换顺序是相反的,于是模型变换的代码会放在最后——而且紧接的就是物体的原始坐标在那儿了,假如物体很多,模型变换错综复杂,那你都搞不清该在哪放置获取矩阵的代码了——很不现实。那么退一步看呢?有没有直接从物体顶点坐标(原始坐标)到光源视觉下该顶点的位置坐标的变换呢?明显没有!这时候你得好好看图了——路径还有一条:从相机的视图空间,经过世界空间到光源的视图空间!后者前面说了,很容易获得,可前者呢?这可是要求正常的那个视图变换的逆矩阵啊!视图变换矩阵与模型变换矩阵不同,它与后继操作(也就是模型变换)泾渭分明,可在确定“分界线”位置获得。关键是引入求逆!米办法了,去找个OPENGL矩阵类央求个求逆算法回来吧~~(注意是在设定正常相机时候去获得。)右边那些项之间的变换矩阵可轻松获得,于是我们跳到光源版本的屏幕空间吧,它是个平面,Z都被“归”掉了,而X,Y方向范围是[-1,1],缩半后向左上角偏移就有一个[0,1]的纹理坐标啦。等等!我刚才抽的是哪根烟!哪能跳得那么快啊!光源版本的视图空间(Light's Eye Space)到光源版本的屏幕空间(Light's Screen Space)中间的投影过程是有归一,裁减,透视相除等几个主要操作的,而图中的末端只不过是屏幕空间未进行透视相除(各分量除以w并舍弃z,由流水线完成)前的样子而已(所以也叫裁减空间)!(P.S.我只是学BOB大叔口头禅咋,我不是吸烟哦~)这么说可能更明白点:你看书gluPerspective生成的变换矩阵不都是个4*4矩阵么,不可能让坐标变成2*2的,透视相除要在opengl流水线上做。好了,再说下去就超过本篇主题范围了——知道这里的过程的结果是光源版本的裁减空间(Light's Clip Space)就可以了。但是呢?现在是求“纹理变换矩阵”,很明显要自己做透视相除将上面的结果转化成2*2矩阵啊。当然可以按照某教程那样通过glTexGenfv来直接生成纹理(貌似也可免去求逆了),但是这样shader是处理不到的。因此我们干脆就把上述结果作为gl_TextureMatrix[0]交给shader,让vertex shader做剩下来的工作吧:&vec4 texcoord = gl_TextureMatrix[0] * gl_ModelViewMatrix * gl_V & &shadowTexcoord = texcoord / texcoord.w; &shadowTexcoord = 0.5 * shadowTexcoord +0.5;&&& vec4 texcoord = gl_TextureMatrix[0] * gl_ModelViewMatrix * gl_V&&&&&&& shadowTexcoord = texcoord / texcoord.w;&&&& shadowTexcoord = 0.5 * shadowTexcoord +0.5;很明显透视相除和偏移后shadowTexcoord还是个 vec4,没有舍弃什么。是这样的,在fragment shader里shadow2DProj获取深度纹理的值,需要的纹理坐标要是个vec4(s,t,p,q)。另外回应一下,上面把gl_TextureMatrix[0] 乘 gl_ModelViewMatrix就能通过前者里那个 相机逆矩阵抵消gl_ModelViewMatrix里那个相机矩阵了,满足图中要求:&texcoord = gl_TextureMatrix[0] * gl_ModelViewMatrix * gl_Vertex =Light_ProjectionMatrix * Light_ViewMatrix * Camera_ViewInverseMatrix &* gl_ModelViewMatrix * gl_Vertex =Light_ProjectionMatrix * Light_ViewMatrix * Camera_ViewInverseMatrix &* Camera_ViewMatrix * ModelMatrix * gl_Vertex =Light_ProjectionMatrix * Light_ViewMatrix * ModelMatrix * gl_Vertextexcoord = gl_TextureMatrix[0] * gl_ModelViewMatrix * gl_Vertex=Light_ProjectionMatrix * Light_ViewMatrix * Camera_ViewInverseMatrix * gl_ModelViewMatrix * gl_Vertex=Light_ProjectionMatrix * Light_ViewMatrix * Camera_ViewInverseMatrix * Camera_ViewMatrix * ModelMatrix * gl_Vertex=Light_ProjectionMatrix * Light_ViewMatrix * ModelMatrix * gl_Vertex2.当光被视线斩断我确实把相机视觉下看到的场景的顶点映射到那张光源视觉下“拍到”的深度纹理图上了。但是那部分在相机中可以看到,但是在光源视觉下看不到的场景怎么办?它们无法在深度纹理图上找到它们的对应部分啊!这是个难题,也许有某些技术可以做得好,但我还没有探究到那里。目前我能确认的:一是把光源位置调得远一点,甚至无限远(辅以远近裁减面调节),这样光源处所看到的场景就更大了,出来的深度图信息更丰富,但是这样的话,光源所看到的场景细节就淡了,无疑得出的阴影锯齿加重;二是不要限定光源只看原点——但是看哪里呢?即使与相机焦点一致,也还是会出现这个问题的;三是把深度图范围弄大点,FBO不是可以做到截出比屏幕大的范围到大纹理里吗?但是光源的视野也就那么点了,还得让流水线的屏幕外裁减功能失效,因此我通过增大FBO纹理效果不明显。没辙了,你看我的demo,调整光源位置(靠近目标点,即减小光源视野,用空格视现)阴影消失,或者出来了一些黑黑的东西,(即该部分没有深度比较)就是这个原因。更详细截图,包括成功之前的,看这里:Shadow Map Demo1 -本DEMO使用了shader,需要下载glew库到指定目录,下载见此:OpenGL常用的库放上本DEMO:shadowmapdemo1byzwqxin.rar按键:&&& →&& ←&& ↑&& ↓& PageUp& 移动光源 && 鼠标左键下按不放并移动鼠标:旋转视角;鼠标滚轮移动&& 鼠标右键:自动旋转开关&& 空格:(默认光源目标点定位于原点)正常模式/射线可见模式/光源目标点定位于与相机焦点一致P.S.下篇:[Shadow Map阴影贴图技术之探Ⅲ]本文来源于ZwqXin
, 转载请注明原文地址: 这里是ZwqXin关于Shadow Map阴影贴图的OpenGL实现记录的第三辑。在启动新动作之前,总结一下这些天关于Shadow Map的一些琐事,问题的存在。——& 前文见:Shadow Map阴影贴图技术之探ⅠShadow Map阴影贴图技术之探Ⅱ1.图谈光线与视线的那些破事儿从Nvidia的CSM paper里要来的一张图。因为我觉得这个问题还是图片说话比较好:你看图中,蓝色线内的是光源“眼中”的场景(far和near在这里无关要紧),黑色那个是视锥,蓝色物体被投影到某树上,我们看得到该影子(树在视锥内)。这当然是理想情况。仔细看看这种理想情况是怎么做出来的?灯光足够远,整个视锥在“光的视野”内。如果蓝色框框向上移动一下看怎么样?如果黑色框框向下移呢?我们能够观察到阴影的部分,永远只有光视野与相机视野的交集。2.反锯齿的动作既然如此,那么把光有那么远移那么远好么,最好是无限远产生平行光好么?不好。原因之前说过了,就像你人一样,你越远,看的东西越模糊,乃至形状都分不清了——光也一样,这样的影子实在糟糕。怎样取得光源距离在各方面的平衡呢?我觉得应该恰到好处地具体问题具体分析。有时候你得模拟一个照射大地的太阳,你能让它怎么近呢?(下面讲到的CSM技术是解决此类问题的。)但是一些小场景你就得合理控制了——又通常光源位置都不是说变就变的(譬如房间内的蜡烛),这时候可通过近远裁减面等等适当调节光的“视野”。对光源过远而产生的锯齿,衍生出级种着力于解决这类问题的技术(后述)。另外还有传统的反锯齿技术,譬如4X,16X这些。譬如这里我尝试按OpenGL Shading Language(橙书)13.2做的:&float lookup(float x, float y) { && float factor = 1.0; && && shadowTexcoord = shadowTexcoord& + vec4(x,y,0.0,0.0) *&&& && float depth = shadow2DProj(shadowmap, shadowTexcoord).r; &&& && depth = clamp(depth, 0.0, 1.0); &&&&&&& if(depth != 1.0)factor = 0.5; && } & void main() { &&&&&& float mass = 0.0; &&&&&& mass += lookup( 0.001,& 0.001); &&&&&& mass += lookup( 0.001, -0.001); &&&&&& mass += lookup(-0.001,& 0.001); &&&&&& mass += lookup(-0.001, -0.001); & && if(shadowTexcoord.x &= 0.0 && shadowTexcoord.y &= 0.0& && && shadowTexcoord.x &= 1.0 && shadowTexcoord.y &= 1.0 ) &&&&& { &&&&& gl_FragColor = vec4(gl_Color.rgb* diffuse.rgb * mass*0.25, 1.0); && } && else && { &&&&& gl_FragColor = vec4(gl_Color.rgb* diffuse.rgb, 1.0); && }float lookup(float x, float y){&& float factor = 1.0;&&&& shadowTexcoord = shadowTexcoord& + vec4(x,y,0.0,0.0) *&&&&& float depth = shadow2DProj(shadowmap, shadowTexcoord).r;&&&&& depth = clamp(depth, 0.0, 1.0);&&&&&&& if(depth != 1.0)factor = 0.5;&&}void main(){&&&&&& float mass = 0.0;&&&&&& mass += lookup( 0.001,& 0.001);&&&&&& mass += lookup( 0.001, -0.001);&&&&&& mass += lookup(-0.001,& 0.001);&&&&&& mass += lookup(-0.001, -0.001);&& if(shadowTexcoord.x &= 0.0 && shadowTexcoord.y &= 0.0&&& && shadowTexcoord.x &= 1.0 && shadowTexcoord.y &= 1.0 )&&&&& {&&&&& gl_FragColor = vec4(gl_Color.rgb* diffuse.rgb * mass*0.25, 1.0);&& }&& else&& {&&&&& gl_FragColor = vec4(gl_Color.rgb* diffuse.rgb, 1.0);&& }原理就是ping-pong模式的多次采样取均值——让阴影边界模糊。这当然比难看的锯齿好,但一般要搞高精度的效果才略显好。譬如我上面那样仅仅4次采样:可能是PICASA的压缩,本来也没这么丑的 - - 。但是你也知道这软阴影不象软阴影,跟人眼睛得了散光看东西一样。3.穿过透明我曾经在探Ⅰ讲shadow map好处的时候说过它比Volume有些地方的优势很明显。其中之一就是对待透明物时的问题。其实在Shadow Volume之探系列里我是想实现的,而且初期想以为很简单,谁知道即使拜托Google大神,也没看到有什么内容是关于这个的,可见Volume对这类型的遮光物是先天不足。而Shadow Map,本来以为“接下来努力实现让光线穿过透明物产生真实的阴影吧”,谁知道就那么几分钟就搞好了 - - 。说到底,Volume认真对待每个图元,而Map,管你是人不...不,管你是什么图元模型,一律照杀....其实就是在fragment shader里做alpha test,剔除背景色。&void main() { & vec4 alphatexcolor = tex2D(alphatex, gl_TexCoord[0].xy); & if( alphatexcolor.x & 0.05 && alphatexcolor.y & 0.05 && alphatexcolor.z & 0.05) &&&&&&&& & && gl_FragColor = gl_Color * diffuse * }void main(){& vec4 alphatexcolor = tex2D(alphatex, gl_TexCoord[0].xy);& if( alphatexcolor.x & 0.05 && alphatexcolor.y & 0.05 && alphatexcolor.z & 0.05)&&&&&&&&&& gl_FragColor = gl_Color * diffuse *}譬如这里是黑色背景的树,于是把接近黑色的的像素剔除,把此shader应用于画树的那部分函数,直接可得结果。怎么样,图元是矩形,阴影不是吧?对其他背景色也是一样的。但是最好还是在photoshop里把所有图片背景色转为一样的吧(建议与清屏时那颜色一致,我用的是黑色来glClear)。另外纹理图上多少会残留一点点因颜色容差造成的“碎块”,这时候与屏幕背景色一致的话,用透明就可以消除它们了(这里我没做)。4. Shadow Map延伸技术1.PSM():不太了解(事实上所有的都不太了解 - -),不过大概来说,就是为了解决上面提到的“光视野”与“相机视野”的矛盾,通过另类的光源版本投影矩阵“配合”好这两个视锥(硬来,改变光路),让“眼所见即光所见”。副作用当然是光源移动的时候的不连续啦。2.LiPSM (Light Space Perspective Shadow Map)不同于PSM之处在于它不会“硬来”,而是在光源空间“新建”一个光源视锥(平行于原光路)。这通常是比较直观的图形学解决之道:神不知鬼不觉地偷偷弄一个适合的光源视锥,原来都不影响。影子是根据这个新光源视锥来的——但是在外面,观看场景的我们只看到原来的一个光源,还有物体还有影子——自然而然地被骗了。(有点像Shadow Volume里乱走空间的光源呵呵)3.TSM(Trapezoidal Shadow Maps )你可以看看这个网址:Anti-aliasing and Continuity with Trapezoidal Shadow Maps,该介绍的都有了,比PSM,比Bounding Box (旧的SM技术),优势从一个视频明显体现!但是......那个,看看算法:Trapezoidal Shadow Maps (TSM) - Recipe, 那个真◆视觉梯形的弄法真是.....囧。世界太可怕了TAT。极高的反锯齿性能,视觉。关键在于把光源投影矩阵替换成那个复杂扭曲的梯形(发现优化技术很多都在于对光源投影矩阵动手脚啊)。4.CSM(Cascaded Shadow Maps)Cascaded是级联的意思。此方式与前不同,相当于给阴影做LOD。前面说过,这是针对大规模场景光照的阴影技术(太阳普照~),从另一个角度来解决锯齿:通过不同的阴影深度图。你这么理解吧,太阳光下的树的影子,你离它越近,场景应用的深度图越清晰(因为移近平行的太阳光对阴影生成影响不大,而在此距离下“截频”出来的深度图是高精的),你离它越远,当然,场景变大,物体变多,但是这时候用的深度图有必要那么精确吗?于是就换了。这样,控制得好的话,近看远看都不成问题。当然,细节问题有待探讨。(最后用一张恶搞图结束)下篇预告:Shadow Map阴影贴图技术之探Ⅳ [CSM]本文来源于ZwqXin
, 转载请注明原文地址: &&这里是ZwqXin关于Shadow Map阴影贴图的OpenGL实现记录的第四辑。终于回到了这个节骨眼上,请与我一起进军时尚的Cascaded Shadow Maps(CSM)吧。——Shadow Map阴影贴图技术之探ⅠShadow Map阴影贴图技术之探Ⅱ Shadow Map阴影贴图技术之探Ⅲ上篇(Ⅲ)里的最后最后,提及了几种比较有名的Shadow Map的延展技术,Cascaded Shadow Maps是其中比较近期才出现的,而且它引进了Cascade(级联,层)这个概念,与另一个颇为我们中国人骄傲的名词PSSM(Parallel-split Shadow Maps)中的Parallel-split指的是同一个概念。事实上两者的原理是基本一样的。它先在我们的视锥上动手脚,用几个与近远平面平行的截面把视锥分成几份(Parallel-split);然后针对每一份,通过修改光源投影矩阵,使之后生成的Shadow Map中只有该份“Splited视锥”里的物体;这样,在pass1阶段就生成了几张针对不同“Splited视锥”的Shadow Maps,在渲染阶段,依据像素深度就可以判断该位置应用哪张Shadow Map了。这样做的好处在上篇已经讲过了。在距离眼睛近的地方,应用的是分辨率高的阴影图,距离眼睛远的地方则是低分辨率。这样是符合视觉特点的,而且没有什么浪费的地方。如图,假设光从视锥正上方射下来(其他方向同理),按CSM的意思,应该把光源视觉下的投影面放在图示位置(四条短的水平的线)。这里我把视锥分割成四份,因此需要对应的四张ShadowMap,与人看东西一样,视像面越靠近阴影(假设位于被投影面,图中长水平线),看到的阴影越清晰。反映在生成阴影图阶段,表现为具体caster(被光源直接照射的投射物表面)在光源投影面上占据的范围大。假设阴影图尺寸是固定的(譬如),在第一个“Splited视锥”和第四个“Splited视锥”里的投射阴影的物体[投射物]大小也相同(其阴影在实际世界里占地面积必然也相同),则其阴影在阴影图里占的像素数会有很大差别(譬如前者占500,000个,后者可能才占5000个),这就是分辨率的差异。最后把ShdowMap帖在场景里(假设在世界空间下该种投射物的阴影应该占100,000个像素),前者就会比后者效果好很多。(一个是需要进行OverSampling,另一个就得进行UnderSampling。)所以越靠近眼睛的、越小的Splited视锥里的阴影越高“画质”,反之则越粗糙(但比起传统Shadow Map技术也许效果还好一点)——而我们正希望要眼前的事物清晰,远处的事物模糊甚至不表现出影子也可以——CSM(或者说,PSSM)做到了。重新回头看看技术实现过程。这里有两个主要的技术点,一是“怎么分割视锥”,二是“怎么设置每个小视锥的光源投影矩阵”。1. Cascade(Split)的准则从上图和上分析可以看出,“Splited视锥”沿视线的长度(Zfar - Znear)应该越分越大比较合理,指数增长符合这个规律,但指数增长一般太夸张了,所以配合一个线性增长比较好。在PSSM里,这两种分法叫 logarithmic split scheme和the uniform split scheme,前者的表达式是经过科学的推导的,这部分也是CSM/PSSM最数学的部分,在GPU GEMS3里有详细的推导,或者你看PSSM推广人Fan Zhang [HKUST]那篇&Hardware-Accelerated Parallel-Split Shadow Maps.& (IF YOU CAN FIND IT)也该有。它从Shadow-Map Aliasing(dp/ds,单位阴影图像素单位对应的屏幕像素)的推导开始,找出能满足使perspective aliasing(由投影缩减效应形成)在各个视锥里均匀分配的分割式。后者只是一个线性式,但它的调和作用避免了“Splited视锥”的过小与过大,通过一个mix因子混合两式子(我在应用中默认分配logarithmic split scheme的因子是0.75,余者0.25):&// & Cascaded Shadow Maps void CCascadingSM::ComputeSplits(float strength, float Dis_Near, float Dis_Far) { && float distance_scale = Dis_Far / Dis_N & && splitfrust[0].ResightNear(Dis_Near); //开始分割 & && float partisionFactor = 0.0; && float lerpValue1 = 0.0, lerpValue2 = 0.0; && float SplitsZ = 0.0; & &&& for(int i = 1; i & NumofS ++i) &&& { &&&&&&& partisionFactor = i / (float)NumofS & &&&&&&& lerpValue1 = Dis_Near + partisionFactor * (Dis_Far - Dis_Near); & &&&&&&& lerpValue2 = Dis_Near * powf(distance_scale, partisionFactor); & &&&&&&& // 分割面的Z值. 1.005f防止前一个子视锥的远裁切面与后一个子视锥的近裁切面冲突 &&&&&&& SplitsZ =& (1-strength) * lerpValue1& + strength * lerpValue2;& &&&&&&&&&&&&&&&&&&& &&&&&&& splitfrust[i].ResightNear(SplitsZ * 1.002f); &&&&&&& splitfrust[i-1].ResightFar(SplitsZ); &&& } & &&& splitfrust[NumofSplits-1].ResightFar(Dis_Far);//结束分割 }// & Cascaded Shadow Mapsvoid CCascadingSM::ComputeSplits(float strength, float Dis_Near, float Dis_Far){&& float distance_scale = Dis_Far / Dis_N&& splitfrust[0].ResightNear(Dis_Near); //开始分割&& float partisionFactor = 0.0;&& float lerpValue1 = 0.0, lerpValue2 = 0.0;&& float SplitsZ = 0.0;&for(int i = 1; i & NumofS ++i)&{&&partisionFactor = i / (float)NumofS&&lerpValue1 = Dis_Near + partisionFactor * (Dis_Far - Dis_Near);&&lerpValue2 = Dis_Near * powf(distance_scale, partisionFactor);&&// 分割面的Z值. 1.005f防止前一个子视锥的远裁切面与后一个子视锥的近裁切面冲突&&&&&&& SplitsZ =& (1-strength) * lerpValue1& + strength * lerpValue2; &&&&&&&&& &&splitfrust[i].ResightNear(SplitsZ * 1.002f);&&splitfrust[i-1].ResightFar(SplitsZ);&}&splitfrust[NumofSplits-1].ResightFar(Dis_Far);//结束分割}2. Crop& It !针对每个光源投影矩阵进行的调整,在CSM/PSSM里称为Crop(这么有诗情画意噶?)。这个过程其实很好理解的,我们在照相的时候,一开始要在CCD液晶屏的画面上把焦点确定吧——Cascaded Shadow Maps技术中的光源就是照相者,光源的视像平面就是屏幕,我们是对每个“Splited视锥”都照一张相,因为照的是casters,所以可以说是照人物相片——把casters所在的“Splited视锥”(对应人物背景)在光源投影空间的中心挪移到视像平面的中心,然后进行光学变焦,使人物背景尽量充满屏幕,从而突出人物——casters,噢,不,应说是shadows。恩,这是个具有平移和缩放的线性变换——CROP MATRIX,合适地构造它,然后乘在光源投影矩阵前面(形成新的投影矩阵),就能完成匹配投影矩阵匹配“Splited视锥”的任务。假如目前处理第i个分割视锥,生成CropMatrix[i],那么对场景坐标系的变换就是:(CropMatrix[i] * LightProjectMatrix) * LightViewMatrix * ModelMatrix * pos。也可认为(CropMatrix[i] * LightProjectMatrix)是二次投影,因为Crop Matrix实质也是个投影矩阵,而且是个名副其实的Otho正交投影矩阵。&// & Cascaded Shadow Maps& void CCascadingSM::ApplyCropProjectMatrix(CFrustum &frust)& {&& &&& CVector3 maxFrustumCoord, minFrustumC& && &&& CMatrix16 CurrentM//当前矩阵& &&& CMatrix16 CropM//协调光源视野与视锥的Crop Matrix&&&&&&&&&&&&&&&&&&&&&& && &&& //光源视图矩阵& &&& glGetFloatv(GL_MODELVIEW_MATRIX, CurrentMatrix.mt);& && &&&&& //生成视锥的AABB特征向量,视锥先经CurrentMatrix变换到光源视图空间& &&& GetFrustumAABBCoords(frust, maxFrustumCoord, minFrustumCoord, &CurrentMatrix);& && &&&& //计算给Crop Matrix的调整参数& &&& float scaleX = 2.0f/(maxFrustumCoord.x - minFrustumCoord.x);& &&& float scaleY = 2.0f/(maxFrustumCoord.y - minFrustumCoord.y);& &&& float offsetX = -0.5f*(maxFrustumCoord.x + minFrustumCoord.x) * scaleX;& &&& float offsetY = -0.5f*(maxFrustumCoord.y + minFrustumCoord.y) * scaleY;& && &&& CropMatrix = CMatrix16(scaleX,&&& 0.0f,& 0.0f, 0.0f,& &&&&&&&&&&&&&&&&&&&&&&&&&&&& 0.0f,& scaleY,& 0.0f, 0.0f,& &&&&&&&&&&&&&&&&&&&&&&&&&&&& 0.0f,&&& 0.0f,& 1.0f,& 0.0f,& &&&&&&&&&&&&&&&&&&&&&&&&& offsetX, offsetY,& 0.0f,& 1.0f );& && && //CropProjectMatrix(光源投影矩阵 = CropMatrix*ProjectZMatrix)& &&&& glLoadIdentity();& &&&& glLoadMatrixf(CropMatrix.mt);& &&&& //以max_Z和min_Z作为远近裁切面的正投影矩阵&& &&&& glOrtho(-1.0, 1.0, -1.0, 1.0, -maxFrustumCoord.z, -minFrustumCoord.z );& && } // & Cascaded Shadow Maps void CCascadingSM::ApplyCropProjectMatrix(CFrustum &frust) {&&&&& CVector3 maxFrustumCoord, minFrustumC&&&&&& CMatrix16 CurrentM//当前矩阵&&&& CMatrix16 CropM//协调光源视野与视锥的Crop Matrix&&&&&&&&&&&&&&&&&&&&&&&&&&& //光源视图矩阵&&&& glGetFloatv(GL_MODELVIEW_MATRIX, CurrentMatrix.mt);&&&&&&&& //生成视锥的AABB特征向量,视锥先经CurrentMatrix变换到光源视图空间&&&& GetFrustumAABBCoords(frust, maxFrustumCoord, minFrustumCoord, &CurrentMatrix);&&&&&&& //计算给Crop Matrix的调整参数&&&& float scaleX = 2.0f/(maxFrustumCoord.x - minFrustumCoord.x);&&&& float scaleY = 2.0f/(maxFrustumCoord.y - minFrustumCoord.y);&&&& float offsetX = -0.5f*(maxFrustumCoord.x + minFrustumCoord.x) * scaleX;&&&& float offsetY = -0.5f*(maxFrustumCoord.y + minFrustumCoord.y) * scaleY;&&&&&& CropMatrix = CMatrix16(scaleX,&&& 0.0f,& 0.0f, 0.0f,&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 0.0f,& scaleY,& 0.0f, 0.0f,&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 0.0f,&&& 0.0f,& 1.0f,& 0.0f,&&&&&&&&&&&&&&&&&&&&&&&&&& offsetX, offsetY,& 0.0f,& 1.0f );&&&&& //CropProjectMatrix(光源投影矩阵 = CropMatrix*ProjectZMatrix)&&&&& glLoadIdentity();&&&&& glLoadMatrixf(CropMatrix.mt);&&&&& //以max_Z和min_Z作为远近裁切面的正投影矩阵&&&&&& glOrtho(-1.0, 1.0, -1.0, 1.0, -maxFrustumCoord.z, -minFrustumCoord.z );&& }CropMatrix简直就跟glOrtho生成的矩阵一模一样,功用也一样。只不过这里我没有对Z坐标进行变换,因为把它交给生成光源投影矩阵的glOrtho了(反而它只变换Z坐标)。前面不是说把坐标都变换到光源投影CLip空间后再提取AABB吗,为什么就到光源视图空间就比较了?因为这里是平行光的投影,所以用的是正交投影glOrtho,在glOrtho中没有对X,Y坐标进行变换(看看它的spec就知道了,-1与1为参数是不改变X,Y数值的),所以两个空间下的X,Y坐标是一致的,而CropMatrix正是只变换X,Y坐标,所以实在没必要多此一举。但有两种情况是“需要多此一举”的。一是光源为点光源且需要透视投影;二是在光源与视锥之间还有其他caster。对第二种情况尤其值得注意。看回我在文章最上面放的自画示意图,有个打了X的地方,那里假设有只bird,那么它会否对地面产生阴影呢?——按照CSM基础理论,不会!因为CropMATRIX修改后的光源投影平面已经越过它了,已经看不见它了——我们只能看见视锥里(更准确说是视锥的AABB包围盒里)的物体所留下的阴影!解决法是把该物件bondingbox在光源视图空间下的最大Z坐标作为上述算法最后的minFrustumCoord.z,使光源投影平面恰在该位置而不再下降。这样做多了些麻烦,而且该“Splited视锥”对应的Shadow Map的分辨率会降低,物体离视锥越远,分辨率下降越严重。所以,如非必要投射那样的物体(或者部分穿出视锥之外的物体)的阴影,不必这样做:先计算普适意义下的光源投影矩阵和视图矩阵(类似传统SM那样),用它们的积Light-ProjectView把各个小视锥变换到CLIP投影空间,用同样方法得到该空间下的包围盒(特征向量maxFrustumCoord, minFrustumCoord),这里继续计算的Crop矩阵就需要用到Z值了,因为我们要修改其中的minFrustumCoord.z。让它等于-1——OPENGL在CLIP投影空间的最小坐标值。没错,即使该物件在光源正体位置之上,也把它计算入要投影的物件集(casters)里(况且平行光源本来该是无限远而不是在那个虚拟位置上的)。最后依然是:CropMatrix[i] *( LightProjectMatrix * LightViewMatrix) * ModelMatrix * pos。&// & Cascaded Shadow Maps&& &&& CVector3 maxFrustumCoord, minFrustumC& & //..... &&& GetFrustumAABBCoords(frust, maxFrustumCoord, minFrustumCoord, &CurrentMV); & &&& minFrustumCoord.z = -1.0f; & &&&& //计算给Crop Matrix的调整参数 &&& float scaleX = 2.0f/(maxFrustumCoord.x - minFrustumCoord.x); &&& float scaleY = 2.0f/(maxFrustumCoord.y - minFrustumCoord.y); &&& float scaleZ& = 2.0f / (maxFrustumCoord.z - minFrustumCoord.z); &&& float offsetX = -0.5f*(maxFrustumCoord.x + minFrustumCoord.x) * scaleX; &&& float offsetY = -0.5f*(maxFrustumCoord.y + minFrustumCoord.y) * scaleY; &&& float offsetZ = -0.5f*(maxFrustumCoord.z + minFrustumCoord.z) * scaleZ; & && &&& CropMatrix = CMatrix16(scaleX,&&& 0.0f,& 0.0f, 0.0f,& &&&&&&&&&&&&&&&&&&&&&&&&&&&& 0.0f,& scaleY,& 0.0f, 0.0f,& &&&&&&&&&&&&&&&&&&&&&&&&&&&& 0.0f,&&& 0.0f,& scaleZ,& 0.0f,& &&&&&&&&&&&&&&&&&&&&&&&&& offsetX, offsetY,& 0.0f,& 1.0f );& //....// & Cascaded Shadow Maps&&&&& CVector3 maxFrustumCoord, minFrustumC&& //.....&GetFrustumAABBCoords(frust, maxFrustumCoord, minFrustumCoord, &CurrentMV);&minFrustumCoord.z = -1.0f;& //计算给Crop Matrix的调整参数&&& float scaleX = 2.0f/(maxFrustumCoord.x - minFrustumCoord.x);&float scaleY = 2.0f/(maxFrustumCoord.y - minFrustumCoord.y);&float scaleZ& = 2.0f / (maxFrustumCoord.z - minFrustumCoord.z);&float offsetX = -0.5f*(maxFrustumCoord.x + minFrustumCoord.x) * scaleX;&float offsetY = -0.5f*(maxFrustumCoord.y + minFrustumCoord.y) * scaleY;&float offsetZ = -0.5f*(maxFrustumCoord.z + minFrustumCoord.z) * scaleZ;&&&&& CropMatrix = CMatrix16(scaleX,&&& 0.0f,& 0.0f, 0.0f,&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 0.0f,& scaleY,& 0.0f, 0.0f,&&&&&&&&&&&&&&&&&&&&&&&&&&&&& 0.0f,&&& 0.0f,& scaleZ,& 0.0f,&&&&&&&&&&&&&&&&&&&&&&&&&& offsetX, offsetY,& 0.0f,& 1.0f ); //....&3. Cast 阴影通过上面矩阵配合(0,1)映射矩阵之类的生成shadow maps后,这就来到第二PASS了,它与传统Shadow Map(Shadow Map阴影贴图技术之探Ⅰ)一样,只是根据像素深度决定用哪张而已。注意,把视锥分割的是近/远平面,其值是距视点的距离,定义于视图空间——把它变换到眼睛的屏幕CLIP空间,就能在shader里“分割”像素深度,把像素都分到SplitNum个区域里(应用中我取了4个)。好了,接下来你知道怎么用if-else来Cast 阴影图了吧。&// & Cascaded Shadow Maps //fragment shader中获取当前像素阴影状态: //shadow_color [阴影factor], 还是1.0[表明不贡献阴影之factor] & const float shadow_color = 0.3; const float depth_error = 0.005; //上面提到的那几个分割值,藏在xyz通道了 uniform vec3 frustum_& uniform sampler2DA & vec4 shadeFact() { && int index = 3; &&& && //决定cascade,应用的shadowMap index && //gl_FragCoord(当前pixel的x,y窗口坐标,z分量为深度) & && if(gl_FragCoord.z & frustum_far.x)& && { &&&& index = 0; && } && else if(gl_FragCoord.z & frustum_far.y) && { &&&& index = 1; && } && else if(gl_FragCoord.z & frustum_far.z) && { &&&& index = 2; && } &&& &&&& //转换像素位置参量pos, 到光源视觉(Croped)-纹理空间 &&&& vec4 shadowTexcoord = gl_TextureMatrix[index] * & &&&& //对纹理投影,变换到纹理空间的场景坐标总作为TEXCOORD,这时就得自行为之“透视相除”了 &&&& //小声:对正交投影其实是不必的。。。 &&&& if(shadowTexcoord.w != 1.0) &&&& { &&&&&&& shadowTexcoord = shadowTexcoord / shadowTexcoord.w; &&&& } & &&&& //映射到(0~1)以进行纹理检索 &&&& shadowTexcoord = 0.5 * shadowTexcoord + 0.5;& &&&&& &&&& //本像素的位置在当前空间(光源视觉(Croped)-纹理空间)的实际深度 &&&& float realDepth = shadowTexcoord.z; & &&&& //Texture Array 中以z分量选择纹理Layer(Shadow Map No.i) &&&& shadowTexcoord.z = float(index);& &&&&& &&& //检索出Shadow Map中对应位置(x,y)的深度值 &&& float depth =& texture2DArray(shadowmap, shadowTexcoord.xyz).x; & &&& //当 depth &= realDepth, 该位置所属caster 或 no-shadow领域, 输出阴影分量1.0[无阴影] &&& //当 depth && realDepth, 该位置所属shadowed领域&&&&&&&&&& , 输出阴影分量0.0[有阴影] &&& float diff = depth - realD & &&& //为了精度问题,如果差值diff是个很小很小的负量,把该量设定为1.0 &&& //当diff & -0.005(根据应用调节), 认为depth - realDepth &= 0.0[无阴影] &&& diff = diff / depth_error + 1.0; & &&& return vec4(diff & 0.0 ? shadow_color : 1.0) ; }// & Cascaded Shadow Maps//fragment shader中获取当前像素阴影状态://shadow_color [阴影factor], 还是1.0[表明不贡献阴影之factor]const float shadow_color = 0.3;const float depth_error = 0.005;//上面提到的那几个分割值,藏在xyz通道了uniform vec3 frustum_ uniform sampler2DAvec4 shadeFact(){&& int index = 3;&&&&& //决定cascade,应用的shadowMap index&& //gl_FragCoord(当前pixel的x,y窗口坐标,z分量为深度)&& if(gl_FragCoord.z & frustum_far.x)&&& {&&&& index = 0;&& }&& else if(gl_FragCoord.z & frustum_far.y)&& {&&&& index = 1;&& }&& else if(gl_FragCoord.z & frustum_far.z)&& {&&&& index = 2;&& }&&&&&&& //转换像素位置参量pos, 到光源视觉(Croped)-纹理空间&&&& vec4 shadowTexcoord = gl_TextureMatrix[index] *&&&& //对纹理投影,变换到纹理空间的场景坐标总作为TEXCOORD,这时就得自行为之“透视相除”了&&&& //小声:对正交投影其实是不必的。。。&&&& if(shadowTexcoord.w != 1.0)&&&& {&&&&&&& shadowTexcoord = shadowTexcoord / shadowTexcoord.w;&&&& }&&&& //映射到(0~1)以进行纹理检索&&&& shadowTexcoord = 0.5 * shadowTexcoord + 0.5;&&&&&&&&&& //本像素的位置在当前空间(光源视觉(Croped)-纹理空间)的实际深度&&&& float realDepth = shadowTexcoord.z;&&&& //Texture Array 中以z分量选择纹理Layer(Shadow Map No.i)&&&& shadowTexcoord.z = float(index);&&&&&&&&& //检索出Shadow Map中对应位置(x,y)的深度值&&& float depth =& texture2DArray(shadowmap, shadowTexcoord.xyz).x;&&& //当 depth &= realDepth, 该位置所属caster 或 no-shadow领域, 输出阴影分量1.0[无阴影]&&& //当 depth && realDepth, 该位置所属shadowed领域&&&&&&&&&& , 输出阴影分量0.0[有阴影]&&& float diff = depth - realD&&& //为了精度问题,如果差值diff是个很小很小的负量,把该量设定为1.0&&& //当diff & -0.005(根据应用调节), 认为depth - realDepth &= 0.0[无阴影]&&& diff = diff / depth_error + 1.0;&&& return vec4(diff & 0.0 ? shadow_color : 1.0) ;}最后是放出演示DEMO了吧:请看 [Shadow Map Demo2]在该日志将展示DEMO并浅谈一下CSM一些小细节的地方,包括caster-receiver-splitedFrustum组合生成的SCREEN DEPENDENT的crop矩阵。最后是这段时间个人学习Shadow技法的小小总结。本文来源于ZwqXin
, 转载请注明原文地址: &
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:5296730次
积分:65472
积分:65472
排名:第19名
原创:71篇
转载:4230篇
评论:825条
声明:早期转载的文章未标明转载敬请原谅,以后将陆续改过来,向原创者致敬!
有问题可留言
痞子龙3D编程
QQ技术交流群:
(3)(8)(5)(8)(14)(13)(3)(44)(42)(46)(40)(123)(114)(128)(159)(168)(40)(45)(43)(38)(5)(6)(7)(2)(3)(7)(24)(5)(5)(16)(17)(16)(66)(7)(55)(2)(37)(16)(1)(10)(6)(37)(5)(31)(18)(31)(128)(333)(203)(256)(59)(78)(57)(16)(39)(10)(27)(16)(8)(26)(32)(53)(56)(45)(142)(228)(6)(10)(6)(9)(6)(9)(22)(25)(18)(83)(208)(442)(111)(32)(1)

我要回帖

更多关于 texture转texture2d 的文章

 

随机推荐