Shadow Map 原理和改进_ronintao的博客

文章推薦指數: 80 %
投票人數:10人

shadow map是一种常用的实时阴影的生成方法。

通常用来生成平行光(direction light)的阴影。

点光源也可以用,不过要更加复杂一些(称 ... ShadowMap原理和改进 ronintao 于 2016-07-0711:50:54 发布 42146 收藏 88 分类专栏: Unity 文章标签: unity render 版权声明:本文为博主原创文章,遵循CC4.0BY-SA版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/ronintao/article/details/51649664 版权 Unity 专栏收录该内容 4篇文章 3订阅 订阅专栏 参考      1、CommonTechniquestoImproveShadowDepthMaps      2、Tutorial16:Shadowmapping      3、ShadowMapping      4、ShadowMappingAlgorithms      5、ShadowMap阴影贴图技术之探      6、CascadedShadowMaps 写在前面     之前已经很久没有再更新博客,上一篇已经是3年前的记录了,当时还比较菜(现在当然仍然菜),所以写的东西大多不是很好……     这期间其实也学了很多新的东西。

学习中比较痛苦的一点,是经常发现所学的东西要么就只有一个大意的原理讲解,不会特别深入;要么就是只有一个实现的简单方法,不知道所以然(国内的资料很多是这样);能够两者兼顾的通常是比较难啃的英文资料,隔一段时间回去再翻阅,由于语言问题又需要花比较长的时间;另外一个问题就是各个相关知识点比较散落,不方便融汇贯通来看。

    因此,又产生了回来重开博客,进行汇总的想法。

    另外回来重新翻阅之前的博客,发现多了很多留言,以后慢慢的回复填坑吧…… 一、shadowmap原理      shadowmap是一种常用的实时阴影的生成方法。

通常用来生成平行光(directionlight)的阴影。

点光源也可以用,不过要更加复杂一些(称为omnidirectionalshadowmaps,生成的是cubemap,如果我会填坑的话,会在以后讲到)。

     说到xxmap,第一反应当然是会生成并利用某种纹理。

没错,shadowmap的核心就是要生成并利用shadowdepthmap。

下面具体来说。

1、阴影算法的任务      shadowmap(以及shadowvolume)等shadow算法,到底是做什么的?这其实是需要解决的第一个问题。

这些阴影算法的核心,实际上就是要针对任意给定的一个物体片段点(fragmentpoint),鉴定出它是否处在阴影之中。

     所以阴影算法,归根结底是要给出这样一个函数: boolShadowAlgorithm(vector3fragmentPoint){ if(IsInShadow(fragmentPoint)) returntrue; else returnfalse; }      各种不同的阴影算法,实际上就是“是否在阴影中”的鉴别方法不同。

2、阴影产生的自然原理      什么样的点是在阴影中的?看看下面这张图 图1、阴影(图中橘黄色的是不透明的物体)      在平行光的照射下,直觉的可以知道,点A、点D在阴影之外,而点B、点C在阴影之中。

直觉判断的依据,就是由于光线的直线传播特性,一旦遇到遮挡,就会产生阴影。

换言之,对于光线来说不可见的点,就是在阴影中的点。

     那么上面的判断函数 IsInShadow,就可以进一步写为如下的伪代码: boolIsInShadow(vector3fragmentPoint){ return!IsVisibleToLight(fragmentPoint,mLight); }     其中mLight是需要生成阴影的平行光,fragmentPoint是需要进行判断的片段点。

3、基于自然原理的提炼:lightspace的 shadowdepthmap      一提到这种可见性的判定,自然的就会想到depthtest。

我们站在光源的位置,按照光线传播的视角,观察场景,计算场景中的物体距离光源的距离(也就是该视角下的深度),并记录各个位置上的最小值(即距离光源最近的点的位置),从而获得一张shadowdepthmap。

     其实,上面的流程和普通的camera获取depthmap的流程是基本一样的,只是要改换到光源位置来观察而已。

4、应用到场景中:利用shadowdepthmap      获取了shadowdepthmap之后,就要利用它来进行鉴别。

根据第2小节的讨论,可以知道,如果某个点,其在光源视角下的depth大于shadowmap中对应位置的depth,则意味着它被某个物体遮挡,因此是在阴影中的;反之则不在阴影之中。

     所以,对于世界中的某个点p,我们只要将其转移到lightspace,比较他在lightspace下的depth,就可以判定它是否是在阴影之中了。

二、基本的shadowmap流程      根据第一章的讨论,可以大致梳理出流程,下面逐步来说: 1、在lightspace摆放camera      1)投影方式      camera的投影方式有两种:orthographic和perspective。

那么应该使用哪一种呢?      这就牵涉到shadowmap的原理:以光源的角度观察世界。

由于我们这里使用的是平行光,所以它眼中的世界是没有透视关系的,因此应该使用orthographic。

