OpenGL Shadow Mapping Tutorial - Paul's Projects
文章推薦指數: 80 %
The shadow map is a snapshot of the light's viewport, which is a 2d projection of the light's clip space. In order to perform the texture projection, we will ...
Home
MetaballsII
SonicPlayer
VMachine
OpenGLTutorials
ShadowMapping
SimpleBumpMapping
OpenGLProjects
Direct3DProjects
MegaReader
ContactMe
ShadowMapping
ShadowmappingwasintroducedbyLanceWilliamsin1978,inapaperentitled"Castingcurvedshadowsoncurvedsurfaces".
Ithasbeenextensivelyusedsince,bothinofflinerenderingandrealtimegraphics.
ShadowmappingisusedbyPixar'sRendermanandwasusedonmajorfilmssuchas"ToyStory".
Shadowmappingisjustoneofmanydifferentwaysofproducingshadowsinyourgraphicsapplications,eachwithitsownadvantagesanddisadvantages.
Inthecaseofshadowmapping,theseinclude:
Advantages:
Noknowledgeorprocessingofthescenegeometryisrequired,sinceshadowmappingisanimagespacetechnique,workingautomaticallywithobjectscreatedoralteredontheGPU.
Onlyasingletextureisrequiredtoholdshadowinginformationforeachlight;thestencilbufferisnotused.
Avoidsthehighfillrequirementofshadowvolumes.
Disadvantages:
Aliasing,especiallywhenusingsmallshadowmaps.
Thescenegeometrymustberenderedonceperlightinordertogeneratetheshadowmapforaspotlight,andmoretimesforanomnidirectionalpointlight.
Thistutorialwillfocusonbasicshadowmappingforasinglespotlight,butthereareplentyofpapersabouthowtoextendandimprovethetechnique.
Theory-Shadowmappingasadepthtest
Considerasimplescenelitbyasinglepointlight,withhardshadows.
Howdoesagivenpointinthesceneknowwhetheritislit,orinshadow?
Putsimply,apointinthesceneislitifthereisnothingblockingastraightlinepathbetweenthelightandthatpoint.
Thekeystepinunderstandingshadowmappingisthatthesepointsareexactlythosewhichwouldbevisible(i.e.notoccluded)toaviewerplacedatthelightsource.
Wealreadyhaveatechniquetoseewhatisvisibletoagivenviewer,anduseitwhendrawingalmostanysceneusing3dhardware.
Thattechniqueisz-buffering.
So,thepointswhichwouldpassthedepthtestifwewererenderingthescenefromthelight'spointofviewarepreciselythosewhichshouldnotbeinshadow.
Ifwedrawthescenefromthelight'spointofview,wecansavethevaluesfromthedepthbuffer.
Then,wedrawthescenefromthecamera'spointofview,andusethesaveddepthbufferasatexturewhichisprojectedfromthelight'sposition.
Atagivenpoint,wecanthencomparethevaluefromthetextureprojectedontothepointtothedistancefromthepointtothelight,andhencecalculatewhichpointsshouldbeinshadow.
LettingthevalueinthesaveddepthtexturebeD,andthedistancefromthepointtothelightbeR,wehave:
R = D
Therewasnothingoccludingthispointwhendrawingfromthelightsource,sothispointisunshadowed.
R > D
Theremusthavebeenanobjectinfrontofthispointwhenlookingfromthelight'sposition.
Thispointisthusinshadow.
Application
HowdowegoaboutperformingtheaboveusingOpenGL?
Thetechniquerequiresatleast2passes,buttokeepeachpasssimple,wewilluse3.
Firstly,wedrawthescenefromthelight'spointofview.
ThisisacheivedbyusinggluLookAttolookfromthelight'spositionatthecentreofthescene.
Thesceneisthendrawnasnormal,andthedepthbufferread.
Allthecalculationsfortheshadowingareperformedattheprecisionofthedepthbuffer.
Usinganequalitytotestforanunshadowedpointislikelytoproducemanyincorrectresults,duetoalackofprecision.
Thisisthesamereasonasthatbehind"Donotcomparefloatswith==".
So,whendrawingthescenefromthelight'spointofview,weinstructOpenGLtocullfrontfaces.
Thusthebackfacesofourobjectsaredrawnintotheshadowmap.
Hencethedepthvaluesstoredintheshadowmaparegreaterthanthedepthofthefaceswhichcanbeseenbythelight.
BymarkingasunshadowedpointsforwhichD>=R,allsurfacesvisibletothelightwillbeunshadowed.
Therewillnowbeaprecisionproblemwiththebackfaces(withrespecttothelight),butsincetheseareshadowedbydefinition,theresultofthecomparisondoesnotmatter.
Thistechniquewillonlyworkifallobjectsareclosed.
Ifyouhaveopenobjectsinyourscene,itispossibleinsteadtousepolygonoffsettoincreasethedepthvaluesstoredinthedepthbuffer.
Forsimplicity,wewilldrawthisfirstpasstothestandardbackbuffer.
Thismeansthatourwindowmustbelargeenoughtofittheshadowmaptexturewithinit,andthewindowmustnotbeoccludedbyothers.
Theserestrictionscanbebypassedbyusinganoff-screenpbufferwhengeneratingtheshadowmap.
Theothertwopassesaredrawnfromthecamera'spointofview.
Firstly,wedrawtheentirescenewithadimlight,asitwouldbeshownifshadowed.
Intheory,thispassshoulddrawthesceneusingonlyambientlight.
However,inorderthatthecurvedsurfacesinshadowdonotappearunnaturallyflat,weuseadimdiffuselightsource.
Thethirdpassiswheretheshadowcomparisonmentionedaboveoccurs.
Thiscomparisonissovitaltoshadowmapping,itisactuallypossibletogetthehardwaretoperformthecomparisonperpixel,usingtheARBapprovedextension,ARB_shadow.
Wesetupthetextureunitsothatthecomparisonwillaffectthealphavalueaswellasthecolorcomponents.
Anyfragmentswhich"fail"thecomparison(R>D)willgenerateanalphavalueof0,andanywhichpasswillhavealphaof1.
Byusingthealphatest,wecandiscardanyfragmentswhichshouldbeshadowed.
Now,usingabrightlightwithspecularenabledwecandrawthelitpartsofthescene.
Usingalinearfilteronthedepthtexturewillfilterthevaluesproducedaftertheshadowcomparison.
Thisiscalled"PercentageCloserFiltering"(orPCF)andwillproduceslightlysoftshadowedges.
Ifweallowtheloweralphavaluestopassthealphatesthowever,thelitfragments,modulatedbytheshadowmap,mayactuallybedarkerthantheshadowedpixelalreadywithintheframebuffer.
Thisproducesadarkborderaroundtheshadowedregions.
So,inthisdemo,thealphatestisusedtodiscardallbutfullylitregions.
Thedarkbordercouldbeeliminatedbyusingadifferent,morecomplicatedmethodtocombinethe2passes.
Inmymainshadowmappingproject,MAXblendingisusedtocombinetheresults.
However,tokeepthistutorialassimpleaspossible,PCFhasnotbeenused.
Projectivetexturing
Howdoweprojectthelight'sdepthbuffer,encodedinatexture,ontothescene'sgeometrywhenrenderedfromthecamera'spointofview?
Firstly,let'slookatthecoordinatespacesandmatricesinvolvedinthisdemo:
Theshadowmapisasnapshotofthelight'sviewport,whichisa2dprojectionofthelight'sclipspace.
Inordertoperformthetextureprojection,wewilluseOpenGL'sEYE_LINEARtexturecoordinategeneration,whichgeneratestexturecoordinatesforavertexbaseduponitseye-spaceposition.
Weneedtomapthesegeneratedtexturecoordinatestoonesappropriateforaddressingtheshadowmap,usingthetexturematrix.
Thetexturematrixthusneedstoperformtheoperationsymbolisedbythegreenarrowabove.
Thebestwaytodothisistouse:
where:
Tisthetexturematrix
Plisthelight'sprojectionmatrix
Vlisthelight'sviewmatrix
Vcisthecamera'sviewmatrix
RememberingthatOpenGLappliesamatrixMtoatexturecoordinatesetTasMT,thiswilltransformthecamera'seyespacecoordinatesintothelight'sclipspacebygoingthroughworldspaceandthelight'seyespace.
Thisavoidsobjectspaceandtheuseofanymodelmatrices,andhencedoesnotneedtoberecalculatedforeachmodelwearedrawing.
Thereisonefinaloperationwhichneedstobeperformedonthetexturecoordinatesoncetheyareinthelight'sclipspace.
Aftertheperspectivedivide,theclipspaceX,YandZcoordinatesareintherange-1to1(written[-1,1]).
ThetexturemapisaddressedbyXandYcoordinatesin[0,1],andthedepthvaluestoredinitisalsoin[0,1].
Weneedtogenerateasimplematrixtomap[-1,1]to[0,1]foreachofX,YandZcoordinates,andpre-multiplyourtexturematrixTbyit.
Wecanactuallyperformthisprojectionavoidinguseofthetexturematrixaltogether.
ThiscanbeacheivedasweactuallyspecifyamatrixwhenweenableEYE_LINEARtexgen.
Typicalcodetoenablethetexturecoordinategenerationforasinglecoordinateis:
glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR);
glTexGenfv(GL_S,GL_EYE_PLANE,VECTOR4D(1.0f,0.0f,0.0f,0.0f));
glEnable(GL_TEXTURE_GEN_S);
Ifwelookattheeyeplanesforallfourtexturecoordinatestogether,theyformthe4x4identitymatrix.
Texturecoordinatesaregeneratedbaseduponthis"texgen"matrix,andarethenmanipulatedusingthetexturematrix.
Wecangainasmallspeed-upbyignoringthetexturematrixandplacingwhatwewoulduseforthetexturematrixdirectlyintotheeyeplanes.
Finally,themostexpensivepartofsettinguptheprojectioniscalculatingtheinverseofVc.
OpenGLwillevendothatforus!
Whentheeyeplanesarespecified,theGLwillautomaticallypost-multiplythemwiththeinverseofthecurrentmodelviewmatrix.
Allwehavetodoisensurethatatthistime,themodelviewmatrixcontainsthecamera'sviewmatrix.
Theinverseofthiswillthenbemultipliedontoourtexgenmatrix.
So,thefinalcodetosetupthetextureprojection,includingtheseoptimisations,is:
//Calculatetexturematrixforprojection
//Thismatrixtakesusfromeyespacetothelight'sclipspace
//Itispostmultipliedbytheinverseofthecurrentviewmatrixwhenspecifyingtexgen
staticMATRIX4X4biasMatrix
(0.5f,0.0f,0.0f,0.0f,
0.0f,0.5f,0.0f,0.0f,
0.0f,0.0f,0.5f,0.0f,
0.5f,0.5f,0.5f,1.0f);
MATRIX4X4textureMatrix=biasMatrix*lightProjectionMatrix*lightViewMatrix;
//Setuptexturecoordinategeneration.
glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR);
glTexGenfv(GL_S,GL_EYE_PLANE,textureMatrix.GetRow(0));
glEnable(GL_TEXTURE_GEN_S);
glTexGeni(GL_T,GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR);
glTexGenfv(GL_T,GL_EYE_PLANE,textureMatrix.GetRow(1));
glEnable(GL_TEXTURE_GEN_T);
glTexGeni(GL_R,GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR);
glTexGenfv(GL_R,GL_EYE_PLANE,textureMatrix.GetRow(2));
glEnable(GL_TEXTURE_GEN_R);
glTexGeni(GL_Q,GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR);
glTexGenfv(GL_Q,GL_EYE_PLANE,textureMatrix.GetRow(3));
glEnable(GL_TEXTURE_GEN_Q);
Extensionsused
TheonlytwoextensionswewilluseinthisprojectareARB_depth_textureandARB_shadow.
ARB_depth_textureprovidesanewtextureformattoholdthedepthbuffer.
ByusingDEPTH_COMPONENTasboththeformatandinternalformatofatexture,wegetasingle-channeltextureofthesameprecisionasthedepthbuffer.
Forexample,ifwehavea24bitdepthbuffer,aDEPTH_COMPONENTtexturewillhaveasingle24bitchannel,perfectforstoringvaluesreadfromthedepthbuffer.
AswithRGBtextures,wecanuseCopyTex[Sub]Image2Dtocopydatafromtheframebufferintothetexture.
Whenweareusingadepthtexture,thisinformationwillautomaticallybecopiedfromthedepthbufferratherthanthecolorbuffer.
Thiscard-localcopysavesusfromhavingtoreadthedepthbufferintosystemmemoryandthensavethisasatexture.
ARB_shadowprovidestheautomaticshadowcomparisonmentionedabove.
Ratherthanwritepossiblylargeamountsofcodetocreateandinitialisefunctionpointersfortheextensions,wewillusetheextensionloadinglibrary"GLee",byBenWoodhouse.
Thiswilldotheworkforus,andcanbedownloadedfromhttp://elf-stone.com/glee.php.
Thelatestversionisalsoincludedinthesourcedownloadattheendofthistutorial.
Code
Theamountofcoderequiredforthistutorialisactuallyquitesmall.Thereasonsforthisareprimarily:
Thereisnoneedtomanuallygenerateanyspecificgeometry,sincetheshadowmappingalgorithmdoesnotrequiresilhouetteedgedeterminationoranyadditionalvertexpropertiessuchastangentvectors.
AllgeometrycanbedrawnusingglutSolidSphereandsimilarcommands.
Themajorityoftheworkrequiredisdoneinhardware.
Theshadowmappingcomparisonneedsonlyafewlinestoenableit,thenitwillbeperformedautomatically.
InmyOpenGLShadowMappingdemoontheprojectspage,shadowmappingat8bitprecisionisdone"manually"forhardwarewhichdoesnotsupporttheARB_shadowextension.
Thisrequiresquitealotmorework.
The8bitprecisionalsomeansthatmanyartifactscanappearonallbutthesmallestspotlightfrusta.
Nowthathardwareshadowmappingsupportisrelativelywidelyavailable,withitseaseofuseandhighprecision,thereisverylittlecallforthemanualapproach.
Sincethecodeforshadowmappingisquiteconcise,IhaveincludedacoupleofsimpleclassesfrommyusualOpenGLprojects.
TheTIMERclassimplementsasimpletimerusingtimeGetTime.
Thetimercanbepausedandunpaused,andsimplyreturnsthenumberofmillisecondssinceitwaslastreset.
Weusethistoanimatethesceneataconstantspeed,independentoftheframerate.
TheFPS_COUNTERclassimplementsasimpleframespersecondcountersoyoucanseehowwellthedemocodeisrunning.
Eachframe,thiscounterisinternallyincremented,andeachsecondthenumberofframesdisplayedisupdated.
TheDrawScenefunction,foundinscene.cpp,drawsthescenewhichwewanttodisplay.
Ittakesafloatingpointangleasaparameter,whichisusedtorotatethespheres.
voidDrawScene(floatangle)
{
Firstwecreate3unsignedintegerstoholdtheidentifiersforthedisplaylists.
Onedisplaylistisusedforeachpartofthescene.
Sincethevariablesaredeclaredstatic,theywillretaintheirvaluebetweencallstothefunction.
//Displaylistsforobjects
staticGLuintspheresList=0,torusList=0,baseList=0;
Ifthevariable"spheresList"iszero,weuseglGenListstosaveanewdisplaylistidentifierintospheresList.
Thiswillbenon-zero.
Hencethecodebetweenthebracesisexecutedonlyonthefirstcallofthisfunction.
ThisfillsthedisplaylistwiththeOpenGLcommandstogenerate4spheres.
//Createsphereslistifnecessary
if(!spheresList)
{
spheresList=glGenLists(1);
glNewList(spheresList,GL_COMPILE);
{
glColor3f(0.0f,1.0f,0.0f);
glPushMatrix();
glTranslatef(0.45f,1.0f,0.45f);
glutSolidSphere(0.2,24,24);
glTranslatef(-0.9f,0.0f,0.0f);
glutSolidSphere(0.2,24,24);
glTranslatef(0.0f,0.0f,-0.9f);
glutSolidSphere(0.2,24,24);
glTranslatef(0.9f,0.0f,0.0f);
glutSolidSphere(0.2,24,24);
glPopMatrix();
}
glEndList();
}
Wesimilarlygenerateadisplaylistforatorusandaflatbase.
//Createtorusifnecessary
if(!torusList)
{
torusList=glGenLists(1);
glNewList(torusList,GL_COMPILE);
{
glColor3f(1.0f,0.0f,0.0f);
glPushMatrix();
glTranslatef(0.0f,0.5f,0.0f);
glRotatef(90.0f,1.0f,0.0f,0.0f);
glutSolidTorus(0.2,0.5,24,48);
glPopMatrix();
}
glEndList();
}
//Createbaseifnecessary
if(!baseList)
{
baseList=glGenLists(1);
glNewList(baseList,GL_COMPILE);
{
glColor3f(0.0f,0.0f,1.0f);
glPushMatrix();
glScalef(1.0f,0.05f,1.0f);
glutSolidCube(3.0f);
glPopMatrix();
}
glEndList();
}
Nowwedrawthescenebycallingthedisplaylists,rotatingthespheresby"angle".
Eachtimeafterthefirstthatthisfunctioniscalled,thisistheonlypartwhichwillbeexecuted.
//Drawobjects
glCallList(baseList);
glCallList(torusList);
glPushMatrix();
glRotatef(angle,0.0f,1.0f,0.0f);
glCallList(spheresList);
glPopMatrix();
}
Nowlet'slookatthemainsourcefile,wherealloftheinterestingcodelives.
Firstupistoincludethenecessaryheaders,including"GLee.h",theheaderfortheextensionloadinglibrary.
#defineWIN32_LEAN_AND_MEAN
#include
延伸文章資訊
- 1Point Shadows - LearnOpenGL
The technique is mostly similar to directional shadow mapping: we generate a depth map from the l...
- 2阴影映射 - LearnOpenGL-CN
阴影映射(Shadow Mapping)背后的思路非常简单:我们以光的位置为视角进行渲染, ... 然而帧缓冲对象不是完全不包含颜色缓冲的,所以我们需要显式告诉OpenGL我们不适用 ...
- 3Shadow Mapping - LearnOpenGL
For example, take a look at the following image of a scene with and without shadows: comparrison ...
- 4Tutorial 23 - Shadow Mapping - Part 1 - OGLdev
There are many techniques that generate shadows and in this two part tutorial we are going to stu...
- 5Is there an easy way to get shadows in OpenGL? - Stack ...
3. Not really. · OpenGL does not make shadows, it renders every point as if there is no hinder be...