ShadowMap基于的原理:SM算法是一个2-pass绘制算法,第一pass从光源视点绘制场景,生成SM纹理,第2pass从视点视图按常规方法绘制场景

从光源的位置观察场景,这时候我们看不到的地方就是该有阴影的地方,于是可以使用比较像素到光源距离的方法来确定某个像素是否在阴影之中。

于是我们需要记录我们看得到的像素的距离值,以便做比较。

首先,创建在光源位置处的观察坐标系,这一步应该在CPU阶段完成,这里为描述方便,写为HLSL代码

这里假设观察方向指向原点

float3 dirZ = -normalize(lightPos);

float3 up = float3(0,0,1);

float3 dirX = cross(up, dirZ);

float3 dirY = cross(dirZ, dirX);

接下来,把场景中所有顶点变换到这个光源-观察空间中(light-view-space)

实际中应使用矩阵进行变换,这里直接做

float4 vPos;

vInPos.xyz-=vLightPos.xyz; //首先是平移变换

vPos.x=dot(vInPos,x_dir);   //接下来是分别绕3个轴旋转

vPos.y=dot(vInPos,y_dir);

vPos.z=dot(vInPos,z_dir);

vPos.w=1;

然后进行light-view-space空间里的投影变换,所使用的矩阵要根据光源的特点进行改变,如FOV等

最后将这时得到的结果渲染到一张纹理上,把它称作shadowmap

这张纹理一般使用R16F,或者R32F格式,而一些集成显卡中或者OPENGL不支持这样的格式,还可以使用整数纹理,这时需要对深度值进行压缩。

渲染结束后,在纹理中保存的就是在光源处能看见的像素到光源的距离了,为了节省shader指令,可以使用距离的平方

即在浮点纹理中为 return dot(vLightVec,vLightVec);

在整数纹理中为  float fDepth=dot(vLightVec,vLightVec);

return float4( floor(fDepth) / 256.0f, frac(fDepth), frac(fDepth), frac(fDepth), frac(fDepth));

通过把frac(fDepth)同时写入蓝色和alpha通道,这样可以节省一条指令,否则需要另一个指令来填充这些通道

mov r2.gba r0.g   //r0.g包含frac(fDepth)

vertex shader

  1. float4x4 matViewProjection;
  2. float4x4 matProjection;
  3. float4 vLightPos;
  4. float fDistScale;
  5. float fTime0_X;
  6. struct VS_OUTPUT
  7. {
  8. float4 Position : POSITION0;
  9. float3 vLightVec:TEXCOORD0;
  10. };
  11. VS_OUTPUT vs_main(float4 vInPos:POSITION )
  12. {
  13. VS_OUTPUT output;
  14. output.Position = mul(vInPos, matViewProjection );
  15. //光源运动,做demo时应在cpu阶段完成
  16. float3 vLightPos;
  17. vLightPos.x=cos(1.321*fTime0_X);
  18. vLightPos.z=sin(0.923*fTime0_X);
  19. vLightPos.xz=normalize(vLightPos.xz)*100;
  20. vLightPos.y=100;
  21. //创建在光源处的坐标系,应在cpu完成
  22. float3 z_dir=-normalize(vLightPos);
  23. float3 up=float3(0,0,1);
  24. float3 x_dir=cross(up,z_dir);
  25. float3 y_dir=cross(z_dir,x_dir);
  26. //将顶点变换到光源空间中,实际中应用矩阵变换
  27. float4 vPos;
  28. vInPos.xyz-=vLightPos.xyz;
  29. vPos.x=dot(vInPos,x_dir);
  30. vPos.y=dot(vInPos,y_dir);
  31. vPos.z=dot(vInPos,z_dir);
  32. vPos.w=1;
  33. //在光源空间中投影,实际中应根据FOV等因素构造特殊的投影矩阵,这里用默认的
  34. output.Position=mul(vPos,matProjection);
  35. //fDistScale用于把距离值规格化到[0,1]之间,fDistScale一般取1/farZClip
  36. output.vLightVec=fDistScale*vInPos;
  37. return( output );
  38. }

