OpenGL Cascaded Shadow Maps - Make Games, Not War
文章推薦指數: 80 %
To begin with, cascaded Shadow Mapping is a technique which uses several shadow maps to further increase the resolution of real-time ... Skiptocontent AfterfinallygettingCSMtoworkinmyengineI’dthoughtI’dmakeasimpleproject inOpenGLforanyoneinterested.Itisbasedonthisexample:https://mynameismjp.wordpress.com/2013/09/10/shadow-maps/ whichexplainsitingreatdetail. I’llgothroughtherelevantcodeandadownloadlinkforanyoneinterested Tobeginwith,cascadedShadowMappingisatechniquewhichusesseveralshadowmapstofurther increasetheresolutionofreal-timerenderedshadows.ItworksbypartitioningtheviewfrustumandcreatinganAxis-AlignedBoundingBox whichisusedtocreatethelightviewprojectionmatrix. Itworksby: Calculatingthefrustumcornersinworldspace Findthelongestradiusofthefrustum CreateanAABBaroundthesphereformedbytheradius CreateanorthographicprojectionusingtheAABB Thisfigureillustratestheprocess[1]: Firstofallwe’llneedtocreatethetexturesthatwillholdourdepthvalues.Sincewearegoingtobeusingmultipletexturesfor we’llcreateaseperateframebufferwiththecolorbufferturnedoffthatwillstoreourvaluesinatexturearray mShadowMapSize=size; //Directionallightshadowmapbuffer glGenFramebuffers(1,&amp;mCascadedShadowFBO); glBindFramebuffer(GL_FRAMEBUFFER,mCascadedShadowFBO); glGenTextures(1,&amp;mCascadedTextureArray); glBindTexture(GL_TEXTURE_2D_ARRAY,mCascadedTextureArray); glBindTexture(GL_TEXTURE_2D_ARRAY,mCascadedTextureArray); glTexImage3D(GL_TEXTURE_2D_ARRAY,0,GL_DEPTH_COMPONENT32F,mShadowMapSize,mShadowMapSize,MAX_SPLITS,0,GL_DEPTH_COMPONENT,GL_FLOAT,NULL); glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_COMPARE_MODE,GL_COMPARE_R_TO_TEXTURE); glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_COMPARE_FUNC,GL_LEQUAL); glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,mCascadedTextureArray,0); glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); //restoredefaultFBO glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0); NoticethattheglTexImage3DfunctiontakesthewidthandheightbutalsoadepthvaluedefinedbyMAX_SPLITS(inthiscaseitsvalueis4)thatrepresentsthenumberofarraysused. Nowthatwe’vegotourbuffersetup,we’llgothroughhowwesplittheviewfrustumsandsetuptherelevantmatrices. ThealgorithmtocalculatethesplitdistancesisexplainedindetailbyNvidiahere: http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html anditworksbyusingalogarithmicanduniformsplitschemetogetthemostoptimalsplitdistances.Incode: for(unsignedinti=0;i<MAX_SPLITS;++i) { GLfloatp=(i+1)/static_cast<GLfloat>(MAX_SPLITS); GLfloatlog=minZ*std::pow(ratio,p); GLfloatuniform=minZ+range*p; GLfloatd=lambda*(log-uniform)+uniform; cascadeSplits[i]=(d-nearClip)/clipRange; } Oncethesplitdistancesarecalculated,we’llcreatetheorthographicmatrix. glm::vec3frustumCornersWS[8]= { glm::vec3(-1.0f,1.0f,-1.0f), glm::vec3(1.0f,1.0f,-1.0f), glm::vec3(1.0f,-1.0f,-1.0f), glm::vec3(-1.0f,-1.0f,-1.0f), glm::vec3(-1.0f,1.0f,1.0f), glm::vec3(1.0f,1.0f,1.0f), glm::vec3(1.0f,-1.0f,1.0f), glm::vec3(-1.0f,-1.0f,1.0f), }; glm::mat4invViewProj=glm::inverse(inCam->mProjectionMatrix*inCam->getViewMatrix()); for(unsignedinti=0;i<8;++i) { glm::vec4inversePoint=invViewProj*glm::vec4(frustumCornersWS[i],1.0f); frustumCornersWS[i]=glm::vec3(inversePoint/inversePoint.w); } for(unsignedinti=0;i<4;++i) { glm::vec3cornerRay=frustumCornersWS[i+4]-frustumCornersWS[i]; glm::vec3nearCornerRay=cornerRay*prevSplitDistance; glm::vec3farCornerRay=cornerRay*splitDistance; frustumCornersWS[i+4]=frustumCornersWS[i]+farCornerRay; frustumCornersWS[i]=frustumCornersWS[i]+nearCornerRay; } glm::vec3frustumCenter=glm::vec3(0.0f); for(unsignedinti=0;i<8;++i) frustumCenter+=frustumCornersWS[i]; frustumCenter/=8.0f; GLfloatfar=-INFINITY; GLfloatnear=INFINITY; GLfloatradius=0.0f; for(unsignedinti=0;i<8;++i) { GLfloatdistance=glm::length(frustumCornersWS[i]-frustumCenter); radius=glm::max(radius,distance); } radius=std::ceil(radius*16.0f)/16.0f; glm::vec3maxExtents=glm::vec3(radius,radius,radius); glm::vec3minExtents=-maxExtents; WestartoffbymultiplyingourNormalizedDeviceCoordinateswithourinverseViewProjectionmatrixtogetthefrustumcornersinworldspace.Oncewehaveourcornerswecancreatearaybetweenthenearandcorrespondingfarcorner,normalizeitandthenmultiplyitbythenewlengthandthenourpreviouslengthbecomesthestartingpointforthenextpartition.ThenwegetthelongestradiusofthissliceanduseitasthebasisforourAABB. //Positiontheviewmatrixlookingdownthecenterofthefrustumwithanarbitrarylighhtdirection glm::vec3lightDirection=frustumCenter-glm::normalize(glm::vec3(-0.1f,-0.5f,0.0f))*-minExtents.z; lightViewMatrix=glm::mat4(1.0f); lightViewMatrix=glm::lookAt(lightDirection,frustumCenter,glm::vec3(0.0f,1.0f,0.0f)); glm::vec3cascadeExtents=maxExtents-minExtents; lightOrthoMatrix=glm::ortho(minExtents.x,maxExtents.x,minExtents.y,maxExtents.y,0.0f,cascadeExtents.z); Wesetourviewmatrixusinganabritrarylightdirectionpositionedatthecenterofthefrustum.UsingtheYaxisfortheupvector. Inordertoavoidlightshimmeringweneedtocreatearoundingmatrixsowemoveintexelsizedincrements.Youcanthinkofitasfindingouthowmuchweneedtomovetheorthographicmatrixsoitmatchesupwithshadowmap,itisdonelikethis: glm::mat4shadowMatrix=lightOrthoMatrix*lightViewMatrix; glm::vec4shadowOrigin=glm::vec4(0.0f,0.0f,0.0f,1.0f); shadowOrigin=shadowMatrix*shadowOrigin; shadowOrigin=shadowOrigin*mShadowMapSize/2.0f; glm::vec4roundedOrigin=glm::round(shadowOrigin); glm::vec4roundOffset=roundedOrigin-shadowOrigin; roundOffset=roundOffset*2.0f/mShadowMapSize; roundOffset.z=0.0f; roundOffset.w=0.0f; glm::mat4shadowProj=lightOrthoMatrix; shadowProj[3]+=roundOffset; lightOrthoMatrix=shadowProj; Thenwestorealltheuniformsweneedforthelookupinourshaderandweusedepthclampingsothattheshadowmapskeepfrommovingthroughobjectswhichcausesshadowstodisappear. //NowfinallyrenderthesceneintoFBO glUseProgram(currentShader.getProgram()); glBindFramebuffer(GL_FRAMEBUFFER,mCascadedShadowFBO); glViewport(0,0,mShadowMapSize,mShadowMapSize); glFramebufferTextureLayer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,mCascadedTextureArray,0,cascadeIterator); glClear(GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_CLAMP); glCullFace(GL_FRONT); glm::mat4lightViewProjection=lightOrthoMatrix*lightViewMatrix; glUniformMatrix4fv(currentShader.getUniformLocation("lightViewProjectionMatrix"),1,GL_FALSE,&lightViewProjection[0][0]); //Thisfunctionjustrendersthedepthofthe4cubeswepreviouslysaw renderDepthCPU(cubeVAO,currentShader); glBindFramebuffer(GL_FRAMEBUFFER,0); currentShader.unbindShader(); glDisable(GL_DEPTH_CLAMP); Thefragmentshaderissimilartowhatwewoulddoinnormalshadowmappingexceptweneedtochoosethecorrecttexturefromthearraybycheckingthedepthvalue. for(uinti=0;i<numOfCascades-1;++i) { if(positiveViewSpaceZ<cascadedSplits[i]) { cascadeIdx=i+1; } } Afterthecorrecttextureischosenyoucandoyourshadowcalculationsthewayyounormallywouldandapplyinganyfilteringtechniqueofyourchoosing. Belowisalinktoadownloadforanyoneinterested.TherelevantcodeisintheCascadedShadowTechniqueclassandthedefaultShader.fs.TheFrustumclasssimplyholdsthecornersofthefrustumandtheCameraclassrecalculatesitwheneveritmoves.AlsointhelinkbelowisagoodwriteupbyJonathanBlowonhowtooptimizethistechnique. [1]http://the-witness.net/news/2010/03/graphics-tech-shadow-maps-part-1/ Download Sharethis:TwitterFacebookLikethis:LikeLoading... Postnavigation NextPostNextpost:TiledDeferredShadinginOpenGLusingCompute Shaders 2thoughtson“OpenGLCascadedShadow Maps” fantasticarticle,thankyouverymuch LikeLike Reply Thedemoworks,butfoundoneminorissueinfunctionCascadedShadowTechnique::initialize(GLfloatsize): glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,mCascadedTextureArray,0); throwsGL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENTerror. SothereissomethingwrongwiththepreviouslycreatedmCascadedTextureArraytexture. Couldn’tfigureoutyetwhat. LikeLike Reply LeaveaReplyCancelreply Enteryourcommenthere... Fillinyourdetailsbeloworclickanicontologin: Email(required)(Addressnevermadepublic) Name(required) Website YouarecommentingusingyourWordPress.comaccount. ( Log Out / Change ) YouarecommentingusingyourTwitteraccount. ( Log Out / Change ) YouarecommentingusingyourFacebookaccount. ( Log Out / Change ) Cancel Connectingto%s Notifymeofnewcommentsviaemail.Notifymeofnewpostsviaemail. Δ Privacy&Cookies:Thissiteusescookies.Bycontinuingtousethiswebsite,youagreetotheiruse. Tofindoutmore,includinghowtocontrolcookies,seehere: CookiePolicy Follow Following MakeGames,NotWar Signmeup AlreadyhaveaWordPress.comaccount?Loginnow. MakeGames,NotWar Customize Follow Following Signup Login Copyshortlink Reportthiscontent ViewpostinReader Managesubscriptions Collapsethisbar LoadingComments... WriteaComment... Email(Required) Name(Required) Website %dbloggerslikethis:
延伸文章資訊
- 1Cascaded Shadow maps not quite right - Stack Overflow
I've mostly implemented cascading shadow maps (CSM), ... And samples the shadow map array with th...
- 2OpenGL Cascaded Shadow Maps - Make Games, Not War
To begin with, cascaded Shadow Mapping is a technique which uses several shadow maps to further i...
- 3Cascaded Shadow Maps(CSM)實時陰影的原理與實現 - GetIt01
本文翻譯自:https://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascaded...
- 4Cascaded Shadow Maps(CSM)实时阴影的原理与实现 - 知乎专栏
本文翻译自: https://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascade...
- 5重疊的陰影圖- Win32 apps
shadow map coverage ... cascade overlap increases as light direction becomes parallel ... Interva...