而如果使用的是点光源,那么就应该使用perspective了。

     2)视锥体(frustum)      确定了投影方式为orthographic之后,就要确定它的视锥体。

显然我们需要将view视角下所有物体都包含到视锥体中,但是过大也没有意义,会引起不必要的渲染开销,另外也可以更充分的利用shadowdepthmap。

     这里有两种方式(这一小节的图,全部来自于微软的网站):      a.FitToScene 图2、任意指定的视锥体和fittoscene设定下的视锥体      左边是一个任意设定的视锥体,里面的梯形是view视角的frustum,右边是调整shadowfrustum,使他正好包含整个scene的情况。

     可以看到,如果按照左边的设置,视锥体中有一部分区域是完全空着的,那么截取出的shadowdepthtexture,对应的这一部分也就完全为空,因而利用率更低。

另一方面,左边的视锥体远近平面距离较远,由于深度取值实际是转换到0~1范围内的,这就意味着有部分值域是利用不上的,那么深度值的精度也被降低了。

     那么,如果给定场景的AABB,如何确定灯光的frustum?如果当前lightspace的只需要将该AABB的顶点转移到lightspace,然后取出xmin,xmax,ymin,ymax,zmin,zmax。

由于camera的观察视野由xy分量确定,观察的深度由z确定,因此将camera摆放在x,y的中心点,size取其半径的较大值,然后将zmin设置为near,zmax设置为far即可。

     例如下面unity的代码,lightCamera就是我们摆放在灯光处的camera privatevoidFitToScene(){ ListaabbBounds=GetSceneAABBCorners(sceneObject); floatxmin=float.MaxValue,xmax=float.MinValue; floatymin=float.MaxValue,ymax=float.MinValue; floatzmin=float.MaxValue,zmax=float.MinValue; foreach(Vector3cornerPointsinaabbBounds){ Vector3pointInLightSpace=transform.worldToLocalMatrix.MultiplyPoint(cornerPoints); xmin=Mathf.Min(xmin,pointInLightSpace.x); xmax=Mathf.Max(xmax,pointInLightSpace.x); ymin=Mathf.Min(ymin,pointInLightSpace.y); ymax=Mathf.Max(ymax,pointInLightSpace.y); zmin=Mathf.Min(zmin,pointInLightSpace.z); zmax=Mathf.Max(zmax,pointInLightSpace.z); } floatxsize=(xmax-xmin)/2; floatysize=(ymax-ymin)/2; floatzsize=(zmax-zmin)/2; lightCamera.transform.localPosition=newVector3((xmin+xmax)/2,(ymin+ymax)/2,0); lightCamera.orthographicSize=Mathf.Max(xsize,ysize); lightCamera.nearClipPlane=zmin; lightCamera.farClipPlane=zmax; }      b. FittoView 图3、fittoview设定下的视锥体      这种方法是调整视锥体使他正好包含viewfrustum。

可以看到在左边的情况下,其表现非常好,只需要包含很少的区域,右边的情况对比fittoscene则有得有失。

     值得注意的是,view视角下的阴影,并不一定完全由view视角内包含的物体产生,可以看下面这张图,view视角很小的情况下就会出现问题。

图4、fittoview在viewfrustum很小时的阴影丢失      因此在实际使用中,是使用灯光的无穷远视锥体和viewfrustum以及场景的AABB盒的交集来做的 图5、综合使用scene与viewfrustum来解决阴影丢失      另外这种方法还有个问题在于视锥体的确定和viewfrustum产生了关联。

