Instancing is a technique where we draw many (equal mesh data) objects at once with a single render call, saving us all the CPU -> GPU communications each time ...
Ifyou'rerunningAdBlock,pleaseconsiderwhitelistingthissiteifyou'dliketosupportLearnOpenGL;andnoworries,Iwon'tbemadifyoudon't:)
IntroductionGettingstartedOpenGLCreatingawindowHelloWindowHelloTriangleShadersTexturesTransformationsCoordinateSystemsCameraReviewLightingColorsBasicLightingMaterialsLightingmapsLightcastersMultiplelightsReviewModelLoadingAssimpMeshModelAdvancedOpenGLDepthtestingStenciltestingBlendingFacecullingFramebuffersCubemapsAdvancedDataAdvancedGLSLGeometryShaderInstancingAntiAliasingAdvancedLightingAdvancedLightingGammaCorrectionShadowsShadowMappingPointShadowsNormalMappingParallaxMappingHDRBloomDeferredShadingSSAOPBRTheoryLightingIBLDiffuseirradianceSpecularIBLInPracticeDebuggingTextRendering2DGameBreakoutSettingupRenderingSpritesLevelsCollisionsBallCollisiondetectionCollisionresolutionParticlesPostprocessingPowerupsAudioRendertextFinalthoughtsGuestArticlesHowtopublish2020OITIntroductionWeightedBlendedSkeletalAnimation2021CSMSceneSceneGraphFrustumCullingTessellationHeightmapTessellationDSACoderepositoryTranslationsAbout
BTC
1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa
ETH/ERC20
0x1de59bd9e52521a46309474f8372531533bd7c43
Instancing
Advanced-OpenGL/Instancing
Sayyouhaveascenewhereyou'redrawingalotofmodelswheremostofthesemodelscontainthesamesetofvertexdata,butwithdifferentworldtransformations.Thinkofascenefilledwithgrassleaves:eachgrassleaveisasmallmodelthatconsistsofonlyafewtriangles.You'llprobablywanttodrawquiteafewofthemandyourscenemayendupwiththousandsormaybetensofthousandsofgrassleavesthatyouneedtorendereachframe.Becauseeachleafisonlyafewtriangles,theleafisrenderedalmostinstantly.However,thethousandsofrendercallsyou'llhavetomakewilldrasticallyreduceperformance.
Ifweweretoactuallyrendersuchalargeamountofobjectsitwilllookabitlikethisincode:
for(unsignedinti=0;iGPUcommunicationseachtimeweneedtorenderanobject.TorenderusinginstancingallweneedtodoischangetherendercallsglDrawArraysandglDrawElementstoglDrawArraysInstancedandglDrawElementsInstancedrespectively.Theseinstancedversionsoftheclassicrenderingfunctionstakeanextraparametercalledtheinstancecountthatsetsthenumberofinstanceswewanttorender.WesentalltherequireddatatotheGPUonce,andthentelltheGPUhowitshoulddrawalltheseinstanceswithasinglecall.TheGPUthenrendersalltheseinstanceswithouthavingtocontinuallycommunicatewiththeCPU.
Byitselfthisfunctionisabituseless.Renderingthesameobjectathousandtimesisofnousetoussinceeachoftherenderedobjectsisrenderedexactlythesameandthusalsoatthesamelocation;wewouldonlyseeoneobject!ForthisreasonGLSLaddedanotherbuilt-invariableinthevertexshadercalledgl_InstanceID.
Whendrawingwithoneoftheinstancedrenderingcalls,gl_InstanceIDisincrementedforeachinstancebeingrenderedstartingfrom0.Ifweweretorenderthe43thinstanceforexample,gl_InstanceIDwouldhavethevalue42inthevertexshader.Havingauniquevalueperinstancemeanswecouldnowforexampleindexintoalargearrayofpositionvaluestopositioneachinstanceatadifferentlocationintheworld.
Togetafeelforinstanceddrawingwe'regoingtodemonstrateasimpleexamplethatrendersahundred2Dquadsinnormalizeddevicecoordinateswithjustonerendercall.Weaccomplishthisbyuniquelypositioningeachinstancedquadbyindexingauniformarrayof100offsetvectors.Theresultisaneatlyorganizedgridofquadsthatfilltheentirewindow:
Eachquadconsistsof2triangleswithatotalof6vertices.Eachvertexcontainsa2DNDCpositionvectorandacolorvector.Belowisthevertexdatausedforthisexample-thetrianglesaresmallenoughtoproperlyfitthescreenwhenthere'sa100ofthem:
floatquadVertices[]={
//positions//colors
-0.05f,0.05f,1.0f,0.0f,0.0f,
0.05f,-0.05f,0.0f,1.0f,0.0f,
-0.05f,-0.05f,0.0f,0.0f,1.0f,
-0.05f,0.05f,1.0f,0.0f,0.0f,
0.05f,-0.05f,0.0f,1.0f,0.0f,
0.05f,0.05f,0.0f,1.0f,1.0f
};
Thequadsarecoloredinthefragmentshaderthatreceivesacolorvectorfromthevertexshaderandsetsitasitsoutput:
#version330core
outvec4FragColor;
invec3fColor;
voidmain()
{
FragColor=vec4(fColor,1.0);
}
Nothingnewsofar,butatthevertexshaderit'sstartingtogetinteresting:
#version330core
layout(location=0)invec2aPos;
layout(location=1)invec3aColor;
outvec3fColor;
uniformvec2offsets[100];
voidmain()
{
vec2offset=offsets[gl_InstanceID];
gl_Position=vec4(aPos+offset,0.0,1.0);
fColor=aColor;
}
Herewedefinedauniformarraycalledoffsetsthatcontainatotalof100offsetvectors.Withinthevertexshaderweretrieveanoffsetvectorforeachinstancebyindexingtheoffsetsarrayusinggl_InstanceID.Ifwenowweretodraw100quadswithinstanceddrawingwe'dget100quadslocatedatdifferentpositions.
Wedoneedtoactuallysettheoffsetpositionsthatwecalculateinanestedfor-loopbeforeweentertherenderloop:
glm::vec2translations[100];
intindex=0;
floatoffset=0.1f;
for(inty=-10;y<10;y+=2)
{
for(intx=-10;x<10;x+=2)
{
glm::vec2translation;
translation.x=(float)x/10.0f+offset;
translation.y=(float)y/10.0f+offset;
translations[index++]=translation;
}
}
Herewecreateasetof100translationvectorsthatcontainsanoffsetvectorforallpositionsina10x10grid.Inadditiontogeneratingthetranslationsarray,we'dalsoneedtotransferthedatatothevertexshader'suniformarray:
shader.use();
for(unsignedinti=0;i<100;i++)
{
shader.setVec2(("offsets["+std::to_string(i)+"]")),translations[i]);
}
Withinthissnippetofcodewetransformthefor-loopcounteritoastringtodynamicallycreatealocationstringforqueryingtheuniformlocation.Foreachitemintheoffsetsuniformarraywethensetthecorrespondingtranslationvector.
Nowthatallthepreparationsarefinishedwecanstartrenderingthequads.TodrawviainstancedrenderingwecallglDrawArraysInstancedorglDrawElementsInstanced.Sincewe'renotusinganelementindexbufferwe'regoingtocalltheglDrawArraysversion:
glBindVertexArray(quadVAO);
glDrawArraysInstanced(GL_TRIANGLES,0,6,100);
TheparametersofglDrawArraysInstancedareexactlythesameasglDrawArraysexceptthelastparameterthatsetsthenumberofinstanceswewanttodraw.Sincewewanttodisplay100quadsina10x10gridwesetitequalto100.Runningthecodeshouldnowgiveyouthefamiliarimageof100colorfulquads.
Instancedarrays
Whilethepreviousimplementationworksfineforthisspecificusecase,wheneverwearerenderingalotmorethan100instances(whichisquitecommon)wewilleventuallyhitalimitontheamountofuniformdatawecansendtotheshaders.Onealternativeoptionisknownasinstancedarrays.Instancedarraysaredefinedasavertexattribute(allowingustostoremuchmoredata)thatareupdatedperinstanceinsteadofpervertex.
Withvertexattributes,atthestartofeachrunofthevertexshader,theGPUwillretrievethenextsetofvertexattributesthatbelongtothecurrentvertex.Whendefiningavertexattributeasaninstancedarrayhowever,thevertexshaderonlyupdatesthecontentofthevertexattributeperinstance.Thisallowsustousethestandardvertexattributesfordatapervertexandusetheinstancedarrayforstoringdatathatisuniqueperinstance.
Togiveyouanexampleofaninstancedarraywe'regoingtotakethepreviousexampleandconverttheoffsetuniformarraytoaninstancedarray.We'llhavetoupdatethevertexshaderbyaddinganothervertexattribute:
#version330core
layout(location=0)invec2aPos;
layout(location=1)invec3aColor;
layout(location=2)invec2aOffset;
outvec3fColor;
voidmain()
{
gl_Position=vec4(aPos+aOffset,0.0,1.0);
fColor=aColor;
}
Wenolongerusegl_InstanceIDandcandirectlyusetheoffsetattributewithoutfirstindexingintoalargeuniformarray.
Becauseaninstancedarrayisavertexattribute,justlikethepositionandcolorvariables,weneedtostoreitscontentinavertexbufferobjectandconfigureitsattributepointer.We'refirstgoingtostorethetranslationsarray(fromtheprevioussection)inanewbufferobject:
unsignedintinstanceVBO;
glGenBuffers(1,&instanceVBO);
glBindBuffer(GL_ARRAY_BUFFER,instanceVBO);
glBufferData(GL_ARRAY_BUFFER,sizeof(glm::vec2)*100,&translations[0],GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER,0);
Thenwealsoneedtosetitsvertexattributepointerandenablethevertexattribute:
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER,instanceVBO);
glVertexAttribPointer(2,2,GL_FLOAT,GL_FALSE,2*sizeof(float),(void*)0);
glBindBuffer(GL_ARRAY_BUFFER,0);
glVertexAttribDivisor(2,1);
WhatmakesthiscodeinterestingisthelastlinewherewecallglVertexAttribDivisor.ThisfunctiontellsOpenGLwhentoupdatethecontentofavertexattributetothenextelement.Itsfirstparameteristhevertexattributeinquestionandthesecondparametertheattributedivisor.Bydefault,theattributedivisoris0whichtellsOpenGLtoupdatethecontentofthevertexattributeeachiterationofthevertexshader.Bysettingthisattributeto1we'retellingOpenGLthatwewanttoupdatethecontentofthevertexattributewhenwestarttorenderanewinstance.Bysettingitto2we'dupdatethecontentevery2instancesandsoon.Bysettingtheattributedivisorto1we'reeffectivelytellingOpenGLthatthevertexattributeatattributelocation2isaninstancedarray.
IfwenowweretorenderthequadsagainwithglDrawArraysInstancedwe'dgetthefollowingoutput:
Thisisexactlythesameasthepreviousexample,butnowwithinstancedarrays,whichallowsustopassalotmoredata(asmuchasmemoryallowsus)tothevertexshaderforinstanceddrawing.
Forfunwecouldslowlydownscaleeachquadfromtop-righttobottom-leftusinggl_InstanceIDagain,becausewhynot?
voidmain()
{
vec2pos=aPos*(gl_InstanceID/100.0);
gl_Position=vec4(pos+aOffset,0.0,1.0);
fColor=aColor;
}
Theresultisthatthefirstinstancesofthequadsaredrawnextremelysmallandthefurtherwe'reintheprocessofdrawingtheinstances,theclosergl_InstanceIDgetsto100andthusthemorethequadsregaintheiroriginalsize.It'sperfectlylegaltouseinstancedarraystogetherwithgl_InstanceIDlikethis.
Ifyou'restillabitunsureabouthowinstancedrenderingworksorwanttoseehoweverythingfitstogetheryoucanfindthefullsourcecodeoftheapplicationhere.
Whilefunandall,theseexamplesaren'treallygoodexamplesofinstancing.Yes,theydogiveyouaneasyoverviewofhowinstancingworks,butinstancinggetsmostofitspowerwhendrawinganenormousamountofsimilarobjects.Forthatreasonwe'regoingtoventureintospace.
Anasteroidfield
Imagineascenewherewehaveonelargeplanetthat'satthecenterofalargeasteroidring.Suchanasteroidringcouldcontainthousandsortensofthousandsofrockformationsandquicklybecomesun-renderableonanydecentgraphicscard.Thisscenarioprovesitselfparticularlyusefulforinstancedrendering,sincealltheasteroidscanberepresentedwithasinglemodel.Eachsingleasteroidthengetsitsvariationfromatransformationmatrixuniquetoeachasteroid.
Todemonstratetheimpactofinstancedrenderingwe'refirstgoingtorenderasceneofasteroidshoveringaroundaplanetwithoutinstancedrendering.Thescenewillcontainalargeplanetmodelthatcanbedownloadedfromhereandalargesetofasteroidrocksthatweproperlypositionaroundtheplanet.Theasteroidrockmodelcanbedownloadedhere.
Withinthecodesamplesweloadthemodelsusingthemodelloaderwe'vepreviouslydefinedinthemodelloadingchapters.
Toachievetheeffectwe'relookingforwe'llbegeneratingamodeltransformationmatrixforeachasteroid.Thetransformationmatrixfirsttranslatestherocksomewhereintheasteroidring-thenwe'lladdasmallrandomdisplacementvaluetotheoffsettomaketheringlookmorenatural.Fromtherewealsoapplyarandomscaleandarandomrotation.Theresultisatransformationmatrixthattranslateseachasteroidsomewherearoundtheplanetwhilealsogivingitamorenaturalanduniquelookcomparedtotheotherasteroids.
unsignedintamount=1000;
glm::mat4*modelMatrices;
modelMatrices=newglm::mat4[amount];
srand(glfwGetTime());//initializerandomseed
floatradius=50.0;
floatoffset=2.5f;
for(unsignedinti=0;i