Tutorial 23 - Shadow Mapping - Part 1 - OGLdev
文章推薦指數: 80 %
The results of the 3D pipeline in OpenGL end up in something which is called a 'framebuffer object' (a.k.a FBO). This concept wraps within it the color ... Background Theconceptofshadowisinseparablefromtheconceptoflight,asyouneedlightinordertocast ashadow.Therearemanytechniquesthatgenerateshadowsandinthistwoparttutorialwearegoing tostudyoneofthemorebasicandsimpleones-shadowmapping. Whenitcomestorasterizationandshadowsthequestionthatyouaskyourselfis-isthispixellocated inshadowornot?Let'saskthisdifferently-doesthepathfromthelightsourcetothepixelgoes throughanotherobjectornot?Ifitdoes-thepixelisprobablyinshadow(assumingtheotherobject isnottransparent...),andifnot-thepixelisnotinshadow.Inaway,thisquestionissimilartothe questionweaskedourselvesintheprevioustutorial-howtomakesurethatwhentwoobjectsoverlapeach otherwewillseethecloserone.Ifweplacethecameraforamomentatthelightoriginthetwoquestions becomeone.Wewantthepixelsthatfailthedepthtest(i.e.theonesthatarefurtherawayandhavepixels beforethem)tobeinshadow.Onlythepixelsthatwinthedepthtestmustbeinlight.Theyaretheones thatareindirectcontactwiththelightsourceandthereisnothinginbetweenthatconcealsthem.Inanutshell, thisistheideabehindshadowmapping. Soitlookslikethedepthtestcanhelpusdetectwhetherapixelisinshadowornotbutthereisaproblem. Thecameraandthelightarenotalwayspositionedinthesameplace.Thedepthtestisnormallyusedto solvethevisibilityproblemfromthecamerapointofview,sohowcanweharnessitforshadowdetectionwhen thelightislocatedfurtheraway?Thesolutionistorenderthescenetwice.Firstfromthelightpointof view.Theresultsofthisrenderpassdon'treachthecolorbuffer.Instead,theclosestdepthvaluesare renderedintoanapplicationcreateddepthbuffer(insteadoftheonethatisautomaticallygeneratedbyGLUT). Inthesecondpassthesceneisrenderedasusualfromthecamerapointofview.Thedepthbufferthatwe've createdisboundtothefragmentshaderforreading.Foreachpixelwefetchthecorrespondingdepthfromthat depthbuffer.Wealsocalculatethedepthofthispixelfromthelightpointofview.Sometimesthetwodepth valueswillbeidentical.Thisisthecasewherethispixelwasclosesttothelightsoitsdepthvalueended upinthedepthbuffer.Ifthathappenweconsiderthepixelasifitisinlightandcalculateitscolorasusual. Ifthedepthvaluesaredifferentitmeansthereisanotherpixelthatcoversthispixelwhenlookingatitfrom thelightposition.Inthiscaseweaddsomeshadowfactortothecolorcalculationinordertosimulatethe shadoweffect.Takealookatthefollowingpicture: Oursceneismadeupoftwoobjects-thesurfaceandthecube.Thelightsourceislocatedatthetopleftcorner andispointingatthecube.Inthefirstpasswerenderintothedepthbuffer fromthepointofviewofthelightsource.Let'sfocusonthethreepointsA,BandC.WhenBisrendereditsdepth valuegoesintothedepthbuffer.Thereasonisthatthereisnothinginbetweenthepointandthelight.Bydefault, itistheclosestpointtothelightonthatline.However,whenAandCarerenderedthey"compete"ontheexactsame spotinthedepthbuffer.Bothpointsareonthesamestraightlinefromthelightsourcesoafterperspectiveprojection takesplacetherasterizerfindsoutbothpointsneedtogotothesamepixelonthescreen.Thisisthedepthtestand pointC"wins"it. Inthesecondpasswerenderthesurfaceandthecubefromthecamerapointofview.Inadditiontoeverythingwehave doneinourlightingshaderperpixelwealsocalculatethedistancefromthelightsourcetothepixelandcompare ittothecorrespondingvalueinthedepthbuffer.WhenwerasterizepointBthetwovaluesshouldroughlybesame (somedifferencesareexpectedduetodifferencesininterpolationandfloatingpointprecisionissues).Therefore, wedecidethatBisnotinshadowandactaccordingly.WhenwerasterizepointAwefindoutthatthestoreddepth valueisclearlysmallerthanthedepthofA.Therefore,wedecidethatAisinshadowandapplysomeshadowfactortoit inordertogetitdarkerthanusual. This,inanutshell,istheshadowmappingalgorithm(thedepthbufferthatwerendertointhefirstpass iscalledthe"shadowmap").Wearegoingtostudyitintwostages.Inthefirststage(thistutorial)wewill learnhowtorenderintotheshadowmap.Theprocessofrenderingsomething(depth,color,etc)intoan applicationcreatedtextureisknownas'rendertotexture'.Wewilldisplaytheshadowmaponthescreen usingasimpletexturemappingtechniquethatwearealreadyfamiliarwith.Thisisagooddebuggingstep asgettingtheshadowmapcorrectiscrucialinordertogetthecompleteshadoweffectworkingcorrectly. Inthenexttutorialwewillseehowtousetheshadowmapinordertodothe"inshadow/notinshadow"decision. Thesourcesofthistutorialincludeasimplequadmeshthatcanbeusedtodisplaytheshadowmap.Thequadismade upoftwotrianglesandthetexturecoordinatesaresetupsuchthattheycovertheentiretexturespace.When thequadisrenderedthetexturecoordinatesareinterpolatedbytherasterizer,allowingyoutosampleanentire textureanddisplayitonscreen. Sourcewalkthru (shadow_map_fbo.h:50) classShadowMapFBO { public: ShadowMapFBO(); ~ShadowMapFBO(); boolInit(unsignedintWindowWidth,unsignedintWindowHeight); voidBindForWriting(); voidBindForReading(GLenumTextureUnit); private: GLuintm_fbo; GLuintm_shadowMap; }; Theresultsofthe3DpipelineinOpenGLendupinsomethingwhichiscalleda'framebufferobject'(a.k.aFBO).This conceptwrapswithinitthecolorbuffer(whichisdisplayedonscreen),thedepthbufferaswell asafewotherbuffersforadditionalusages.WhenglutInitDisplayMode()iscalleditcreatesthe defaultframebufferusingthespecifiedparameters.Thisframebufferismanagedbythewindowing systemandcannotbedeletedbyOpenGL.Inadditiontothedefaultframebuffer,anapplicationcan createFBOsofitsown.Theseobjectscanbemanipulatedandusedforvarious techniquesunderthecontroloftheapplication.TheShadowMapFBOclassprovidesaneasytouse interfacetoaFBOwhichwillbeusedfortheshadowmappingtechnique.Internally, thisclasscontainstwoOpenGLhandles.Thehandle'm_fbo'representstheactualFBO. TheFBOencapsulateswithinittheentirestateoftheframebuffer.Oncethisobject iscreatedandconfiguredproperlywecanchangeframebuffersbysimplybindingadifferentobject. Notethatonlythedefaultframebuffercanbeusedtodisplaysomethingonthescreen.Theframebuffers createdbytheapplicationcanonlybeusedfor"offscreenrendering".Thiscanbeanintermediate renderingpass(e.g.ourshadowmappingbuffer)whichcanlaterbeusedforthe"real"renderingpass thatgoestothescreen. Initself,theframebufferisjustaplaceholder.Tomakeitusableweneedtoattachtexturesto oneormoreoftheavailableattachmentpoints.Thetexturescontaintheactualstoragespaceoftheframebuffer. OpenGLdefinesthefollowingattachmentpoints: COLOR_ATTACHMENTi-thetexturethatwillbeattachedherewillreceivethecolorthatcomesoutof thefragmentshader.The'i'suffixmeansthattherecanbemultipletexturesattachedascolor attachmentssimultaneously.Thereisamechanisminthefragmentshaderthatenablesrendering intoseveralcolorbuffersatthesametime. DEPTH_ATTACHMENT-thetexturethatwillbeattachedherewillreceivetheresultsofthedepthtest. STENCIL_ATTACHMENT-thetexturethatwillbeattachedherewillserveasthestencilbuffer.The stencilbufferenableslimitingtheareaofrasterizationandcanbeusedforvarioustechniques. DEPTH_STENCIL_ATTACHMENT-thisoneissimplyacombinationofdepthandstencilbuffersas thetwoareoftenusedtogether. Fortheshadowmappingtechniquewewillonlyneedadepthbuffer.Thememberattribute'm_shadowMap'is thehandleofthetexturethatwillbeattachedtotheDEPTH_ATTACHMENTattachmentpoint.TheShadowMapFBO alsoprovidesacoupleofmethodsthatwillbeusedinthemainrenderfunction.Wewillcall BindForWriting()beforerenderingintotheshadowmapandBindForReading()whenstartingthesecondrenderingpass. (shadow_map_fbo.cpp:43) glGenFramebuffers(1,&m_fbo); HerewecreatetheFBO.Sameasintexturesandbuffers,wespecifytheaddressofan arrayofGLuintsanditssize.Thearrayispopulatedwiththehandles. (shadow_map_fbo.cpp:46) glGenTextures(1,&m_shadowMap); glBindTexture(GL_TEXTURE_2D,m_shadowMap); glTexImage2D(GL_TEXTURE_2D,0,GL_DEPTH_COMPONENT,WindowWidth,WindowHeight,0,GL_DEPTH_COMPONENT,GL_FLOAT,NULL); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE); Nextwecreatethetexturethatwillserveastheshadowmap.Ingeneral,thisisastandard2Dtexturewith somespecificconfigurationtomakeitsuitableforitspurpose: TheinternalformatisGL_DEPTH_COMPONENT.Thisisdifferentfromtheprevioususeofthisfunction wheretheinternalformatwasusuallyoneofthecolortypes(e.g.GL_RGB).GL_DEPTH_COMPONENTmeansasingle floatingpointnumberthatrepresentsthenormalizeddepth. ThelastparameterofglTexImage2Disnull.Thismeansthatwearenotsupplyinganydatabywhichto initializethebuffer.Thismakessenseknowingthatwewantthebuffertocontainthedepthvaluesof eachframeandeachframeisabitdifferent.WheneverwestartanewframewewilluseglClear()to clearoutthebuffer.Thisisalltheinitializationthatweneedforthecontent. WetellOpenGLthatincaseatexturecoordinategoesoutofbounditneedstoclampittothe[0,1] range.Thiscanhappenwhentheprojectionwindowfromthecamerapointofviewcontainsmorethan theprojectionwindowfromthelightpointofview.Toavoidstrangeartifactssuchastheshadowrepeating itselfelsewhere(duetowraparound)weclampthetexturecoordinates. (shadow_map_fbo.cpp:54) glBindFramebuffer(GL_FRAMEBUFFER,m_fbo); WehavegeneratedtheFBO,thetextureobjectandalsoconfiguredthetextureobjectforshadowmapping. NowweneedtoattachthetextureobjecttotheFBO.Thefirstthingweneedtodoistobindthe FBO.Thiswillmakeit"current"andthenallfutureFBOoperationswillapplytoit.Thisfunction takestheFBOhandleandthedesiredtarget.ThetargetcanbeGL_FRAMEBUFFER,GL_DRAW_FRAMEBUFFERorGL_READ_FRAMEBUFFER. GL_READ_FRAMEBUFFERisusedwhenwewanttoreadfromtheFBOusingglReadPixels(notinthis tutorial).GL_DRAW_FRAMEBUFFERisusedwhenwewanttorenderintotheFBO.WhenweuseGL_FRAMEBUFFERboth thereadingandwritingstateisupdatedandthisistherecommendedwayforinitializingtheFBO.Wewill useGL_DRAW_FRAMEBUFFERwhenweactuallystarttorender. (shadow_map_fbo.cpp:55) glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,m_shadowMap,0); HereweattachtheshadowmaptexturetothedepthattachmentpointoftheFBO.Thelastparameter tothisfunctionindicatesthemipmaplayertouse.Mipmappingisatexturemappingfeaturewhere atextureisrepresentedatdifferentresolutions,startingfromthehighestresolutionatmipmap0 anddecreasingresolutionsinmipmaps1-N.Thecombinationofamipmappedtextureandtrilinearfiltering providesmorepleasantresultsbycombiningtexelsfromneighboringmipmaplevels(whennosingle levelisperfect).Herewehaveasinglemipmaplevelsoweuse0.Weprovidetheshadowmaphandle asthefourthparameter.Ifweuse0hereitwilldetachthecurrenttexturefromthespecified attachmentpoint(depthinthecaseabove). (shadow_map_fbo.cpp:58) glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); Sincewearenotgoingtorenderintothecolorbuffer(onlyintothedepth)weexplicitlyspecify itusingtheabovecall.Bydefault,thecolorbuffertargetissettoGL_COLOR_ATTACHMENT0,butourFBO isn'tevengoingtocontainacolorbuffer.Therefore,itisbettertotellOpenGLourintentionsexplicitly. ThevalidparameterstothisfunctionsareGL_NONEandGL_COLOR_ATTACHMENT0toGL_COLOR_ATTACHMENTmwhere 'm'isGL_MAX_COLOR_ATTACHMENTS-1.TheseparametersarevalidonlyforFBOs.Ifthedefaultframebuffer isusedthevalidparametersareGL_NONE,GL_FRONT_LEFT,GL_FRONT_RIGHT,GL_BACK_LEFTandGL_BACK_RIGHT. Theseallowyoutorenderdirectlyintothefrontorbackbuffers(whereeachonehasaleftandrightbuffer). WealsosetthereadbuffertoGL_NONE(remember,wearenotgoingtocalloneoftheglReadPixelAPIs). ThisismainlytoavoidproblemswithGPUsthatsupportonlyOpenGL3.xandnot4.x. (shadow_map_fbo.cpp:61) GLenumStatus=glCheckFramebufferStatus(GL_FRAMEBUFFER); if(Status!=GL_FRAMEBUFFER_COMPLETE){ printf("FBerror,status:0x%x\n",Status); returnfalse; } WhentheconfigurationoftheFBOisfinisheditisveryimportanttoverifythatitsstateiswhatthe OpenGLspecdefinesas"complete".Thismeansthatnoerrorwasdetectedandthattheframebuffer cannowbeused.Thecodeabovechecksthat. (shadow_map_fbo.cpp:72) voidShadowMapFBO::BindForWriting() { glBindFramebuffer(GL_DRAW_FRAMEBUFFER,m_fbo); } Wewillneedtotogglebetweenrenderingintotheshadowmapandrenderingintothedefaultframebuffer. Inthesecondpasswewillalsoneedtobindourshadowmapforinput.Thisfunctionandthenext oneprovideeasytousewrapperstodothat.TheabovefunctionsimplybindstheFBOforwritingas wedidearlier.Wewillcallitbeforethefirstrenderpass... (shadow_map_fbo.cpp:78) voidShadowMapFBO::BindForReading(GLenumTextureUnit) { glActiveTexture(TextureUnit); glBindTexture(GL_TEXTURE_2D,m_shadowMap); } ...andthisfunctionwillbeusedbeforethesecondrenderpasstobindtheshadowmapforreading. Notethatwebindspecificallythetextureobject,ratherthantheFBOitself.Thisfunctiontakes thetextureunittowhichtheshadowmapwillbebound.Thetextureunitindexmustbesynchronized withtheshader(sincetheshaderhasasampler2Duniformvariabletoaccessthetexture).Itisvery importanttonotethatwhileglActiveTexturetakesthetextureindexasanenum(e.g.GL_TEXTURE0,GL_TEXTURE1,etc), theshaderneedssimplytheindexitself(0,1,etc).Thiscanbethesourceofmanybugs(believeme,Iknow). (shadow_map.vs) #version330 layout(location=0)invec3Position; layout(location=1)invec2TexCoord; layout(location=2)invec3Normal; uniformmat4gWVP; outvec2TexCoordOut; voidmain() { gl_Position=gWVP*vec4(Position,1.0); TexCoordOut=TexCoord; } Wearegoingtousethesameshaderprogramforbothrenderpasses.Thevertexshaderwillbeusedby bothpasseswhilethefragmentshaderwillbeusedonlybythesecondpass.Sincewearedisabling writingtothecolorbufferinthefirstpassthefragmentshaderwillsimplybeleftunusedthere.Thevertexshader aboveisverysimple.Itgeneratestheclipspacecoordinatebymultiplyingthelocalspaceposition bytheWVPmatrixandpassesthroughthetexturecoordinates.Inthefirstpassthetexturecoordinates areredundant(nofragmentshader).However,thereisnorealimpactanditissimplertosharethe vertexshader.Asyoucansee,fromthepointofviewoftheshaderitmakesnodifferencewhether thisisaZpassorarealrenderpass.Whatmakesthedifferenceisthattheapplicationpassesa lightpointofviewWVPmatrixinthefirstpassandacamerapointofviewWVPmatrixinthesecondpass.Inthefirst passtheZbufferwillbepopulatedbytheclosestZvaluesfromthelightpointofviewandonthesecond passfromthecamerapointofview.Inthesecondpasswealsoneedthetexturecoordinatesinthefragment shaderbecausewewillsamplefromtheshadowmap(whichisnowinputtotheshader). (shadow_map.fs) #version330 invec2TexCoordOut; uniformsampler2DgShadowMap; outvec4FragColor; voidmain() { floatDepth=texture(gShadowMap,TexCoordOut).x; Depth=1.0-(1.0-Depth)*25.0; FragColor=vec4(Depth); } Thisisthefragmentshaderthatisusedtodisplaytheshadowmapintherenderpass.The2Dtexturecoordinates areusedtofetchthedepthvaluefromtheshadowmap.Theshadowmaptexturewascreatedwiththetype GL_DEPTH_COMPONENTasitsinternalformat.Thismeansthatthebasictexelisasinglefloatingpointvalueand notacolor.Thisiswhy'.x'isusedduringsampling. TheperspectiveprojectionmatrixhasaknownbehaviorthatwhenitnormalizestheZinthepositionvectorit reservesmorevaluesinthe[0,1]rangetothecloserlocationsratherthanthelocationsthatarefurtheraway fromthecamera.TherationalistoallowgreaterZprecisionaswegetclosertothecamerabecauseerrorshere aremorenoticeable.Whenwedisplaythecontentsofthedepthbufferwemayrunintoacasewheretheresulting imageisnotclearenough.Therefore,afterwesamplethedepthfromtheshadowmapwesharpenitbyscaling thedistanceofthecurrentpointtothefaredge(whereZis1.0)andthensubstractingtheresultfrom1.0again. Thisamplifiestherangeandimprovesthefinalimage.Weusethenewdepthvaluetocreateacolorbybroadcasting itacrossallthecolorchannels.Thismeanswewillgetsomevariationofgray(whiteatthefarclippingplaneand blackatthenearclippingplane). Nowlet'sseehowtocombinethepiecesofcodeaboveandcreatetheapplication. (tutorial23.cpp:106) virtualvoidRenderSceneCB() { m_pGameCamera->OnRender(); m_scale+=0.05f; ShadowMapPass(); RenderPass(); glutSwapBuffers(); } Themainrenderfunctionhasbecomemuchsimplerasmostfunctionalitymovedtootherfunctions.Firstwetake careofthe"global"stufflikeupdatingthepositionofthecameraandtheclassmemberwhichisusedto rotatetheobject.Thenwecallafunctiontorenderintotheshadowmaptexturefollowedbyafunctionto displaytheresults.Finally,glutSwapBuffer()iscalledtodisplayittothescreen. (tutorial23.cpp:117) virtualvoidShadowMapPass() { m_shadowMapFBO.BindForWriting(); glClear(GL_DEPTH_BUFFER_BIT); Pipelinep; p.Scale(0.1f,0.1f,0.1f); p.Rotate(0.0f,m_scale,0.0f); p.WorldPos(0.0f,0.0f,5.0f); p.SetCamera(m_spotLight.Position,m_spotLight.Direction,Vector3f(0.0f,1.0f,0.0f)); p.SetPerspectiveProj(20.0f,WINDOW_WIDTH,WINDOW_HEIGHT,1.0f,50.0f); m_pShadowMapTech->SetWVP(p.GetWVPTrans()); m_pMesh->Render(); glBindFramebuffer(GL_FRAMEBUFFER,0); } WestarttheshadowmappassbybindingintheshadowmapFBO.Fromnowonallthedepthvalueswillgointo ourshadowmaptextureandcolorwriteswillbediscarded.Weclearthedepthbuffer(only)beforewestart doinganything.Thenwesetupthepipelineclassinordertorenderthemesh(atankfromQuake2issupplied withthetutorialsource).Thesinglepointworthnoticing hereisthatthecameraisupdatedbasedonthepositionanddirectionofthespotlight.Werenderthemesh andthenswitchbacktothedefaultframebufferbybindingFBOzero. (tutorial23.cpp:135) virtualvoidRenderPass() { glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); m_pShadowMapTech->SetTextureUnit(0); m_shadowMapFBO.BindForReading(GL_TEXTURE0); Pipelinep; p.Scale(5.0f,5.0f,5.0f); p.WorldPos(0.0f,0.0f,10.0f); p.SetCamera(m_pGameCamera->GetPos(),m_pGameCamera->GetTarget(),m_pGameCamera->GetUp()); p.SetPerspectiveProj(30.0f,WINDOW_WIDTH,WINDOW_HEIGHT,1.0f,50.0f); m_pShadowMapTech->SetWVP(p.GetWVPTrans()); m_pQuad->Render(); } Therenderpassstartsbyclearingbothcoloranddepthbuffers.Thesebuffersbelondtothedefaultframebuffer. Wetelltheshadertousetextureunit0andbindtheshadowmaptextureforreadingontextureunit0. Fromhereoneverythingisasusual.Wescalethequadup,placeitdirectlyinfrontofthecameraandrenderit. Duringrasterizationtheshadowmapissampledanddisplayed. Note:inthistutorial'scodewenolongerautomaticallyloadawhitetexturewhenthemeshfiledoes notspecifyone.Thereasonistobeabletobindtheshadowmapinstead.Ifameshdoesnotcontaina texturewesimplybindnoneandthisallowsthecallingcodetobinditsowntexture. Nexttutorial commentspoweredbyDisqus
延伸文章資訊
- 1Tutorial 16 : Shadow mapping
As such, rendering the shadow map is done with an orthographic projection matrix. An orthographic...
- 2阴影映射 - LearnOpenGL-CN
阴影映射(Shadow Mapping)背后的思路非常简单:我们以光的位置为视角进行渲染, ... 然而帧缓冲对象不是完全不包含颜色缓冲的,所以我们需要显式告诉OpenGL我们不适用 ...
- 3Shadow Mapping - LearnOpenGL
comparrison of shadows in a scene with and without in OpenGL ... in fragment shader of shadowmap ...
- 4OpenGL - 阴影映射- Tutorial 16 : Shadow mapping - CSDN博客
文章目录Basic shadowmap - shadow map的基础知识Rendering the shadow map - 渲染shadow mapSetting up the render...
- 5Shadow Mapping - OpenGL ES SDK for Android - GitHub Pages
Shadow Mapping. Yellow cube represents the spot light source. The application displays two cubes ...