只要观察视角变动,则截取尺寸就有可能变化,而在fittoscene情况下,则是相对固定的。

     这种情况的代码与上面的差别不大,可以看到near和farclipplane仍然取决于scene,而size则完全取决于viewfrustrum privatevoidFitToView(){ ListsceneAABBBounds=GetSceneAABBCorners(sceneObject); ListviewAABBBounds=GetViewCorners(viewCamera); RoninBound3DsceneBound=GetBoundInLightSpace(sceneAABBBounds); RoninBound3DviewBound=GetBoundInLightSpace(viewAABBBounds); lightCamera.transform.localPosition=newVector3(viewBound.XCenter,viewBound.YCenter,0); lightCamera.orthographicSize=Mathf.Max(viewBound.XSize/2,viewBound.YSize/2); lightCamera.nearClipPlane=sceneBound.zMin; lightCamera.farClipPlane=sceneBound.zMax;}      其中获取场景AABB的由于比较简单这里就不给出,获取perspectivecamera的边角稍微麻烦一点,在unity中代码如下: privateListGetViewCorners(Cameracam){ //inunity,thefovistheverticalfieldofview;horizontalFOVvariesdependingontheviewport'saspectratio.Fieldofviewisignoredwhencameraisorthographic(seeorthographic). Listcorners=newList(); floatySizeNear=cam.nearClipPlane*Mathf.Tan(cam.fieldOfView/2*Mathf.Deg2Rad); floatxSizeNear=cam.aspect*ySizeNear; floatySizeFar=cam.farClipPlane*Mathf.Tan(cam.fieldOfView/2*Mathf.Deg2Rad); floatxSizeFar=cam.aspect*ySizeFar; corners.Add(cam.transform.localToWorldMatrix.MultiplyPoint(newVector3(xSizeNear/2,ySizeNear/2,cam.nearClipPlane))); corners.Add(cam.transform.localToWorldMatrix.MultiplyPoint(newVector3(-xSizeNear/2,ySizeNear/2,cam.nearClipPlane))); corners.Add(cam.transform.localToWorldMatrix.MultiplyPoint(newVector3(xSizeNear/2,-ySizeNear/2,cam.nearClipPlane))); corners.Add(cam.transform.localToWorldMatrix.MultiplyPoint(newVector3(-xSizeNear/2,-ySizeNear/2,cam.nearClipPlane))); corners.Add(cam.transform.localToWorldMatrix.MultiplyPoint(newVector3(xSizeFar/2,ySizeFar/2,cam.farClipPlane))); corners.Add(cam.transform.localToWorldMatrix.MultiplyPoint(newVector3(-xSizeFar/2,ySizeFar/2,cam.farClipPlane))); corners.Add(cam.transform.localToWorldMatrix.MultiplyPoint(newVector3(xSizeFar/2,-ySizeFar/2,cam.farClipPlane))); corners.Add(cam.transform.localToWorldMatrix.MultiplyPoint(newVector3(-xSizeFar/2,-ySizeFar/2,cam.farClipPlane))); returncorners; }     以上的代码还有另外一个问题,viewcamera的frustum有时farclip会非常的大,导致viewfrustum的aabb非常的大,包含了大量不需要的信息。

所以在实际使用中,可以先获取viewcamera的maxdepth,然后再进行计算(当然可能还有更好的方式) 2、截取 shadowdepthmap      1)texturesize      要将shadowdepthmap保存为多大尺寸的纹理是个非常重要的问题。

尺寸越大,包含的信息越多,当然越精确,但是消耗的内存也越多。

尺寸太小会引发锯齿问题。

这里先不展开讨论,在后面的部分会详细讲到。

     2)编码方式      texturesize体现的是位置的精确度,而该像素点上的值,则是其深度值的精度。

如果仅仅利用纹理像素中的一维,则精度显然不够高,而且十分浪费。

所以可以通过编码的方式,将其四位(RGBA模式下)统统利用起来。

     例如unity中的 EncodeFloatRGBA //Encoding/decoding[0..1)floatsinto8bit/channelRGBA.Notethat1.0willnotbeencodedproperly. inlinefloat4EncodeFloatRGBA(floatv) { float4kEncodeMul=float4(1.0,255.0,65025.0,16581375.0); floatkEncodeBit=1.0/255.0; float4enc=kEncodeMul*v; enc=frac(enc); enc-=enc.yzww*kEncodeBit; returnenc; }     在需要取出的时候再进行解码: inlinefloatDecodeFloatRG(float2enc) { float2kDecodeDot=float2(1.0,1/255.0); returndot(enc,kDecodeDot); }      这样就提高了深度值的精度。

3、开始渲染阴影      对于一般的物体,那么直接渲染即可,而对于一些透明的物体,或者需要特殊处理的物体,则可以在这一步再做点文章。

     比如说对于一些透明的物体,只希望他的边界处产生阴影,则可以只对他的边界处记录Z值。

    这里会在以后补全(希望我会填这个坑) 4、对于每个点,转到lightspace,转换矩阵的确定      首先要明确的是转换的目的。

上面截取的depthmap是在lightspace中获得的,那么对于场景中任意的一点,就需要知道他在lightspace中的位置。

     那么和viewspace中的mvp转换一样,这里也需要转移到lightspace的mvp矩阵。

     在unity中这几个矩阵其实可以直接获取,不过在《Unity3DShaderLab开发实战详解》中的第13章中有出现一个具体的矩阵,但是这个矩阵有一些问题(这是一本充满bug的好书),因此在这里额外讨论一下这个矩阵如何确定。

     这里我们分别对m、v、p进行讨论:      1)model矩阵:local->world      显然,lightspace和viewspace所面对的世界是一样的,那么将坐标从local坐标系转换到world坐标系的model矩阵就是相同的。

那么在unity中,在shader中直接使用 _Object2World矩阵就可以了      2)view矩阵:world->view      在unity中可以直接获取这个矩阵,使用 Camera.worldToCameraMatrix 即可。

而在《实战》一书中,使用的是灯光的Transform.worldToLocalMatrix。

     乍一想,这两个矩阵的功效应该相同:对lightspace来说,所谓的viewspace,其实不就是灯光看到的世界吗,那不就是灯光本地的世界吗?      然而将这两个矩阵打印出来,会发现在Z轴方向有符号上的差异。

     详细的讨论可以参考 unity,相机空间与相机gameObject的局部空间 这篇文章。

