Point Shadows - LearnOpenGL
文章推薦指數: 80 %
The technique is mostly similar to directional shadow mapping: we generate a depth map from the light's perspective(s), sample the depth map based on the ...
Ifyou'rerunningAdBlock,pleaseconsiderwhitelistingthissiteifyou'dliketosupportLearnOpenGL;andnoworries,Iwon'tbemadifyoudon't:)
IntroductionGettingstartedOpenGLCreatingawindowHelloWindowHelloTriangleShadersTexturesTransformationsCoordinateSystemsCameraReviewLightingColorsBasicLightingMaterialsLightingmapsLightcastersMultiplelightsReviewModelLoadingAssimpMeshModelAdvancedOpenGLDepthtestingStenciltestingBlendingFacecullingFramebuffersCubemapsAdvancedDataAdvancedGLSLGeometryShaderInstancingAntiAliasingAdvancedLightingAdvancedLightingGammaCorrectionShadowsShadowMappingPointShadowsNormalMappingParallaxMappingHDRBloomDeferredShadingSSAOPBRTheoryLightingIBLDiffuseirradianceSpecularIBLInPracticeDebuggingTextRendering2DGameBreakoutSettingupRenderingSpritesLevelsCollisionsBallCollisiondetectionCollisionresolutionParticlesPostprocessingPowerupsAudioRendertextFinalthoughtsGuestArticlesHowtopublish2020OITIntroductionWeightedBlendedSkeletalAnimation2021CSMSceneSceneGraphFrustumCullingTessellationHeightmapTessellationDSACoderepositoryTranslationsAbout
BTC
1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa
ETH/ERC20
0x1de59bd9e52521a46309474f8372531533bd7c43
PointShadows
Advanced-Lighting/Shadows/Point-Shadows
Inthelastchapterwelearnedtocreatedynamicshadowswithshadowmapping.Itworksgreat,butit'smostlysuitedfordirectional(orspot)lightsastheshadowsaregeneratedonlyinthedirectionofthelightsource.Itisthereforealsoknownasdirectionalshadowmappingasthedepth(orshadow)mapisgeneratedfromonlythedirectionthelightislookingat.
Whatthischapterwillfocusonisthegenerationofdynamicshadowsinallsurroundingdirections.Thetechniquewe'reusingisperfectforpointlightsasarealpointlightwouldcastshadowsinalldirections.Thistechniqueisknownaspoint(light)shadowsormoreformerlyasomnidirectionalshadowmaps.
Thischapterbuildsuponthepreviousshadowmappingchaptersounlessyou'refamiliarwithtraditionalshadowmappingitisadvisedtoreadtheshadowmappingchapterfirst.
Thetechniqueismostlysimilartodirectionalshadowmapping:wegenerateadepthmapfromthelight'sperspective(s),samplethedepthmapbasedonthecurrentfragmentposition,andcompareeachfragmentwiththestoreddepthvaluetoseewhetheritisinshadow.Themaindifferencebetweendirectionalshadowmappingandomnidirectionalshadowmappingisthedepthmapweuse.
Thedepthmapweneedrequiresrenderingascenefromallsurroundingdirectionsofapointlightandassuchanormal2Ddepthmapwon'twork;whatifweweretouseacubemapinstead?Becauseacubemapcanstorefullenvironmentdatawithonly6faces,itispossibletorendertheentirescenetoeachofthefacesofacubemapandsampletheseasthepointlight'ssurroundingdepthvalues.
Thegenerateddepthcubemapisthenpassedtothelightingfragmentshaderthatsamplesthecubemapwithadirectionvectortoobtaintheclosestdepth(fromthelight'sperspective)atthatfragment.Mostofthecomplicatedstuffwe'vealreadydiscussedintheshadowmappingchapter.Whatmakesthistechniqueabitmoredifficultisthedepthcubemapgeneration.
Generatingthedepthcubemap
Tocreateacubemapofalight'ssurroundingdepthvalueswehavetorenderthescene6times:onceforeachface.One(quiteobvious)waytodothis,isrenderthescene6timeswith6differentviewmatrices,eachtimeattachingadifferentcubemapfacetotheframebufferobject.Thiswouldlooksomethinglikethis:
for(unsignedinti=0;i<6;i++)
{
GLenumface=GL_TEXTURE_CUBE_MAP_POSITIVE_X+i;
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,face,depthCubemap,0);
BindViewMatrix(lightViewMatrices[i]);
RenderScene();
}
Thiscanbequiteexpensivethoughasalotofrendercallsarenecessaryforthissingledepthmap.Inthischapterwe'regoingtouseanalternative(moreorganized)approachusingalittletrickinthegeometryshaderthatallowsustobuildthedepthcubemapwithjustasinglerenderpass.
First,we'llneedtocreateacubemap:
unsignedintdepthCubemap;
glGenTextures(1,&depthCubemap);
Andassigneachofthesinglecubemapfacesa2Ddepth-valuedtextureimage:
constunsignedintSHADOW_WIDTH=1024,SHADOW_HEIGHT=1024;
glBindTexture(GL_TEXTURE_CUBE_MAP,depthCubemap);
for(unsignedinti=0;i<6;++i)
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i,0,GL_DEPTH_COMPONENT,
SHADOW_WIDTH,SHADOW_HEIGHT,0,GL_DEPTH_COMPONENT,GL_FLOAT,NULL);
Anddon'tforgettosetthetextureparameters:
glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP,GL_TEXTURE_WRAP_R,GL_CLAMP_TO_EDGE);
Normallywe'dattachasinglefaceofacubemaptexturetotheframebufferobjectandrenderthescene6times,eachtimeswitchingthedepthbuffertargetoftheframebuffertoadifferentcubemapface.Sincewe'regoingtouseageometryshader,thatallowsustorendertoallfacesinasinglepass,wecandirectlyattachthecubemapasaframebuffer'sdepthattachmentwithglFramebufferTexture:
glBindFramebuffer(GL_FRAMEBUFFER,depthMapFBO);
glFramebufferTexture(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,depthCubemap,0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER,0);
Again,notethecalltoglDrawBufferandglReadBuffer:weonlycareaboutdepthvalueswhengeneratingadepthcubemapsowehavetoexplicitlytellOpenGLthisframebufferobjectdoesnotrendertoacolorbuffer.
Withomnidirectionalshadowmapswehavetworenderpasses:first,wegeneratethedepthcubemapandsecond,weusethedepthcubemapinthenormalrenderpasstoaddshadowstothescene.Thisprocesslooksabitlikethis:
//1.firstrendertodepthcubemap
glViewport(0,0,SHADOW_WIDTH,SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER,depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
ConfigureShaderAndMatrices();
RenderScene();
glBindFramebuffer(GL_FRAMEBUFFER,0);
//2.thenrendersceneasnormalwithshadowmapping(usingdepthcubemap)
glViewport(0,0,SCR_WIDTH,SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
ConfigureShaderAndMatrices();
glBindTexture(GL_TEXTURE_CUBE_MAP,depthCubemap);
RenderScene();
Theprocessisexactlythesameaswithdefaultshadowmapping,althoughthistimewerendertoanduseacubemapdepthtexturecomparedtoa2Ddepthtexture.
Lightspacetransform
Withtheframebufferandcubemapset,weneedsomewaytotransformallthescene'sgeometrytotherelevantlightspacesinall6directionsofthelight.Justliketheshadowmappingchapterwe'regoingtoneedalightspacetransformationmatrix\(T\),butthistimeoneforeachface.
Eachlightspacetransformationmatrixcontainsbothaprojectionandaviewmatrix.Fortheprojectionmatrixwe'regoingtouseaperspectiveprojectionmatrix;thelightsourcerepresentsapointinspacesoperspectiveprojectionmakesmostsense.Eachlightspacetransformationmatrixusesthesameprojectionmatrix:
floataspect=(float)SHADOW_WIDTH/(float)SHADOW_HEIGHT;
floatnear=1.0f;
floatfar=25.0f;
glm::mat4shadowProj=glm::perspective(glm::radians(90.0f),aspect,near,far);
Importanttonotehereisthefieldofviewparameterofglm::perspectivethatwesetto90degrees.Bysettingthisto90degreeswemakesuretheviewingfieldisexactlylargeenoughtofillasinglefaceofthecubemapsuchthatallfacesaligncorrectlytoeachotherattheedges.
Astheprojectionmatrixdoesnotchangeperdirectionwecanre-useitforeachofthe6transformationmatrices.Wedoneedadifferentviewmatrixperdirection.Withglm::lookAtwecreate6viewdirections,eachlookingatonefacedirectionofthecubemapintheorder:right,left,top,bottom,nearandfar.
std::vector<:mat4>shadowTransforms;
shadowTransforms.push_back(shadowProj*
glm::lookAt(lightPos,lightPos+glm::vec3(1.0,0.0,0.0),glm::vec3(0.0,-1.0,0.0));
shadowTransforms.push_back(shadowProj*
glm::lookAt(lightPos,lightPos+glm::vec3(-1.0,0.0,0.0),glm::vec3(0.0,-1.0,0.0));
shadowTransforms.push_back(shadowProj*
glm::lookAt(lightPos,lightPos+glm::vec3(0.0,1.0,0.0),glm::vec3(0.0,0.0,1.0));
shadowTransforms.push_back(shadowProj*
glm::lookAt(lightPos,lightPos+glm::vec3(0.0,-1.0,0.0),glm::vec3(0.0,0.0,-1.0));
shadowTransforms.push_back(shadowProj*
glm::lookAt(lightPos,lightPos+glm::vec3(0.0,0.0,1.0),glm::vec3(0.0,-1.0,0.0));
shadowTransforms.push_back(shadowProj*
glm::lookAt(lightPos,lightPos+glm::vec3(0.0,0.0,-1.0),glm::vec3(0.0,-1.0,0.0));
Herewecreate6viewmatricesandmultiplythemwiththeprojectionmatrixtogetatotalof6differentlightspacetransformationmatrices.Thetargetparameterofglm::lookAteachlooksintothedirectionofasinglecubemapface.
Thesetransformationmatricesaresenttotheshadersthatrenderthedepthintothecubemap.
Depthshaders
Torenderdepthvaluestoadepthcubemapwe'regoingtoneedatotalofthreeshaders:avertexandfragmentshader,andageometryshaderinbetween.
Thegeometryshaderwillbetheshaderresponsiblefortransformingallworld-spaceverticestothe6differentlightspaces.Therefore,thevertexshadersimplytransformsverticestoworld-spaceanddirectsthemtothegeometryshader:
#version330core
layout(location=0)invec3aPos;
uniformmat4model;
voidmain()
{
gl_Position=model*vec4(aPos,1.0);
}
Thegeometryshaderwilltakeasinput3triangleverticesandauniformarrayoflightspacetransformationmatrices.Thegeometryshaderisresponsiblefortransformingtheverticestothelightspaces;thisisalsowhereitgetsinteresting.
Thegeometryshaderhasabuilt-invariablecalledgl_Layerthatspecifieswhichcubemapfacetoemitaprimitiveto.Whenleftalone,thegeometryshaderjustsendsitsprimitivesfurtherdownthepipelineasusual,butwhenweupdatethisvariablewecancontroltowhichcubemapfacewerendertoforeachprimitive.Thisofcourseonlyworkswhenwehaveacubemaptextureattachedtotheactiveframebuffer.
#version330core
layout(triangles)in;
layout(triangle_strip,max_vertices=18)out;
uniformmat4shadowMatrices[6];
outvec4FragPos;//FragPosfromGS(outputperemitvertex)
voidmain()
{
for(intface=0;face<6;++face)
{
gl_Layer=face;//built-invariablethatspecifiestowhichfacewerender.
for(inti=0;i<3;++i)//foreachtrianglevertex
{
FragPos=gl_in[i].gl_Position;
gl_Position=shadowMatrices[face]*FragPos;
EmitVertex();
}
EndPrimitive();
}
}
Thisgeometryshaderisrelativelystraightforward.Wetakeasinputatriangle,andoutputatotalof6triangles(6*3equals18vertices).Inthemainfunctionweiterateover6cubemapfaceswherewespecifyeachfaceastheoutputfacebystoringthefaceintegerintogl_Layer.Wethengeneratetheoutputtrianglesbytransformingeachworld-spaceinputvertextotherelevantlightspacebymultiplyingFragPoswiththeface'slight-spacetransformationmatrix.NotethatwealsosenttheresultingFragPosvariabletothefragmentshaderthatwe'llneedtocalculateadepthvalue.
InthelastchapterweusedanemptyfragmentshaderandletOpenGLfigureoutthedepthvaluesofthedepthmap.Thistimewe'regoingtocalculateourown(linear)depthasthelineardistancebetweeneachclosestfragmentpositionandthelightsource'sposition.Calculatingourowndepthvaluesmakesthelatershadowcalculationsabitmoreintuitive.
#version330core
invec4FragPos;
uniformvec3lightPos;
uniformfloatfar_plane;
voidmain()
{
//getdistancebetweenfragmentandlightsource
floatlightDistance=length(FragPos.xyz-lightPos);
//mapto[0;1]rangebydividingbyfar_plane
lightDistance=lightDistance/far_plane;
//writethisasmodifieddepth
gl_FragDepth=lightDistance;
}
ThefragmentshadertakesasinputtheFragPosfromthegeometryshader,thelight'spositionvector,andthefrustum'sfarplanevalue.Herewetakethedistancebetweenthefragmentandthelightsource,mapittothe[0,1]rangeandwriteitasthefragment'sdepthvalue.
Renderingthescenewiththeseshadersandthecubemap-attachedframebufferobjectactiveshouldgiveyouacompletelyfilleddepthcubemapforthesecondpass'sshadowcalculations.
Omnidirectionalshadowmaps
Witheverythingsetupitistimetorendertheactualomnidirectionalshadows.Theprocedureissimilartothedirectionalshadowmappingchapter,althoughthistimewebindacubemaptextureinsteadofa2Dtextureandalsopassthelightprojection'sfarplanevariabletotheshaders.
glViewport(0,0,SCR_WIDTH,SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
shader.use();
//...senduniformstoshader(includinglight'sfar_planevalue)
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP,depthCubemap);
//...bindothertextures
RenderScene();
HeretherenderScenefunctionrendersafewcubesinalargecuberoomscatteredaroundalightsourceatthecenterofthescene.
Thevertexandfragmentshaderaremostlysimilartotheoriginalshadowmappingshaders:thedifferencebeingthatthefragmentshadernolongerrequiresafragmentpositioninlightspaceaswecannowsamplethedepthvalueswithadirectionvector.
Becauseofthis,thevertexshaderdoesn'tneedstotransformitspositionvectorstolightspacesowecanremovetheFragPosLightSpacevariable:
#version330core
layout(location=0)invec3aPos;
layout(location=1)invec3aNormal;
layout(location=2)invec2aTexCoords;
outvec2TexCoords;
outVS_OUT{
vec3FragPos;
vec3Normal;
vec2TexCoords;
}vs_out;
uniformmat4projection;
uniformmat4view;
uniformmat4model;
voidmain()
{
vs_out.FragPos=vec3(model*vec4(aPos,1.0));
vs_out.Normal=transpose(inverse(mat3(model)))*aNormal;
vs_out.TexCoords=aTexCoords;
gl_Position=projection*view*model*vec4(aPos,1.0);
}
Thefragmentshader'sBlinn-Phonglightingcodeisexactlythesameaswehadbeforewithashadowmultiplicationattheend:
#version330core
outvec4FragColor;
inVS_OUT{
vec3FragPos;
vec3Normal;
vec2TexCoords;
}fs_in;
uniformsampler2DdiffuseTexture;
uniformsamplerCubedepthMap;
uniformvec3lightPos;
uniformvec3viewPos;
uniformfloatfar_plane;
floatShadowCalculation(vec3fragPos)
{
[...]
}
voidmain()
{
vec3color=texture(diffuseTexture,fs_in.TexCoords).rgb;
vec3normal=normalize(fs_in.Normal);
vec3lightColor=vec3(0.3);
//ambient
vec3ambient=0.3*color;
//diffuse
vec3lightDir=normalize(lightPos-fs_in.FragPos);
floatdiff=max(dot(lightDir,normal),0.0);
vec3diffuse=diff*lightColor;
//specular
vec3viewDir=normalize(viewPos-fs_in.FragPos);
vec3reflectDir=reflect(-lightDir,normal);
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.FragPos);
vec3lighting=(ambient+(1.0-shadow)*(diffuse+specular))*color;
FragColor=vec4(lighting,1.0);
}
Thereareafewsubtledifferences:thelightingcodeisthesame,butwenowhaveasamplerCubeuniformandtheShadowCalculationfunctiontakesthecurrentfragment'spositionasitsargumentinsteadofthefragmentpositioninlightspace.Wenowalsoincludethelightfrustum'sfar_planevaluethatwe'lllaterneed.
ThebiggestdifferenceisinthecontentoftheShadowCalculationfunctionthatnowsamplesdepthvaluesfromacubemapinsteadofa2Dtexture.Let'sdiscussitscontentstepbystep.
Thefirstthingwehavetodoisretrievethedepthofthecubemap.Youmayrememberfromthecubemapsectionofthischapterthatwestoredthedepthasthelineardistancebetweenthefragmentandthelightposition;we'retakingasimilarapproachhere:
floatShadowCalculation(vec3fragPos)
{
vec3fragToLight=fragPos-lightPos;
floatclosestDepth=texture(depthMap,fragToLight).r;
}
Herewetakethedifferencevectorbetweenthefragment'spositionandthelight'spositionandusethatvectorasadirectionvectortosamplethecubemap.Thedirectionvectordoesn'tneedtobeaunitvectortosamplefromacubemapsothere'snoneedtonormalizeit.TheresultingclosestDepthvalueisthenormalizeddepthvaluebetweenthelightsourceanditsclosestvisiblefragment.
TheclosestDepthvalueiscurrentlyintherange[0,1]sowefirsttransformitbackto[0,far_plane]bymultiplyingitwithfar_plane.
closestDepth*=far_plane;
Nextweretrievethedepthvaluebetweenthecurrentfragmentandthelightsource,whichwecaneasilyobtainbytakingthelengthoffragToLightduetohowwecalculateddepthvaluesinthecubemap:
floatcurrentDepth=length(fragToLight);
Thisreturnsadepthvalueinthesame(orlarger)rangeasclosestDepth.
Nowwecancomparebothdepthvaluestoseewhichiscloserthantheotheranddeterminewhetherthecurrentfragmentisinshadow.Wealsoincludeashadowbiassowedon'tgetshadowacneasdiscussedinthepreviouschapter.
floatbias=0.05;
floatshadow=currentDepth-bias>closestDepth?1.0:0.0;
ThecompleteShadowCalculationthenbecomes:
floatShadowCalculation(vec3fragPos)
{
//getvectorbetweenfragmentpositionandlightposition
vec3fragToLight=fragPos-lightPos;
//usethelighttofragmentvectortosamplefromthedepthmap
floatclosestDepth=texture(depthMap,fragToLight).r;
//itiscurrentlyinlinearrangebetween[0,1].Re-transformbacktooriginalvalue
closestDepth*=far_plane;
//nowgetcurrentlineardepthasthelengthbetweenthefragmentandlightposition
floatcurrentDepth=length(fragToLight);
//nowtestforshadows
floatbias=0.05;
floatshadow=currentDepth-bias>closestDepth?1.0:0.0;
returnshadow;
}
Withtheseshaderswealreadygetprettygoodshadowsandthistimeinallsurroundingdirectionsfromapointlight.Withapointlightpositionedatthecenterofasimplesceneit'lllookabitlikethis:
Youcanfindthesourcecodeofthisdemohere.
Visualizingcubemapdepthbuffer
Ifyou'resomewhatlikemeyouprobablydidn'tgetthisrightonthefirsttrysoitmakessensetodosomedebugging,withoneoftheobviouschecksbeingvalidatingwhetherthedepthmapwasbuiltcorrectly.AsimpletricktovisualizethedepthbufferistotaketheclosestDepthvariableintheShadowCalculationfunctionanddisplaythatvariableas:
FragColor=vec4(vec3(closestDepth/far_plane),1.0);
Theresultisagrayedoutscenewhereeachcolorrepresentsthelineardepthvaluesofthescene:
Youcanalsoseetheto-beshadowedregionsontheoutsidewall.Ifitlookssomewhatsimilar,youknowthedepthcubemapwasproperlygenerated.
PCF
Sinceomnidirectionalshadowmapsarebasedonthesameprinciplesoftraditionalshadowmappingitalsohasthesameresolutiondependentartifacts.Ifyouzoomincloseenoughyoucanagainseejaggededges.Percentage-closerfilteringorPCFallowsustosmoothoutthesejaggededgesbyfilteringmultiplesamplesaroundthefragmentpositionandaveragetheresults.
IfwetakethesamesimplePCFfilterofthepreviouschapterandaddathirddimensionweget:
floatshadow=0.0;
floatbias=0.05;
floatsamples=4.0;
floatoffset=0.1;
for(floatx=-offset;x
延伸文章資訊
- 1Example: Setting the Shadow Quality and Bias - PTC Support
- 2OpenGL 3D Game Tutorial 38: Shadow Mapping (1/2) - YouTube
- 3Tutorial 16 : Shadow mapping
- 4Is 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...
- 5Shadow Mapping with Today's OpenGL Hardware - CiteSeerX
determining the shadow volume is hard work. • Light maps ... For example, the shadow map texture'...