Shadow Mapping - LearnOpenGL

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

comparrison of shadows in a scene with and without in OpenGL ... in fragment shader of shadowmap painting stage ( mentioned in lesson "Advanced GLSL", ... Ifyou'rerunningAdBlock,pleaseconsiderwhitelistingthissiteifyou'dliketosupportLearnOpenGL;andnoworries,Iwon'tbemadifyoudon't:) IntroductionGettingstartedOpenGLCreatingawindowHelloWindowHelloTriangleShadersTexturesTransformationsCoordinateSystemsCameraReviewLightingColorsBasicLightingMaterialsLightingmapsLightcastersMultiplelightsReviewModelLoadingAssimpMeshModelAdvancedOpenGLDepthtestingStenciltestingBlendingFacecullingFramebuffersCubemapsAdvancedDataAdvancedGLSLGeometryShaderInstancingAntiAliasingAdvancedLightingAdvancedLightingGammaCorrectionShadowsShadowMappingPointShadowsNormalMappingParallaxMappingHDRBloomDeferredShadingSSAOPBRTheoryLightingIBLDiffuseirradianceSpecularIBLInPracticeDebuggingTextRendering2DGameBreakoutSettingupRenderingSpritesLevelsCollisionsBallCollisiondetectionCollisionresolutionParticlesPostprocessingPowerupsAudioRendertextFinalthoughtsGuestArticlesHowtopublish2020OITIntroductionWeightedBlendedSkeletalAnimation2021CSMSceneSceneGraphFrustumCullingTessellationHeightmapTessellationDSACoderepositoryTranslationsAbout BTC 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa ETH/ERC20 0x1de59bd9e52521a46309474f8372531533bd7c43 ShadowMapping Advanced-Lighting/Shadows/Shadow-Mapping Shadowsarearesultoftheabsenceoflightduetoocclusion.Whenalightsource'slightraysdonothitanobjectbecauseitgetsoccludedbysomeotherobject,theobjectisinshadow.Shadowsaddagreatdealofrealismtoalitsceneandmakeiteasierforaviewertoobservespatialrelationshipsbetweenobjects.Theygiveagreatersenseofdepthtooursceneandobjects.Forexample,takealookatthefollowingimageofascenewithandwithoutshadows: Youcanseethatwithshadowsitbecomesmuchmoreobvioushowtheobjectsrelatetoeachother.Forinstance,thefactthatoneofthecubesisfloatingabovetheothersisonlyreallynoticeablewhenwehaveshadows. Shadowsareabittrickytoimplementthough,specificallybecauseincurrentreal-time(rasterizedgraphics)researchaperfectshadowalgorithmhasn'tbeendevelopedyet.Thereareseveralgoodshadowapproximationtechniques,buttheyallhavetheirlittlequirksandannoyanceswhichwehavetotakeintoaccount. Onetechniqueusedbymostvideogamesthatgivesdecentresultsandisrelativelyeasytoimplementisshadowmapping.Shadowmappingisnottoodifficulttounderstand,doesn'tcosttoomuchinperformanceandquiteeasilyextendsintomoreadvancedalgorithms(likeOmnidirectionalShadowMapsandCascadedShadowMaps). Shadowmapping Theideabehindshadowmappingisquitesimple:werenderthescenefromthelight'spointofviewandeverythingweseefromthelight'sperspectiveislitandeverythingwecan'tseemustbeinshadow.Imagineafloorsectionwithalargeboxbetweenitselfandalightsource.Sincethelightsourcewillseethisboxandnotthefloorsectionwhenlookinginitsdirectionthatspecificfloorsectionshouldbeinshadow. Hereallthebluelinesrepresentthefragmentsthatthelightsourcecansee.Theoccludedfragmentsareshownasblacklines:thesearerenderedasbeingshadowed.Ifweweretodrawalineorrayfromthelightsourcetoafragmentontheright-mostboxwecanseetherayfirsthitsthefloatingcontainerbeforehittingtheright-mostcontainer.Asaresult,thefloatingcontainer'sfragmentislitandtheright-mostcontainer'sfragmentisnotlitandthusinshadow. Wewanttogetthepointontheraywhereitfirsthitanobjectandcomparethisclosestpointtootherpointsonthisray.Wethendoabasictesttoseeifatestpoint'sraypositionisfurtherdowntheraythantheclosestpointandifso,thetestpointmustbeinshadow.Iteratingthroughpossiblythousandsoflightraysfromsuchalightsourceisanextremelyinefficientapproachanddoesn'tlenditselftoowellforreal-timerendering.Wecandosomethingsimilar,butwithoutcastinglightrays.Instead,weusesomethingwe'requitefamiliarwith:thedepthbuffer. Youmayrememberfromthedepthtestingchapterthatavalueinthedepthbuffercorrespondstothedepthofafragmentclampedto[0,1]fromthecamera'spointofview.Whatifweweretorenderthescenefromthelight'sperspectiveandstoretheresultingdepthvaluesinatexture?Thisway,wecansampletheclosestdepthvaluesasseenfromthelight'sperspective.Afterall,thedepthvaluesshowthefirstfragmentvisiblefromthelight'sperspective.Westoreallthesedepthvaluesinatexturethatwecalladepthmaporshadowmap. Theleftimageshowsadirectionallightsource(alllightraysareparallel)castingashadowonthesurfacebelowthecube.Usingthedepthvaluesstoredinthedepthmapwefindtheclosestpointandusethattodeterminewhetherfragmentsareinshadow.Wecreatethedepthmapbyrenderingthescene(fromthelight'sperspective)usingaviewandprojectionmatrixspecifictothatlightsource.Thisprojectionandviewmatrixtogetherformatransformation\(T\)thattransformsany3Dpositiontothelight's(visible)coordinatespace. Adirectionallightdoesn'thaveapositionasit'smodelledtobeinfinitelyfaraway.However,forthesakeofshadowmappingweneedtorenderthescenefromalight'sperspectiveandthusrenderthescenefromapositionsomewherealongthelinesofthelightdirection. Intherightimageweseethesamedirectionallightandtheviewer.Werenderafragmentatpoint\(\bar{\color{red}{P}}\)forwhichwehavetodeterminewhetheritisinshadow.Todothis,wefirsttransformpoint\(\bar{\color{red}{P}}\)tothelight'scoordinatespaceusing\(T\).Sincepoint\(\bar{\color{red}{P}}\)isnowasseenfromthelight'sperspective,itszcoordinatecorrespondstoitsdepthwhichinthisexampleis0.9.Usingpoint\(\bar{\color{red}{P}}\)wecanalsoindexthedepth/shadowmaptoobtaintheclosestvisibledepthfromthelight'sperspective,whichisatpoint\(\bar{\color{green}{C}}\)withasampleddepthof0.4.Sinceindexingthedepthmapreturnsadepthsmallerthanthedepthatpoint\(\bar{\color{red}{P}}\)wecanconcludepoint\(\bar{\color{red}{P}}\)isoccludedandthusinshadow. Shadowmappingthereforeconsistsoftwopasses:firstwerenderthedepthmap,andinthesecondpasswerenderthesceneasnormalandusethegenerateddepthmaptocalculatewhetherfragmentsareinshadow.Itmaysoundabitcomplicated,butassoonaswewalkthroughthetechniquestep-by-stepit'lllikelystarttomakesense. Thedepthmap Thefirstpassrequiresustogenerateadepthmap.Thedepthmapisthedepthtextureasrenderedfromthelight'sperspectivethatwe'llbeusingfortestingforshadows.Becauseweneedtostoretherenderedresultofasceneintoatexturewe'regoingtoneedframebuffersagain. Firstwe'llcreateaframebufferobjectforrenderingthedepthmap: unsignedintdepthMapFBO; glGenFramebuffers(1,&depthMapFBO); Nextwecreatea2Dtexturethatwe'lluseastheframebuffer'sdepthbuffer: constunsignedintSHADOW_WIDTH=1024,SHADOW_HEIGHT=1024; unsignedintdepthMap; glGenTextures(1,&depthMap); glBindTexture(GL_TEXTURE_2D,depthMap); glTexImage2D(GL_TEXTURE_2D,0,GL_DEPTH_COMPONENT, SHADOW_WIDTH,SHADOW_HEIGHT,0,GL_DEPTH_COMPONENT,GL_FLOAT,NULL); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); Generatingthedepthmapshouldn'tlooktoocomplicated.Becauseweonlycareaboutdepthvalueswespecifythetexture'sformatsasGL_DEPTH_COMPONENT.Wealsogivethetextureawidthandheightof1024:thisistheresolutionofthedepthmap. Withthegenerateddepthtexturewecanattachitastheframebuffer'sdepthbuffer: glBindFramebuffer(GL_FRAMEBUFFER,depthMapFBO); glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,depthMap,0); glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); glBindFramebuffer(GL_FRAMEBUFFER,0); Weonlyneedthedepthinformationwhenrenderingthescenefromthelight'sperspectivesothereisnoneedforacolorbuffer.AframebufferobjecthoweverisnotcompletewithoutacolorbuffersoweneedtoexplicitlytellOpenGLwe'renotgoingtorenderanycolordata.WedothisbysettingboththereadanddrawbuffertoGL_NONEwithglDrawBufferandglReadbuffer. Withaproperlyconfiguredframebufferthatrendersdepthvaluestoatexturewecanstartthefirstpass:generatethedepthmap.Whencombinedwiththesecondpass,thecompleterenderingstagewilllookabitlikethis: //1.firstrendertodepthmap glViewport(0,0,SHADOW_WIDTH,SHADOW_HEIGHT); glBindFramebuffer(GL_FRAMEBUFFER,depthMapFBO); glClear(GL_DEPTH_BUFFER_BIT); ConfigureShaderAndMatrices(); RenderScene(); glBindFramebuffer(GL_FRAMEBUFFER,0); //2.thenrendersceneasnormalwithshadowmapping(usingdepthmap) glViewport(0,0,SCR_WIDTH,SCR_HEIGHT); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); ConfigureShaderAndMatrices(); glBindTexture(GL_TEXTURE_2D,depthMap); RenderScene(); Thiscodeleftoutsomedetails,butit'llgiveyouthegeneralideaofshadowmapping.WhatisimportanttonoteherearethecallstoglViewport.Becauseshadowmapsoftenhaveadifferentresolutioncomparedtowhatweoriginallyrenderthescenein(usuallythewindowresolution),weneedtochangetheviewportparameterstoaccommodateforthesizeoftheshadowmap.Ifweforgettoupdatetheviewportparameters,theresultingdepthmapwillbeeitherincompleteortoosmall. Lightspacetransform AnunknownintheprevioussnippetofcodeistheConfigureShaderAndMatricesfunction.Inthesecondpassthisisbusinessasusual:makesureproperprojectionandviewmatricesareset,andsettherelevantmodelmatricesperobject.However,inthefirstpassweneedtouseadifferentprojectionandviewmatrixtorenderthescenefromthelight'spointofview. Becausewe'remodellingadirectionallightsource,allitslightraysareparallel.Forthisreason,we'regoingtouseanorthographicprojectionmatrixforthelightsourcewherethereisnoperspectivedeform: floatnear_plane=1.0f,far_plane=7.5f; glm::mat4lightProjection=glm::ortho(-10.0f,10.0f,-10.0f,10.0f,near_plane,far_plane); Hereisanexampleorthographicprojectionmatrixasusedinthischapter'sdemoscene.Becauseaprojectionmatrixindirectlydeterminestherangeofwhatisvisible(e.g.whatisnotclipped)youwanttomakesurethesizeoftheprojectionfrustumcorrectlycontainstheobjectsyouwanttobeinthedepthmap.Whenobjectsorfragmentsarenotinthedepthmaptheywillnotproduceshadows. Tocreateaviewmatrixtotransformeachobjectsothey'revisiblefromthelight'spointofview,we'regoingtousetheinfamousglm::lookAtfunction;thistimewiththelightsource'spositionlookingatthescene'scenter. glm::mat4lightView=glm::lookAt(glm::vec3(-2.0f,4.0f,-1.0f), glm::vec3(0.0f,0.0f,0.0f), glm::vec3(0.0f,1.0f,0.0f)); Combiningthesetwogivesusalightspacetransformationmatrixthattransformseachworld-spacevectorintothespaceasvisiblefromthelightsource;exactlywhatweneedtorenderthedepthmap. glm::mat4lightSpaceMatrix=lightProjection*lightView; ThislightSpaceMatrixisthetransformationmatrixthatweearlierdenotedas\(T\).WiththislightSpaceMatrix,wecanrenderthesceneasusualaslongaswegiveeachshaderthelight-spaceequivalentsoftheprojectionandviewmatrices.However,weonlycareaboutdepthvaluesandnotalltheexpensivefragment(lighting)calculations.Tosaveperformancewe'regoingtouseadifferent,butmuchsimplershaderforrenderingtothedepthmap. Rendertodepthmap Whenwerenderthescenefromthelight'sperspectivewe'dmuchratheruseasimpleshaderthatonlytransformstheverticestolightspaceandnotmuchmore.ForsuchasimpleshadercalledsimpleDepthShaderwe'llusethefollowingvertexshader: #version330core layout(location=0)invec3aPos; uniformmat4lightSpaceMatrix; uniformmat4model; voidmain() { gl_Position=lightSpaceMatrix*model*vec4(aPos,1.0); } Thisvertexshadertakesaper-objectmodel,avertex,andtransformsallverticestolightspaceusinglightSpaceMatrix. Sincewehavenocolorbufferanddisabledthedrawandreadbuffers,theresultingfragmentsdonotrequireanyprocessingsowecansimplyuseanemptyfragmentshader: #version330core voidmain() { //gl_FragDepth=gl_FragCoord.z; } Thisemptyfragmentshaderdoesnoprocessingwhatsoever,andattheendofitsrunthedepthbufferisupdated.Wecouldexplicitlysetthedepthbyuncommentingitsoneline,butthisiseffectivelywhathappensbehindthesceneanyways. Renderingthedepth/shadowmapnoweffectivelybecomes: simpleDepthShader.use(); glUniformMatrix4fv(lightSpaceMatrixLocation,1,GL_FALSE,glm::value_ptr(lightSpaceMatrix)); glViewport(0,0,SHADOW_WIDTH,SHADOW_HEIGHT); glBindFramebuffer(GL_FRAMEBUFFER,depthMapFBO); glClear(GL_DEPTH_BUFFER_BIT); RenderScene(simpleDepthShader); glBindFramebuffer(GL_FRAMEBUFFER,0); HeretheRenderScenefunctiontakesashaderprogram,callsallrelevantdrawingfunctionsandsetsthecorrespondingmodelmatriceswherenecessary. Theresultisanicelyfilleddepthbufferholdingtheclosestdepthofeachvisiblefragmentfromthelight'sperspective.Byrenderingthistextureontoa2Dquadthatfillsthescreen(similartowhatwedidinthepost-processingsectionattheendoftheframebufferschapter)wegetsomethinglikethis: Forrenderingthedepthmapontoaquadweusedthefollowingfragmentshader: #version330core outvec4FragColor; invec2TexCoords; uniformsampler2DdepthMap; voidmain() { floatdepthValue=texture(depthMap,TexCoords).r; FragColor=vec4(vec3(depthValue),1.0); } Notethattherearesomesubtlechangeswhendisplayingdepthusingaperspectiveprojectionmatrixinsteadofanorthographicprojectionmatrixasdepthisnon-linearwhenusingperspectiveprojection.Attheendofthischapterwe'lldiscusssomeofthesesubtledifferences. Youcanfindthesourcecodeforrenderingascenetoadepthmaphere. Renderingshadows Withaproperlygenerateddepthmapwecanstartrenderingtheactualshadows.Thecodetocheckifafragmentisinshadowis(quiteobviously)executedinthefragmentshader,butwedothelight-spacetransformationinthevertexshader: #version330core layout(location=0)invec3aPos; layout(location=1)invec3aNormal; layout(location=2)invec2aTexCoords; outVS_OUT{ vec3FragPos; vec3Normal; vec2TexCoords; vec4FragPosLightSpace; }vs_out; uniformmat4projection; uniformmat4view; uniformmat4model; uniformmat4lightSpaceMatrix; voidmain() { vs_out.FragPos=vec3(model*vec4(aPos,1.0)); vs_out.Normal=transpose(inverse(mat3(model)))*aNormal; vs_out.TexCoords=aTexCoords; vs_out.FragPosLightSpace=lightSpaceMatrix*vec4(vs_out.FragPos,1.0); gl_Position=projection*view*vec4(vs_out.FragPos,1.0); } WhatisnewhereistheextraoutputvectorFragPosLightSpace.WetakethesamelightSpaceMatrix(usedtotransformverticestolightspaceinthedepthmapstage)andtransformtheworld-spacevertexpositiontolightspaceforuseinthefragmentshader. Themainfragmentshaderwe'llusetorenderthesceneusestheBlinn-Phonglightingmodel.Withinthefragmentshaderwethencalculateashadowvaluethatiseither1.0whenthefragmentisinshadowor0.0whennotinshadow.Theresultingdiffuseandspecularcomponentsarethenmultipliedbythisshadowcomponent.Becauseshadowsarerarelycompletelydark(duetolightscattering)weleavetheambientcomponentoutoftheshadowmultiplications. #version330core outvec4FragColor; inVS_OUT{ vec3FragPos; vec3Normal; vec2TexCoords; vec4FragPosLightSpace; }fs_in; uniformsampler2DdiffuseTexture; uniformsampler2DshadowMap; uniformvec3lightPos; uniformvec3viewPos; floatShadowCalculation(vec4fragPosLightSpace) { [...] } voidmain() { vec3color=texture(diffuseTexture,fs_in.TexCoords).rgb; vec3normal=normalize(fs_in.Normal); vec3lightColor=vec3(1.0); //ambient vec3ambient=0.15*lightColor; //diffuse vec3lightDir=normalize(lightPos-fs_in.FragPos); floatdiff=max(dot(lightDir,normal),0.0); vec3diffuse=diff*lightColor; //specular vec3viewDir=normalize(viewPos-fs_in.FragPos); floatspec=0.0; vec3halfwayDir=normalize(lightDir+viewDir); spec=pow(max(dot(normal,halfwayDir),0.0),64.0); vec3specular=spec*lightColor; //calculateshadow floatshadow=ShadowCalculation(fs_in.FragPosLightSpace); vec3lighting=(ambient+(1.0-shadow)*(diffuse+specular))*color; FragColor=vec4(lighting,1.0); } Thefragmentshaderislargelyacopyfromwhatweusedintheadvancedlightingchapter,butwithanaddedshadowcalculation.WedeclaredafunctionShadowCalculationthatdoesmostoftheshadowwork.Attheendofthefragmentshader,wemultiplythediffuseandspecularcontributionsbytheinverseoftheshadowcomponente.g.howmuchthefragmentisnotinshadow.Thisfragmentshadertakesasextrainputthelight-spacefragmentpositionandthedepthmapgeneratedfromthefirstrenderpass. Thefirstthingtodotocheckwhetherafragmentisinshadow,istransformthelight-spacefragmentpositioninclip-spacetonormalizeddevicecoordinates.Whenweoutputaclip-spacevertexpositiontogl_Positioninthevertexshader,OpenGLautomaticallydoesaperspectivedividee.g.transformclip-spacecoordinatesintherange[-w,w]to[-1,1]bydividingthex,yandzcomponentbythevector'swcomponent.Astheclip-spaceFragPosLightSpaceisnotpassedtothefragmentshaderthroughgl_Position,wehavetodothisperspectivedivideourselves: floatShadowCalculation(vec4fragPosLightSpace) { //performperspectivedivide vec3projCoords=fragPosLightSpace.xyz/fragPosLightSpace.w; [...] } Thisreturnsthefragment'slight-spacepositionintherange[-1,1]. Whenusinganorthographicprojectionmatrixthewcomponentofavertexremainsuntouchedsothisstepisactuallyquitemeaningless.However,itisnecessarywhenusingperspectiveprojectionsokeepingthislineensuresitworkswithbothprojectionmatrices. Becausethedepthfromthedepthmapisintherange[0,1]andwealsowanttouseprojCoordstosamplefromthedepthmap,wetransformtheNDCcoordinatestotherange[0,1]: projCoords=projCoords*0.5+0.5; Withtheseprojectedcoordinateswecansamplethedepthmapastheresulting[0,1]coordinatesfromprojCoordsdirectlycorrespondtothetransformedNDCcoordinatesfromthefirstrenderpass.Thisgivesustheclosestdepthfromthelight'spointofview: floatclosestDepth=texture(shadowMap,projCoords.xy).r; Togetthecurrentdepthatthisfragmentwesimplyretrievetheprojectedvector'szcoordinatewhichequalsthedepthofthisfragmentfromthelight'sperspective. floatcurrentDepth=projCoords.z; TheactualcomparisonisthensimplyacheckwhethercurrentDepthishigherthanclosestDepthandifso,thefragmentisinshadow: floatshadow=currentDepth>closestDepth?1.0:0.0; ThecompleteShadowCalculationfunctionthenbecomes: floatShadowCalculation(vec4fragPosLightSpace) { //performperspectivedivide vec3projCoords=fragPosLightSpace.xyz/fragPosLightSpace.w; //transformto[0,1]range projCoords=projCoords*0.5+0.5; //getclosestdepthvaluefromlight'sperspective(using[0,1]rangefragPosLightascoords) floatclosestDepth=texture(shadowMap,projCoords.xy).r; //getdepthofcurrentfragmentfromlight'sperspective floatcurrentDepth=projCoords.z; //checkwhethercurrentfragposisinshadow floatshadow=currentDepth>closestDepth?1.0:0.0; returnshadow; } Activatingthisshader,bindingthepropertextures,andactivatingthedefaultprojectionandviewmatricesinthesecondrenderpassshouldgiveyouaresultsimilartotheimagebelow: Ifyoudidthingsrightyoushouldindeedsee(albeitwithquiteafewartifacts)shadowsonthefloorandthecubes.Youcanfindthesourcecodeofthedemoapplicationhere. Improvingshadowmaps Wemanagedtogetthebasicsofshadowmappingworking,butasyoucanwe'renotthereyetduetoseveral(clearlyvisible)artifactsrelatedtoshadowmappingweneedtofix.We'llfocusonfixingtheseartifactsinthenextsections. Shadowacne Itisobvioussomethingiswrongfromthepreviousimage.AcloserzoomshowsusaveryobviousMoiré-likepattern: Wecanseealargepartofthefloorquadrenderedwithobviousblacklinesinanalternatingfashion.Thisshadowmappingartifactiscalledshadowacneandcanbeexplainedbythefollowingimage: Becausetheshadowmapislimitedbyresolution,multiplefragmentscansamplethesamevaluefromthedepthmapwhenthey'rerelativelyfarawayfromthelightsource.Theimageshowsthefloorwhereeachyellowtiltedpanelrepresentsasingletexelofthedepthmap.Asyoucansee,severalfragmentssamplethesamedepthsample. Whilethisisgenerallyokay,itbecomesanissuewhenthelightsourcelooksatanangletowardsthesurfaceasinthatcasethedepthmapisalsorenderedfromanangle.Severalfragmentsthenaccessthesametilteddepthtexelwhilesomeareaboveandsomebelowthefloor;wegetashadowdiscrepancy.Becauseofthis,somefragmentsareconsideredtobeinshadowandsomearenot,givingthestripedpatternfromtheimage. Wecansolvethisissuewithasmalllittlehackcalledashadowbiaswherewesimplyoffsetthedepthofthesurface(ortheshadowmap)byasmallbiasamountsuchthatthefragmentsarenotincorrectlyconsideredabovethesurface. Withthebiasapplied,allthesamplesgetadepthsmallerthanthesurface'sdepthandthustheentiresurfaceiscorrectlylitwithoutanyshadows.Wecanimplementsuchabiasasfollows: floatbias=0.005; floatshadow=currentDepth-bias>closestDepth?1.0:0.0; Ashadowbiasof0.005solvestheissuesofourscenebyalargeextent,butyoucanimaginethebiasvalueishighlydependentontheanglebetweenthelightsourceandthesurface.Ifthesurfacewouldhaveasteepangletothelightsource,theshadowsmaystilldisplayshadowacne.Amoresolidapproachwouldbetochangetheamountofbiasbasedonthesurfaceangletowardsthelight:somethingwecansolvewiththedotproduct: floatbias=max(0.05*(1.0-dot(normal,lightDir)),0.005); Herewehaveamaximumbiasof0.05andaminimumof0.005basedonthesurface'snormalandlightdirection.Thisway,surfaceslikethefloorthatarealmostperpendiculartothelightsourcegetasmallbias,whilesurfaceslikethecube'sside-facesgetamuchlargerbias.Thefollowingimageshowsthesamescenebutnowwithashadowbias: Choosingthecorrectbiasvalue(s)requiressometweakingasthiswillbedifferentforeachscene,butmostofthetimeit'ssimplyamatterofslowlyincrementingthebiasuntilallacneisremoved. Peterpanning Adisadvantageofusingashadowbiasisthatyou'reapplyinganoffsettotheactualdepthofobjects.Asaresult,thebiasmaybecomelargeenoughtoseeavisibleoffsetofshadowscomparedtotheactualobjectlocationsasyoucanseebelow(withanexaggeratedbiasvalue): Thisshadowartifactiscalledpeterpanningsinceobjectsseemslightlydetachedfromtheirshadows.Wecanusealittletricktosolvemostofthepeterpanningissuebyusingfrontfacecullingwhenrenderingthedepthmap.YoumayrememberfromthefacecullingchapterthatOpenGLbydefaultcullsback-faces.BytellingOpenGLwewanttocullfrontfacesduringtheshadowmapstagewe'reswitchingthatorderaround. Becauseweonlyneeddepthvaluesforthedepthmapitshouldn'tmatterforsolidobjectswhetherwetakethedepthoftheirfrontfacesortheirbackfaces.Usingtheirbackfacedepthsdoesn'tgivewrongresultsasitdoesn'tmatterifwehaveshadowsinsideobjects;wecan'tseethereanyways. Tofixpeterpanningwecullallfrontfacesduringtheshadowmapgeneration.NotethatyouneedtoenableGL_CULL_FACEfirst. glCullFace(GL_FRONT); RenderSceneToDepthMap(); glCullFace(GL_BACK);//don'tforgettoresetoriginalcullingface Thiseffectivelysolvesthepeterpanningissues,butonlyforsolidobjectsthatactuallyhaveaninsidewithoutopenings.Inoursceneforexample,thisworksperfectlyfineonthecubes.However,ontheflooritwon'tworkaswellascullingthefrontfacecompletelyremovesthefloorfromtheequation.Thefloorisasingleplaneandwouldthusbecompletelyculled.Ifonewantstosolvepeterpanningwiththistrick,carehastobetakentoonlycullthefrontfacesofobjectswhereitmakessense. Anotherconsiderationisthatobjectsthatareclosetotheshadowreceiver(likethedistantcube)maystillgiveincorrectresults.However,withnormalbiasvaluesyoucangenerallyavoidpeterpanning. Oversampling Anothervisualdiscrepancywhichyoumaylikeordislikeisthatregionsoutsidethelight'svisiblefrustumareconsideredtobeinshadowwhilethey're(usually)not.Thishappensbecauseprojectedcoordinatesoutsidethelight'sfrustumarehigherthan1.0andwillthussamplethedepthtextureoutsideitsdefaultrangeof[0,1].Basedonthetexture'swrappingmethod,wewillgetincorrectdepthresultsnotbasedontherealdepthvaluesfromthelightsource. Youcanseeintheimagethatthereissomesortofimaginaryregionoflight,andalargepartoutsidethisareaisinshadow;thisarearepresentsthesizeofthedepthmapprojectedontothefloor.Thereasonthishappensisthatweearliersetthedepthmap'swrappingoptionstoGL_REPEAT. Whatwe'dratherhaveisthatallcoordinatesoutsidethedepthmap'srangehaveadepthof1.0whichasaresultmeansthesecoordinateswillneverbeinshadow(asnoobjectwillhaveadepthlargerthan1.0).Wecandothisbyconfiguringatexturebordercolorandsetthedepthmap'stexturewrapoptionstoGL_CLAMP_TO_BORDER: glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_BORDER); floatborderColor[]={1.0f,1.0f,1.0f,1.0f}; glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR,borderColor); Nowwheneverwesampleoutsidethedepthmap's[0,1]coordinaterange,thetexturefunctionwillalwaysreturnadepthof1.0,producingashadowvalueof0.0.Theresultnowlooksmoreplausible: Thereseemstostillbeonepartshowingadarkregion.Thosearethecoordinatesoutsidethefarplaneofthelight'sorthographicfrustum.Youcanseethatthisdarkregionalwaysoccursatthefarendofthelightsource'sfrustumbylookingattheshadowdirections. Alight-spaceprojectedfragmentcoordinateisfurtherthanthelight'sfarplanewhenitszcoordinateislargerthan1.0.InthatcasetheGL_CLAMP_TO_BORDERwrappingmethoddoesn'tworkanymoreaswecomparethecoordinate'szcomponentwiththedepthmapvalues;thisalwaysreturnstrueforzlargerthan1.0. Thefixforthisisalsorelativelyeasyaswesimplyforcetheshadowvalueto0.0whenevertheprojectedvector'szcoordinateislargerthan1.0: floatShadowCalculation(vec4fragPosLightSpace) { [...] if(projCoords.z>1.0) shadow=0.0; returnshadow; } Checkingthefarplaneandclampingthedepthmaptoamanuallyspecifiedbordercolorsolvestheover-samplingofthedepthmap.Thisfinallygivesustheresultwearelookingfor: Theresultofallthisdoesmeanthatweonlyhaveshadowswheretheprojectedfragmentcoordinatessitinsidethedepthmaprangesoanythingoutsidethelightfrustumwillhavenovisibleshadows.Asgamesusuallymakesurethisonlyoccursinthedistanceitisamuchmoreplausibleeffectthantheobviousblackregionswehadbefore. PCF Theshadowsrightnowareaniceadditiontothescenery,butit'sstillnotexactlywhatwewant.Ifyouweretozoominontheshadowstheresolutiondependencyofshadowmappingquicklybecomesapparent. Becausethedepthmaphasafixedresolution,thedepthfrequentlyusuallyspansmorethanonefragmentpertexel.Asaresult,multiplefragmentssamplethesamedepthvaluefromthedepthmapandcometothesameshadowconclusions,whichproducesthesejaggedblockyedges. Youcanreducetheseblockyshadowsbyincreasingthedepthmapresolution,orbytryingtofitthelightfrustumascloselytothesceneaspossible. Another(partial)solutiontothesejaggededgesiscalledPCF,orpercentage-closerfiltering,whichisatermthathostsmanydifferentfilteringfunctionsthatproducesoftershadows,makingthemappearlessblockyorhard.Theideaistosamplemorethanoncefromthedepthmap,eachtimewithslightlydifferenttexturecoordinates.Foreachindividualsamplewecheckwhetheritisinshadowornot.Allthesub-resultsarethencombinedandaveragedandwegetanicesoftlookingshadow. OnesimpleimplementationofPCFistosimplysamplethesurroundingtexelsofthedepthmapandaveragetheresults: floatshadow=0.0; vec2texelSize=1.0/textureSize(shadowMap,0); for(intx=-1;x<=1;++x) { for(inty=-1;y<=1;++y) { floatpcfDepth=texture(shadowMap,projCoords.xy+vec2(x,y)*texelSize).r; shadow+=currentDepth-bias>pcfDepth?1.0:0.0; } } shadow/=9.0; HeretextureSizereturnsavec2ofthewidthandheightofthegivensamplertextureatmipmaplevel0.1dividedoverthisreturnsthesizeofasingletexelthatweusetooffsetthetexturecoordinates,makingsureeachnewsamplesamplesadifferentdepthvalue.Herewesample9valuesaroundtheprojectedcoordinate'sxandyvalue,testforshadowocclusion,andfinallyaveragetheresultsbythetotalnumberofsamplestaken. Byusingmoresamplesand/orvaryingthetexelSizevariableyoucanincreasethequalityofthesoftshadows.BelowyoucanseetheshadowswithsimplePCFapplied: Fromadistancetheshadowslookalotbetterandlesshard.Ifyouzoominyoucanstillseetheresolutionartifactsofshadowmapping,butingeneralthisgivesgoodresultsformostapplications. Youcanfindthecompletesourcecodeoftheexamplehere. ThereisactuallymuchmoretoPCFandquiteafewtechniquestoconsiderablyimprovethequalityofsoftshadows,butforthesakeofthischapter'slengthwe'llleavethatforalaterdiscussion. Orthographicvsperspective Thereisadifferencebetweenrenderingthedepthmapwithanorthographicoraperspectiveprojectionmatrix.Anorthographicprojectionmatrixdoesnotdeformthescenewithperspectivesoallview/lightraysareparallel.Thismakesitagreatprojectionmatrixfordirectionallights.Aperspectiveprojectionmatrixhoweverdoesdeformallverticesbasedonperspectivewhichgivesdifferentresults.Thefollowingimageshowsthedifferentshadowregionsofbothprojectionmethods: Perspectiveprojectionsmakemostsenseforlightsourcesthathaveactuallocations,unlikedirectionallights.Perspectiveprojectionsaremostoftenusedwithspotlightsandpointlights,whileorthographicprojectionsareusedfordirectionallights. Anothersubtledifferencewithusingaperspectiveprojectionmatrixisthatvisualizingthedepthbufferwilloftengiveanalmostcompletelywhiteresult.Thishappensbecausewithperspectiveprojectionthedepthistransformedtonon-lineardepthvalueswithmostofitsnoticeablerangeclosetothenearplane.Tobeabletoproperlyviewthedepthvaluesaswedidwiththeorthographicprojectionyoufirstwanttotransformthenon-lineardepthvaluestolinearaswediscussedinthedepthtestingchapter: #version330core outvec4FragColor; invec2TexCoords; uniformsampler2DdepthMap; uniformfloatnear_plane; uniformfloatfar_plane; floatLinearizeDepth(floatdepth) { floatz=depth*2.0-1.0;//BacktoNDC return(2.0*near_plane*far_plane)/(far_plane+near_plane-z*(far_plane-near_plane)); } voidmain() { floatdepthValue=texture(depthMap,TexCoords).r; FragColor=vec4(vec3(LinearizeDepth(depthValue)/far_plane),1.0);//perspective //FragColor=vec4(vec3(depthValue),1.0);//orthographic } Thisshowsdepthvaluessimilartowhatwe'veseenwithorthographicprojection.Notethatthisisonlyusefulfordebugging;thedepthchecksremainthesamewithorthographicorprojectionmatricesastherelativedepthsdonotchange. Additionalresources Tutorial16:Shadowmapping:similarshadowmappingtutorialbyopengl-tutorial.orgwithafewextranotes. ShadowMapping-Part1:anothershadowmappingtutorialbyogldev. HowShadowMappingWorks:a3-partYouTubetutorialbyTheBennyBoxonshadowmappinganditsimplementation. CommonTechniquestoImproveShadowDepthMaps:agreatarticlebyMicrosoftlistingalargenumberoftechniquestoimprovethequalityofshadowmaps. HI



請為這篇文章評分?