其实问题的本质就在于unity中的坐标系是左手坐标系,而cameraview坐标系是右手坐标系。

所以在z轴上发生了方向的反转,这样的反转之后,camera所见的物体z<0,且越远,z越负。

     因此,这里需要使用 Camera.worldToCameraMatrix来获取view矩阵,或者使用对角矩阵[1,1,-1,1]左乘 Transform.worldToLocalMatrix。

两者得到的结果是相同的。

     3)projection矩阵:view->projection      在unity中可以直接从 Camera.projectionMatrix中获取。

     《实战》一书中,在light处放置的是一个perspective的camera,所以其投影矩阵是透视投影,他这里给出的矩阵有比较多的错误。

     这里的求法可以参考我之前的博客《【OpenGL】02-OpenGL中的坐标系》 最后的结论:      在unity的官方手册上也有相同的公式,可见链接最下面的范例。

     由于实际使用中,camera处在viewspace的原点,因此 Right-Left=WidthAtNearClip; Top-Bottom=HeightAtNearClip; Right+Left=0; Top+Bottom=0;     而在近平面的width和height可以由camera的fov、aspect和near求取出来: floatfov=c.fieldOfView; floatn=c.nearClipPlane; floatf=c.farClipPlane; floataspect=c.aspect; floatheightAtNear=2*n*Mathf.Tan(fov/2f*Mathf.Deg2Rad); floatwidthAtNear=aspect*heightAtNear;     将这个结果带入原矩阵,并定义 floatd=1/Mathf.Tan(fov/2f*Mathf.Deg2Rad);     则得到      这样求出的投影矩阵,和Camera.projectionMatrix是相等的。

     但这个矩阵仍然不能直接使用,需要根据GPU情况再进行处理(这里的原理我并不清楚,期待今后可以补完),还需要使用 GL.GetGPUProjectionMatrix(c.projectionMatrix,false)     才能够得到shader中使用的投影矩阵。

     4)正投影的投影矩阵      仍然可以使用 Camera.projectionMatrix然后使用 GL.GetGPUProjectionMatrix转换后得到。

具体的 Camera.projectionMatrix矩阵计算方法依旧可以参考我之前的博客 《【OpenGL】02-OpenGL中的坐标系》       5)correction矩阵      注意到《实战》一书中,还有一个correction矩阵,这个矩阵是做什么的?      一个顶点,经过MVP变化之后,其xyz分量的取值范围是[-1,1],现在我们需要使用这个变化过的顶点值来找到shadowdepthmap中对应的点来比较深度,即要作为UV使用,而UV的取值范围是[0,1],所以需要进行一个值域的变换,这就是这个矩阵的作用。

    需要注意的是,要使这个矩阵成立,该vector4的w分量必须是1。

在shader中运算的时候必须注意。

5、将上面求得的转换矩阵赋值给shader      在unity中,可以使用下面的代码来达到效果。

Shader.SetGlobalMatrix("LightMV",matrix);    在后面的实践中,有时我们还需要传递数组给shader,这个特性在目前的版本(5.3)中还不支持,从官网的论坛上来看,将在5.4中支持该特性。

6、在物体的shader中进行深度比较,来确定阴影     到了这一步,就只需要简单的比较深度就可以了,当然这里也是可以扩展的改善的,后面的bias里面我们会谈到。

7、基础shadowmap的代码     代码可以参考附例中的" 01BasicShadowMap",可以看到综合的流程。

三、对这个流程的一些思考      除了上一章中对于每个独立步骤的讨论之外,对于流程整体,这里还有一些思考。

1、对静态平行光源,shadowdepthmap可以预生成吗?      如果场景中需要投射阴影的物体是静态的,且不使用fittoview时是可以的。

2、多个光源要怎样处理?      现在针对单个光源,是放在mainpass中执行的,对于多个光源,则每个光源都需要有自己独立的depthmap,以及自己的转换矩阵,在shader中分别处理。

可以写在一个pass中,也可以分散到多个pass(类似于forwardadd,其实感觉forwardadd是最合适的处理位置)。

    现在我们已经完成了基础的shadowmap。

但是现在的效果还很难让人满意,所以还需要很多改进。

下面我们针对问题逐个来看。

四、问题一:ShadowAcne&&SelfShadowing 1、现象      在任何改进措施都不采用的情况下,运行 " 01BasicShadowMap",会看到一张惨不忍睹的画面(当然,阴影还是有的) 图6、无改进的shadowmap中的shadowacne      更常见的情况是下图左边这样的锯齿状条纹: 图7、条纹状的shadowacne      这种现象,就被称为 ShadowAcne或者Self-Shadowing 2、原因      ShadowMapping 里的 Shadowacne一节解释的比较清楚,根本原因就是shadowdepthmap的分辨率不够,因此多个pixel会对应map上的同一个点。

