(Shadow Mapping) 阴影映射原理与实现
转载请声明出处:http://blog.****.net/xiaoge132/article/details/51458489
阴影贴图(Shadow mapping)
是在三维计算机图形中加入阴影的过程。阴影贴图的概念最初是由 Lance Williams 于 1978年在“在曲面上投射阴影”这篇论文中提出的。从那时开始,这种方法就已经用于场景预渲染、实时甚至是许多游戏设备以及高端电脑游戏中。在Pixar 的RenderMan 中就使用了阴影贴图技术,同样,在 玩具总动员 这样的游戏中也使用了这项技术。
像素与以纹理形式保存的光照深度缓冲区或者深度图像比较,通过这种方式计算像素是否处于光源照射范围之内,从而生成阴影。(引自维基百科)
原理:
阴影贴图是一种使用深度纹理来为渲染阴影提供解决方案的多通道计算。它的关键是,就是用投射光源代替最终视口来观察场景。通过移动视口到光源位置,可以观察到这个位置每个东西都是明亮的,因为从光的角度来看是没有阴影的。
从光源的角度将场景的深度渲染到一张深度缓冲区中,我们可以在场景中获得一张阴影或者无阴影的贴图,一张阴影贴图。
上图很好的解释了Shadow Mapping的原理
一 首先将摄像机设置到光源所在的位置(观察方向面对场景,此时叫摄像机观察矩阵为观察矩阵1”,同时设置合理的投影矩阵(这里称此矩阵为投影矩阵1)。
二 然后对与景进行第一次绘制.此次绘制将每个可以看见的片元(可以观察到指的是片元到光源间无遮挡)到光源的距离记录到一幅纹理图中的对应像素中,供后面的步骤使用。
三 接着将摄像机恢复到实际摄像机所处的位置,绘制场景。此次绘制时将前面步骤产生的纹理采用投影贴图的方式应用到场景中,进行投影贴图时采用的投影矩阵为投影矩阵1、观察矩阵为观察矩阵1.
四 绘制每个片元时,根据投影贴图纹理采样的结果换算出光源与此片元连线中距光源最近的片元距离(ZA),再计算出此片元距光源的实际距离(ZB)。若ZB〉ZA,则需要绘制的片元处于阴影中,采用阴影的颜色着色,否则此片兀不在阴影中,进行既定的光照着色。
距离纹理的生成:
就是对上述步骤一 步骤二的实现,纹理图的内容是通过讲摄像机放置到光源位置观察场景绘制出来的
看看其中片元着色器的代码 见frag_shadow.sh
- precision mediump float;
- varying vec4 vPosition; //接收从顶点着色器过来的参数
- uniform highp vec3 uLightLocation; //光源位置
- void main()
- {
- float dis=distance(vPosition.xyz,uLightLocation);//计算距离
- float zsbf=floor(dis);//取整数
- float xsbf=fract(dis);//取小数
- xsbf=floor(xsbf*1024.0);//将小数放大1024倍取整
- float hzsbf=floor(zsbf/256.0);
- float lzsbf=mod(zsbf,256.0);
- float hxsbf=floor(xsbf/32.0);
- float lxsbf=mod(xsbf,32.0);
- float r=hzsbf/256.0;
- float g=lzsbf/256.0;
- float b=hxsbf/32.0;
- float a=lxsbf/32.0;
- gl_FragColor=vec4(r,g,b,a);
- }
阴影场景的绘制:
现在要将摄像机恢复到实际原来的位置,将前面步骤产生的纹理贴图应用到场景中.
我们改一下位置就可以得到了这个场景了
同时看下列的片元着色器frag.sh
- precision mediump float; //给出默认浮点精度
- uniform sampler2D sTexture; //纹理内容数据
- uniform highp vec3 uLightLocation; //光源位置
- uniform highp mat4 uMVPMatrixGY; //总变换矩阵(光源)
- varying vec4 ambient; //接收从顶点着色器传来的环境光最终强度
- varying vec4 diffuse; //接收从顶点着色器传来的散射光最终强度
- varying vec4 specular; //接收从顶点着色器传来的镜面光最终强度
- varying vec4 vPosition; //接收从顶点着色器传来的变换后顶点位置
- void main(){
- //将片元的位置投影到光源处虚拟摄像机的近平面上
- vec4 gytyPosition=uMVPMatrixGY * vec4(vPosition.xyz,1);
- gytyPosition=gytyPosition/gytyPosition.w; //进行透视除法
- float s=(gytyPosition.s+1.0)/2.0; //将投影后的坐标换算为纹理坐标
- float t=(gytyPosition.t+1.0)/2.0;
- vec4 depth4=texture2D(sTexture, vec2(s,t)); //对投影纹理(距离纹理)图进行采样
- //将采样出的颜色值换算成最小距离值ZA
- float minDis=depth4.r*256.0*256.0+depth4.g*256.0+depth4.b+depth4.a/32.0;
- float currDis=distance(vPosition.xyz,uLightLocation); //计算光源到此片元的距离ZB
- vec4 finalColor=vec4(0.95,0.95,0.95,1.0); //物体的颜色
- if(s>=0.0&&s<=1.0&&t>=0.0&&t<=1.0) { //若纹理坐标在合法范围内则考虑投影贴图
- if(minDis<=currDis-3.0) { //若实际距离大于最小距离则在阴影中
- gl_FragColor= finalColor*ambient*1.2; //仅用环境光着色
- } else{//不在阴影中用三个通道光照着色
- gl_FragColor = finalColor*ambient+finalColor*specular+finalColor*diffuse;
- }
- } else{ //不在阴影中用三个通道光照着色
- gl_FragColor = finalColor*ambient+finalColor*specular+finalColor*diffuse;
- }
- }
· 比较ZA与ZB的大小时加入了一个修正值3.0,此值一股需要根据具体情况进行调整、如果不加入修正值,场景绘制出来可能导致产生很严重的“自身阴影”问题.
参考书籍:
OpenGL es游戏开发下卷