shadow mapping实现动态shadow实现记录 【转】
http://blog.csdn.net/iaccepted/article/details/45826539
前段时间一直在弄一个室内场景,首先完成了render,效果还可以。然后给其加上shadow,使其更逼真。这里主要记录下在做的过程中遇到的问题。
1.是在导入场景的时候,由于场景比较大(200M)左右,所以在iOS上加载这么大的场景会频繁的memorywarning,然后就会被系统kill掉。这个问题的解决方法是通过改变数据类型来达到压缩的目的。顶点的坐标double是没法改变的,如果改变会严重影响场景的准确度。这里主要是改变normal和 uv的类型,其实在正常的精度范围内,normal和 uv不需要double或者float来存储,可以用一个short来存储,这里就要把normal 和 uv 加一个偏移值(比如乘以10000),这个样就可以节省大量的存储空间,初步测试,加载完整个场景内存占用大约为300M多点,现在的ipad完全可以承受。然后在shader中正真使用的时候只需要把数据还原就行,这样几乎没有多少效率的损失但是却极大的减少了内存的占用。
2.场景内不同的mesh的材质均各不相同,加载的时候可以将同一材质的mesh归在一起,这样在绘制的时候,可以方便的赋予材质信息。刚开始写了加载场景和材质的类,但是最后为了可靠性以及后续支持不同格式的模型加载,选择了使用开源库assimp来负责模型加载这一块。在github上找到源码进行编译,编译出支持armv7,arm64的版本然后在ios中使用。使用的过程中需要导入libz, libc++, libstdc++等等的框架。然后就可以在ios上使用该库。
3.shadow mapping的原理:简单的说就是先渲染场景的深度信息,即从光源的位置放置相机进行一遍渲染,当然这里只取得深度信息即可,为了方便使用,将深度信息渲染到一张深度纹理中,然后将该深度纹理传入shader,在正常相机位置渲染,在shader中将每个点经过模型转换后的深度值与深度纹理中的深度值作比较,若当前深度值大于深度纹理中的深度值,那么很显然该点处在shadow中,需要暗化处理,反之亦然。
4.生成纹理贴图之后,将其传入shader中,这里遇到的问题是,开始时深度信息一直错误,将深度纹理以正常纹理贴图的方式呈现出来是完全乱掉的。后来在opengl官网找到下面的一句话:
If a texture has a depth or depth-stencilimage format and has the depth comparison activated, it cannot be used with anormal sampler. Attemptingto do so results in undefined behavior. Such textures must be used with ashadow sampler.
在windows平台上倒是没有遇到这个问题,能正常的把包含depthimage format的texture正常的贴出来,但是在ios平台上使用OES2.0时不能正常工作,好像确实出现了未定义的现象。
5.将以上texture重新以sampler2DShadow的形式接受,并进行绘制时纹理仍然错误,这次错误的原因很好定位,主要是忘记将坐标空间转到纹理空间(0-1),通过shadowMVP矩阵转换的坐标的x和y值均在[-1, 1]区间,所以要使用一个bias矩阵进行转换将[-1, 1]转换到[0, 1]。
6.转换完成之后可以使用shadow2DProj函数来完成深度值的比较,当然这里也可以自己比较,但是自己比较的时候要注意将x和y的值除以w分量然后再取出深度值,这其实也是shadow2DProj中的实现方式,这个函数会先将x和y分量除以w分量然后再取出深度值与深度纹理中的值进行比较,若深度测试成功(即深度值小于深度纹理中的深度值)则返回1,若深度测试失败则返回0。这样我们就可以进行shadow的处理。
7.以上都能正确之后,shadow效果应该是可以做出来了,但是可能会出现z-fighting的问题,而且可能相当的严重,如果上面我们不是用shadow2DProj来进行深度值比较的话,那么我们可以在比较深度值的时候加一个小的容错误差,当然这个值的大小要亲自多次试验看效果才能选定,因为对不同的场景,不同的机器这个值的差异可能很大,我这里简单试了一下,去0.006时的效果还是比较好的。但是如果我们使用了shadow2DProj的话,那么比较函数不是我们自己实现的,那么我们就没办法加上这个小的容错误差。这时候我们可以在绘制的时候使用polygonoffset来避免z-fighting的问题,试了下这样的效果也的确是非常棒。
以上就是在shadowmapping实现过程中遇到的几个问题。
虽然shadowmapping的原理很简单,但是正真实现一个效果比较好的shadowmapping还是有点麻烦的。
下一步要打算实现SSAO。