图8、shadowacne成因图      图中黄色箭头是照射的光线,黑色长方形是实际物体表面,黄色的波浪线是shadowmap中的对应值的情况。

     可以看到,由于map是对场景的离散取样,所以黄色的线段呈阶梯状的波浪变化,相对于实际场景中的情况,就有一部分比实际场景中的深度要大(对应着黑色线段部分),着部分不会产生阴影(注意图画反了);一部分比实际场景中的深度要小(对应着黄色线段部分),这部分会产生阴影,所以就出现了条纹状的阴影。

     由于这种情况,是物体的实际深度,与自己的采样深度,相比较不相等(实际深度大于采样深度)导致的,所以可谓是自己(采样的副本)遮挡了自己(实际的物体),所以被称为selfshadowing。

3、对策      解决的方法很简单,其实只有实际深度大于采样深度的时候才有问题,那么我们在计算实际深度的时候,往灯光方向拉一点,让他减小一点就可以了 图9、shadowacne的对策:ShadowBias      这就是shadowbias的原理,具体怎么做,我们后面来看。

五、问题二:PeterPanning 1、现象 图9、peter-panning      这个现象只有在加入了shadowbias的时候才会出现,如上图,会看到影子脱离了物体(像飞起来的小飞侠一样),被称为peter-panning。

2、原因      上面现象中提到了shadowbias,容易知道,其实就是shadowbias加的太多了。

     shadow的计算是根据计算中使用的深度与shadowmap的深度对比得到的,如果shadowbias加的太多,就会导致物体计算影子时的深度,与他的实际深度差别太大。

所以影子就和物体产生了分离。

3、对策      解决的方法,当然就是不要使用太大的shadowbias。

六、ShadowBias      前面两节一直在说要加入shadowbias,但是可以看到,加小了没用,会有shadowacne;加多了又会导致peterpanning。

所以要怎么加,是有一定技巧的,最终的目标是要找到一个刚好能够消除shadowacne的bias值。

     现在常用的shadowbias的计算方法,是基于物体斜度的,称为slopescalebaseddepthbias。

     简单来说就是     公式的具象化可以参考下图: 图10、slopescaledepthbiasdiagram      这样做的原理是,当斜度大的时候,shadowmap上的一个点,所对应的实际物体的一小块面,其深度变化就会更大。

     举例来说,一个x方向的斜坡,同样是在x方向上差0.01,如果坡角是45度,那么深度差就是0.01,如果坡角是60度,那么深度差就是0.017. 如果采样点都是在x=0.005处,那么对坡角60的情况,就需要bias至少为0.0085,才可以保证不会产生shadowacne,而45度的情况,bias只要0.005就可以了 图11、slopescaledepthbias示例      对于这个公式,需要做一些说明:     1)坡度如何确定?当然是用顶点的normal,但是需要注意的是,我们是在灯光坐标系下观察物体的,所以normal需要转换到lightspace中。

    2)里面的两个参数factorSlope和 constantBias如何确定?NVidia告诉我们:这是一门艺术....所以慢慢调吧。

     shadowbias的代码,可以参考范例中"02ShadowMapWithBias"部分。

七、问题三:HardShadow&&SoftShadow 1、现象      现在我们生成的阴影,其边缘是没有过渡的,这样也容易产生锯齿。

(本节的图来自于nvidia的gpugems) 图12、hardshadow和边缘的锯齿      而现实世界中的影子(物理原理我忘记了,希望以后会填坑),边缘会更淡,有一定的渐变效果。

     我们现在产生的这种无渐变的影子,就被称为hardshadow,有边缘效应的则称为softshadow。

2、原因      原因是显然的:我们的判断是0-1的,只是检查是否在阴影之中,只有是和不是两种情况,当然会产生hardshadow。

3、对策      常用的解决方案称为shadowfiltering,在下一段,我们会展开来说。

八、ShadowFilter&&PCF(PercentageCloserFiltering)      由于对PCF以外的shadowfilter了解甚少,因此我对于shadowfilter的理解还不够清楚。

其他可查的filter手段包括"Exponentialshadowmapfiltering",“VarianceShadowMaps”(这个很常用,有机会要补完),都是用来解决shadowmap的硬边缘和锯齿问题。

     对于PCF,其核心思路就是,如果是影子内部,则他周围的一圈点肯定也在阴影之中,如果是影子边缘,则他周围就会有些点不在阴影里,且越靠边,这些不在阴影中的邻居越多。

     所以检测一个点是否是在影子边缘,只要观察他的邻居就可以了。

     在实际代码中,输入一个点A,我们在 shadowdepthmap中取出他和他周围点的深度,然后和A的深度做比较(相当于用A的深度来代表他周围那一圈点的深度),然后根据有多少个点在阴影中,确定点A处影子的强度。

     原理是十分简单的,根据取点的方法和范围,又分为 2×2PCF,4×4PCF等等;根据每个点对影子强度加成的系数不同,又分为平均采样,泊松采样等等。

     代码可以参见范例中的"03ShadowMapWithPCF" 九、问题四:近处锯齿PerspectiveAliasing 1、现象 图13、锯齿      如左图,在场景比较大的情况下容易出现这种情况,在viewcamera的近处,产生了锯齿状的影子。