pixel shader

  1. float4 ps_main(float3 vLightVec:TEXCOORD0) : COLOR0
  2. {
  3. //压缩深度值到整数纹理
  4. float fDepth=dot(vLightVec,vLightVec);
  5. return float4(floor(fDepth)/256.0f,frac(fDepth),frac(fDepth),frac(fDepth));
  6. }
  1. float4 ps_main(float3 vLightVec:TEXCOORD0) : COLOR0{      // 压缩深度值到整数纹 理   float fDepth=dot(vLightVec,vLightVec);   return float4(floor(fDepth)/256.0f,frac(fDepth),frac(fDepth),frac(fDepth));}

到这里准备工作就结束了,接下来就是要渲染阴影的时候了

这时回到正常观察位置即camera位置,不再在light-view-space中观察

将场景中每个顶点再变换到light-view-space中,这是为了要找到顶点在light-view-space中的位置,以便与纹理中的距离值做比较

将已经变换的顶点再进行投影,再将投影平面坐标变换到纹理坐标空间,即把范围为[-1,1]的x,y坐标变换到[0,1]的范围中去,以便匹配相应的纹素texel

然后就是光照,既然有阴影必然有光照效果,而且应该是逐像素光照,可以使用任何光照模型,phong,blinn或者Oren-Nayar

最后的距离比较决定是否为阴影,光照计算,都在像素shader中进行

  1. float fBackProjCut;
  2. float fKa;
  3. float fKd;
  4. float fKs;
  5. float4 vLightColor;
  6. float fShadowBias;
  7. sampler ShadowMap;
  8. sampler SpotLight;
  9. float4 ps_main(
  10. float3 vNormal:TEXCOORD0,
  11. float3 vViewVec:TEXCOORD1,
  12. float3 vLightVec:TEXCOORD2,
  13. float4 vShadowCrd:TEXCOORD3) : COLOR0
  14. {
  15. vNormal=normalize(vNormal);
  16. float fDepth=dot(vLightVec,vLightVec);
  17. vLightVec=normalize(vLightVec);;
  18. float fDiffuse=saturate(dot(vLightVec,vNormal));
  19. float fSpecular=pow(saturate(dot(reflect(-normalize(vViewVec),vNormal),vLightVec)),16);
  20. float3 vShadowMap=tex2Dproj(ShadowMap,vShadowCrd);
  21. float fClosestDepth=vShadowMap.r*256+vShadowMap.g;
  22. //光照图,聚光灯
  23. float fSpotLight=tex2Dproj(SpotLight,vShadowCrd).r;
  24. //把像素的深度值与对应纹理中的深度值比较,得出阴影值0(无) or 1(有)
  25. float fShadow=(fDepth-fShadowBias<fClosestDepth);
  26. //cut back projection保证不会照亮聚光灯背后的像素
  27. fShadow=fShadow*(vShadowCrd.w>fBackProjCut);
  28. fShadow*=fSpotLight;
  29. return fKa*vLightColor+(fKd*fDiffuse*vLightColor+fKs*fSpecular)*fShadow;
  30. }

float fBackProjCut;float fKa;float fKd;float fKs;float4 vLightColor;float fShadowBias;sampler ShadowMap;sampler SpotLight;float4 ps_main(               float3 vNormal:TEXCOORD0,               float3 vViewVec:TEXCOORD1,               float3 vLightVec:TEXCOORD2,               float4 vShadowCrd:TEXCOORD3) : COLOR0{      vNormal=normalize(vNormal);   float fDepth=dot(vLightVec,vLightVec);   vLightVec=normalize(vLightVec);;      float fDiffuse=saturate(dot(vLightVec,vNormal));   float fSpecular=pow(saturate(dot(reflect(-normalize(vViewVec),vNormal),vLightVec)),16);   float3 vShadowMap=tex2Dproj(ShadowMap,vShadowCrd);   float fClosestDepth=vShadowMap.r*256+vShadowMap.g;      // 光照图,聚光灯   float fSpotLight=tex2Dproj(SpotLight,vShadowCrd).r;      //把像素 的深度值与对应纹理中的深度值比较,得出阴影值0(无) or 1(有)   float fShadow=(fDepth-fShadowBias& lt;fClosestDepth);         //cut back projection保证不会照亮聚光灯背后的像 素   fShadow=fShadow*(vShadowCrd.w>fBackProjCut);   fShadow*=fSpotLight;      return fKa*vLightColor+(fKd*fDiffuse*vLightColor+fKs*fSpecular)*fShadow;

}

由于SM算法基于图像空间,所以有一些缺陷,如果视点与光源位置差异很大,会产生明显走样,阴影边缘处会出现明显的阶梯状,这可以使用靠近百分比过滤PCF来改善,因为SM纹理中每个纹素texel可能不是投影到单一屏幕像素上,纹理分辨率越低,走样越严重。

这里的算法也不适用于点光源,仅仅在聚光灯时有效,要用于点光源,则需要3D纹理,进行6次渲染,过几天把这个做一下。

最新文章

  1. 到爱尔兰敲代码 / Come, Coding in Ireland
  2. Hierarchical Softmax
  3. Node.js——Async
  4. Js-字符转换数字
  5. STM32的串口
  6. String.split()方法你可能不知道的一面
  7. [OM]Dropship SO(直发/直运订单)的流程
  8. Performance Test of List&lt;T&gt;, LinkedList&lt;T&gt;, Queue&lt;T&gt;, ConcurrentQueue&lt;T&gt;
  9. 关于Go语言共享内存操作的小实例
  10. iOS UIActivityIndicatorView 的使用
  11. TestNG+ExtentReports生成超漂亮的测试报告
  12. centos 6.5静态网址简单配置
  13. Dynamics CRM Plugin DLL恢复工具
  14. 开源GIS知识
  15. Linux了解知识点
  16. python ironicclient源码分析
  17. C#保存日志文件到txt中,可追加保存,定时删除最后一次操作半年前日志文件
  18. C#_反射机制
  19. BZOJ1078 [SCOI2008]斜堆 堆
  20. Slony-I同步复制部署

热门文章

  1. 【POJ 2585】Window Pains 拓扑排序
  2. Mark Down 简单标记语言
  3. tzcacm去年训练的好题的AC代码及题解
  4. API生命周期第三阶段:API实施:使用swagger codegen生成可部署工程,择取一个作为mock service
  5. JSON的使用_检查JSON工具
  6. JDBC 学习笔记(三)—— JDBC 常用接口和类,JDBC 编程步骤
  7. 【bzoj4260】Codechef REBXOR Trie树
  8. HDU-2853 Assignment
  9. 浅谈android反调试之 API判断
  10. JDK7的maven项目切换到JDK8全纪录