LearnOpenGL - CSM

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

Cascaded shadow mapping is a direct answer to the third point, however while implementing it we will solve the first two points, too. Ifyou'rerunningAdBlock,pleaseconsiderwhitelistingthissiteifyou'dliketosupportLearnOpenGL;andnoworries,Iwon'tbemadifyoudon't:) IntroductionGettingstartedOpenGLCreatingawindowHelloWindowHelloTriangleShadersTexturesTransformationsCoordinateSystemsCameraReviewLightingColorsBasicLightingMaterialsLightingmapsLightcastersMultiplelightsReviewModelLoadingAssimpMeshModelAdvancedOpenGLDepthtestingStenciltestingBlendingFacecullingFramebuffersCubemapsAdvancedDataAdvancedGLSLGeometryShaderInstancingAntiAliasingAdvancedLightingAdvancedLightingGammaCorrectionShadowsShadowMappingPointShadowsNormalMappingParallaxMappingHDRBloomDeferredShadingSSAOPBRTheoryLightingIBLDiffuseirradianceSpecularIBLInPracticeDebuggingTextRendering2DGameBreakoutSettingupRenderingSpritesLevelsCollisionsBallCollisiondetectionCollisionresolutionParticlesPostprocessingPowerupsAudioRendertextFinalthoughtsGuestArticlesHowtopublish2020OITIntroductionWeightedBlendedSkeletalAnimation2021CSMSceneSceneGraphFrustumCullingTessellationHeightmapTessellationDSACoderepositoryTranslationsAbout BTC 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa ETH/ERC20 0x1de59bd9e52521a46309474f8372531533bd7c43 CSM Guest-Articles/2021/CSM CascadedShadowMapping ShadowmappingasdescribedonLearnOpenGLisapowerful,andrelativelysimpletechnique.However,ifimplementedas-isfromtheabovereferredtutorial,theavidOpenGLstudentwillnoticeafewshortcomings. Theshadowmapisalwayscreatedaroundtheorigin,andnotontheareathecameraisactuallylookingat.Itwouldbebestofcourseifwecouldshadowmapthewholescene,withsufficientresolution,butoncurrenthardwarethisisnotfeasible.Inrealitywewanttheshadowmapstobecreatedonobjectsthatareinview,savingourpreciousGPUmemoryforthingsthatmatter. Theshadowmaporthographicprojectionmatrixisnotproperlyfittedontotheviewfrustum.Toachievethebestpossibleresolutionforourshadowmaps,theorthomatrixneedstobeastightlyfittothecamerafrustumaspossible,becauseagain:ifit’slargerthatmeansthatlessdetailisspentontheobjectsthatareactuallyvisible. Theshadowmaps(evenwithadvancedPCFfunctions)areblurryifwewanttheshadowrenderingdistancetobelarge,aswewouldinagamewithafirst-personcamera.Wecanincreasetheresolutionoftheshadowmapstomitigatethis,butGPUmemoryisaresourceweshouldbeconservativeof. Cascadedshadowmappingisadirectanswertothethirdpoint,howeverwhileimplementingitwewillsolvethefirsttwopoints,too.Thecoreinsightincascadedshadowmappingis,thatwedon’tneedthesameamountofshadowdetailforthingsthatarefarfromus.Wewantcrispshadowsforstuffthat’sneartothenearplane,andweareabsolutelyfinewithblurrinessforobjectsthatarehundredsofunitsaway:it’snotgoingtobenoticeableatall.Howcanweachievethis?Theanswerisbeautifulinitssimplicity:justrenderdifferentshadowmapsforthingsthatarecloseandforthosethatarefaraway,andsamplefromthemaccordingtothedepthofthefragmentinthefragmentshader.Thehigh-levelalgorithmisasfollows: Divideourordinaryviewfrustumintonsubfrusta,wherethefarplaneofthei-thfrustumisthenearplaneofthe(i+1)-thfrustum Computethetightlyfittingorthomatrixforeachfrustum Foreachfrustumrenderashadowmapasifseenfromourdirectionallight Sendallshadowmapstoourfragmentshader Renderthescene,andaccordingtothefragment’sdepthvaluesamplefromthecorrectshadowmap Soundssimpleright?Well,itis,butasitofteniswhenitcomestoourfriendOpenGL:thedevilisinthedetails. IntheaboveimagewecanseetheedgesofshadowcascadesinCounter-Strike:GlobalOffensive.Theimagewascapturedonlowgraphicssettings. Worldcoordinatesoftheviewfrustum Beforegettingourhandsdirtywithshadows,weneedtotackleamoreabstractproblem:makingourprojectionmatrixfitnicelyontoagenericfrustum.Tobeabletodothis,weneedtoknowtheworldspacecoordinatesofthefrustuminquestion.Whilethismightsounddauntingatfirst,wealreadyhaveallthetoolsnecessaryinourrepertoire.Ifwethinkbackontheexcellentcoordinatesystemstutorial,thebeheadedpyramidofthefrustumonlylooksthatwayinworldcoordinates,andourviewandprojectionmatricesdothejoboftransformingthisunusualshapeintotheNDCcube.AndweknowthecoordinatesofthecornersoftheNDCcube:thecoordinatesareintherange[-1,1]onthethreeaxes.Becausematrixmultiplicationisareversibleprocess,wecanapplytheinverseoftheviewandprojectionmatricesonthecornerpointsoftheNDCcubetogetthefrustumcornersinworldspace. $$v_{NDC}=M_{proj}M_{view}v_{world}$$ $$v_{world}=(M_{proj}M_{view})^{-1}v_{NDC}$$ std::vector<:vec4>getFrustumCornersWorldSpace(constglm::mat4&proj,constglm::mat4&view) { constautoinv=glm::inverse(proj*view); std::vector<:vec4>frustumCorners; for(unsignedintx=0;x<2;++x) { for(unsignedinty=0;y<2;++y) { for(unsignedintz=0;z<2;++z) { constglm::vec4pt= inv*glm::vec4( 2.0f*x-1.0f, 2.0f*y-1.0f, 2.0f*z-1.0f, 1.0f); frustumCorners.push_back(pt/pt.w); } } } returnfrustumCorners; } Theprojectionmatrixdescribedhereisaperspectivematrix,usingthecamera’saspectratioandfov,andusingthenearandfarplaneofthecurrentfrustumbeinganalyzed.Theviewmatrixistheviewmatrixofourcamera. constautoproj=glm::perspective( glm::radians(camera.Zoom), (float)SCR_WIDTH/(float)SCR_HEIGHT, nearPlane, farPlane ); TheaboveimageisthecourtesyofOGLDev Thelightview-projectionmatrix Thismatrix-asinordinaryshadowmapping–istheproductoftheviewandprojectionmatrixthattransformsthesceneasifitwereseenbythelight.Calculatingtheviewmatrixissimple,weknowthedirectionourlightiscomingfrom,andweknowapointinworldspacethatitdefinitelyislookingat:thecenterofthefrustum.Thepositionofthefrustumcentercanbeobtainedbyaveragingthecoordinatesofthefrustumcorners.(Thisissobecauseaveragingthecoordinatesofthenearandfarplanegivesusthecenterofthoserectangles,andtakingthemidpointofthesetwopointsgivesusthecenterofthefrustum.) glm::vec3center=glm::vec3(0,0,0); for(constauto&v:corners) { center+=glm::vec3(v); } center/=corners.size(); constautolightView=glm::lookAt( center+lightDir, center, glm::vec3(0.0f,1.0f,0.0f) ); Theprojectionmatrixisbitmorecomplex.Becausethelightinquestionisadirectionallight,thematrixneedstobeanorthographicprojectionmatrix,thismuchisclear.Tounderstandhowwedeterminetheleft,right,topetc.parametersofthematriximaginethesceneyouaredrawingfromtheperspectiveofthelight.Fromthisviewpointtheshadowmapwearetryingtorenderisgoingtobeanaxisalignedrectangle,andthisrectangle–asweestablishedbefore–needstofitonthefrustumtightly.Soweneedtoobtainthecoordinatesofthefrustuminthisspace,andtakethemaximumandminimumofthecoordinatesalongthecoordinateaxes.Whilethismightsounddauntingatfirst,thisperspectiveisexactlywhatourlightviewmatrixtransformationgivesus.Weneedtotransformthefrustumcornerpointsinthelightviewspace,andfindthemaximumandminimumcoordinates. floatminX=std::numeric_limits::max(); floatmaxX=std::numeric_limits::min(); floatminY=std::numeric_limits::max(); floatmaxY=std::numeric_limits::min(); floatminZ=std::numeric_limits::max(); floatmaxZ=std::numeric_limits::min(); for(constauto&v:corners) { constautotrf=lightView*v; minX=std::min(minX,trf.x); maxX=std::max(maxX,trf.x); minY=std::min(minY,trf.y); maxY=std::max(maxY,trf.y); minZ=std::min(minZ,trf.z); maxZ=std::max(maxZ,trf.z); } Wearegoingtocreateourprojectionmatrixfromtheproductoftwomatrices.First,wearegoingtocreateanorthoprojectionmatrix,withtheleft,right,top,bottomparameterssetto-1or1,andthezvaluessettominZandmaxZ.Thiscreatesa3Drectanglesittingontheoriginwithwidthandheightof2,anddepthof(maxZ–minZ).InthecodeweincreasetheamountofspacecoveredbyminZandmaxZbymultiplyingordividingthemwithazMult.Thisisbecausewewanttoincludegeometrywhichisbehindorinfrontofourfrustumincameraspace.Thinkaboutit:notonlygeometrywhichisinthefrustumcancastshadowsonasurfaceinthefrustum! Beforecreatingtheactualprojectionmatrixwearegoingtoincreasethesizeofthespacecoveredbythenearandfarplaneofthelightfrustum.Wedothisby"pullingback"thenearplane,and"pushingaway"thefarplane.InthecodeweachievethisbydividingormultiplyingbyzMult.Thisisbecausewewanttoincludegeometrywhichisbehindorinfrontofourfrustumincameraspace.Thinkaboutit:notonlygeometrywhichisinthefrustumcancastshadowsonasurfaceinthefrustum! //Tunethisparameteraccordingtothescene constexprfloatzMult=10.0f; if(minZ<0) { minZ*=zMult; } else { minZ/=zMult; } if(maxZ<0) { maxZ/=zMult; } else { maxZ*=zMult; } constglm::mat4lightProjection=glm::ortho(minX,maxX,minY,maxY,minZ,maxZ); returnlightProjection*lightView; Multiplyingtheviewandprojectionmatricestogether,wegettheview-projectionmatrixofthelightforthegivenfrustum.Weneedtodothisprocedureforeveryfrustuminourcascade. 2Darraytextures Whileweletourstomachsdigestwhatwelearnedaboutfrustumfittingweshouldfigureouthowtostoreourshadowmaps.Inprinciplethereisnolimitonhowmanycascadeswecando,sohardcodinganarbitraryvaluedoesn’tseemlikeawiseidea.Also,itquicklybecomestiresometypingoutandbindingsampler2Dsforourshaders.OpenGLhasagoodsolutiontothisproblemintheformof2Darraytextures.Thisobjectisanarrayoftextures,whichhavethesamedimensions.Tousetheminshadersdeclarethemlikethis: uniformsampler2DArrayshadowMap; Tosamplefromthemwecanusetheregulartexturefunctionwithavec3parameterfortexturecoordinates,thethirddimensionspecifyingwhichlayertosamplefrom,startingfrom0. texture(depthMap,vec3(TexCoords,currentLayer)) Creatingourarraytextureisslightlydifferentthancreatingaregularoldtexture2D.InsteadofglTexImage2DweuseglTexImage3Dtoallocatememory,andwhenbindingthetexturetotheframebufferweuseglFramebufferTextureinsteadofglFramebufferTexture2D.Theparametersofthesefunctionsaresomewhatself-explanatoryifweknowtheoldversions.WhencallingtheOpenGLfunctions,weneedtopassGL_TEXTURE_2D_ARRAYinsteadofGL_TEXTURE_2Dasthetarget,totellOpenGLwhatkindoftexturewearedealingwith. glGenFramebuffers(1,&lightFBO); glGenTextures(1,&lightDepthMaps); glBindTexture(GL_TEXTURE_2D_ARRAY,lightDepthMaps); glTexImage3D( GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT32F, depthMapResolution, depthMapResolution, int(shadowCascadeLevels.size())+1, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MIN_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MAG_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_BORDER); constexprfloatbordercolor[]={1.0f,1.0f,1.0f,1.0f}; glTexParameterfv(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_BORDER_COLOR,bordercolor); glBindFramebuffer(GL_FRAMEBUFFER,lightFBO); glFramebufferTexture(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,lightDepthMaps,0); glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); intstatus=glCheckFramebufferStatus(GL_FRAMEBUFFER); if(status!=GL_FRAMEBUFFER_COMPLETE) { std::cout<1.0) { return0.0; } //calculatebias(basedondepthmapresolutionandslope) vec3normal=normalize(fs_in.Normal); floatbias=max(0.05*(1.0-dot(normal,lightDir)),0.005); if(layer==cascadeCount) { bias*=1/(farPlane*0.5f); } else { bias*=1/(cascadePlaneDistances[layer]*0.5f); } Pleasenotethattherearedifferentstrategiesforapplyingbiaswhendealingwithshadowmaps.Iwilllinktoafewsourcesdetailingtheseinthecitationssection. Therestofthefunctionisthesameasbefore,theonlydifferenceisthatwearesamplingfroma2Darraytexture,henceweneedtoaddathirdparametertothetextureandthetextureSizefunctions. //PCF floatshadow=0.0; vec2texelSize=1.0/vec2(textureSize(shadowMap,0)); for(intx=-1;x<=1;++x) { for(inty=-1;y<=1;++y) { floatpcfDepth=texture( shadowMap, vec3(projCoords.xy+vec2(x,y)*texelSize, layer) ).r; shadow+=(currentDepth-bias)>pcfDepth?1.0:0.0; } } shadow/=9.0; //keeptheshadowat0.0whenoutsidethefar_planeregionofthelight'sfrustum. if(projCoords.z>1.0) { shadow=0.0; } returnshadow; Andthat'sit!Ifwedideverythingcorrectlyweshouldseethattherendererswitchesbetweenshadowmapsbasedonthedistance.Trysettingsomeunreasonablecascadeplanedistances(forexampleonlyone,whichisafewunitsfromthecamera)toseeifthecodereallydoeswork.Youshouldseeanoticabledegradationinshadowqualitybetweenthetwosidesoftheplane.Ifyouseemoireartifactsonthescreentrychangingaroundbiasparametersabit. Youcanfindthefullsourcecodeforthecascadedshadowmappingdemohere. Closingthoughts InthesampleprojectprovidedyoucantoggledepthmapvisualizationbypressingF.Whenindepthmapvisualizationmodeyoucanpressthe+keytoswapbetweenthedifferentlayers. WhenbrowsingthroughthecodeyoumightwonderwhyistheUBOarraylength16.Thisisjustanarbitrarychoice,tomeitseemedunlikelythatanyonewouldusemorethan16shadowcascades,sothisseemedlikeanicenumbertoallocate. Inthecodeexamplethecubepositionsandrotationsarerandomized.IfyouwanttotrytheexamplewithstaticgeometryIsuggestyouseedtherandomnumbergenerator. IfyouaregoingthroughthesourcesgivenbelowyoumightnoticethattheNVIDIAarticleistacklingthelightprojectionmatrixcreationinaroundaboutwaycomparedtowhatisgiveninthisarticle.However,whattheyaredoingismathematicallyequivalenttowhatwearedoinginrespecttothex-ycoordinatesinlightviewspace,whichiswhatmatters.Thiscanbeseenbypluggingstuffintothematrixgiveninthisarticlebysongho. AdditionalResources NVIDIApaperonthesubject:incomprehensibleinmyopinionbuthastobementioned Aseriesofincrediblyhelpfulandusefulforumposts AnotherinterestingtutorialfromOGLDev AnarticlefromMicrosoft:nicepicturesillustratingsomeissueswithCSM Anarticleaboutshadowbias Someinformativedrawingsaboutshadowbiasstrategies Articleby:MártonÁrbócz HI



請為這篇文章評分?