2、原因      根本原因仍然是shadowmap的分辨率不够(可以看出,这一点是shadowmap方法的软肋)。

     在camera的近处,场景中的物体的分辨率比较高,一小段面片,会对应着大量的pixel,而此时的shadowmap精度没有发生变化,所以就会有大量的pixel对应着shadowmap中的同一点,因而产生锯齿情况(当然使用PCF会略有好转)。

     MSDN上的这张图很能说明问题: 图14、单张shadowmap时的效果情况图      图中的方块中,颜色越深,表示shadowmap的利用情况越差。

可以看到,在camera近处,shadowmap的利用率低,最终产生了锯齿。

     另外一方面,对于较大的场景,如果使用一张shadowmap来记录整个场景的depth,就容易不够用,也容易导致锯齿。

3、对策      解决的方案参见下一段:CSM 十、CSM(CascadedShadowMap)      针对上面的问题,容易想到,既然一张不够,那么我们就多来几张。

这就是CSM的出发点。

那么场景就变成了这样: 图15、多张shadowmap时的效果情况图      同上面的图一样,图中的方块颜色越深,表示shadowmap的利用情况越差。

可以看到在使用多张shadowmap的情况下,效果明显提高了。

     CSM本身也是一个很大的话题,其中许多关键点也有多种实现方式,具体可以参考这里。

我这里由于懒的原因,只实现了最基本的CSM。

     下面来说明具体流程: 1、首先要将整个场景分割成多块      这里也主要有两种方法:      一种是FitToScene,他的特点是所有区块的近平面都是相同的,变化的是远平面。

见下图左。

     另一种是FitToCascade,他是普通的按照viewcamera的深度范围,将场景划分为若干块。

见下图右。

图15、FitToScene&&FitToCascade      显然FitToCascade对map的利用率更高,但是FitToScene也有好处,主要是用来解决闪烁问题(这里我并没有理解透彻,期望以后可以补全)。

2、对每个区块,进行正常的shadowmap截取      这一步和普通的shadowmap并没有什么不同,只是要针对每个区块进行一次,从而会得到多个shadowdepthmap。

每次截取,都需要记录截取时lightcamera的MVP矩阵。

3、在shader中,判断应该使用哪个shadowmap      由于每个区块划分的依据是深度,因此简单的做法是将顶点的深度与划分区块的深度进行比较,就可以知道归属了。

     另一种复杂的方法被称为Map-basedcascadeselection。

由于我本人理解有限,这里也不展开讲了。

4、选出shadowmap之后,按照普通的shadowmap方法进行深度比较和filtering      这一步与普通的shadowmap也是相同的。

同样可以使用shadowbias和pcf。

5、在普通的CSM流程之后,还可以进行CSM区块之间的blending等操作来改善效果     这一步可以说是对基本CSM的改进了,我这里的代码没有实现。

总体来说,就是针对两张shadowmap交接处的不匹配现象,通过blending来进行平滑过渡。

     以上就是CSM的基本流程,具体的代码可以参考范例的"4CSM"部分。

十一、范例      最后附上范例,地址:http://download.csdn.net/detail/ronintao/9569628。

该范例使用unity5.3.4编写,由于水平所限,仅仅实现了最基本的功能。

ronintao 关注 关注 42 点赞 踩 25 评论 88 收藏 打赏 扫一扫,分享内容 点击复制链接 专栏目录 图形学基础|阴影技术ShadowMap 【黑键】 04-10 2525 实时阴影技术总结 实时渲染中的软阴影技术 ShadowMap原理和改进 以上几个参考文章写的非常好. 我只是做了最第一篇的简单摘录.作为我入门的学习. 阴影的实现 在光线追踪算法中,实现阴影(shadowray)更加符合直觉. 在光栅化算法中,基于ShadowMap的实现更加常见. 阴影的"软硬" 理想中的点光源会造成只有本影区的硬阴影(hardshadows) 但是现实中的光源毕... shadowmap范例 07-07 shadowmap范例,对应解释文档参考http://blog.csdn.net/ronintao/article/details/51649664 评论 25 您还未登录,请先 登录 后发表或查看评论 ShadowMap_PCF 06-07 ShadowMap_PCF Unity中ShadowMap动态阴影的实现 MonoBehaviour的博客 03-16 456 具体的实现如下: 1,生成ShadowMap 2,根据ShadowmMap实现阴影的绘制 ShadowMap的生成: v2fvert(appdatav) { v2fo; o.vertex=mul(unity_ObjectToWorld,v.vertex); float3normal=normalize(mul(v.normal,unity_WorldToObject)); 终于搞定了SHADOWMAP, coder 03-13 1789 5*5pcf shadowmap的原理与实现 the_shy33的博客 09-01 821 最近在预研一个pbr模型的时候接触到了阴影渲染的部分,因为之前一直用的是Unity内置的宏去生成阴影,并且需要用到平行光。

对于阴影的制作有各种各样的办法,而shadowmap就是最典型生成阴影的一种方法,在Unity内部也是用shadowmap的变种**CSM(CascadedShadowmap)和SSSM(ScreenSpaceShadowmap)**的两种阴影。

在Unity内部如果目标平台不支持SSSM的话,它会转为CSM的阴影。

下面就简单说一下shadowmap的原理和实现过程。

shadow shadowMap简单回顾 qq_36091277的博客 05-16 104 编程指南:shadowMap示例连接 编程指南部分代码 获取gl变量 varcanvas=document.getElementById('webgl'); vargl=getWebGLContext(canvas); 获取Plane、Triangle顶点缓冲区,颜色缓冲区,索引缓冲区(数组转的) vartriangle=initVertexBuffersForTriangle(gl);//三角形 varplane=initVertexBuffersForPlane(gl); three.js聚光灯SpotLight使用,调整聚光灯颜色、位置、角度、强度、距离、衰减指数、方向、可见性、是否产生阴影属性(vue中使用three.js09) 点燃火柴的博客 09-11 1798 聚光灯SpotLight使用一、聚光灯介绍二、如何使用聚光灯1.创建聚光灯2.聚光灯的属性2.1颜色-color2.2是否可见-visible2.3角度-angle2.4强度-intensity2.5距离-distance2.6光照衰减指数-decay2.7照射面光影衰减百分比-penumbra2.8目标-target2.9是否产生阴影-castShadow2.10位置-position三、demo效果四、demo代码 一、聚光灯介绍 聚光灯是一个沿着特定方向方散发的光源,照射范围在三维空间中构成一个圆锥体 点光源阴影 m0_57980287的博客 06-23 143 文章目录点光源阴影(OmnidirectionalShadowMaps)摊牌原理实现显示立方体深度贴图副作用锯齿 点光源阴影(OmnidirectionalShadowMaps) 摊牌 由点光源产生的阴影,过去的名字是万向阴影贴图(OmnidirectionalShadowMaps) 点光源可以照射的方向是空间中的各个方向,因此他产生的阴影也是在空间中的各个方向 原理 先回顾一下阴影贴图中提到的绘制阴影的原理:首先在光源的视角下渲染场景,得到一张深度贴图,然后在摄像机的视角下再次渲染场景, 版本升级后ShadowMap内存骤增 UWA—简单优化,优化简单! 09-03 242 这是第174篇UWA技术知识分享的推送。

今天我们继续为大家精选了若干和开发、优化相关的问题,建议阅读时间10分钟,认真读完必有收获。

UWA问答社区:answer.uwa4d.com UWAQQ群2:793972859(原群已满员) 本期目录: 版本升级后ShadowMap内存骤增 UGUI的Outline的替代方案 xLua对Obj的引用无法释放 坐标系转换 内存 Q:自从升级... shadowmap中的深入测试 coder 11-06 186 我发现shadowmap,如果正交投影,不论多少,都是同样的,只要在远切面内 如下图 如果是透视投影, 即使在远切面1000.0f内,投射投影,也不是说在1000以内就行,得是在100以内还行,阴影图会出现个小的物体,再小就看不见了, 下图是900的位置 下图是300的位置 下图是100的位置 下图是视点为10的位置 如果想出来效果,就要增加阴影图尺... 用shader创建shadowmap时出现了问题 03-23 如果有一个非常大的场景,场景中的街道上有一个人,这个人站在太阳下。

如果以太阳的视角创建shadowmap,那么人在这张shadowmap最多也就占1像素。

要是把这张shadowmap贴回场景,估计太阳下那个人的阴影可能会极度的不正常,就算提高shadowmap的分辨率也不是一个好的解决方案。

有没有解决这类问题的方法,谢谢! shadowmapdepthbias yaorongzhen123的专栏 09-11 364 研究过shadowmapping技术的人应该都知道shadowmap会有一种叫self-shadowing(有的文章也叫shadowacne)的问题,如下图所示: 因为shadowmap的精度有限,当要渲染的fragment在lightspace中距Light很远的时候,就会有多个附近的fragement会sampershadowmap中同一个texel,但是即使这些frag... OpenGLES学习教程(十六)ShadowMap的理解 HelloCaptain 07-20 747 目前游戏上的阴影一般用ShadowMap实现。

我一直在学习LearnOpenGL上的文章,ShadowMap这一篇,说实话我两年前就开始看了,一直没明白,或者说没有认真花时间去看。

最近在给lives2d堆功能,终于也到了阴影的实现。

LearnOpenGL上ShadowMap教程地址: http://learnopengl-cn.readthedocs.io/zh/latest/05%2... 全局光照算法:reflectiveshadowmaps 最新发布 JMXIN422的博客 02-26 132 1.技术理解 RSM的全称是reflectiveshadowmaps,受到InstantRadiosity这个离线技术的启发,其思想和ShadowMap的思想近似。

在正式介绍和了解这个技术之前,我需要确定RSM用处何在?我想,《RTR4》中对它的分类很正确——DynamicDiffuseGlobalIllumination,这是一个处理动态全局漫反射的技术: GI(全局光照):用于二次及以上bounce造成的间接光。

Dynamic(动态):可以实时更新,可作用于动态物体。

Diffuse( 使用shadowmap实现软阴影效果 痞子龙3D编程 07-23 3502 在实时阴影渲染中shadowmap是一种颇具潜力的算法,相比shadowvolume算法具有实现简单、适应范围广的优点,而且shadowmap是基于图像的算法,在实现软阴影上具有先天优势,下面我们就来看看如何用shadowmap实现软阴影效果。

通常的shadowmap算法产生的 LearnOpenGL笔记6.4PointShadows(点光源阴影贴图) u013617851的博客 01-05 227 我们学习了使用阴影贴图创建动态阴影。

但它主要适用于定向(或聚光)灯,因为阴影仅在光源方向上生成。

因此,它也被称为directionalshadowmapping定向阴影贴图,因为深度(或阴影)贴图仅从光所看到的方向生成。

本章将重点关注的是在所有周围方向上动态阴影的生成。

我们使用的技术非常适合点光源,因为真正的点光源会向各个方向投射阴影。

这种技术被称为点(光)阴影或更早以前称为omnidirectionalshadowmaps全向阴影贴图。

directionalshadowmappi three中shadow的常用参数 qq_38694034的博客 04-04 1007 当我们投射阴影的时候如果想调节阴影的清晰度:可以将shadow.mapSize设置为更大的值:  light.shadow.map.width=light.shadow.map.height=1024  // 还可以选的值128,256,512,2048等当我们投射阴影的时候如果想调节阴影的大小:可以设置shadow.camera的参数:vard=50;light.s... ShadowMap基本原理 l364244206的专栏 07-23 528 unity的阴影实现方式是采用Shdowmap技术,但是一直不知道其中的原理。

它的原理并不复杂,假设有一个摄像机在灯光的位置,从灯光的位置往物体看,这时候会有一张光源空间的深度信息图,这就是ShadowMap。

凡是物体的深度值大于ShadowMap上的深度值的都是被遮挡的部分,表示处于阴影中。

所需知识点: 1.模型空间到屏幕空间的变换过程http://www.idivecat.co... “相关推荐”对你有帮助么? 非常没帮助 没帮助 一般 有帮助 非常有帮助 提交 ©️2022CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页 ronintao CSDN认证博客专家 CSDN认证企业博客 码龄14年 暂无认证 27 原创 21万+ 周排名 174万+ 总排名 23万+ 访问 等级 1840 积分 223 粉丝 107 获赞 60 评论 236 收藏 私信 关注 热门文章 Unity中的旋转 52607 ShadowMap原理和改进 42142 Unity中的坐标系 26905 【OpenGL】02-OpenGL中的坐标系 16337 【cocos2D-x学习】9.音乐炫台——音效与特效 9178 分类专栏 cocos2D-x 16篇 C/C++ 4篇 Android OpenGL 3篇 Unity 4篇 C# 1篇 渲染 最新评论 Unity中的坐标系 夏日蛙鸣: 好文章,收益匪浅啊,不愧是大佬~ ShadowMap原理和改进 大A气氛组: 感谢大佬,受益匪浅 ShadowMap原理和改进 william607: 感谢作者,有一些图缺了,可以麻烦再补一下吗 ShadowMap原理和改进 weixin_46269678: 基于斜度的偏移主要是解决极端角度下,比如光线接近平行表面时产生的shadowacne ShadowMap原理和改进 Qiu.D: 对,我也是,锯齿更加严重了 您愿意向朋友推荐“博客详情页”吗? 强烈不推荐 不推荐 一般般 推荐 强烈推荐 提交 最新文章 C#中运算符重载的几点注意 凹凸材质:从BumpMap到ReliefMap Unity中的旋转 2017年1篇 2016年4篇 2014年2篇 2013年23篇 目录 目录 分类专栏 cocos2D-x 16篇 C/C++ 4篇 Android OpenGL 3篇 Unity 4篇 C# 1篇 渲染 目录 打赏作者 ronintao 你的鼓励将是我创作的最大动力 ¥2 ¥4 ¥6 ¥10 ¥20 输入1-500的整数 余额支付 (余额:--) 扫码支付 扫码支付:¥2 获取中 扫码支付 您的余额不足,请更换扫码支付或充值 打赏作者 实付元 使用余额支付 点击重新获取 扫码支付 钱包余额 0 抵扣说明: 1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。

2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值



請為這篇文章評分?