344

Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

  • Upload
    others

  • View
    0

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some
Page 2: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

1.1

1.2

1.3

1.4

1.5

1.6

1.7

1.8

1.9

1.10

1.11

1.12

1.13

1.14

1.15

1.16

1.17

1.18

1.19

1.20

1.21

1.22

1.23

1.24

1.25

1.26

1.27

1.28

TableofContentsIntroduction

Firststeps

TheGameLoop

Abriefaboutcoordinates

Rendering

MoreonRendering

Transformations

Textures

Camera

Loadingmorecomplexmodels

Lettherebelight

Lettherebeevenmorelight

HUD

SkyBoxandsomeoptimizations

HeightMaps

TerrainCollisions

Fog

NormalMapping

Shadows

Animations

Particles

InstancedRendering

Audio

3DObjectpicking

Hudrevisited-NanoVG

Optimizations

CascadedShadowMaps

Assimp

2

Page 3: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

3

Page 4: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

3DGameDevelopmentwithLWJGL3Thisonlinebookwillintroducethemainconceptsrequiredtowritea3DgameusingtheLWJGL3library.

LWJGLisaJavalibrarythatprovidesaccesstonativeAPIsusedinthedevelopmentofgraphics(OpenGL),audio(OpenAL)andparallelcomputing(OpenCL)applications.ThislibraryleveragesthehighperformanceofnativeOpenGLapplicationswhileusingtheJavalanguage.

Myinitialgoalwastolearnthetechniquesinvolvedinwritinga3DgameusingOpenGL.Alltheinformationrequiredwasthereintheinternetbutitwasnotorganizedandsometimesitwasveryhardtofindandevenincompleteormisleading.

Istartedtocollectsomematerials,developsomeexamplesanddecidedtoorganizethatinformationintheformofabook.

SourceCodeThesourcecodeofthesamplesofthisbookareinGitHub.

ThesourcecodeforthebookitselfisalsopublishedinGitHub.

LicenseThebookislicensedunderAttribution-ShareAlike4.0International(CCBY-SA4.0)

ThesourcecodeforthebookislicensedunderApachev2.0

SupportIfyoulikethebookpleaserateitwithastartandshareit.Ifyouwanttocontributewithadonationyoucandoadonation:

OrifyoupreferBitcoin:1Kwe78faWarzGTsWXtdGvjjbS9RmW1j3nb.

Commentsarewelcome

Introduction

4

Page 5: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Suggestionsandcorrectionsaremorethanwelcome(andifyoudolikeitpleaserateitwithastar).Pleasesendthemusingthediscussionforumandmakethecorrectionsyouconsiderinordertoimprovethebook.

AuthorAntonioHernándezBejarano

SpecialThanksToallthereadersthathavecontributedwithcorrections,improvementsandideas.

Introduction

5

Page 6: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

FirststepsInthisbookwewilllearntheprincipaltechniquesinvolvedindeveloping3Dgames.WewilldevelopoursamplesinJavaandwewillusetheLightweightJavaGameLibrary(LWJGL).TheLWJGLlibraryenablestheaccesstolow-levelAPIs(ApplicationProgrammingInterface)suchasOpenGL.

LWJGLisalowlevelAPIthatactslikeawrapperaroundOpenGL.Ifyourideaistostartcreating3Dgamesinashortperiodoftimemaybeyoushouldconsiderotheralternativeslike[JmonkeyEngine].ByusingthislowlevelAPIyouwillhavetogothroughmanyconceptsandwritelotsoflinesofcodebeforeyouseetheresults.Thebenefitofdoingitthiswayisthatyouwillgetamuchbetterunderstandingof3Dgraphicsandalsoyoucangetbettercontrol.

AssaidinthepreviousparagraphswewillbeusingJavaforthisbook.WewillbeusingJava8,soyouneedtodownloadtheJavaSDKfromOracle’spages.JustchoosetheinstallerthatsuitsyourOperatingSystemandinstallit.ThisbookassumesthatyouhaveamoderateunderstandingoftheJavalanguage.

ThesourcecodethataccompaniesthisbookhasbeendevelopedusingtheNetbeansIDE.YoucandownloadthelatestversionofthatIDEfromhttps://netbeans.org/.InordertoexecuteNetbeansyouonlyneedtheJavaSEversionbutremembertodownloadtheversionthatcorrespondstoyourJDKversion(32bitsor64bits).

ForbuildingoursampleswewillbeusingMaven.MavenisalreadyintegratedinNetbeansandyoucandirectlyopenthedifferentsamplesfromNetbeans.JustopenthefolderthatcontainsthechaptersampleandNetbeanswilldetectthatitisamavenproject.

Firststeps

6

Page 7: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

MavenbuildsprojectsbasedonanXMLfilenamedpom.xml(ProjectObjectModel)whichmanagesprojectdependencies(thelibrariesyouneedtouse)andthestepstobeperformedduringthebuildprocess.Mavenfollowstheprincipleofconventionoverconfiguration,thatis,ifyousticktothestandardprojectstructureandnamingconventionstheconfigurationfiledoesnotneedtoexplicitlysaywheresourcefilesareorwherecompiledclassesshouldbelocated.

Thisbookdoesnotintendtobeamaventutorial,sopleasefindtheinformationaboutitinthewebincaseyouneedit.Thesourcecodefolderdefinesaparentprojectwhichdefinesthepluginstobeusedandcollectstheversionsofthelibrariesemployed.

LWJGL3.1introducedsomechangesinthewaythattheprojectisbuilt.Nowthebasecodeismuchmoremodular,andwecanbemoreselectiveinthepackagesthatwewanttouseinsteadofusingagiantmonolithicjarfile.Thiscomesatacost:Younowneedtocarefullyspecifythedependenciesonebyone.Butthedownloadpageincludesafancyscriptthatgeneratesthepomfileforyou.Inourcase,wewilljustbeusingGLFWandOpenGLbindings.Youcancheckwhatthepomfilelookslikeinthesourcecode.

TheLWJGLplatformdependencyalreadytakescareofunpackingnativelibrariesforyourplatform,sothere'snoneedtouseotherplugins(suchasmavennatives).WejustneedtosetupthreeprofilestosetapropertythatwillconfiguretheLWJGLplatform.TheprofileswillsetupthecorrectvaluesofthatpropertyforWindows,LinuxandMacOSfamilies.

Firststeps

7

Page 8: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

<profiles>

<profile>

<id>windows-profile</id>

<activation>

<os>

<family>Windows</family>

</os>

</activation>

<properties>

<native.target>natives-windows</native.target>

</properties>

</profile>

<profile>

<id>linux-profile</id>

<activation>

<os>

<family>Linux</family>

</os>

</activation>

<properties>

<native.target>natives-linux</native.target>

</properties>

</profile>

<profile>

<id>OSX-profile</id>

<activation>

<os>

<family>mac</family>

</os>

</activation>

<properties>

<native.target>natives-osx</native.target>

</properties>

</profile>

</profiles>

Insideeachproject,theLWJGLplatformdependencywillusethecorrectpropertyestablishedintheprofileforthecurrentplatform.

<dependency>

<groupId>org.lwjgl</groupId>

<artifactId>lwjgl-platform</artifactId>

<version>${lwjgl.version}</version>

<classifier>${native.target}</classifier>

</dependency>

Besidesthat,everyprojectgeneratesarunnablejar(onethatcanbeexecutedbytypingjava-jarname_of_the_jar.jar).Thisisachievedbyusingthemaven-jar-pluginwhichcreatesajarwithaMANIFEST.MFfilewiththecorrectvalues.Themostimportantattributeforthatfile

Firststeps

8

Page 9: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

isMain-Class,whichsetstheentrypointfortheprogram.Inaddition,allthedependenciesaresetasentriesintheClass-Pathattributeforthatfile.Inordertoexecuteitonanothercomputer,youjustneedtocopythemainjarfileandthelibdirectory(withallthejarsincludedthere)whicharelocatedunderthetargetdirectory.

ThejarsthatcontainLWJGLclasses,alsocontainthenativelibraries.LWJGLwillalsotakecareofextractingthemandaddingthemtothepathwheretheJVMwilllokforlibraries.

Chapter1sourcecodeistakendirectlyfromthegettingstartedsampleintheLWJGLsite(http://www.lwjgl.org/guide).YouwillseethatwearenotusingSwingorJavaFXasourGUIlibrary.InsteadofthatweareusingGLFWwhichisalibrarytohandleGUIcomponents(Windows,etc.)andevents(keypresses,mousemovements,etc.)withanOpenGLcontextattachedinastraightforwardway.PreviousversionsofLWJGLprovidedacustomGUIAPIbut,forLWJGL3,GLFWisthepreferredwindowingAPI.

Thesamplessourcecodeisverywelldocumentedandstraightforwardsowewon’trepeatthecommentshere.

Ifyouhaveyourenvironmentcorrectlysetupyoushouldbeabletoexecuteitandseeawindowwitharedbackground.

ThesourcecodeofthisbookispublishedinGitHub.

Firststeps

9

Page 10: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

TheGameLoopInthischapterwewillstartdevelopingourgameenginebycreatingourgameloop.Thegameloopisthecorecomponentofeverygame.Itisbasicallyanendlessloopwhichisresponsibleforperiodicallyhandlinguserinput,updatinggamestateandrenderingtothescreen.

Thefollowingsnippetshowsthestructureofagameloop:

while(keepOnRunning){

handleInput();

updateGameState();

render();

}

So,isthatall?Arewefinishedwithgameloops?Well,notyet.Theabovesnippethasmanypitfalls.Firstofallthespeedthatthegamelooprunsatwillbedifferentdependingonthemachineitrunson.Ifthemachineisfastenoughtheuserwillnotevenbeabletoseewhatishappeninginthegame.Moreover,thatgameloopwillconsumeallthemachineresources.

Thus,weneedthegamelooptotryrunningataconstantrateindependentlyofthemachineitrunson.Letussupposethatwewantourgametorunataconstantrateof50FramesPerSecond(FPS).Ourgameloopcouldbesomethinglikethis:

doublesecsPerFrame=1.0d/50.0d;

while(keepOnRunning){

doublenow=getTime();

handleInput();

updateGameState();

render();

sleep(now+secsPerFrame–getTime());

}

Thisgameloopissimpleandcouldbeusedforsomegamesbutitalsopresentssomeproblems.Firstofall,itassumesthatourupdateandrendermethodsfitintheavailabletimewehaveinordertorenderataconstantrateof50FPS(thatis,secsPerFramewhichisequalto20ms.).

TheGameLoop

10

Page 11: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Besidesthat,ourcomputermaybeprioritizingothertasksthatpreventourgameloopfromexecutingforacertainperiodoftime.So,wemayendupupdatingourgamestateatveryvariabletimestepswhicharenotsuitableforgamephysics.

Finally,sleepaccuracymayrangetotenthofasecond,sowearenotevenupdatingataconstantframerateevenifourupdateandrendermethodstakenotime.So,asyouseetheproblemisnotsosimple.

OntheInternetyoucanfindtonsofvariantsforgameloops.Inthisbookwewilluseanottoocomplexapproachthatcanworkwellinmanysituations.Soletusmoveonandexplainthebasisforourgameloop.ThepatternusedhereisusuallycalledFixedStepGameLoop.

Firstofallwemaywanttocontrolseparatelytheperiodatwhichthegamestateisupdatedandtheperiodatwhichthegameisrenderedtothescreen.Whydowedothis?Well,updatingourgamestateataconstantrateismoreimportant,especiallyifweusesomephysicsengine.Onthecontrary,ifourrenderingisnotdoneintimeitmakesnosensetorenderoldframeswhileprocessingourgameloop.Wehavetheflexibilitytoskipsomeframes.

Letushavealookathowourgamelooplookslike:

doublesecsPerUpdate=1.0d/30.0d;

doubleprevious=getTime();

doublesteps=0.0;

while(true){

doubleloopStartTime=getTime();

doubleelapsed=loopStartTime-previous;

previous=current;

steps+=elapsed;

handleInput();

while(steps>=secsPerUpdate){

updateGameState();

steps-=secsPerUpdate;

}

render();

sync(current);

}

Withthisgameloopweupdateourgamestateatfixedsteps.Buthowdowecontrolthatwedonotexhaustthecomputer'sresourcesbyrenderingcontinuously?Thisisdoneinthesyncmethod:

TheGameLoop

11

Page 12: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatevoidsync(doubleloopStartTime){

floatloopSlot=1f/50;

doubleendTime=loopStartTime+loopSlot;

while(getTime()<endTime){

try{

Thread.sleep(1);

}catch(InterruptedExceptionie){}

}

}

Sowhatarewedoingintheabovemethod?Insummarywecalculatehowmanysecondsourgameloopiterationshouldlast(whichisstoredintheloopSlotvariable)andwewaitforthatamountoftimetakingintoconsiderationthetimewespentinourloop.Butinsteadofdoingasinglewaitforthewholeavailabletimeperiodwedosmallwaits.Thiswillallowothertaskstorunandwillavoidthesleepaccuracyproblemswementionedbefore.Then,whatwedois:

1. Calculatethetimeatwhichweshouldexitthiswaitmethodandstartanotheriterationofourgameloop(whichisthevariableendTime).

2. Comparethecurrenttimewiththatendtimeandwaitjustonemillisecondifwehavenotreachedthattimeyet.

NowitistimetostructureourcodebaseinordertostartwritingourfirstversionofourGameEngine.Butbeforedoingthatwewilltalkaboutanotherwayofcontrollingtherenderingrate.Inthecodepresentedabove,wearedoingmicro-sleepsinordertocontrolhowmuchtimeweneedtowait.Butwecanchooseanotherapproachinordertolimittheframerate.Wecanusev-sync(verticalsynchronization).Themainpurposeofv-syncistoavoidscreentearing.Whatisscreentearing?It’savisualeffectthatisproducedwhenweupdatethevideomemorywhileit’sbeingrendered.Theresultwillbethatpartoftheimagewillrepresentthepreviousimageandtheotherpartwillrepresenttheupdatedone.Ifweenablev-syncwewon’tsendanimagetotheGPUwhileitisbeingrenderedontothescreen.

Whenweenablev-syncwearesynchronizingtotherefreshrateofthevideocard,whichattheendwillresultinaconstantframerate.Thisisdonewiththefollowingline:

glfwSwapInterval(1);

Withthatlinewearespecifyingthatwemustwait,atleast,onescreenupdatebeforedrawingtothescreen.Infact,wearenotdirectlydrawingtothescreen.Weinsteadstoretheinformationtoabufferandweswapitwiththismethod:

glfwSwapBuffers(windowHandle);

TheGameLoop

12

Page 13: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

So,ifweenablev-syncweachieveaconstantframeratewithoutperformingthemicro-sleepstochecktheavailabletime.Besidesthat,theframeratewillmatchtherefreshrateofourgraphicscard.Thatis,ifit’ssetto60Hz(60timespersecond),wewillhave60FramesPerSecond.Wecanscaledownthatratebysettinganumberhigherthan1intheglfwSwapIntervalmethod(ifwesetitto2,wewouldget30FPS).

Let’sgetbacktoreorganizethesourcecode.FirstofallwewillencapsulatealltheGLFWWindowinitializationcodeinaclassnamedWindowallowingsomebasicparameterizationofitscharacteristics(suchastitleandsize).ThatWindowclasswillalsoprovideamethodtodetectkeypresseswhichwillbeusedinourgameloop:

publicbooleanisKeyPressed(intkeyCode){

returnglfwGetKey(windowHandle,keyCode)==GLFW_PRESS;

}

TheWindowclassbesidesprovidingtheinitializationcodealsoneedstobeawareofresizing.Soitneedstosetupacallbackthatwillbeinvokedwheneverthewindowisresized.Thecallbackwillreceivethewidthandheight,inpixels,oftheframebuffer(therenderingarea,inthissample,thedisplayarea).Ifyouwantthewidth,heightoftheframebufferinscreencoordinatesyoumayusethetheglfwSetWindowSizeCallbackmethod.Screencoordinatesdon'tnecessarillycorrespondtopixels(forinstance,onaMacwithRetinadisplay.SincewearegoingtousethatinformationwhenperformingsomeOpenGLcalls,weareinterestedinpixelsnotinscreencoordinates.YoucangetmoreinfomationintheGLFWdocumentation.

//Setupresizecallback

glfwSetFramebufferSizeCallback(windowHandle,(window,width,height)->{

Window.this.width=width;

Window.this.height=height;

Window.this.setResized(true);

});

WewillalsocreateaRendererclasswhichwillhandleourgamerenderlogic.Bynow,itwilljusthaveanemptyinitmethodandanothermethodtoclearthescreenwiththeconfiguredclearcolor:

publicvoidinit()throwsException{

}

publicvoidclear(){

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

}

TheGameLoop

13

Page 14: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

ThenwewillcreateaninterfacenamedIGameLogicwhichwillencapsulateourgamelogic.Bydoingthiswewillmakeourgameenginereusableacrossdifferenttitles.Thisinterfacewillhavemethodstogettheinput,toupdatethegamestateandtorendergame-specificdata.

publicinterfaceIGameLogic{

voidinit()throwsException;

voidinput(Windowwindow);

voidupdate(floatinterval);

voidrender(Windowwindow);

}

ThenwewillcreateaclassnamedGameEnginewhichwillcontainourgameloopcode.ThisclasswillimplementtheRunnableinterfacesincethegameloopwillberuninsideaseparatethread:

publicclassGameEngineimplementsRunnable{

//..[Removedcode]..

privatefinalThreadgameLoopThread;

publicGameEngine(StringwindowTitle,intwidth,intheight,booleanvsSync,IGame

LogicgameLogic)throwsException{

gameLoopThread=newThread(this,"GAME_LOOP_THREAD");

window=newWindow(windowTitle,width,height,vsSync);

this.gameLogic=gameLogic;

//..[Removedcode]..

}

ThevSyncparameterallowsustoselectifwewanttousev-syncornot.YoucanseewecreateanewThreadwhichwillexecutetherunmethodofourGameEngineclasswhichwillcontainourgameloop:

TheGameLoop

14

Page 15: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicvoidstart(){

gameLoopThread.start();

}

@Override

publicvoidrun(){

try{

init();

gameLoop();

}catch(Exceptionexcp){

excp.printStackTrace();

}

}

OurGameEngineclassprovidesastartmethodwhichjuststartsourThreadsotherunmethodwillbeexecutedasynchronously.Thatmethodwillperformtheinitializationtasksandwillrunthegameloopuntilourwindowisclosed.ItisveryimportanttoinitializeGLFWinsidethethreadthatisgoingtoupdateitlater.Thus,inthatinitmethodourWindowandRendererinstancesareinitialized.

InthesourcecodeyouwillseethatwecreatedotherauxiliaryclassessuchasTimer(whichwillprovideutilitymethodsforcalculatingelapsedtime)andwillbeusedbyourgamelooplogic.

OurGameEngineclassjustdelegatestheinputandupdatemethodstotheIGameLogicinstance.IntherendermethoditdelegatesalsototheIGameLogicinstanceandupdatesthewindow.

protectedvoidinput(){

gameLogic.input(window);

}

protectedvoidupdate(floatinterval){

gameLogic.update(interval);

}

protectedvoidrender(){

gameLogic.render(window);

window.update();

}

Ourstartingpoint,ourclassthatcontainsthemainmethodwilljustonlycreateaGameEngineinstanceandstartit.

TheGameLoop

15

Page 16: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicclassMain{

publicstaticvoidmain(String[]args){

try{

booleanvSync=true;

IGameLogicgameLogic=newDummyGame();

GameEnginegameEng=newGameEngine("GAME",

600,480,vSync,gameLogic);

gameEng.start();

}catch(Exceptionexcp){

excp.printStackTrace();

System.exit(-1);

}

}

}

Attheendweonlyneedtocreateorgamelogicclass,whichforthischapterwillbeasimplerone.Itwilljustincrease/decreasetheclearcolorofthewindowwhenevertheuserpressestheup/downkey.Therendermethodwilljustclearthewindowwiththatcolor.

TheGameLoop

16

Page 17: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicclassDummyGameimplementsIGameLogic{

privateintdirection=0;

privatefloatcolor=0.0f;

privatefinalRendererrenderer;

publicDummyGame(){

renderer=newRenderer();

}

@Override

publicvoidinit()throwsException{

renderer.init();

}

@Override

publicvoidinput(Windowwindow){

if(window.isKeyPressed(GLFW_KEY_UP)){

direction=1;

}elseif(window.isKeyPressed(GLFW_KEY_DOWN)){

direction=-1;

}else{

direction=0;

}

}

@Override

publicvoidupdate(floatinterval){

color+=direction*0.01f;

if(color>1){

color=1.0f;

}elseif(color<0){

color=0.0f;

}

}

@Override

publicvoidrender(Windowwindow){

if(window.isResized()){

glViewport(0,0,window.getWidth(),window.getHeight());

window.setResized(false);

}

window.setClearColor(color,color,color,0.0f);

renderer.clear();

}

}

Intherendermethodwegetnotifiedwhenthewindowhasbeenresizedinordertoupdatetheviewporttolocatethecenterofthecoordinatestothecenterofthewindow.

TheGameLoop

17

Page 18: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Theclasshierarchythatwehavecreatedwillhelpustoseparateourgameenginecodefromthecodeofaspecificgame.Althoughitmayseemnecessaryatthismomentweneedtoisolategenerictasksthateverygamewillusefromthestatelogic,artworkandresourcesofaspecificgameinordertoreuseourgameengine.Inlaterchapterswewillneedtorestructurethisclasshierarchyasourgameenginegetsmorecomplex.

ThreadingissuesIfyoutrytorunthesourcecodeprovidedaboveinOSXyouwillgetanerrorlikethis:

Exceptioninthread"GAME_LOOP_THREAD"java.lang.ExceptionInInitializerError

Whatdoesthismean?TheansweristhatsomefunctionsoftheGLFWlibrarycannotbecalledinaThreadwhichisnotthemainThread.Wearedoingtheinitializingstuff,includingwindowcreationintheinitmethodoftheGameEngineclass.Thatmethodgetscalledintherunmethodofthesameclass,whichisinvokedbyanewThreadinsteadtheonethat'susedtolaunchtheprogram.

ThisisaconstraintoftheGLFWlibraryandbasicallyitimpliesthatweshouldavoidthecreationofnewThreadsforthegameloop.WecouldtrytocreatealltheWindowsrelatedstuffinthemainthreadbutwewillnotbeabletorenderanything.Theproblemisthat,OpenGLcallsneedtobeperformedinthesameThreadthatitscontextwascreated.

OnWindowsandLinuxplatforms,althoughwearenotusingthemainthreadtoinitializetheGLFWstuffthesampleswillwork.TheproblemiswithOSX,soweneedtochangethesourcecodeoftherunmethodoftheGameEngineclasstosupportthatplatformlikethis:

publicvoidstart(){

StringosName=System.getProperty("os.name");

if(osName.contains("Mac")){

gameLoopThread.run();

}else{

gameLoopThread.start();

}

}

WhatwearedoingisjustignoringthegameloopthreadwhenweareinOSXandexecutethegameloopcodedirectlyinthemainThread.ThisisnotaperfectsolutionbutitwillallowyoutorunthesamplesonMac.Othersolutionsfoundintheforums(suchasexecutingtheJVMwiththe-XstartOnFirstThreadflagseemtonotwork).

TheGameLoop

18

Page 19: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

InthefutureitmaybeinterestingtoexploreifLWJGLprovidesotherGUIlibrariestocheckifthisrestrictionappliestothem.(ManythankstoTimoBühlmannforpointingoutthisissue).

PlatformDifferences(OSX)YouwillbeabletorunthecodedescribedaboveonWindowsorLinux,butwestillneedtodosomemodificationsforOSX.Asit'sstatedinthGLFWdocumentation:

TheonlyOpenGL3.xand4.xcontextscurrentlysupportedbyOSXareforward-compatible,coreprofilecontexts.Thesupportedversionsare3.2on10.7Lionand3.3and4.1on10.9Mavericks.Inallcases,yourGPUneedstosupportthespecifiedOpenGLversionforcontextcreationtosucceed.

So,inordertosupportfeaturesexplainedinlaterchaptersweneedtoaddtheselinestotheWindowclassbeforethewindowiscreated:

glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);

glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,2);

glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);

glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT,GL_TRUE);

ThiswillmaketheprogramusethehighestOpenGLversionpossiblebetween3.2and4.1.Ifthoselinesarenotincluded,aLegacyversionofOpenGLisused.

TheGameLoop

19

Page 20: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

AbriefaboutcoordinatesInthischapterwewilltalkalittlebitaboutcoordinatesandcoordinatesystemstryingtointroducesomefundamentalmathematicalconceptsinasimplewaytosupportthetechniquesandtopicsthatwewilladdressinsubsequentchapters.Wewillassumesomesimplificationswhichmaysacrificeprecisenessforthesakeoflegibility.

Welocateobjectsinspacebyspecifyingitscoordinates.Thinkaboutamap.Youspecifyapointonamapbystatingitslatitudeorlongitude.Withjustapairofnumbersapointispreciselyidentified.Thatpairofnumbersarethepointcoordinates(thingsarealittlebitmorecomplexinreality,sinceamapisaprojectionofanonperfectellipsoid,theearth,somoredataisneededbutit’sagoodanalogy).

Acoordinatesystemisasystemwhichemploysoneormorenumbers,thatis,oneormorecoordinatestouniquelyspecifythepositionofapoint.Therearedifferentcoordinatesystems(Cartesian,polar,etc.)andyoucantransformcoordinatesfromonesystemtoanother.WewillusetheCartesiancoordinatesystem.

IntheCartesiancoordinatesystem,fortwodimensions,acoordinateisdefinedbytwonumbersthatmeasurethesigneddistancetotwoperpendicularaxes,xandy.

Continuingwiththemapanalogy,coordinatesystemsdefineanorigin.Forgeographiccoordinatestheoriginissettothepointwheretheequatorandthezeromeridiancross.Dependingonwherewesettheorigin,coordinatesforaspecificpointaredifferent.Acoordinatesystemmayalsodefinetheorientationoftheaxis.Inthepreviousfigure,thex

Abriefaboutcoordinates

20

Page 21: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

coordinateincreasesaslongaswemovetotherightandtheycoordinateincreasesaswemoveupwards.But,wecouldalsodefineanalternativeCartesiancoordinatesystemwithdifferentaxisorientationinwhichwewouldobtaindifferentcoordinates.

Asyoucanseeweneedtodefinesomearbitraryparameters,suchastheoriginandtheaxisorientationinordertogivetheappropriatemeaningtothepairofnumbersthatconstituteacoordinate.Wewillrefertothatcoordinatesystemwiththesetofarbitraryparametersasthecoordinatespace.Inordertoworkwithasetofcoordinateswemustusethesamecoordinatespace.Thegoodnewsisthatwecantransformscoordinatesfromonespacetoanotherjustbyperformingtranslationsandrotations.

Ifwearedealingwith3Dcoordinatesweneedanadditionalaxis,thezaxis.3Dcoordinateswillbeformedbyasetofthreenumbers(x,y,z).

Abriefaboutcoordinates

21

Page 22: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Asin2DCartesiancoordinatespaceswecanchangetheorientationoftheaxesin3Dcoordinatespacesaslongastheaxesareperpendicular.Thenextfigureshowsanother3Dcoordinatespace.

3Dcoordinatescanbeclassifiedintwotypes:lefthandedandrighthanded.Howdoyouknowwhichtypeitis?Takeyourhandandforma“L”betweenyourthumbandyourindexfingers,themiddlefingershouldpointinadirectionperpendiculartotheothertwo.Thethumbshouldpointtothedirectionwherethexaxisincreases,theindexfingershouldpointwheretheyaxisincreasesandthemiddlefingershouldpointwherethezaxisincreases.Ifyouareabletodothatwithyourlefthand,thenitslefthanded,ifyouneedtouseyourrighthandisright-handed.

Abriefaboutcoordinates

22

Page 23: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

2Dcoordinatespacesareallequivalentsincebyapplyingrotationwecantransformfromonetoanother.3Dcoordinatespaces,onthecontrary,arenotallequal.Youcanonlytransformfromonetoanotherbyapplyingrotationiftheybothhavethesamehandedness,thatis,ifbotharelefthandedorrighthanded.

Nowthatwehavedefinedsomebasictopicslet’stalkaboutsomecommonlyusedtermswhendealingwith3Dgraphics.Whenweexplaininlaterchaptershowtorender3Dmodelswewillseethatweusedifferent3Dcoordinatespaces,thatisbecauseeachofthosecoordinatespaceshasacontext,apurpose.Asetofcoordinatesismeaninglessunlessitreferstosomething.Whenyouexaminethiscoordinates(40.438031,-3.676626)theymaysaysomethingtoyouornot.ButifIsaythattheyaregeometriccoordinates(latitudeandlongitude)youwillseethattheyarethecoordinatesofaplaceinMadrid.

Whenwewillload3Dobjectswewillgetasetof3Dcoordinates.Thosecoordinatesareexpressedina3Dcoordinatespacewhichiscalledobjectcoordinatespace.Whenthegraphicsdesignersarecreatingthose3Dmodelstheydon’tknowanythingaboutthe3Dscenethatthismodelwillbedisplayedin,sotheycanonlydefinethecoordinatesusingacoordinatespacethatisonlyrelevantforthemodel.

Whenwewillbedrawinga3Dsceneallofour3Dobjectswillberelativetothesocalledworldspacecoordinatespace.Wewillneedtotransformfrom3Dobjectspacetoworldspacecoordinates.Someobjectswillneedtoberotated,stretchedorenlargedandtranslatedinordertobedisplayedproperlyina3Dscene.

Wewillalsoneedtorestricttherangeofthe3Dspacethatisshown,whichislikemovingacamerathroughour3Dspace.Thenwewillneedtotransformworldspacecoordinatestocameraorviewspacecoordinates.Finallythesecoordinatesneedtobetransformedtoscreencoordinates,whichare2D,soweneedtoproject3Dviewcoordinatestoa2Dscreencoordinatespace.

Abriefaboutcoordinates

23

Page 24: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

ThefollowingpictureshowsOpenGLcoordinates,(thezaxisisperpendiculartothescreen)andcoordinatesarebetween-1and+1.

Don’tworryifyoudon’thaveaclearunderstandingofalltheseconcepts.Theywillberevisitedduringnextchapterswithpracticalexamples.

Abriefaboutcoordinates

24

Page 25: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

RenderingInthischapterwewilllearntheprocessesthattakesplacewhilerenderingasceneusingOpenGL.IfyouareusedtoolderversionsofOpenGL,thatisfixed-functionpipeline,youmayendthischapterwonderingwhyitneedstobesocomplex.Youmayendupthinkingthatdrawingasimpleshapetothescreenshouldnotrequiresomanyconceptsandlinesofcode.Letmegiveyouanadviceforthoseofyouthatthinkthatway.Itisactuallysimplerandmuchmoreflexible.Youonlyneedtogiveitachance.ModernOpenGLletsyouthinkinoneproblematatimeanditletsyouorganizeyourcodeandprocessesinamorelogicalway.

Thesequenceofstepsthatendsupdrawinga3Drepresentationintoyour2Dscreeniscalledthegraphicspipeline.FirstversionsofOpenGLemployedamodelwhichwascalledfixed-functionpipeline.Thismodelemployedasetofstepsintherenderingprocesswhichdefinedafixedsetofoperations.Theprogrammerwasconstrainedtothesetoffunctionsavailableforeachstep.Thus,theeffectsandoperationsthatcouldbeappliedwerelimitedbytheAPIitself(forinstance,“setfog”or“addlight”,buttheimplementationofthosefunctionswerefixedandcouldnotbechanged).

Thegraphicspipelinewascomposedofthesesteps:

Rendering

25

Page 26: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

OpenGL2.0introducedtheconceptofprogrammablepipeline.Inthismodel,thedifferentstepsthatcomposethegraphicspipelinecanbecontrolledorprogrammedbyusingasetofspecificprogramscalledshaders.ThefollowingpicturedepictsasimplifiedversionoftheOpenGLprogrammablepipeline:

TherenderingstartstakingasitsinputalistofverticesintheformofVertexBuffers.But,whatisavertex?Avertexisadatastructurethatdescribesapointin2Dor3Dspace.Andhowdoyoudescribeapointina3Dspace?Byspecifyingitsx,yandzcoordinates.AndwhatisaVertexBuffer?AVertexBufferisanotherdatastructurethatpacksalltheverticesthatneedtoberendered,byusingvertexarrays,andmakesthatinformationavailabletotheshadersinthegraphicspipeline.

Thoseverticesareprocessedbythevertexshaderwhosemainpurposeistocalculatetheprojectedpositionofeachvertexintothescreenspace.Thisshadercangeneratealsootheroutputsrelatedtocolourortexture,butitsmaingoalistoprojecttheverticesintothescreenspace,thatis,togeneratedots.

Thegeometryprocessingstageconnectstheverticesthataretransformedbythevertexshadertoformtriangles.Itdoessobytakingintoconsiderationtheorderinwhichtheverticeswerestoredandgroupingthemusingdifferentmodels.Whytriangles?Atriangleislikethebasicworkunitforgraphiccards.It’sasimplegeometricshapethatcanbecombinedandtransformedtoconstructcomplex3Dscenes.Thisstagecanalsouseaspecificshadertogroupthevertices.

Rendering

26

Page 27: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Therasterizationstagetakesthetrianglesgeneratedinthepreviousstages,clipsthemandtransformsthemintopixel-sizedfragments.

Thosefragmentsareusedduringthefragmentprocessingstagebythefragmentshadertogeneratepixelsassigningthemthefinalcolorthatgetswrittenintotheframebuffer.Theframebufferisthefinalresultofthegraphicspipeline.Itholdsthevalueofeachpixelthatshouldbedrawntothescreen.

Keepinmindthat3Dcardsaredesignedtoparallelizealltheoperationsdescribedabove.Theinputdatacanbeprocessesinparallelinordertogeneratethefinalscene.

Solet'sstartwritingourfirstshaderprogram.ShadersarewrittenbyusingtheGLSLlanguage(OpenGLShadingLanguage)whichisbasedonANSIC.Firstwewillcreateafilenamed“vertex.vs”(TheextensionisforVertexShader)undertheresourcesdirectorywiththefollowingcontent:

#version330

layout(location=0)invec3position;

voidmain()

{

gl_Position=vec4(position,1.0);

}

ThefirstlineisadirectivethatstatestheversionoftheGLSLlanguageweareusing.ThefollowingtablerelatestheGLSLversion,theOpenGLthatmatchesthatversionandthedirectivetouse(Wikipedia:https://en.wikipedia.org/wiki/OpenGL_Shading_Language#Versions).

Rendering

27

Page 28: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

GLSVersion OpenGLVersion ShaderPreprocessor

1.10.59 2.0 #version110

1.20.8 2.1 #version120

1.30.10 3.0 #version130

1.40.08 3.1 #version140

1.50.11 3.2 #version150

3.30.6 3.3 #version330

4.00.9 4.0 #version400

4.10.6 4.1 #version410

4.20.11 4.2 #version420

4.30.8 4.3 #version430

4.40 4.4 #version440

4.50 4.5 #version450

Thesecondlinespecifiestheinputformatforthisshader.DatainanOpenGLbuffercanbewhateverwewant,thatis,thelanguagedoesnotforceyoutopassaspecificdatastructurewithapredefinedsemantic.Fromthepointofviewoftheshaderitisexpectingtoreceiveabufferwithdata.Itcanbeaposition,apositionwithsomeadditionalinformationorwhateverwewant.Thevertexshaderisjustreceivinganarrayoffloats.Whenwefillthebuffer,wedefinethebufferchunksthataregoingtobeprocessedbytheshader.

So,firstweneedtogetthatchunkintosomethingthat’smeaningfultous.Inthiscasewearesayingthat,startingfromtheposition0,weareexpectingtoreceiveavectorcomposedof3attributes(x,y,z).

TheshaderhasamainblocklikeanyotherCprogramwhichinthiscaseisverysimple.Itisjustreturningthereceivedpositionintheoutputvariablegl_Positionwithoutapplyinganytransformation.Younowmaybewonderingwhythevectorofthreeattributeshasbeenconvertedintoavectoroffourattributes(vec4).Thisisbecausegl_Positionisexpectingtheresultinvec4formatsinceitisusinghomogeneouscoordinates.Thatis,it’sexpectingsomethingintheform(x,y,z,w),wherewrepresentsanextradimension.Whyaddanotherdimension?Inlaterchaptersyouwillseethatmostoftheoperationsweneedtodoarebasedonvectorsandmatrices.Someofthoseoperationscannotbecombinedifwedonothavethatextradimension.Forinstancewecouldnotcombinerotationandtranslationoperations.(Ifyouwanttolearnmoreonthis,thisextradimensionallowustocombineaffineandlineartransformations.Youcanlearnmoreaboutthisbyreadingtheexcellentbook“3DMathPrimerforGraphicsandGamedevelopment,byFletcherDunnandIanParberry).

Rendering

28

Page 29: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Letusnowhavealookatourfirstfragmentshader.Wewillcreateafilenamed“fragment.fs”(TheextensionisforFragmentShader)undertheresourcesdirectorywiththefollowingcontent:

#version330

outvec4fragColor;

voidmain()

{

fragColor=vec4(0.0,0.5,0.5,1.0);

}

Thestructureisquitesimilartoourvertexshader.Inthiscasewewillsetafixedcolourforeachfragment.Theoutputvariableisdefinedinthesecondlineandsetasavec4fragColor.Nowthatwehaveourshaderscreated,howdoweusethem?Thisisthesequenceofstepsweneedtofollow:

1. CreateaOpenGLProgram2. Loadthevertexandshadercodefiles.3. Foreachshader,createanewshaderprogramandspecifyitstype(vertex,fragment).4. Compiletheshader.5. Attachtheshadertotheprogram.6. Linktheprogram.

Attheendtheshaderwillbeloadedinthegraphicscardandwecanuseitbyreferencinganidentifier,theprogramidentifier.

packageorg.lwjglb.engine.graph;

importstaticorg.lwjgl.opengl.GL20.*;

publicclassShaderProgram{

privatefinalintprogramId;

privateintvertexShaderId;

privateintfragmentShaderId;

publicShaderProgram()throwsException{

programId=glCreateProgram();

if(programId==0){

thrownewException("CouldnotcreateShader");

}

}

Rendering

29

Page 30: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicvoidcreateVertexShader(StringshaderCode)throwsException{

vertexShaderId=createShader(shaderCode,GL_VERTEX_SHADER);

}

publicvoidcreateFragmentShader(StringshaderCode)throwsException{

fragmentShaderId=createShader(shaderCode,GL_FRAGMENT_SHADER);

}

protectedintcreateShader(StringshaderCode,intshaderType)throwsException{

intshaderId=glCreateShader(shaderType);

if(shaderId==0){

thrownewException("Errorcreatingshader.Type:"+shaderType);

}

glShaderSource(shaderId,shaderCode);

glCompileShader(shaderId);

if(glGetShaderi(shaderId,GL_COMPILE_STATUS)==0){

thrownewException("ErrorcompilingShadercode:"+glGetShaderInfoLog(s

haderId,1024));

}

glAttachShader(programId,shaderId);

returnshaderId;

}

publicvoidlink()throwsException{

glLinkProgram(programId);

if(glGetProgrami(programId,GL_LINK_STATUS)==0){

thrownewException("ErrorlinkingShadercode:"+glGetProgramInfoLog(pr

ogramId,1024));

}

if(vertexShaderId!=0){

glDetachShader(programId,vertexShaderId);

}

if(fragmentShaderId!=0){

glDetachShader(programId,fragmentShaderId);

}

glValidateProgram(programId);

if(glGetProgrami(programId,GL_VALIDATE_STATUS)==0){

System.err.println("WarningvalidatingShadercode:"+glGetProgramInfoLo

g(programId,1024));

}

}

publicvoidbind(){

glUseProgram(programId);

}

Rendering

30

Page 31: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicvoidunbind(){

glUseProgram(0);

}

publicvoidcleanup(){

unbind();

if(programId!=0){

glDeleteProgram(programId);

}

}

}

TheconstructoroftheShaderProgramcreatesanewprograminOpenGLandprovidesmethodstoaddvertexandfragmentshaders.ThoseshadersarecompiledandattachedtotheOpenGLprogram.Whenallshadersareattachedthelinkmethodshouldbeinvokedwhichlinksallthecodeandverifiesthateverythinghasbeendonecorrectly.

Oncetheshaderprogramhasbeenlinked,thecompiledvertexandfragmentshaderscanbefreedup(bycallingglDetachShader)

Regardingverification,thisisdonethroughtheglValidateProgramcall.Thismethodisusedmainlyfordebuggingpurposes,anditshouldberemovedwhenyourgamereachesproductionstage.ThismethodtriestovalidateiftheshaderiscorrectgiventhecurrentOpenGLstate.Thismeans,thatvalidationmayfailinsomecaseseveniftheshaderiscorrect,duetothefactthatthecurrentstateisnotcompleteenoughtoruntheshader(somedatamayhavenotbeenuploadedyet).So,insteadoffailing,wejustprintanerrormessagetothestandarderroroutput.

ShaderProgramalsoprovidesmethodstoactivatethisprogramforrendering(bind)andtostopusingit(unbind).Finallyitprovidesacleanupmethodtofreealltheresourceswhentheyarenolongerneeded.

Sincewehaveacleanupmethod,letuschangeourIGameLogicinterfaceclasstoaddacleanupmethod:

voidcleanup();

Thismethodwillbeinvokedwhenthegameloopfinishes,soweneedtomodifytherunmethodoftheGameEngineclass:

Rendering

31

Page 32: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

@Override

publicvoidrun(){

try{

init();

gameLoop();

}catch(Exceptionexcp){

excp.printStackTrace();

}finally{

cleanup();

}

}

Nowwecanuseorshadersinordertodisplayatriangle.WewilldothisintheinitmethodofourRendererclass.Firstofall,wecreatetheshaderprogram:

publicvoidinit()throwsException{

shaderProgram=newShaderProgram();

shaderProgram.createVertexShader(Utils.loadResource("/vertex.vs"));

shaderProgram.createFragmentShader(Utils.loadResource("/fragment.fs"));

shaderProgram.link();

}

Wehavecreatedautilityclasswhichbynowprovidesamethodtoretrievethecontentsofafilefromtheclasspath.Thismethodisusedtoretrievethecontentsofourshaders.

Nowwecandefineourtriangleasanarrayoffloats.Wecreateasinglefloatarraywhichwilldefinetheverticesofthetriangle.Asyoucanseethere’snostructureinthatarray.Asitisrightnow,OpenGLcannotknowthestructureofthatdata.It’sjustasequenceoffloats:

float[]vertices=newfloat[]{

0.0f,0.5f,0.0f,

-0.5f,-0.5f,0.0f,

0.5f,-0.5f,0.0f

};

Thefollowingpicturedepictsthetriangleinourcoordinatessystem.

Rendering

32

Page 33: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Nowthatwehaveourcoordinates,weneedtostorethemintoourgraphicscardandtellOpenGLaboutthestructure.Wewillintroducenowtwoimportantconcepts,VertexArrayObjects(VAOs)andVertexBufferObject(VBOs).Ifyougetlostinthenextcodefragmentsrememberthatattheendwhatwearedoingissendingthedatathatmodelstheobjectswewanttodrawtothegraphicscardmemory.Whenwestoreitwegetanidentifierthatservesuslatertorefertoitwhiledrawing.

LetusfirststartwithVertexBufferObject(VBOs).AVBOisjustamemorybufferstoredinthegraphicscardmemorythatstoresvertices.Thisiswherewewilltransferourarrayoffloatsthatmodelatriangle.Aswesaidbefore,OpenGLdoesnotknowanythingaboutourdatastructure.Infactitcanholdnotjustcoordinatesbutotherinformation,suchastextures,colour,etc.AVertexArrayObjects(VAOs)isanobjectthatcontainsoneormoreVBOswhichareusuallycalledattributelists.Eachattributelistcanholdonetypeofdata:position,colour,texture,etc.Youarefreetostorewhicheveryouwantineachslot.

AVAOislikeawrapperthatgroupsasetofdefinitionsforthedatathatisgoingtobestoredinthegraphicscard.WhenwecreateaVAOwegetanidentifier.Weusethatidentifiertorenderitandtheelementsitcontainsusingthedefinitionswespecifiedduringitscreation.

Soletuscontinuecodingourexample.ThefirstthingthatwemustdoistostoreourarrayoffloatsintoaFloatBuffer.ThisismainlyduetothefactthatwemustinterfacewiththeOpenGLlibrary,whichisC-based,sowemusttransformourarrayoffloatsintosomethingthatcanbemanagedbythelibrary.

FloatBufferverticesBuffer=MemoryUtil.memAllocFloat(vertices.length);

verticesBuffer.put(vertices).flip();

Rendering

33

Page 34: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

WeusetheMemoryUtilclasstocreatethebufferinoff-heapmemorysothatit'saccessiblebytheOpenGLlibrary.Afterwehavestoredthedata(withtheputmethod)weneedtoresetthepositionofthebuffertothe0positionwiththeflipmethod(thatis,wesaythatwe’vefinishingwritingtoit).Remember,thatJavaobjects,areallocatedinaspacecalledtheheap.TheheapisalargebunchofmemoryreservedintheJVM'sprocessmemory.Memorystoredintheheapcannotbeaccessedbynativecode(JNI,themechanismthatallowscallingnativecodefromJavadoesnotallowthat).TheonlywayofsharingmemorydatabetweenJavaandnativecodeisbydirectlyallocatingmemoryinJava.

IfyoucomefrompreviousversionsofLWJGLit'simportanttostressoutafewtopics.YoumayhavenoticedthatwedonotusetheutilityclassBufferUtilstocreatethebuffers.InsteadweusetheMemoryUtilclass.ThisisduetothefactthatBufferUtilswasnotveryefficient,andhasbeenmantainedonlyforbackwardscompatibility.Instead,LWJGL3proposestwomethodsforbuffermanagement:

Auto-managedbuffers,thatis,buffersthatareautomaticallycollectedbytheGarbageCollector.Thesebuffersaremainlyusedforshortlivedoperations,orfordatathatistransferredtotheGPUanddoesnotneedtobepresentintheprocessmemory.Thisisachievedbyusingtheorg.lwjgl.system.MemoryStackclass.Manuallymanagedbuffers.Inthiscaseweneedtocarefulleyfreethemoncewearefinished.Thesebuffersareintendedforlongtimeoperationsorforlargeamountsofdata.ThisisachievedbyusingtheMemoryUtilclass.

Youcanconsultthedetailshere:https://blog.lwjgl.org/memory-management-in-lwjgl-3/.

Inthiscase,ourdataissenttotheGPUsowecoulduseauto-managedbuffers.Butsince,lateron,wewillusethemtoholdpotentiallylargevolumesofdatawewillneedtomanuallymanagethem.ThisisthereasonwhyweareusingtheMemoryUtilclassandthus,whywearefreeingthebufferinafinallyblock.Innextchapterswewilllearnhowtouseauto-managedbuffers.

NowweneedtocreatetheVAOandbindit.

vaoId=glGenVertexArrays();

glBindVertexArray(vaoId);

ThenweneedtocreatetheVBO,binditandputthedataintoit.

vboId=glGenBuffers();

glBindBuffer(GL_ARRAY_BUFFER,vboId);

glBufferData(GL_ARRAY_BUFFER,verticesBuffer,GL_STATIC_DRAW);

memFree(verticesBuffer);

Rendering

34

Page 35: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Nowcomesthemostimportantpart.WeneedtodefinethestructureofourdataandstoreitinoneoftheattributelistsoftheVAO.Thisisdonewiththefollowingline.

glVertexAttribPointer(0,3,GL_FLOAT,false,0,0);

Theparametersare:

index:Specifiesthelocationwheretheshaderexpectsthisdata.size:Specifiesthenumberofcomponentspervertexattribute(from1to4).Inthiscase,wearepassing3Dcoordinates,soitshouldbe3.type:Specifiesthetypeofeachcomponentinthearray,inthiscaseafloat.normalized:Specifiesifthevaluesshouldbenormalizedornot.stride:Specifiesthebyteoffsetbetweenconsecutivegenericvertexattributes.(Wewillexplainitlater).offset:Specifiesanoffsettothefirstcomponentinthebuffer.

AfterwearefinishedwithourVBOwecanunbinditandtheVAO(bindthemto0)

//UnbindtheVBO

glBindBuffer(GL_ARRAY_BUFFER,0);

//UnbindtheVAO

glBindVertexArray(0);

Oncethishasbeencompletedwemustfreetheoff-heapmemorythatwasallocatedbytheFloatBuffer.ThisisdonebymanuallycallingmemFree,asJavagarbagecollectionwillnotcleanupoff-heapallocations.

if(verticesBuffer!=null){

MemoryUtil.memFree(verticesBuffer);

}

That’sallthecodethatshouldbeinourinitmethod.Ourdataisalreadyinthegraphicscard,readytobeused.Weonlyneedtomodifyourrendermethodtouseiteachrenderstepduringourgameloop.

Rendering

35

Page 36: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicvoidrender(Windowwindow){

clear();

if(window.isResized()){

glViewport(0,0,window.getWidth(),window.getHeight());

window.setResized(false);

}

shaderProgram.bind();

//BindtotheVAO

glBindVertexArray(vaoId);

glEnableVertexAttribArray(0);

//Drawthevertices

glDrawArrays(GL_TRIANGLES,0,3);

//Restorestate

glDisableVertexAttribArray(0);

glBindVertexArray(0);

shaderProgram.unbind();

}

Asyoucanseewejustclearthewindow,bindtheshaderprogram,bindtheVAO,drawtheverticesstoredintheVBOassociatedtotheVAOandrestorethestate.That’sit.

WealsoaddedacleanupmethodtoourRendererclasswhichfreesacquiredresources.

publicvoidcleanup(){

if(shaderProgram!=null){

shaderProgram.cleanup();

}

glDisableVertexAttribArray(0);

//DeletetheVBO

glBindBuffer(GL_ARRAY_BUFFER,0);

glDeleteBuffers(vboId);

//DeletetheVAO

glBindVertexArray(0);

glDeleteVertexArrays(vaoId);

}

And,that’sall!Ifyoufollowedthestepscarefullyyouwillseesomethinglikethis.

Rendering

36

Page 37: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Ourfirsttriangle!Youmaythinkthatthiswillnotmakeitintothetoptengamelist,andyouwillbetotallyright.Youmayalsothinkthatthishasbeentoomuchworkfordrawingaboringtriangle.Butkeepinmindthatweareintroducingkeyconceptsandpreparingthebaseinfrastructuretodomorecomplexthings.Pleasebepatientandcontinuereading.

Rendering

37

Page 38: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

MoreonRenderingInthischapterwewillcontinuetalkingabouthowOpenGLrendersthings.Inordertotidyupourcodealittlebitlet’screateanewclasscalledMeshwhich,takingasaninputanarrayofpositions,createstheVBOandVAOobjectsneededtoloadthatmodelintothegraphicscard.

packageorg.lwjglb.engine.graph;

importjava.nio.FloatBuffer;

importstaticorg.lwjgl.opengl.GL11.*;

importstaticorg.lwjgl.opengl.GL15.*;

importstaticorg.lwjgl.opengl.GL20.*;

importstaticorg.lwjgl.opengl.GL30.*;

importorg.lwjgl.system.MemoryUtil;

publicclassMesh{

privatefinalintvaoId;

privatefinalintvboId;

privatefinalintvertexCount;

publicMesh(float[]positions){

FloatBufferverticesBuffer=null;

try{

verticesBuffer=MemoryUtil.memAllocFloat(positions.length);

vertexCount=positions.length/3;

verticesBuffer.put(positions).flip();

vaoId=glGenVertexArrays();

glBindVertexArray(vaoId);

vboId=glGenBuffers();

glBindBuffer(GL_ARRAY_BUFFER,vboId);

glBufferData(GL_ARRAY_BUFFER,verticesBuffer,GL_STATIC_DRAW);

glVertexAttribPointer(0,3,GL_FLOAT,false,0,0);

glBindBuffer(GL_ARRAY_BUFFER,0);

glBindVertexArray(0);

}finally{

if(verticesBuffer!=null){

MemoryUtil.memFree(verticesBuffer);

}

}

}

MoreonRendering

38

Page 39: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicintgetVaoId(){

returnvaoId;

}

publicintgetVertexCount(){

returnvertexCount;

}

publicvoidcleanUp(){

glDisableVertexAttribArray(0);

//DeletetheVBO

glBindBuffer(GL_ARRAY_BUFFER,0);

glDeleteBuffers(vboId);

//DeletetheVAO

glBindVertexArray(0);

glDeleteVertexArrays(vaoId);

}

}

WewillcreateourMeshinstanceinourDummyGameclass,removingtheVAOandVBOcodefromRendererinitmethod.OurrendermethodintheRendererclasswillacceptalsoaMeshinstancetorender.ThecleanupmethodwillalsobesimplifiedsincetheMeshclassalreadyprovidesoneforfreeingVAOandVBOresources.

MoreonRendering

39

Page 40: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicvoidrender(Meshmesh){

clear();

if(window.isResized()){

glViewport(0,0,window.getWidth(),window.getHeight());

window.setResized(false);

}

shaderProgram.bind();

//Drawthemesh

glBindVertexArray(mesh.getVaoId());

glEnableVertexAttribArray(0);

glDrawArrays(GL_TRIANGLES,0,mesh.getVertexCount());

//Restorestate

glDisableVertexAttribArray(0);

glBindVertexArray(0);

shaderProgram.unbind();

}

publicvoidcleanup(){

if(shaderProgram!=null){

shaderProgram.cleanup();

}

}

Oneimportantthingtonoteisthisline:

glDrawArrays(GL_TRIANGLES,0,mesh.getVertexCount());

OurMeshcountsthenumberofverticesbydividingthepositionarrayby3(sincewearepassingX,YandZcoordinates)).Nowthatwecanrendermorecomplexshapes,letustrytorenderamorecomplexshape.Letusrenderaquad.Aquadcanbeconstructedbyusingtwotrianglesasshowninthenextfigure.

MoreonRendering

40

Page 41: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Asyoucanseeeachofthetwotrianglesiscomposedofthreevertices.ThefirstoneformedbytheverticesV1,V2andV4(theorangeone)andthesecondoneformedbytheverticesV4,V2andV3(thegreenone).Verticesarespecifiedinacounter-clockwiseorder,sothefloatarraytobepassedwillbe[V1,V2,V4,V4,V2,V3].Thus,theinitmethodinourDummyGameclasswillbe:

@Override

publicvoidinit()throwsException{

renderer.init();

float[]positions=newfloat[]{

-0.5f,0.5f,0.0f,

-0.5f,-0.5f,0.0f,

0.5f,0.5f,0.0f,

0.5f,0.5f,0.0f,

-0.5f,-0.5f,0.0f,

0.5f,-0.5f,0.0f,

};

mesh=newMesh(positions);

}

Nowyoushouldseeaquadrenderedlikethis:

MoreonRendering

41

Page 42: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Arewedoneyet?Unfortunatelynot.Thecodeabovestillpresentssomeissues.Wearerepeatingcoordinatestorepresentthequad.WearepassingtwiceV2andV4coordinates.Withthissmallshapeitmaynotseemabigdeal,butimagineamuchmorecomplex3Dmodel.Wewouldberepeatingthecoordinatesmanytimes.Keepinmindalsothatnowwearejustusingthreefloatsforrepresentingthepositionofavertex.Butlateronwewillneedmoredatatorepresentthetexture,etc.Alsotakeintoconsiderationthatinmorecomplexshapesthenumberofverticessharedbetweentrianglescanbeevenhigherlikeinthefigurebelow(whereavertexcanbesharedbetweensixtriangles).

AttheendwewouldneedmuchmorememorybecauseofthatduplicateinformationandthisiswhereIndexBufferscometotherescue.Fordrawingthequadweonlyneedtospecifyeachvertexoncethisway:V1,V2,V3,V4).Eachvertexhasapositioninthearray.V1hasposition0,V2hasposition1,etc:

V1 V2 V3 V4

0 1 2 3

Thenwespecifytheorderinwhichthoseverticesshouldbedrawnbyreferringtotheirposition:

0 1 3 3 1 2

V1 V2 V4 V4 V2 V3

SoweneedtomodifyourMeshclasstoacceptanotherparameter,anarrayofindices,andnowthenumberofverticestodrawwillbethelengthofthatindicesarray.

MoreonRendering

42

Page 43: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicMesh(float[]positions,int[]indices){

vertexCount=indices.length;

AfterwehavecreatedourVBOthatstoresthepositions,weneedtocreateanotherVBOwhichwillholdtheindices.SowerenametheidentifierthatholdstheidentifierforthepositionsVBOandcreateanewonefortheindexVBO(idxVboId).TheprocessofcreatingthatVBOissimilarbutthetypeisnowGL_ELEMENT_ARRAY_BUFFER.

idxVboId=glGenBuffers();

indicesBuffer=MemoryUtil.memAllocInt(indices.length);

indicesBuffer.put(indices).flip();

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,idxVboId);

glBufferData(GL_ELEMENT_ARRAY_BUFFER,indicesBuffer,GL_STATIC_DRAW);

memFree(indicesBuffer);

SincewearedealingwithintegersweneedtocreateanIntBufferinsteadofaFloatBuffer.

Andthat’sit.TheVAOwillcontainnowtwoVBOs,oneforpositionsandanotheronethatwillholdtheindicesandthatwillbeusedforrendering.OurcleanupmethodinourMeshclassmusttakeintoconsiderationthatthereisanotherVBOtofree.

publicvoidcleanUp(){

glDisableVertexAttribArray(0);

//DeletetheVBOs

glBindBuffer(GL_ARRAY_BUFFER,0);

glDeleteBuffers(posVboId);

glDeleteBuffers(idxVboId);

//DeletetheVAO

glBindVertexArray(0);

glDeleteVertexArrays(vaoId);

}

Finally,weneedtomodifyourdrawingcallthatusedtheglDrawArraysmethod:

glDrawArrays(GL_TRIANGLES,0,mesh.getVertexCount());

ToanothercallthatusesthemethodglDrawElements:

glDrawElements(GL_TRIANGLES,mesh.getVertexCount(),GL_UNSIGNED_INT,0);

Theparametersofthatmethodare:

MoreonRendering

43

Page 44: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

mode:Specifiestheprimitivesforrendering,trianglesinthiscase.Nochangeshere.count:Specifiesthenumberofelementstoberendered.type:Specifiesthetypeofvalueintheindicesdata.Inthiscaseweareusingintegers.indices:Specifiestheoffsettoapplytotheindicesdatatostartrendering.

Andnowwecanuseournewerandmuchmoreefficientmethodofdrawingcomplexmodelsbyjustspecifyingtheindices.

publicvoidinit()throwsException{

renderer.init();

float[]positions=newfloat[]{

-0.5f,0.5f,0.0f,

-0.5f,-0.5f,0.0f,

0.5f,-0.5f,0.0f,

0.5f,0.5f,0.0f,

};

int[]indices=newint[]{

0,1,3,3,1,2,

};

mesh=newMesh(positions,indices);

}

Nowlet’saddsomecolourtoourexample.WewillpassanotherarrayoffloatstoourMeshclasswhichholdsthecolourforeachcoordinateinthequad.

publicMesh(float[]positions,float[]colours,int[]indices){

Withthatarray,wewillcreateanotherVBOwhichwillbeassociatedtoourVAO.

//ColourVBO

colourVboId=glGenBuffers();

FloatBuffercolourBuffer=memAllocFloat(colours.length);

colourBuffer.put(colours).flip();

glBindBuffer(GL_ARRAY_BUFFER,colourVboId);

glBufferData(GL_ARRAY_BUFFER,colourBuffer,GL_STATIC_DRAW);

memFree(colourBuffer);

glVertexAttribPointer(1,3,GL_FLOAT,false,0,0);

PleasenoticethatintheglVertexAttribPointercall,thefirstparameterisnowa“1”.Thisisthelocationwhereourshaderwillbeexpectingthatdata.(Ofcourse,sincewehaveanotherVBOweneedtofreeitinthecleanupmethod).

Thenextstepistomodifytheshaders.Thevertexshaderisnowexpectingtwoparameters,thecoordinates(inlocation0)andthecolour(inlocation1).Thevertexshaderwilljustoutputthereceivedcoloursoitcanbeprocessedbythefragmentshader.

MoreonRendering

44

Page 45: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

#version330

layout(location=0)invec3position;

layout(location=1)invec3inColour;

outvec3exColour;

voidmain()

{

gl_Position=vec4(position,1.0);

exColour=inColour;

}

Andnowourfragmentshaderreceivesasaninputthecolourprocessedbyourvertexshaderandusesittogeneratethecolour.

#version330

invec3exColour;

outvec4fragColor;

voidmain()

{

fragColor=vec4(exColour,1.0);

}

Thelastimportantthingtodoistomodifyourrenderingcodetousethatsecondarrayofdata:

publicvoidrender(Windowwindow,Meshmesh){

clear();

if(window.isResized()){

glViewport(0,0,window.getWidth(),window.getHeight());

window.setResized(false);

}

shaderProgram.bind();

//Drawthemesh

glBindVertexArray(mesh.getVaoId());

glEnableVertexAttribArray(0);

glEnableVertexAttribArray(1);

glDrawElements(GL_TRIANGLES,mesh.getVertexCount(),GL_UNSIGNED_INT,0);

//...

MoreonRendering

45

Page 46: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

YoucanseethatweneedtoenabletheVAOattributeatposition1tobeusedduringrendering.WecannowpassanarrayofcolourslikethistoourMeshclassinordertoaddsomecolourtoourquad.

float[]colours=newfloat[]{

0.5f,0.0f,0.0f,

0.0f,0.5f,0.0f,

0.0f,0.0f,0.5f,

0.0f,0.5f,0.5f,

};

Andwewillgetafancycolouredquadlikethis.

MoreonRendering

46

Page 47: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Transformations

ProjectingLet’sgetbacktoournicecolouredquadwecreatedinthepreviouschapter.Ifyoulookcarefullyatit,itmoreresemblesarectangle.Youcanevenchangethewidthofthewindowfrom600pixelsto900andthedistortionwillbemoreevident.What’shappeninghere?

Ifyourevisitourvertexshadercodewearejustpassingourcoordinatesdirectly.Thatis,whenwesaythatavertexhasavalueforcoordinatexof0.5wearesayingtoOpenGLtodrawitatxposition0.5onourscreen.ThefollowingfigureshowstheOpenGLcoordinates(justforxandyaxis).

Thosecoordinatesaremapped,consideringourwindowsize,towindowcoordinates(whichhavetheoriginatthetop-leftcornerofthepreviousfigure).So,ifourwindowhasasizeof900x480,OpenGLcoordinates(1,0)willbemappedtocoordinates(900,0)creatingarectangleinsteadofaquad.

Transformations

47

Page 48: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

But,theproblemismoreseriousthanthat.Modifythezcoordinateofourquadfrom0.0to1.0andto-1.0.Whatdoyousee?Thequadisexactlydrawninthesameplacenomatterifit’sdisplacedalongthezaxis.Whyisthishappening?Objectsthatarefurtherawayshouldbedrawnsmallerthanobjectsthatarecloser.Butwearedrawingthemwiththesamexandycoordinates.

But,wait.Shouldthisnotbehandledbythezcoordinate?Theanswerisyesandno.ThezcoordinatetellsOpenGLthatanobjectiscloserorfartheraway,butOpenGLdoesnotknowanythingaboutthesizeofyourobject.Youcouldhavetwoobjectsofdifferentsizes,onecloserandsmallerandonebiggerandfurtherthatcouldbeprojectedcorrectlyontothescreenwiththesamesize(thosewouldhavesamexandycoordinatesbutdifferentz).OpenGLjustusesthecoordinateswearepassing,sowemusttakecareofthis.Weneedtocorrectlyprojectourcoordinates.

Nowthatwehavediagnosedtheproblem,howdowedothis?Theanswerisusingaprojectionmatrixorfrustum.Theprojectionmatrixwilltakecareoftheaspectratio(therelationbetweensizeandheight)ofourdrawingareasoobjectswon’tbedistorted.Italsowillhandlethedistancesoobjectsfarawayfromuswillbedrawnsmaller.Theprojectionmatrixwillalsoconsiderourfieldofviewandhowfarthemaximumdistanceisthatshouldbedisplayed.

Forthosenotfamiliarwithmatrices,amatrixisabi-dimensionalarrayofnumbersarrangedincolumnsandrows.Eachnumberinsideamatrixiscalledanelement.Amatrixorderisthenumberofrowsandcolumns.Forinstance,hereyoucanseea2x2matrix(2rowsand2columns).

Matriceshaveanumberofbasicoperationsthatcanbeappliedtothem(suchasaddition,multiplication,etc.)thatyoucanconsultinanymathsbook.Themaincharacteristicsofmatrices,relatedto3Dgraphics,isthattheyareveryusefultotransformpointsinthespace.

Youcanthinkabouttheprojectionmatrixasacamera,whichhasafieldofviewandaminimumandmaximumdistance.Thevisionareaofthatcamerawillbeatruncatedpyramid.Thefollowingpictureshowsatopviewofthatarea.

Transformations

48

Page 49: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Aprojectionmatrixwillcorrectlymap3Dcoordinatessotheycanbecorrectlyrepresentedona2Dscreen.Themathematicalrepresentationofthatmatrixisasfollows(don’tbescared).

Whereaspectratioistherelationbetweenourscreenwidthandourscreenheight(a = width/height).Inordertoobtaintheprojectedcoordinatesofagivenpointwejustneedtomultiplytheprojectionmatrixbytheoriginalcoordinates.Theresultwillbeanothervectorthatwillcontaintheprojectedversion.

Soweneedtohandleasetofmathematicalentitiessuchasvectors,matricesandincludetheoperationsthatcanbedoneonthem.Wecouldchoosetowriteallthatcodebyourownfromscratchoruseanalreadyexistinglibrary.WewillchoosetheeasypathanduseaspecificlibraryfordealingwithmathoperationsinLWJGLwhichiscalledJOML(JavaOpenGLMathLibrary).Inordertousethatlibrarywejustneedtoaddanotherdependencytoourpom.xmlfile.

<dependency>

<groupId>org.joml</groupId>

<artifactId>joml</artifactId>

<version>${joml.version}</version>

</dependency>

Anddefinetheversionofthelibrarytouse.

Transformations

49

Page 50: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

<properties>

[...]

<joml.version>1.7.1</joml.version>

[...]

</properties>

Nowthateverythinghasbeensetuplet’sdefineourprojectionmatrix.WewillcreateaninstanceoftheclassMatrix4f(providedbytheJOMLlibrary)inourRendererclass.TheMatrix4fclassprovidesamethodtosetupaprojectionmatrixnamedperspective.Thismethodneedsthefollowingparameters:

FieldofView:TheFieldofViewangleinradians.WewilldefineaconstantthatholdsthatvalueAspectRatio.Distancetothenearplane(z-near)Distancetothefarplane(z-far).

WewillinstantiatethatmatrixinourinitmethodsoweneedtopassareferencetoourWindowinstancetogetitssize(youcanseeitinthesourcecode).Thenewconstantsandvariablesare:

/**

*FieldofViewinRadians

*/

privatestaticfinalfloatFOV=(float)Math.toRadians(60.0f);

privatestaticfinalfloatZ_NEAR=0.01f;

privatestaticfinalfloatZ_FAR=1000.f;

privateMatrix4fprojectionMatrix;

Theprojectionmatrixiscreatedasfollows:

floataspectRatio=(float)window.getWidth()/window.getHeight();

projectionMatrix=newMatrix4f().perspective(FOV,aspectRatio,

Z_NEAR,Z_FAR);

Atthismomentwewillignorethattheaspectratiocanchange(byresizingourwindow).Thiscouldbecheckedintherendermethodtochangeourprojectionmatrixaccordingly.

Nowthatwehaveourmatrix,howdoweuseit?Weneedtouseitinourshader,anditshouldbeappliedtoallthevertices.Atfirst,youcouldthinkofbundlingitinthevertexinput(likethecoordinatesandthecolours).Inthiscasewewouldbewastinglotsofspacesince

Transformations

50

Page 51: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

theprojectionmatrixshouldnotchangeevenbetweenseveralrendercalls.Youmayalsothinkofmultiplyingtheverticesbythematrixinthejavacode.Butthen,ourVBOswouldbeuselessandwewillnotbeusingtheprocesspoweravailableinthegraphicscard.

Theansweristouse“uniforms”.UniformsareglobalGLSLvariablesthatshaderscanuseandthatwewillemploytocommunicatewiththem.

SoweneedtomodifyourvertexshadercodeanddeclareanewuniformcalledprojectionMatrixanduseittocalculatetheprojectedposition.

#version330

layout(location=0)invec3position;

layout(location=1)invec3inColour;

outvec3exColour;

uniformmat4projectionMatrix;

voidmain()

{

gl_Position=projectionMatrix*vec4(position,1.0);

exColour=inColour;

}

AsyoucanseewedefineourprojectionMatrixasa4x4matrixandthepositionisobtainedbymultiplyingitbyouroriginalcoordinates.Nowweneedtopassthevaluesoftheprojectionmatrixtoourshader.First,weneedtogetareferencetotheplacewheretheuniformwillholditsvalues.

ThisisdonewiththemethodglGetUniformLocationwhichreceivestwoparameters:

Theshaderprogramidentifier.Thenameoftheuniform(itshouldmatchtheoncedefinedintheshadercode).

Thismethodreturnsanidentifierholdingtheuniformlocation.Sincewemayhavemorethanoneuniform,wewillstorethoselocationsinaMapindexedbythelocation'sname(Wewillneedthatlocationnumberlater).SointheShaderProgramclasswecreateanewvariablethatholdsthoseidentifiers:

privatefinalMap<String,Integer>uniforms;

Thisvariablewillbeinitializedinourconstructor:

uniforms=newHashMap<>();

Transformations

51

Page 52: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Andfinallywecreateamethodtosetupnewuniformsandstoretheobtainedlocation.

publicvoidcreateUniform(StringuniformName)throwsException{

intuniformLocation=glGetUniformLocation(programId,

uniformName);

if(uniformLocation<0){

thrownewException("Couldnotfinduniform:"+

uniformName);

}

uniforms.put(uniformName,uniformLocation);

}

Now,inourRendererclasswecaninvokethecreateUniformmethodoncetheshaderprogramhasbeencompiled(inthiscase,wewilldoitoncetheprojectionmatrixhasbeeninstantiated).

shaderProgram.createUniform("projectionMatrix");

Atthismoment,wealreadyhaveaholderreadytobesetupwithdatatobeusedasourprojectionmatrix.Sincetheprojectionmatrixwon’tchangebetweenrenderingcallswemaysetupthevaluesrightafterthecreationoftheuniform.Butwewilldoitinourrendermethod.Youwillseelaterthatwemayreusethatuniformtodoadditionaloperationsthatneedtobedoneineachrendercall.

WewillcreateanothermethodinourShaderProgramclasstosetupthedata,namedsetUniform.Basicallywetransformourmatrixintoa4x4FloatBufferbyusingtheutilitymethodsprovidedbytheJOMLlibraryandsendthemtothelocationwestoredinourlocationsmap.

publicvoidsetUniform(StringuniformName,Matrix4fvalue){

//Dumpthematrixintoafloatbuffer

try(MemoryStackstack=MemoryStack.stackPush()){

FloatBufferfb=stack.mallocFloat(16);

value.get(fb);

glUniformMatrix4fv(uniforms.get(uniformName),false,fb);

}

}

Asyoucanseewearecreatingbuffersinadifferentwayhere.Weareusingauto-managedbuffers,andallocatingthemonthestack.Thisisduetothefactthatthesizeofthisbufferissmallandthatitwillnotbeusedbeyondthismethod.Thus,weusetheMemoryStackclass.

NowwecanusethatmethodintheRendererclassintherendermethod,aftertheshaderprogramhasbeenbound:

Transformations

52

Page 53: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

shaderProgram.setUniform("projectionMatrix",projectionMatrix);

Wearealmostdone.Wecannowshowthequadcorrectlyrendered.Soyoucannowlaunchyourprogramandwillobtaina....blackbackgroundwithoutanycolouredquad.What’shappening?Didwebreaksomething?Well,actuallyno.Rememberthatwearenowsimulatingtheeffectofacameralookingatourscene.Andweprovidedtwodistances,onetothefarthestplane(equalto1000f)andonetotheclosestplane(equalto0.01f).Ourcoordinatesare:

float[]positions=newfloat[]{

-0.5f,0.5f,0.0f,

-0.5f,-0.5f,0.0f,

0.5f,-0.5f,0.0f,

0.5f,0.5f,0.0f,

};

Thatis,ourzcoordinatesareoutsidethevisiblezone.Let’sassignthemavalueof-0.05f.Nowyouwillseeagiantgreensquarelikethis:

Whatishappeningnowisthatwearedrawingthequadtooclosetoourcamera.Weareactuallyzoomingintoit.Ifweassignnowavalueof-1.05ftothezcoordinatewecannowseeourcolouredquad.

Transformations

53

Page 54: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Ifwecontinuepushingthequadbackwardswewillseeitbecomingsmaller.Noticealsothatourquaddoesnotresemblearectangleanymore.

ApplyingTransformationsLet’srecallwhatwe’vedonesofar.Wehavelearnedhowtopassdatainanefficientformattoourgraphicscard,andhowtoprojectthatdataandassignthemcoloursusingvertexandfragmentsshaders.Nowweshouldstartdrawingmorecomplexmodelsinour3Dspace.Butinordertodothatwemustbeabletoloadanarbitrarymodelandrepresentitinour3Dspaceataspecificposition,withtheappropriatesizeandtherequiredrotation.

Sorightnow,inordertodothatrepresentationweneedtoprovidesomebasicoperationstoactuponanymodel:

Translation:Moveanobjectbysomeamountonanyofthethreeaxes.Rotation:Rotateanobjectbysomeamountofdegreesaroundanyofthethreeaxes.Scale:Adjustthesizeofanobject.

Theoperationsdescribedaboveareknownastransformations.Andyouprobablemaybeguessingthatthewaywearegoingtoachievethatisbymultiplyingourcoordinatesbyasetofmatrices(onefortranslation,oneforrotationandoneforscaling).Thosethreematriceswillbecombinedintoasinglematrixcalledworldmatrixandpassedasauniformtoourvertexshader.

Thereasonwhyitiscalledworldmatrixisbecausewearetransformingfrommodelcoordinatestoworldcoordinates.Whenyouwilllearnaboutloading3Dmodelsyouwillseethatthosemodelsaredefinedintheirowncoordinatesystems.Theydon’tknowthesizeofyour3Dspaceandtheyneedtobeplacedinit.Sowhenwemultiplyourcoordinatesbyourmatrixwhatwearedoingistransformingfromacoordinatesystem(themodelone)toanothercoordinatesystem(theoneforour3Dworld).

Thatworldmatrixwillbecalculatedlikethis(theorderisimportantsincemultiplicationusingmatricesisnotcommutative):

Transformations

54

Page 55: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

WorldMatrix TranslationMatrix RotationMatrix ScaleMatrix

Ifweincludeourprojectionmatrixinthetransformationmatrixitwouldbelikethis:

Transf = ProjMatrix TranslationMatrix RotationMatrix ScaleMatrix = ProjMatr

Thetranslationmatrixisdefinedlikethis:

TranslationMatrixParameters:

dx:Displacementalongthexaxis.dy:Displacementalongtheyaxis.dz:Displacementalongthezaxis.

Thescalematrixisdefinedlikethis:

ScaleMatrixParameters:

sx:Scalingalongthexaxis.sy:Scalingalongtheyaxis.sz:Scalingalongthezaxis.

Therotationmatrixismuchmorecomplex.Butkeepinmindthatitcanbeconstructedbythemultiplicationof3rotationmatricesforasingleaxis,each.

Now,inordertoapplythoseconceptsweneedtorefactorourcodealittlebit.Inourgamewewillbeloadingasetofmodelswhichcanbeusedtorendermanyobjectsatdifferentpositionsaccordingtoourgamelogic(imagineaFPSgamewhichloadsthreemodelsfordifferentenemies.Thereareonlythreemodelsbutusingthesemodelswecandrawasmanyenemiesaswewant).DoweneedtocreateaVAOandthesetofVBOsforeachofthoseobjects?Theanswerisno.Weonlyneedtoloaditoncepermodel.Whatweneedtodoistodrawitindependentlyaccordingtoitsposition,sizeandrotation.Weneedtotransformthosemodelswhenwearerenderingthem.

SowewillcreateanewclassnamedGameItemthatwillholdareferencetoamodel,toaMeshinstance.AGameIteminstancewillhavevariablesforstoringitsposition,itsrotationstateanditsscale.Thisisthedefinitionofthatclass.

[ ] [ ] [ ]

[ ] [ ] [ ] [ ] [

⎣⎢⎢⎡1000

0100

0010

dx

dy

dz

1 ⎦⎥⎥⎤

⎣⎢⎢⎡sx

000

0sy

00

00sz

0

0001⎦⎥⎥⎤

Transformations

55

Page 56: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

packageorg.lwjglb.engine;

importorg.joml.Vector3f;

importorg.lwjglb.engine.graph.Mesh;

publicclassGameItem{

privatefinalMeshmesh;

privatefinalVector3fposition;

privatefloatscale;

privatefinalVector3frotation;

publicGameItem(Meshmesh){

this.mesh=mesh;

position=newVector3f(0,0,0);

scale=1;

rotation=newVector3f(0,0,0);

}

publicVector3fgetPosition(){

returnposition;

}

publicvoidsetPosition(floatx,floaty,floatz){

this.position.x=x;

this.position.y=y;

this.position.z=z;

}

publicfloatgetScale(){

returnscale;

}

publicvoidsetScale(floatscale){

this.scale=scale;

}

publicVector3fgetRotation(){

returnrotation;

}

publicvoidsetRotation(floatx,floaty,floatz){

this.rotation.x=x;

this.rotation.y=y;

this.rotation.z=z;

}

publicMeshgetMesh(){

returnmesh;

Transformations

56

Page 57: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

}

}

WewillcreateanotherclasswhichwilldealwithtransformationsnamedTransformation.

packageorg.lwjglb.engine.graph;

importorg.joml.Matrix4f;

importorg.joml.Vector3f;

publicclassTransformation{

privatefinalMatrix4fprojectionMatrix;

privatefinalMatrix4fworldMatrix;

publicTransformation(){

worldMatrix=newMatrix4f();

projectionMatrix=newMatrix4f();

}

publicfinalMatrix4fgetProjectionMatrix(floatfov,floatwidth,floatheight,fl

oatzNear,floatzFar){

floataspectRatio=width/height;

projectionMatrix.identity();

projectionMatrix.perspective(fov,aspectRatio,zNear,zFar);

returnprojectionMatrix;

}

publicMatrix4fgetWorldMatrix(Vector3foffset,Vector3frotation,floatscale){

worldMatrix.identity().translate(offset).

rotateX((float)Math.toRadians(rotation.x)).

rotateY((float)Math.toRadians(rotation.y)).

rotateZ((float)Math.toRadians(rotation.z)).

scale(scale);

returnworldMatrix;

}

}

Asyoucanseethisclassgroupstheprojectionandworldmatrices.Givenasetofvectorsthatmodelthedisplacement,rotationandscaleitreturnstheworldmatrix.ThemethodgetWorldMatrixreturnsthematrixthatwillbeusedtotransformthecoordinatesforeachGameIteminstance.ThatclassalsoprovidesamethodthatgetstheprojectionmatrixbasedontheFieldOfView,theaspectratioandthenearandfardistances.

AnimportantthingtonoticeisthatthemulmethodoftheMatrix4fclassmodifiesthematrixinstancewhichthemethodisbeingappliedto.Soifwedirectlymultiplytheprojectionmatrixwiththetransformationmatrixwewillmodifytheprojectionmatrixitself.Thisiswhy

Transformations

57

Page 58: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

wearealwaysinitializingeachmatrixtotheidentitymatrixuponeachcall.

IntheRendererclass,intheconstructormethod,wejustinstantiatetheTransformationwithnoargumentsandintheinitmethodwejustcreatetheuniform.

publicRenderer(){

transformation=newTransformation();

}

publicvoidinit(Windowwindow)throwsException{

//....Somecodebefore...

//Createuniformsforworldandprojectionmatrices

shaderProgram.createUniform("projectionMatrix");

shaderProgram.createUniform("worldMatrix");

window.setClearColor(0.0f,0.0f,0.0f,0.0f);

}

IntherendermethodofourRendererclasswenowreceiveanarrayofGameItems:

publicvoidrender(Windowwindow,GameItem[]gameItems){

clear();

if(window.isResized()){

glViewport(0,0,window.getWidth(),window.getHeight());

window.setResized(false);

}

shaderProgram.bind();

//UpdateprojectionMatrix

Matrix4fprojectionMatrix=transformation.getProjectionMatrix(FOV,window.getWidt

h(),window.getHeight(),Z_NEAR,Z_FAR);

shaderProgram.setUniform("projectionMatrix",projectionMatrix);

//RendereachgameItem

for(GameItemgameItem:gameItems){

//Setworldmatrixforthisitem

Matrix4fworldMatrix=

transformation.getWorldMatrix(

gameItem.getPosition(),

gameItem.getRotation(),

gameItem.getScale());

shaderProgram.setUniform("worldMatrix",worldMatrix);

//Renderthemesforthisgameitem

gameItem.getMesh().render();

}

shaderProgram.unbind();

}

Transformations

58

Page 59: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Weupdatetheprojectionmatrixonceperrendercall.Bydoingitthiswaywecandealwithwindowresizeoperations.ThenweiterateovertheGameItemarrayandcreateatransformationmatrixaccordingtotheposition,rotationandscaleofeachofthem.ThismatrixispushedtotheshaderandtheMeshisdrawn.Theprojectionmatrixisthesameforalltheitemstoberendered.Thisisthereasonwhyit’saseparatevariableinourTransformationclass.

WemovedtherenderingcodetodrawaMeshtothisclass:

publicvoidrender(){

//Drawthemesh

glBindVertexArray(getVaoId());

glEnableVertexAttribArray(0);

glEnableVertexAttribArray(1);

glDrawElements(GL_TRIANGLES,getVertexCount(),GL_UNSIGNED_INT,0);

//Restorestate

glDisableVertexAttribArray(0);

glDisableVertexAttribArray(1);

glBindVertexArray(0);

}

OurvertexshaderismodifiedbysimplyaddinganewworldMatrixmatrixanditusesitwiththeprojectionMatrixtocalculatetheposition:

#version330

layout(location=0)invec3position;

layout(location=1)invec3inColour;

outvec3exColour;

uniformmat4worldMatrix;

uniformmat4projectionMatrix;

voidmain()

{

gl_Position=projectionMatrix*worldMatrix*vec4(position,1.0);

exColour=inColour;

}

Asyoucanseethecodeisexactlythesame.Weareusingtheuniformtocorrectlyprojectourcoordinatestakingintoconsiderationourfrustum,position,scaleandrotationinformation.

Transformations

59

Page 60: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Anotherimportantthingtothinkaboutis,whydon’twepassthetranslation,rotationandscalematricesinsteadofcombiningthemintoaworldmatrix?Thereasonisthatweshouldtrytolimitthematricesweuseinourshaders.Alsokeepinmindthatthematrixmultiplicationthatwedoinourshaderisdoneoncepereachvertex.TheprojectionmatrixdoesnotchangebetweenrendercallsandtheworldmatrixdoesnotchangeperGameIteminstance.Ifwepassedthetranslation,rotationandscalematricesindependentlywewouldbedoingmanymorematrixmultiplications.Thinkaboutamodelwithtonsofvertices.That’salotofextraoperations.

Butyoumaynowthink,thatiftheworldmatrixdoesnotchangeperGameIteminstance,whydidn'twedothematrixmultiplicationinourJavaclass?WewouldmultiplytheprojectionmatrixandtheworldmatrixjustonceperGameItemandwesenditasasingleuniform.Inthiscasewewouldbesavingmanymoreoperations.Theansweristhatthisisavalidpointrightnow.Butwhenweaddmorefeaturestoourgameenginewewillneedtooperatewithworldcoordinatesintheshadersanyway,soit’sbettertohandlethosetwomatricesinanindependentway.

FinallyweonlyneedtochangetheDummyGameclasstocreateaninstanceofGameItemwithitsassociatedMeshandaddsomelogictotranslate,rotateandscaleourquad.Sinceit’sonlyatestexampleanddoesnotaddtoomuchyoucanfinditinthesourcecodethataccompaniesthisbook.

Transformations

60

Page 61: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Textures

Createa3DcubeInthischapterwewilllearnhowtoloadtexturesandusethemintherenderingprocess.Inordertoshowalltheconceptsrelatedtotextureswewilltransformthequadthatwehavebeenusinginpreviouschaptersintoa3Dcube.Withthecodebasewehavecreated,inordertodrawacubewejustneedtocorrectlydefinethecoordinatesofacubeanditshouldbedrawncorrectly.

Inordertodrawacubewejustneedtodefineeightvertices.

Sotheassociatedcoordinatesarraywillbelikethis:

Textures

61

Page 62: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

float[]positions=newfloat[]{

//VO

-0.5f,0.5f,0.5f,

//V1

-0.5f,-0.5f,0.5f,

//V2

0.5f,-0.5f,0.5f,

//V3

0.5f,0.5f,0.5f,

//V4

-0.5f,0.5f,-0.5f,

//V5

0.5f,0.5f,-0.5f,

//V6

-0.5f,-0.5f,-0.5f,

//V7

0.5f,-0.5f,-0.5f,

};

Ofcourse,sincewehave4moreverticesweneedtoupdatethearrayofcolours.Justrepeatthefirstfouritemsbynow.

float[]colours=newfloat[]{

0.5f,0.0f,0.0f,

0.0f,0.5f,0.0f,

0.0f,0.0f,0.5f,

0.0f,0.5f,0.5f,

0.5f,0.0f,0.0f,

0.0f,0.5f,0.0f,

0.0f,0.0f,0.5f,

0.0f,0.5f,0.5f,

};

Finally,sinceacubeismadeofsixfacesweneedtodrawtwelvetriangles(twoperface),soweneedtoupdatetheindicesarray.Rememberthattrianglesmustbedefinedincounter-clockwiseorder.Ifyoudothisbyhand,iseasytomakemistakes.Allwaysputthefacethatyouwanttodefineindicesforinfrontofyou.Then,idenifietheverticesanddrawthetrianglesincounter-clockwiseorder.

Textures

62

Page 63: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

int[]indices=newint[]{

//Frontface

0,1,3,3,1,2,

//TopFace

4,0,3,5,4,3,

//Rightface

3,2,7,5,3,7,

//Leftface

6,1,0,6,0,4,

//Bottomface

2,1,6,2,6,7,

//Backface

7,6,4,7,4,5,

};

InordertobetterviewthecubewewillchangecodethatrotatesthemodelintheDummyGameclasstorotatealongthethreeaxes.

//Updaterotationangle

floatrotation=gameItem.getRotation().x+1.5f;

if(rotation>360){

rotation=0;

}

gameItem.setRotation(rotation,rotation,rotation);

Andthat’sall.Wearenowabletodisplayaspinning3Dcube.Youcannowcompileandrunyourexampleandyouwillobtainsomethinglikethis.

Thereissomethingweirdwiththiscube.Somefacesarenotbeingpaintedcorrectly.Whatishappening?Thereasonwhythecubehasthisaspectisthatthetrianglesthatcomposethecubearebeingdrawninasortofrandomorder.Thepixelsthatarefarawayshouldbedrawnbeforepixelsthatarecloser.Thisisnothappeningrightnowandinordertodothatwemustenabledepthtesting.

Textures

63

Page 64: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

ThiscanbedoneintheWindowclassattheendoftheinitmethod:

glEnable(GL_DEPTH_TEST);

Nowourcubeisbeingrenderedcorrectly!

IfyouseethecodeforthispartofthechapteryoumayseethatwehavedoneaminorreorganizationintheMeshclass.TheidentifiersoftheVBOsarenowstoredinalisttoeasilyiterateoverthem.

AddingtexturetothecubeNowwearegoingtoapplyatexturetoourcube.Atextureisanimagewhichisusedtodrawthecolourofthepixelsofacertainmodel.Youcanthinkofatextureasaskinthatiswrappedaroundyour3Dmodel.Whatyoudoisassignpointsintheimagetexturetotheverticesinyourmodel.WiththatinformationOpenGLisabletocalculatethecolourtoapplytotheotherpixelsbasedonthetextureimage.

Thetextureimagedoesnothavetohavethesamesizeasthemodel.Itcanbelargeror

Textures

64

Page 65: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

smaller.OpenGLwillextrapolatethecolourifthepixeltobeprocessedcannotbemappedtoaspecificpointinthetexture.Youcancontrolhowthisprocessisdonewhenaspecifictextureiscreated.

Sobasicallywhatwemustdo,inordertoapplyatexturetoamodel,isassigningtexturecoordinatestoeachofourvertices.Thetexturecoordinatesystemisabitdifferentfromthecoordinatesystemofourmodel.Firstofall,wehavea2Dtexturesoourcoordinateswillonlyhavetwocomponents,xandy.Besidesthat,theoriginissetupinthetopleftcorneroftheimageandthemaximumvalueofthexoryvalueis1.

Howdowerelatetexturecoordinateswithourpositioncoordinates?Easy,inthesamewaywepassedthecolourinformation.WesetupaVBOwhichwillhaveatexturecoordinateforeachvertexposition.

Solet’sstartmodifyingthecodebasetousetexturesinour3Dcube.Thefirststepistoloadtheimagethatwillbeusedasatexture.Forthistask,inpreviousversionsofLWJGL,theSlick2Dlibrarywascommonlyused.AtthemomentofthiswritingitseemsthatthislibraryisnotcompatiblewithLWJGL3sowewillneedtofollowamoreverboseapproach.Wewillusealibrarycalledpngdecoder,thus,weneedtodeclarethatdependencyinourpom.xmlfile.

<dependency>

<groupId>org.l33tlabs.twl</groupId>

<artifactId>pngdecoder</artifactId>

<version>${pngdecoder.version}</version>

</dependency>

Anddefinetheversionofthelibrarytouse.

Textures

65

Page 66: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

<properties>

[...]

<pngdecoder.version>1.0</pngdecoder.version>

[...]

</properties>

OnethingthatyoumayseeinsomewebpagesisthatthefirstthingwemustdoisenablethetexturesinourOpenGLcontextbycallingglEnable(GL_TEXTURE_2D).Thisistrueifyouareusingthefixed-functionpipepline.SinceweareusingGLSLshadersitisnotrequiredanymore.

NowwewillcreateanewTextureclassthatwillperformallthenecessarystepstoloadatexture.OurtextureimagewillbelocatedintheresourcesfolderandcanbeaccessedasaCLASSPATHresourceandpassedasaninputstreamtothePNGDecoderclass.

PNGDecoderdecoder=newPNGDecoder(

Texture.class.getResourceAsStream(fileName));

ThenweneedtodecodethePNGimageandstoreitscontentintoabufferbyusingthedecodemethodofthePNGDecoderclass.ThePNGimagewillbedecodedinRGBAformat(RGBforRed,Green,BlueandAforAlphaortransparency)whichusesfourbytesperpixel.

Thedecodemethodrequiresthreeparameters:

buffer:TheByteBufferthatwillholdthedecodedimage(sinceeachpixelusesfourbytesitssizewillbe4widthheight).stride:Specifiesthedistanceinbytesfromthestartofalinetothestartofthenextline.Inthiscaseitwillbethenumberofbytesperline.format:Thetargetformatintowhichtheimageshouldbedecoded(RGBA).

ByteBufferbuf=ByteBuffer.allocateDirect(

4*decoder.getWidth()*decoder.getHeight());

decoder.decode(buf,decoder.getWidth()*4,Format.RGBA);

buf.flip();

OneimportantthingtorememberisthatOpenGL,forhistoricalreasons,requiresthattextureimageshaveasize(numberoftexelsineachdimension)ofapoweroftwo(2,4,8,16,....).Somedriversremovethatconstraintbutit’sbettertosticktoittoavoidproblems.

Thenextstepistouploadthetextureintothegraphicscardmemory.Firstofallweneedtocreateanewtextureidentifier.Eachoperationrelatedtothattexturewillusethatidentifiersoweneedtobindit.

Textures

66

Page 67: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

//CreateanewOpenGLtexture

inttextureId=glGenTextures();

//Bindthetexture

glBindTexture(GL_TEXTURE_2D,textureId);

ThenweneedtotellOpenGLhowtounpackourRGBAbytes.Sinceeachcomponentisonebyteinsizeweneedtoaddthefollowingline:

glPixelStorei(GL_UNPACK_ALIGNMENT,1);

Andfinallywecanuploadourtexturedata:

glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,decoder.getWidth(),

decoder.getHeight(),0,GL_RGBA,GL_UNSIGNED_BYTE,buf);

TheglTextImage2Dmethodhasthefollowingparameters:

target:Specifiesthetargettexture(itstype).Inthiscase:GL_TEXTURE_2D.level:Specifiesthelevel-of-detailnumber.Level0isthebaseimagelevel.Levelnisthenthmipmapreductionimage.Moreonthislater.internalformat:Specifiesthenumberofcolourcomponentsinthetexture.width:Specifiesthewidthofthetextureimage.height:Specifiestheheightofthetextureimage.border:Thisvaluemustbezero.format:Specifiestheformatofthepixeldata:RGBAinthiscase.type:Specifiesthedatatypeofthepixeldata.Weareusingunsignedbytesforthis.data:Thebufferthatstoresourdata.

InsomecodesnippetsthatyoumayfindyowwillprobablyseethatfilteringparametersaresetupbeforecallingtheglTextImage2Dmethod.Filteringreferstohowtheimagewillbedrawnwhenscalingandhowpixelswillbeinterpolated.

Ifthoseparametersarenotsetthetexturewillnotbedisplayed.SobeforetheglTextImage2Dmethodyoucouldseesomethinglikethis:

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);

Thisparameterbasicallysaysthatwhenapixelisdrawnwithnodirectonetooneassociationtoatexturecoordinateitwillpickthenearesttexturecoordinatepoint.

Textures

67

Page 68: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Bythismomentwewillnotsetupthoseparameters.Insteadwewillgenerateamipmap.Amipmapisadecreasingresolutionsetofimagesgeneratedfromahighdetailedtexture.Thoselowerresolutionimageswillbeusedautomaticallywhenourobjectisscaled.

Inordertogeneratemipmapswejustneedtosetthefollowingline(inthiscaseaftertheglTextImage2Dmethod:

glGenerateMipmap(GL_TEXTURE_2D);

Andthat’sall,wehavesuccessfullyloadedourtexture.Nowweneedtouseit.AswesaidbeforeweneedtopasstexturecoordinatesasanotherVBO.SowewillmodifyourMeshclasstoacceptanarrayoffloats,thatcontainstexturecoordinates,insteadofthecolour(wecouldhavecoloursandtexturebutinordertosimplifyitwewillstripcoloursoff).Ourconstructorwillbelikethis:

publicMesh(float[]positions,float[]textCoords,int[]indices,

Texturetexture)

ThetexturecoordinatesVBOiscreatedinthesamewayasthecolourone.Theonlydifferenceisthatithastwoelementsinsteadofthree:

vboId=glGenBuffers();

vboIdList.add(vboId);

textCoordsBuffer=MemoryUtil.memAllocFloat(textCoords.length);

textCoordsBuffer.put(textCoords).flip();

glBindBuffer(GL_ARRAY_BUFFER,vboId);

glBufferData(GL_ARRAY_BUFFER,textCoordsBuffer,GL_STATIC_DRAW);

glVertexAttribPointer(1,2,GL_FLOAT,false,0,0);

Nowweneedtousethosetexturesinourshader.Inthevertexshaderwehavechangedtheseconduniformparameterbecausenowit’savec2(wealsochangedtheuniformname,soremembertochangeitintheRendererclass).Thevertexshader,asinthecolourcase,justpassesthetexturecoordinatestobeusedbythefragmentshader.

Textures

68

Page 69: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

#version330

layout(location=0)invec3position;

layout(location=1)invec2texCoord;

outvec2outTexCoord;

uniformmat4worldMatrix;

uniformmat4projectionMatrix;

voidmain()

{

gl_Position=projectionMatrix*worldMatrix*vec4(position,1.0);

outTexCoord=texCoord;

}

Inthefragmentshaderwemustusethosetexturecoordinatesinordertosetthepixelcolours:

#version330

invec2outTexCoord;

outvec4fragColor;

uniformsampler2Dtexture_sampler;

voidmain()

{

fragColor=texture(texture_sampler,outTexCoord);

}

Beforeanalyzingthecodelet’sclarifysomeconcepts.Agraphicscardhasseveralspacesorslotstostoretextures.Eachofthesespacesiscalledatextureunit.Whenweareworkingwithtextureswemustsetthetextureunitthatwewanttoworkwith.Asyoucanseewehaveanewuniformnamedtexture_sampler.Thatuniformhasasampler2Dtypeandwillholdthevalueofthetextureunitthatwewanttoworkwith.

Inthemainfunctionweusethetexturelookupfunctionnamedtexture.Thisfunctiontakestwoarguments:asamplerandatexturecoordinateandwillreturnthecorrectcolour.Thesampleruniformallowustodomulti-texturing.Wewillnotcoverthattopicrightnowbutwewilltrytopreparethecodetoadditeasilylateron.

Thus,inourShaderProgramclasswewillcreateanewmethodthatallowsustosetanintegervalueforauniform:

Textures

69

Page 70: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicvoidsetUniform(StringuniformName,intvalue){

glUniform1i(uniforms.get(uniformName),value);

}

IntheinitmethodoftheRendererclasswewillcreateanewuniform:

shaderProgram.createUniform("texture_sampler");

Also,intherendermethodofourRendererclasswewillsettheuniformvalueto0.(Wearenotusingseveraltexturesrightnowsowearejustusingunit0).

shaderProgram.setUniform("texture_sampler",0);

FinallywejustneedtochangetherendermethodoftheMeshclasstousethetexture.Atthebeginningofthatmethodweputthefollowinglines:

//Activatefirsttextureunit

glActiveTexture(GL_TEXTURE0);

//Bindthetexture

glBindTexture(GL_TEXTURE_2D,texture.getId());

Webasicallyarebindingthetextureidentifiedbytexture.getId()tothetextureunit0.

Rightnow,wehavejustmodifiedourcodebasetosupporttextures.Nowweneedtosetuptexturecoordinatesforour3Dcube.Ourtextureimagefilewillbesomethinglikethis:

Inour3Dmodelwehaveeightvertices.Let’sseehowthiscanbedone.Let’sfirstdefinethefrontfacetexturecoordinatesforeachvertex.

Textures

70

Page 71: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Vertex TextureCoordinate

V0 (0.0,0.0)

V1 (0.0,0.5)

V2 (0.5,0.5)

V3 (0.5,0.0)

Now,let’sdefinethetexturemappingofthetopface.

Vertex TextureCoordinate

V4 (0.0,0.5)

V5 (0.5,0.5)

V0 (0.0,1.0)

V3 (0.5,1.0)

Asyoucanseewehaveaproblem,weneedtosetupdifferenttexturecoordinatesforthesamevertices(V0andV3).Howcanwesolvethis?Theonlywaytosolveitistorepeatsomeverticesandassociatedifferenttexturecoordinates.Forthetopfaceweneedtorepeatthefourverticesandassignthemthecorrecttexturecoordinates.

Textures

71

Page 72: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Sincethefront,backandlateralfacesusethesametexturewewillnotneedtorepeatallofthesevertices.Youhavethecompletedefinitioninthesourcecode,butweneededtomovefrom8pointsto20.Thefinalresultislikethis.

Inthenextchapterswewilllearnhowtoloadmodelsgeneratedby3Dmodelingtoolssowewon’tneedtodefinebyhandthepositionsandtexturecoordinates(whichbytheway,wouldbeimpracticalformorecomplexmodels).

Textures

72

Page 73: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

CameraInthischapterwewilllearnhowtomoveinsidearendered3Dscene,thiscapabilityislikehavingacamerathatcantravelinsidethe3Dworldandinfactisthetermusedtorefertoit.

ButifyoutrytosearchforspecificcamerafunctionsinOpenGLyouwilldiscoverthatthereisnocameraconcept,orinotherwordsthecameraisalwaysfixed,centeredinthe(0,0,0)positionatthecenterofthescreen.

Sowhatwewilldoisasimulationthatgivesustheimpressionthatwehaveacameracapableofmovinginsidethe3Dscene.Howdoweachievethis?Well,ifwecannotmovethecamerathenwemustmovealltheobjectscontainedinour3Dspaceatonce.Inotherwords,ifwecannotmoveacamerawewillmovethewholeworld.

So,supposethatwewouldliketomovethecamerapositionalongthezaxisfromastartingposition(Cx,Cy,Cz)toaposition(Cx,Cy,Cz+dz)togetclosertotheobjectwhichisplacedatthecoordinates(Ox,Oy,Oz).

Whatwewillactuallydoismovetheobject(alltheobjectsinour3Dspaceindeed)intheoppositedirectionthatthecamerashouldmove.Thinkaboutitliketheobjectsbeingplacedinatreadmill.

Acameracanbedisplacedalongthethreeaxis(x,yandz)andalsocanrotatealongthem(roll,pitchandyaw).

Camera

73

Page 74: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Sobasicallywhatwemustdoistobeabletomoveandrotatealloftheobjectsofour3Dworld.Howarewegoingtodothis?Theansweristoapplyanothertransformationthatwilltranslatealloftheverticesofalloftheobjectsintheoppositedirectionofthemovementofthecameraandthatwillrotatethemaccordingtothecamerarotation.Thiswillbedoneofcoursewithanothermatrix,thesocalledviewmatrix.Thismatrixwillfirstperformthetranslationandthentherotationalongtheaxis.

Let'sseehowwecanconstructthatmatrix.Ifyourememberfromthetransformationschapterourtransformationequationwaslikethis:

Transf = [ProjMatrix] ⋅ [TranslationMatrix] ⋅ [RotationMatrix] ⋅ [ScaleMatrix] = [ProjM

Theviewmatrixshouldbeappliedbeforemultiplyingbytheprojectionmatrix,soourequationshouldbenowlikethis:

Transf = [ProjMatrix] ⋅ [V iewMatrix] ⋅ [TranslationMatrix] ⋅ [RotationMatrix] ⋅ [ScaleM

Nowwehavethreematrices,let'sthinkalittlebitaboutthelifecyclesofthosematrices.Theprojectionmatrixshouldnotchangeverymuchwhileourgameisrunning,intheworstcaseitmaychangeonceperrendercall.Theviewmatrixmaychangeonceperrendercallifthecameramoves.TheworldmatrixchangesonceperGameIteminstance,soitwillchangeseveraltimesperrendercall.

So,howmanymatricesshouldwepushtoorvertexshader?Youmayseesomecodethatusesthreeuniformsforeachofthosematrices,butinprinciplethemostefficientapproachwouldbetocombinetheprojectionandtheviewmatrices,let’scallitpvmatrix,andpushtheworldandthepvmatricestoourshader.Withthisapproachwewouldhavethepossibilitytoworkwithworldcoordinatesandwouldbeavoidingsomeextramultiplications.

Actually,themostconvenientapproachistocombinetheviewandtheworldmatrix.Whythis?Becauserememberthatthewholecameraconceptisatrick,whatwearedoingispushingthewholeworldtosimulateworlddisplacementandtoshowonlyasmallportionofthe3Dworld.Soifweworkdirectlywithworldcoordinateswemaybeworkingwithworldcoordinatesthatarefarawayfromtheoriginandwemayincurinsomeprecisionproblems.

Camera

74

Page 75: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Ifweworkinwhat’scalledthecameraspacewewillbeworkingwithpointsthat,althougharefarawayfromtheworldorigin,areclosertothecamera.Thematrixthatresultsofthecombinationoftheviewandtheworldmatrixisoftencalledasthemodelviewmatrix.

Solet’sstartmodifyingourcodetosupportacamera.FirstofallwewillcreateanewclasscalledCamerawhichwillholdthepositionandrotationstateofourcamera.Thisclasswillprovidemethodstosetthenewpositionorrotationstate(setPositionorsetRotation)ortoupdatethosevalueswithanoffsetuponthecurrentstate(movePositionandmoveRotation)

packageorg.lwjglb.engine.graph;

importorg.joml.Vector3f;

publicclassCamera{

privatefinalVector3fposition;

privatefinalVector3frotation;

publicCamera(){

position=newVector3f(0,0,0);

rotation=newVector3f(0,0,0);

}

publicCamera(Vector3fposition,Vector3frotation){

this.position=position;

this.rotation=rotation;

}

publicVector3fgetPosition(){

returnposition;

}

publicvoidsetPosition(floatx,floaty,floatz){

position.x=x;

position.y=y;

position.z=z;

}

publicvoidmovePosition(floatoffsetX,floatoffsetY,floatoffsetZ){

if(offsetZ!=0){

position.x+=(float)Math.sin(Math.toRadians(rotation.y))*-1.0f*offset

Z;

position.z+=(float)Math.cos(Math.toRadians(rotation.y))*offsetZ;

}

if(offsetX!=0){

position.x+=(float)Math.sin(Math.toRadians(rotation.y-90))*-1.0f*o

ffsetX;

position.z+=(float)Math.cos(Math.toRadians(rotation.y-90))*offsetX;

Camera

75

Page 76: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

}

position.y+=offsetY;

}

publicVector3fgetRotation(){

returnrotation;

}

publicvoidsetRotation(floatx,floaty,floatz){

rotation.x=x;

rotation.y=y;

rotation.z=z;

}

publicvoidmoveRotation(floatoffsetX,floatoffsetY,floatoffsetZ){

rotation.x+=offsetX;

rotation.y+=offsetY;

rotation.z+=offsetZ;

}

}

NextintheTransformationclasswewillholdanewmatrixtoholdthevaluesoftheviewmatrix.

privatefinalMatrix4fviewMatrix;

Wewillalsoprovideamethodtoupdateitsvalue.Liketheprojectionmatrixthismatrixwillbethesameforalltheobjectstoberenderedinarendercycle.

publicMatrix4fgetViewMatrix(Cameracamera){

Vector3fcameraPos=camera.getPosition();

Vector3frotation=camera.getRotation();

viewMatrix.identity();

//Firstdotherotationsocamerarotatesoveritsposition

viewMatrix.rotate((float)Math.toRadians(rotation.x),newVector3f(1,0,0))

.rotate((float)Math.toRadians(rotation.y),newVector3f(0,1,0));

//Thendothetranslation

viewMatrix.translate(-cameraPos.x,-cameraPos.y,-cameraPos.z);

returnviewMatrix;

}

Asyoucanseewefirstneedtodotherotationandthenthetranslation.Ifwedotheoppositewewouldnotberotatingalongthecamerapositionbutalongthecoordinatesorigin.PleasealsonotethatinthemovePositionmethodoftheCameraclasswejustnotsimplyincreasethecamerapositionbyandoffset.Wealsotakeintoconsiderationthe

Camera

76

Page 77: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

rotationalongtheyaxis,theyaw,inordertocalculatethefinalposition.Ifwewouldjustincreasethecamerapositionbytheoffsetthecamerawillnotmoveinthedirectionitsfacing.

Besideswhatismentionedabove,wedonothavehereafullfreeflycamera(forinstance,ifwerotatealongthexaxisthecameradoesnotmoveupordowninthespacewhenwemoveitforward).Thiswillbedoneinlaterchapterssinceisalittlebitmorecomplex.

FinallywewillremovethepreviousmethodgetWorldMatrixandaddanewonecalledgetModelViewMatrix.

publicMatrix4fgetModelViewMatrix(GameItemgameItem,Matrix4fviewMatrix){

Vector3frotation=gameItem.getRotation();

modelViewMatrix.identity().translate(gameItem.getPosition()).

rotateX((float)Math.toRadians(-rotation.x)).

rotateY((float)Math.toRadians(-rotation.y)).

rotateZ((float)Math.toRadians(-rotation.z)).

scale(gameItem.getScale());

Matrix4fviewCurr=newMatrix4f(viewMatrix);

returnviewCurr.mul(modelViewMatrix);

}

ThegetModelViewMatrixmethodwillbecalledpereachGameIteminstancesowemustworkoveracopyoftheviewmatrixsotransformationsdonotgetaccumulatedineachcall(RememberthatMatrix4fclassisnotimmutable).

IntherendermethodoftheRendererclasswejustneedtoupdatetheviewmatrixaccordingtothecameravalues,justaftertheprojectionmatrixisalsoupdated.

//UpdateprojectionMatrix

Matrix4fprojectionMatrix=transformation.getProjectionMatrix(FOV,window.getWidth(),

window.getHeight(),Z_NEAR,Z_FAR);

shaderProgram.setUniform("projectionMatrix",projectionMatrix);

//UpdateviewMatrix

Matrix4fviewMatrix=transformation.getViewMatrix(camera);

shaderProgram.setUniform("texture_sampler",0);

//RendereachgameItem

for(GameItemgameItem:gameItems){

//Setmodelviewmatrixforthisitem

Matrix4fmodelViewMatrix=transformation.getModelViewMatrix(gameItem,viewMatrix)

;

shaderProgram.setUniform("modelViewMatrix",modelViewMatrix);

//Renderthemesforthisgameitem

gameItem.getMesh().render();

}

Camera

77

Page 78: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Andthat’sall,ourbasecodesupportstheconceptofacamera.Nowweneedtouseit.Wecanchangethewaywehandletheinputandupdatethecamera.Wewillsetthefollowingcontrols:

Keys“A”and“D”tomovethecameratotheleftandright(xaxis)respectively.Keys“W”and“S”tomovethecameraforwardandbackwards(zaxis)respectively.Keys“Z”and“X”tomovethecameraupanddown(yaxis)respectively.

Wewillusethemousepositiontorotatethecameraalongthexandyaxiswhentherightbuttonofthemouseispressed.Asyoucanseewewillbeusingthemouseforthefirsttime.WewillcreateanewclassnamedMouseInputthatwillencapsulatemouseaccess.Here’sthecodeforthatclass.

packageorg.lwjglb.engine;

importorg.joml.Vector2d;

importorg.joml.Vector2f;

importstaticorg.lwjgl.glfw.GLFW.*;

publicclassMouseInput{

privatefinalVector2dpreviousPos;

privatefinalVector2dcurrentPos;

privatefinalVector2fdisplVec;

privatebooleaninWindow=false;

privatebooleanleftButtonPressed=false;

privatebooleanrightButtonPressed=false;

publicMouseInput(){

previousPos=newVector2d(-1,-1);

currentPos=newVector2d(0,0);

displVec=newVector2f();

}

publicvoidinit(Windowwindow){

glfwSetCursorPosCallback(window.getWindowHandle(),(windowHandle,xpos,ypos)

->{

currentPos.x=xpos;

currentPos.y=ypos;

});

glfwSetCursorEnterCallback(window.getWindowHandle(),(windowHandle,entered)-

>{

inWindow=entered;

});

glfwSetMouseButtonCallback(window.getWindowHandle(),(windowHandle,button,ac

Camera

78

Page 79: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

tion,mode)->{

leftButtonPressed=button==GLFW_MOUSE_BUTTON_1&&action==GLFW_PRESS;

rightButtonPressed=button==GLFW_MOUSE_BUTTON_2&&action==GLFW_PRESS

;

});

}

publicVector2fgetDisplVec(){

returndisplVec;

}

publicvoidinput(Windowwindow){

displVec.x=0;

displVec.y=0;

if(previousPos.x>0&&previousPos.y>0&&inWindow){

doubledeltax=currentPos.x-previousPos.x;

doubledeltay=currentPos.y-previousPos.y;

booleanrotateX=deltax!=0;

booleanrotateY=deltay!=0;

if(rotateX){

displVec.y=(float)deltax;

}

if(rotateY){

displVec.x=(float)deltay;

}

}

previousPos.x=currentPos.x;

previousPos.y=currentPos.y;

}

publicbooleanisLeftButtonPressed(){

returnleftButtonPressed;

}

publicbooleanisRightButtonPressed(){

returnrightButtonPressed;

}

}

TheMouseInputclassprovidesaninitmethodwhichshouldbecalledduringtheinitializationphaseandregistersasetofcallbackstoprocessmouseevents:

glfwSetCursorPosCallback:Registersacallbackthatwillbeinvokedwhenthemouseismoved.glfwSetCursorEnterCallback:Registersacallbackthatwillbeinvokedwhenthemouseentersourwindow.Wewillbereceivingmouseeventsevenifthemouseisnotinourwindow.Weusethiscallbacktotrackwhenthemouseisinourwindow.glfwSetMouseButtonCallback:Registersacallbackthatwillbeinvokedwhenamousebuttonispressed.

Camera

79

Page 80: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

TheMouseInputclassprovidesaninputmethodwhichshouldbecalledwhengameinputisprocessed.ThismethodcalculatesthemousedisplacementfromthepreviouspositionandstoresitintoVector2fdisplVecvariablesoitcanbeusedbyourgame.

TheMouseInputclasswillbeinstantiatedinourGameEngineclassandwillbepassedasaparameterintheinitandupdatemethodsofthegameimplementation(soweneedtochangetheinterfaceaccordingly).

voidinput(Windowwindow,MouseInputmouseInput);

voidupdate(floatinterval,MouseInputmouseInput);

ThemouseinputwillbeprocessedintheinputmethodoftheGameEngineclassbeforepassingthecontroltothegameimplementation.

protectedvoidinput(){

mouseInput.input(window);

gameLogic.input(window,mouseInput);

}

NowwearereadytoupdateourDummyGameclasstoprocessthekeyboardandmouseinput.Theinputmethodofthatclasswillbelikethis:

@Override

publicvoidinput(Windowwindow,MouseInputmouseInput){

cameraInc.set(0,0,0);

if(window.isKeyPressed(GLFW_KEY_W)){

cameraInc.z=-1;

}elseif(window.isKeyPressed(GLFW_KEY_S)){

cameraInc.z=1;

}

if(window.isKeyPressed(GLFW_KEY_A)){

cameraInc.x=-1;

}elseif(window.isKeyPressed(GLFW_KEY_D)){

cameraInc.x=1;

}

if(window.isKeyPressed(GLFW_KEY_Z)){

cameraInc.y=-1;

}elseif(window.isKeyPressed(GLFW_KEY_X)){

cameraInc.y=1;

}

}

ItjustupdatesaVector3fvariablenamedcameraIncwhichholdsthecameradisplacementthatshouldbeapplied.TheupdatemethodoftheDummyGameclassmodifiesthecamerapositionandrotation

Camera

80

Page 81: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

accordingtotheprocesseskeyandmouseevents.

@Override

publicvoidupdate(floatinterval,MouseInputmouseInput){

//Updatecameraposition

camera.movePosition(cameraInc.x*CAMERA_POS_STEP,

cameraInc.y*CAMERA_POS_STEP,

cameraInc.z*CAMERA_POS_STEP);

//Updatecamerabasedonmouse

if(mouseInput.isRightButtonPressed()){

Vector2frotVec=mouseInput.getDisplVec();

camera.moveRotation(rotVec.x*MOUSE_SENSITIVITY,rotVec.y*MOUSE_SENSITIVITY

,0);

}

}

Nowwecanaddmorecubestoourworld,scalethemsetthemupinaspecificlocationandplaywithournewcamera.Asyoucanseeallthecubessharethesamemesh.

GameItemgameItem1=newGameItem(mesh);

gameItem1.setScale(0.5f);

gameItem1.setPosition(0,0,-2);

GameItemgameItem2=newGameItem(mesh);

gameItem2.setScale(0.5f);

gameItem2.setPosition(0.5f,0.5f,-2);

GameItemgameItem3=newGameItem(mesh);

gameItem3.setScale(0.5f);

gameItem3.setPosition(0,0,-2.5f);

GameItemgameItem4=newGameItem(mesh);

gameItem4.setScale(0.5f);

gameItem4.setPosition(0.5f,0,-2.5f);

gameItems=newGameItem[]{gameItem1,gameItem2,gameItem3,gameItem4};

Youwillgetsomethinglikethis.

Camera

81

Page 82: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Camera

82

Page 83: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

LoadingmorecomplexmodelsInthischapterwewilllearntoloadmorecomplexmodelsdefinedinexternalfiles.Thosemodelswillbecreatedby3Dmodellingtools(suchasBlender).Uptonowwewerecreatingourmodelsbyhanddirectlycodingthearraysthatdefinetheirgeometry,inthischapterwewilllearnhowtoloadmodelsdefinedinOBJformat.

OBJ(or.OBJ)isageometrydefinitionopenfileformatdevelopedbyWavefrontTechnologieswhichhasbeenwidelyadopted.AnOBJfiledefinesthevertices,texturecoordinatesandpolygonsthatcomposea3Dmodel.It’sarelativeeasyformattoparsesinceistextbasedandeachlinedefinesanelement(avertex,atexturecoordinate,etc.).

Inan.objfileeachlinestartswithatokenwithidentifiesthetypeofelement:

Commentsarelineswhichstartwith#.Thetoken“v”definesageometricvertexwithcoordinates(x,y,z,w).Example:v0.1550.2110.321.0.Thetoken“vn”definesavertexnormalwithcoordinates(x,y,z).Example:vn0.710.210.82.Moreonthislater.Thetoken“vt”definesatexturecoordinate.Example:vt0.5001.Thetoken“f”definesaface.Withtheinformationcontainedintheselineswewillconstructourindicesarray.Wewillhandleonlythecasewerefacesareexportedastriangles.Itcanhaveseveralvariants:

Itcandefinejustvertexpositions(fv1v2v3).Example:f631.Inthiscasethistriangleisdefinedbythegeometricverticesthatoccupypositions6,3aand1.(Vertexindicesalwaysstartsby1).Itcandefinevertexpositions,texturecoordinatesandnormals(fv1/t1/n1v2/t2/n2V3/t3/n3).Example:f6/4/13/5/37/6/5.Thefirstblockis“6/4/1”anddefinesthecoordinates,texturecoordinatesandnormalvertex.Whatyouseehereistheposition,sowearesaying:pickthegeometricvertexnumbersix,thetexturecoordinatenumber4andthevertexnormalnumberone.

OBJformathasmanymoreentrytypes(likeonetogrouppolygons,definingmaterials,etc.).Bynowwewillsticktothissubset,ourOBJloaderwillignoreotherentrytypes.

Butwhatisanormal?Let’sdefineitfirst.Whenyouhaveaplaneitsnormalisavectorperpendiculartothatplanewhichhasalengthequaltoone.

Loadingmorecomplexmodels

83

Page 84: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Asyoucanseeinthefigureaboveaplanecanhavetwonormals,whichoneshouldweuse?Normalsin3Dgraphicsareusedforlightning,soweshouldchosethenormalwhichisorientedtowardsthesourceoflight.Inotherwordsweshouldchoosethenormalthatpointsoutfromtheexternalfaceofourmodel.

Whenwehavea3Dmodel,itiscomposedbypolygons,trianglesinourcase.Eachtriangleiscomposedbythreevertices.TheNormalvectorforatrianglewillbethevectorperpendiculartothetrianglesurfacewhichhasalengthequaltoone.

Avertexnormalisassociatedtoaspecificvertexandisthecombinationofthenormalsofthesurroundingtriangles(ofcourseitslengthisequaltoone).Hereyoucanseethevertexmodelsofa3Dmesh(takenfromWikipedia)

Normalswillbeusedforlighting.

Solet’sstartcreatingourOBJloader.FirstofallwewillmodifyourMeshclasssincenowit’smandatorytouseatexture.Someoftheobjfilesthatwemayloadmaynotdefinetexturecoordinatesandwemustbeabletorenderthemusingacolourinsteadofatexture.Inthiscasethefacedefinitionwillbelikethis:“fv//n”.

OurMeshclasswillhaveanewattributenamedcolour

privateVector3fcolour;

Loadingmorecomplexmodels

84

Page 85: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

AndtheconstructorwillnotrequireaTextureinstanceanymore.Insteadwewillprovidegettersandsettersfortextureandcolourattributes.

publicMesh(float[]positions,float[]textCoords,float[]normals,int[]indices){

Ofcourse,intherenderandcleanupmethodswemustcheckiftextureattributeisnotnullbeforeusingit.Asyoucanseeintheconstructorwepassnowanewarrayoffloatsnamednormals.Howdoweusenormalsforrendering?TheansweriseasyitwillbejustanotherVBOinsideourVAO,soweneedtoaddthiscode.

//VertexnormalsVBO

vboId=glGenBuffers();

vboIdList.add(vboId);

vecNormalsBuffer=MemoryUtil.memAllocFloat(normals.length);

vecNormalsBuffer.put(normals).flip();

glBindBuffer(GL_ARRAY_BUFFER,vboId);

glBufferData(GL_ARRAY_BUFFER,vecNormalsBuffer,GL_STATIC_DRAW);

glVertexAttribPointer(2,3,GL_FLOAT,false,0,0);

InourrendermethodwemustenablethisVBObeforerenderinganddisableitwhenwehavefinished.

//Drawthemesh

glBindVertexArray(getVaoId());

glEnableVertexAttribArray(0);

glEnableVertexAttribArray(1);

glEnableVertexAttribArray(2);

glDrawElements(GL_TRIANGLES,getVertexCount(),GL_UNSIGNED_INT,0);

//Restorestate

glDisableVertexAttribArray(0);

glDisableVertexAttribArray(1);

glDisableVertexAttribArray(2);

glBindVertexArray(0);

glBindTexture(GL_TEXTURE_2D,0);

NowthatwehavefinishedthemodificationsintheMeshclasswecanchangeourcodetouseeithertexturecoordinatesorafixedcolour.Thusweneedtomodifyourfragmentshaderlikethis:

Loadingmorecomplexmodels

85

Page 86: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

#version330

invec2outTexCoord;

outvec4fragColor;

uniformsampler2Dtexture_sampler;

uniformvec3colour;

uniformintuseColour;

voidmain()

{

if(useColour==1)

{

fragColor=vec4(colour,1);

}

else

{

fragColor=texture(texture_sampler,outTexCoord);

}

}

Asyoucanseewehavecreatetwonewuniforms:

colour:Willcontainthebasecolour.useColour:It’saflagthatwewillsetto1whenwedon’twanttousetextures.

IntheRendererclassweneedtocreatethosetwouniforms.

//Createuniformfordefaultcolourandtheflagthatcontrolsit

shaderProgram.createUniform("colour");

shaderProgram.createUniform("useColour");

Andlikeanyotheruniform,intherendermethodoftheRendererclassweneedtosetthevaluesforthisuniformsforeachgameItem.

for(GameItemgameItem:gameItems){

Meshmesh=gameItem.getMesh();

//Setmodelviewmatrixforthisitem

Matrix4fmodelViewMatrix=transformation.getModelViewMatrix(gameItem,viewMatrix)

;

shaderProgram.setUniform("modelViewMatrix",modelViewMatrix);

//Renderthemesforthisgameitem

shaderProgram.setUniform("colour",mesh.getColour());

shaderProgram.setUniform("useColour",mesh.isTextured()?0:1);

mesh.render();

}

Loadingmorecomplexmodels

86

Page 87: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

NowwecancreateanewclassnamedOBJLoaderwhichparsesOBJfilesandwillcreateaMeshinstancewiththedatacontainedinit.YoumayfindsomeotherimplementationsinthewebthatmaybeabitmoreefficientthanthisonebutIthinkthisversionissimplertounderstand.Thiswillbeanutilityclasswhichwillhaveastaticmethodlikethis:

publicstaticMeshloadMesh(StringfileName)throwsException{

Theparameterfilenamespecifiesthenameofthefile,thatmustbeintheCLASSPATHthatcontainstheOBJmodel.

Thefirstthingthatwewilldointhatmethodistoreadthefilecontentsandstoreallthelinesinanarray.Thenwecreateseveralliststhatwillholdthevertices,thetexturecoordinates,thenormalsandthefaces.

List<String>lines=Utils.readAllLines(fileName);

List<Vector3f>vertices=newArrayList<>();

List<Vector2f>textures=newArrayList<>();

List<Vector3f>normals=newArrayList<>();

List<Face>faces=newArrayList<>();

Thenwillparseeachlineanddependingonthestartingtokenwillgetavertexposition,atexturecoordinate,avertexnormalorafacedefinition.Attheendwewillneedtoreorderthatinformation.

Loadingmorecomplexmodels

87

Page 88: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

for(Stringline:lines){

String[]tokens=line.split("\\s+");

switch(tokens[0]){

case"v":

//Geometricvertex

Vector3fvec3f=newVector3f(

Float.parseFloat(tokens[1]),

Float.parseFloat(tokens[2]),

Float.parseFloat(tokens[3]));

vertices.add(vec3f);

break;

case"vt":

//Texturecoordinate

Vector2fvec2f=newVector2f(

Float.parseFloat(tokens[1]),

Float.parseFloat(tokens[2]));

textures.add(vec2f);

break;

case"vn":

//Vertexnormal

Vector3fvec3fNorm=newVector3f(

Float.parseFloat(tokens[1]),

Float.parseFloat(tokens[2]),

Float.parseFloat(tokens[3]));

normals.add(vec3fNorm);

break;

case"f":

Faceface=newFace(tokens[1],tokens[2],tokens[3]);

faces.add(face);

break;

default:

//Ignoreotherlines

break;

}

}

returnreorderLists(vertices,textures,normals,faces);

Beforetalkingaboutreorderinglet’sseehowfacedefinitionsareparsed.WehavecreateaclassnamedFacewhichparsesthedefinitionofaface.AFaceiscomposedbyalistofindicesgroups,inthiscasesincewearedealingwithtriangleswewillhavethreeindicesgroup).

Loadingmorecomplexmodels

88

Page 89: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

WewillcreateanotherinnerclassnamedIndexGroupthatwillholdtheinformationforagroup.

protectedstaticclassIdxGroup{

publicstaticfinalintNO_VALUE=-1;

publicintidxPos;

publicintidxTextCoord;

publicintidxVecNormal;

publicIdxGroup(){

idxPos=NO_VALUE;

idxTextCoord=NO_VALUE;

idxVecNormal=NO_VALUE;

}

}

OurFaceclasswillbelikethis.

Loadingmorecomplexmodels

89

Page 90: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

protectedstaticclassFace{

/**

*ListofidxGroupgroupsforafacetriangle(3verticesperface).

*/

privateIdxGroup[]idxGroups=newIdxGroup[3];

publicFace(Stringv1,Stringv2,Stringv3){

idxGroups=newIdxGroup[3];

//Parsethelines

idxGroups[0]=parseLine(v1);

idxGroups[1]=parseLine(v2);

idxGroups[2]=parseLine(v3);

}

privateIdxGroupparseLine(Stringline){

IdxGroupidxGroup=newIdxGroup();

String[]lineTokens=line.split("/");

intlength=lineTokens.length;

idxGroup.idxPos=Integer.parseInt(lineTokens[0])-1;

if(length>1){

//Itcanbeemptyiftheobjdoesnotdefinetextcoords

StringtextCoord=lineTokens[1];

idxGroup.idxTextCoord=textCoord.length()>0?Integer.parseInt(textCoor

d)-1:IdxGroup.NO_VALUE;

if(length>2){

idxGroup.idxVecNormal=Integer.parseInt(lineTokens[2])-1;

}

}

returnidxGroup;

}

publicIdxGroup[]getFaceVertexIndices(){

returnidxGroups;

}

}

Whenparsingfaceswemayseeobjectswithnotexturesbutwithvectornormals,inthiscaseafacelinecouldbelikethisf11//117//113//1,soweneedtodetectthosecases.

Nowwecantalkabouthowtoreordertheinformationwehave.Finallyweneedtoreorderthatinformation.OurMeshclassexpectsfourarrays,oneforpositioncoordinates,otherfortexturecoordinates,otherforvectornormalsandanotheronefortheindices.Thefirstthreearraysshallhavethesamenumberofelementssincetheindicesarrayisunique(notethatthesamenumberofelementsdoesnotimplythesamelength.Positionelements,vertexcoordinates,are3Dandarecomposedbythreefloats.Textureelements,texture

Loadingmorecomplexmodels

90

Page 91: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

coordinates,are2Dandthusarecomposedbytwofloats).OpenGLdoesnotallowustodefinedifferentindicesarrayspertypeofelement(ifso,wewouldnotneedtorepeatverticeswhileapplyingtextures).

WhenyouopenanOBJlineyouwillfirstprobablyseethatthelistthatholdstheverticespositionshasahighernumberofelementsthantheliststhatholdthetexturecoordinatesandthenumberofvertices.That’ssomethingthatweneedtosolve.Let’suseasimpleexamplewhichdefinesaquadwithatexturewithapixelheight(justforillustrationpurposes).TheOBJfilemaybelikethis(don’tpaytoomuchattentionaboutthenormalscoordinatesinceit’sjustforillustrationpurpose).

v000

v100

v110

v010

vt01

vt11

vn001

f1/2/12/1/13/2/1

f1/2/13/2/14/1/1

Whenwehavefinishedparsingthefilewehavethefollowinglists(thenumberofeachelementisitspositioninthefileuponorderofappearance)

Nowwewillusethefacedefinitionstoconstructthefinalarraysincludingtheindices.Athingtotakeintoconsiderationisthattheorderinwhichtexturescoordinatesandvectornormalsaredefineddoesnotcorrespondtotheordersinwhichverticesaredefined.Ifthesizeofthelistswouldbethesameandtheywereordered,facedefinitionlineswouldonlyjustneedtoincludeanumberpervertex.

Soweneedtoorderthedataandsetupaccordinglytoourneeds.Thefirstthingthatwemustdoiscreatethreearraysandonelist,oneforthevertices,otherforthetexturecoordinates,otherforthenormalsandthelistfortheindices.Aswehavesaidbeforethethreearrayswillhavethesamenumberofelements(equaltothenumberofvertices).Theverticesarraywillhaveacopyofthelistofvertices.

Loadingmorecomplexmodels

91

Page 92: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Nowwestartprocessingthefaces.Thefirstindexgroupofthefirstfaceis1/2/1.Weusethefirstindexintheindexgroup,theonethatdefinesthegeometricvertextoconstructtheindexlist.Let’snameitasposIndex.Ourfaceisspecifiyingthattheweshouldaddtheindexoftheelementthatoccupiesthefirstpositionintoourindiceslist.SoweputthevalueofposIndexminusoneintotheindicesList(wemustsubstract1sincearraysstartat0butOBJfileformatassumesthattheystartat1).

ThenweusetherestoftheindicesoftheindexgrouptosetupthetexturesArrayandnormalsArray.Thesecondindex,intheindexgroup,is2,sowhatwemustdoisputthesecondtexturecoordinateinthesamepositionastheonethatoccupiesthevertexdesignatedposIndex(V1).

Loadingmorecomplexmodels

92

Page 93: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thenwepickthethirdindex,whichis1,sowhatwemustdoisputthefirstvectornormalcoordinateinthesamepositionastheonethatoccupiesthevertexdesignatedposIndex(V1).

Afterwehaveprocessedthefirstfacethearraysandlistswillbelikethis.

Afterwehaveprocessedthesecondfacethearraysandlistswillbelikethis.

Thesecondfacedefinesverticeswhichalreadyhavebeenassigned,buttheycontainthesamevalues,sothere’snoprobleminreprocessingthis.Ihopetheprocesshasbeenclarifiedenough,itcanbesometrickyuntilyougetit.Themethodsthatreorderthedataaresetbelow.Keepinmindthatwhatwehavearefloatarrayssowemusttransformthose

Loadingmorecomplexmodels

93

Page 94: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

arraysofvertices,texturesandnormalsintoarraysoffloats.Sothelengthofthesearrayswillbethelengthoftheverticeslistmultipliedbythenumberthreeinthecaseofverticesandnormalsormultipliedbytwointhecaseoftexturecoordinates.

Loadingmorecomplexmodels

94

Page 95: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatestaticMeshreorderLists(List<Vector3f>posList,List<Vector2f>textCoordList,

List<Vector3f>normList,List<Face>facesList){

List<Integer>indices=newArrayList();

//Createpositionarrayintheorderithasbeendeclared

float[]posArr=newfloat[posList.size()*3];

inti=0;

for(Vector3fpos:posList){

posArr[i*3]=pos.x;

posArr[i*3+1]=pos.y;

posArr[i*3+2]=pos.z;

i++;

}

float[]textCoordArr=newfloat[posList.size()*2];

float[]normArr=newfloat[posList.size()*3];

for(Faceface:facesList){

IdxGroup[]faceVertexIndices=face.getFaceVertexIndices();

for(IdxGroupindValue:faceVertexIndices){

processFaceVertex(indValue,textCoordList,normList,

indices,textCoordArr,normArr);

}

}

int[]indicesArr=newint[indices.size()];

indicesArr=indices.stream().mapToInt((Integerv)->v).toArray();

Meshmesh=newMesh(posArr,textCoordArr,normArr,indicesArr);

returnmesh;

}

privatestaticvoidprocessFaceVertex(IdxGroupindices,List<Vector2f>textCoordList,

List<Vector3f>normList,List<Integer>indicesList,

float[]texCoordArr,float[]normArr){

//Setindexforvertexcoordinates

intposIndex=indices.idxPos;

indicesList.add(posIndex);

//Reordertexturecoordinates

if(indices.idxTextCoord>=0){

Vector2ftextCoord=textCoordList.get(indices.idxTextCoord);

texCoordArr[posIndex*2]=textCoord.x;

texCoordArr[posIndex*2+1]=1-textCoord.y;

}

if(indices.idxVecNormal>=0){

//Reordervectornormals

Vector3fvecNorm=normList.get(indices.idxVecNormal);

normArr[posIndex*3]=vecNorm.x;

normArr[posIndex*3+1]=vecNorm.y;

normArr[posIndex*3+2]=vecNorm.z;

}

}

Loadingmorecomplexmodels

95

Page 96: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

AnotherthingtonoticeisthattexturecoordinatesareinUVformatsoycoordinatesneedtobecalculatedas1minusthevaluecontainedinthefile.

Now,atlast,wecanrenderobjmodels.I’veincludedanOBJfilethatcontainsthetexturedcubethatwehavebeenusinginpreviouschapters.InordertouseitintheinitmethodofourDummyGameclasswejustneedtoconstructaGameIteminstancelikethis.

Texturetexture=newTexture("/textures/grassblock.png");

mesh.setTexture(texture);

GameItemgameItem=newGameItem(mesh);

gameItem.setScale(0.5f);

gameItem.setPosition(0,0,-2);

gameItems=newGameItem[]{gameItem};

Andwewillgetourfamiliartexturedcube.

Wecannowtrywithothermodels.WecanusethefamousStandfordBunny(itcanbefreelydownloaded)model,whichisincludedintheresources.Thismodelisnottexturedsowecanuseitthisway:

Meshmesh=OBJLoader.loadMesh("/models/bunny.obj");

GameItemgameItem=newGameItem(mesh);

gameItem.setScale(1.5f);

gameItem.setPosition(0,0,-2);

gameItems=newGameItem[]{gameItem};

Loadingmorecomplexmodels

96

Page 97: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Themodellooksalittlebitstrangebecausewehavenotexturesandthere’snolightsowecannotappreciatethevolumesbutyoucancheckthatthemodeliscorrectlyloaded.IntheWindowclasswhenwesetuptheOpenGLparametersaddthisline.

glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);

Youshouldnowseesomethinglikethiswhenyouzoomin.

Nowyoucannowseeallthetrianglesthatcomposethemodel.

WiththisOBJloaderclassyoucannowuseBlendertocreateyourmodels.Blenderisapowerfultoolbutitcanbesomebitofoverwhelmingatfirst,therearelotsofoptions,lotsofkeycombinationsandyouneedtotakeyourtimetodothemostbasicthingsbythefirsttime.Whenyouexportthemodelsusingblenderpleasemakesuretoincludethenormalsandexportfacesastriangles.

Loadingmorecomplexmodels

97

Page 98: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Alsoremebertosplitedgeswhenexporting,sincewecannotassignseveraltexturecoordinatestothesamevertex.Also,weneedthenormalstobedefinedpereachtriangle,notasignedtovertices.Ifyoufindlightproblems(nextchapters),withsomemodels,youshouldverifythenormals.Youcanvisualizetheminsideblender.

Loadingmorecomplexmodels

98

Page 99: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

LettherebelightInthischapterwewilllearnhowtoaddlighttoour3Dgameengine.Wewillnotimplementaphysicalperfectlightmodelbecause,takingasidethecomplexity,itwouldrequireatremendousamountofcomputerrecourses,insteadwewillimplementanapproximationwhichwillprovidedecentresults.WewilluseanalgorithmnamedPhongshading(developedbyBuiTuongPhong).Anotherimportantthingtopointisthatwewillonlymodellightsbutwewon’tmodeltheshadowsthatshouldbegeneratedbythoselights(thiswillbedoneinanotherchapter).

Beforewestart,letusdefinesomelighttypes:

Pointlight:Thistypeoflightmodelsalightsourcethat’semitteduniformlyformapointinspaceinalldirections.Spotlight:Thistypeoflightmodelsalightsourcethat’semittedfromapointinspace,butinsteadofemittinginalldirectionsisrestrictedtoacone.Directionallight:Thistypeforlightmodelsthelightthatwereceivefromthesun,alltheobjectsinthe3Dthespacearehitbyparallelraylightscomingfromaspecificdirection.Nomatteriftheobjectiscloseoroffaraway,alltheraylightsimpacttheobjectswiththesameangle.Ambientlight:Thistypeoflightcomesfromeverywhereinthespaceandilluminatesalltheobjectsinthesameway.

Thus,tomodellightweneedtotakeintoconsiderationthetypeoflightplus,itspositionandsomeotherparameterslikeitscolour.Ofcourse,wemustalsoconsiderthewaythatobjects,impactedbyraylights,absorbandreflectlight.

ThePhongshadingalgorithmwillmodeltheeffectsoflightforeachpointinourmodel,thatisforeveryvertex.Thisiswhyit’scalledalocalilluminationsimulation,andthisisthereasonwhichthisalgorithmwillnotcalculateshadows,itwilljustcalculatethelighttobeappliedtoeveryvertexwithouttakingintoconsiderationifthevertexisbehindanobjectthat

Lettherebelight

99

Page 100: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

blocksthelight.Wewillovercomethisinlaterchapters.But,becauseofthat,isaverysimpleandfastalgorithmthatprovidesverygoodeffects.Wewillusehereasimplifiedversionthatdoesnottakeintoaccountmaterialsdeeply.

ThePhongalgorithmconsidersthreecomponentsforlighting:

Ambientlight:modelslightthatcomesfromeverywhere,thiswillserveustoilluminate(withtherequireintensity)theareasthatarenothitbyanylight,it’slikeabackgroundlight.Diffusereflectance:Ittakesintoconsiderationthatsurfacesthatarefacingthelightsourcearebrighter.Specularreflectance:modelshowlightreflectsinpolishedormetallicsurfaces

Attheendwhatwewanttoobtainisafactorthat,multipliedbycolourassignedtoafragment,willsetthatcolourbrighterordarkerdependingonthelightitreceives.Let’snameourcomponentsasAforambient,DfordiffuseandSforspecular.Thatfactorwillbetheadditionofthosecomponents:

L = A+D + S

Infact,thosecomponentsareindeedcolours,thatisthecolourcomponentsthateachlightcomponentcontributesto.Thisisduetothefactthatlightcomponentswillnotonlyprovideadegreeofintensitybutitcanmodifiythecolourofmodel.Inourfragmentshaderwejustneedtomultiplythatlightcolourbytheoriginalfragmentcolour(obtainedfromatextureorabasecolour).

Wecanassignalsodifferentcolours,forthesamematerials,thatwillbeusedintheambient,diffuseandspecularcomponents.Hence,thesecomponentswillbemodulatedbythecoloursassociatedtothematerial.Ifthematerialhasatexture,wewillsimplyuseasingletextureforeachofthecomponents.

Sothefinalcolourforanontexturedmaterialwillbe:L = A ∗ ambientColour +D ∗ diffuseColour + S ∗ specularColour.

Andthefinalcolourforatexturedmaterialwillbe:

L = A ∗ textureColour +D ∗ textureColour + S ∗ textureColour

AmbientLightcomponentLet’sviewthefirstcomponent,theambientlightcomponentit’sjustaconstantfactorthatwillmakeallofourobjectsbrighterordarker.Wecanuseittosimulatelightforaspecificperiodoftime(dawn,dusk,etc.)alsoitcanbeusedtoaddsomelighttopointsthatarenothit

Lettherebelight

100

Page 101: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

directlybyraylightsbutcouldbelightedbyindirectlight(causedbyreflections)inaneasyway.

Ambientlightistheeasiestcomponenttocalculate,wejustneedtopassacolour,sinceitwillbemultipliedbyourbasecolouritjustmodulatesthatbasecolour.Imaginethatwehavedeterminedthatacolourforafragmentis(1.0, 0.0, 0.0),thatisredcolour.Withoutambientlightitwillbedisplayedasafullyredfragment.Ifwesetambientlightto(0.5, 0.5, 0.5)thefinalcolourwillbe(0.5, 0, 0),thatisadarkerversionofred.Thislightwilldarkenallthefragmentsinthesameway(itmayseemtobealittlestrangetobetalkingaboutlightthatdarkensobjectsbutinfactthatistheeffectthatweget).Besidesthat,itcanaddsomecolouriftheRGBcomponentsarenotthesame,sowejustneedavectortomodulateambientlightintensityandcolour.

DiffuserefletanceLet’stalknowaboutdiffusereflectance.Itmodelsthefactthatsurfaceswhichfaceinaperpendicularwaytothelightsourcelookbrighterthansurfaceswherelightisreceivedinamoreindirectangle.Thoseobjectsreceivemorelight,thelightdensity(letmecallitthisway)ishigher.

But,howdowecalculatethis?Doyourememberfrompreviouschapterthatweintroducedthenormalconcept?Thenormalwasthevectorperpendiculartoasurfacethathadalengthequaltoone.So,Let’sdrawthenormalsforthreepointsinthepreviousfigure,asyoucansee,thenormalforeachpointwillbethevectorperpendiculartothetangentplaneforeachpoint.Insteadofdrawingrayscomingfromthesourceoflightwewilldrawvectorsfromeachpointtothepointoflight(thatis,intheoppositedirection).

Lettherebelight

101

Page 102: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Asyoucansee,thenormalassociatedtoP1,namedN1,isparalleltothevectorthatpointstothelightsource,whichmodelstheoppositeofthelightray(N1hasbeensketcheddisplacedsoyoucanseeit,butit’sequivalentmathematically).P1hasanangleequalto0withthevectorthatpointstothelightsource.It’ssurfaceisperpendiculartothelightsourceandP1wouldbethebrightestpoint.

ThenormalassociatedtoP2,namedN2,hasanangleofaround30degreeswiththevectorthatpointsthelightsource,soitshouldbedarkertanP1.Finally,thenormalassociatedtoP3,namedN3,isalsoparalleltothevectorthatpointstothelightsourcebutbothvectorsareintheoppositedirection.P3hasanangleof180degreeswiththevectorthatpointsthelightsource,andshouldnotgetanylightatall.

Soitseemsthatwehaveagoodapproachtodeterminethelightintensitythatgetstoapointandit’srelatedtotheanglethatformsthenormalwithavectorthatpointstothelightsource.Howcanwecalculatethis?

There’samathematicaloperationthatwecanuseandit’scalleddotproduct.Thisoperationtakestwovectorsandproducesanumber(ascalar),thatispositiveiftheanglebetweenthemissmall,andproducesanegativenumberiftheanglebetweenthemiswide.Ifbothvectorsarenormalized,thatisthebothhavealengthequaltoone,thedotproductwillbebetween−1and1.Thedotproductwillbeoneifbothvectorslookinthesamedirection(angle0)anditwillbe0ifbothvectorsformasquareangleandwillbe−1ifbothvectorsfaceoppositedirection.

Let’sdefinetwovectors,v1andv2,andletalphabetheanglebetweenthem.Thedotproductisdefinedbythefollowingformula.

Lettherebelight

102

Page 103: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Ifbothvectorsarenormalized,theirlength,theirmodulewillbeequaltoone,sothedotproductisequaltothecosineiftheanglebetweenthem.Wewillusethatoperationtocalculatethediffusereflectancecomponent.

Soweneedtocalculatethevectorthatpointstothesourceoflight.Howwedothis?Wehavethepositionofeachpoint(thevertexposition)andwehavethepositionofthelightsource.Firstofall,bothcoordinatesmustbeinthesamecoordinatespace.Tosimplify,let’sassumethattheyarebothinworldcoordinatespace,thenthosepositionsarethecoordinatesofthevectorsthatpointtothevertexposition(V P )andtothelightsource(V S),asshowninthenextfigure.

IfwesubstractVSfromV P wegetthevectorthatwearelookingforwhichit’scalledL.

Nowwecandothedotproductbetweenthevectorthatpointstothelightsourceandthenormal,thatproductiscalledtheLambertterm,duetoJohannLambertwhichwasthefirsttoproposethatrelationtomodelthebrightnessofasurface.

Let’ssummarizehowwecancalculateit,wedefinethefollowingvariables:

vPos:Positionofourvertexinmodelviewspacecoordinates.lPos:Positionofthelightinviewspacecoordinates.

Lettherebelight

103

Page 104: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

intensity:Intensityofthelight(from0to1).lColour:Colourofthelight.normal:Thevertexnormal.Firstweneedtocalculatethevectorthatpointstothelightsourcefromcurrentposition:toLightDirection = lPos− vPos.Theresultofthatoperationneedstobenormalized

Thenweneedtocalculatethediffusefactor(anscalar):diffuseFactor = normal ⋅ toLightDirection.It’scalculatedasdotproductbetweentwovectors,sincewewantittobebetween−1and1bothvectorsneedtobenormalized.Coloursneedtobebetween0and1soifavalueit’slowerthan0wewillsetitto0.

Finallywejustneedtomodulatethelightcolourbythediffusefactorandthelightintensity:

colour = diffuseColour ∗ lColour ∗ diffuseFactor ∗ intensity

SpecularcomponentLet’sviewnowthespecularcomponent,butfirstweneedtoexaminehowlightisreflected.Whenlighthitsasurfacesomepartofitisabsorbedandtheotherpartisreflected,ifyourememberfromyourphysicsclass,reflectioniswhenlightbouncesoffanobject.

Ofcourse,surfacesarenottotallypolished,andifyoulookatcloserdistanceyouwillseealotofimperfections.Besidesthat,youhavemanyraylights(photonsinfact),thatimpactthatsurface,andthatgetreflectedinawiderangeofangles.Thus,whatweseeislikeabeamoflightbeingreflectedfromthesurface.Thatis,lightisdiffusedwhenimpactingoverasurface,andthat’sthedisusecomponentthatwehavebeentalkingaboutpreviously.

Lettherebelight

104

Page 105: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Butwhenlightimpactsapolishedsurface,forinstanceametal,thelightsuffersfromlowerdiffusionandmostofitgetsreflectedintheoppositedirectionasithitthatsurface.

Thisiswhatthespecularcomponentmodels,anditdependsonthematerialcharacteristics.Regardingspecularreflectance,it’simportanttonotethatthereflectedlightwillonlybevisibleifthecameraisinaproperposition,thatis,ifit'sintheareaofwherethereflectedlightisemitted.

Oncethemechanismthat’sbehindsepecularreflectionhasbeenexplainedwearereadytocalculatethatcomponent.Firstweneedavectorthatpointsfromthelightsourcetothevertexpoint.Whenwewerecalculatingthedifussecomponentwecalculatedjusttheopposite,avectorthatpointstothelightsource.toLightDirection,solet’scalculateitasfromLightDirection = −(toLightDirection).

ThenweneedtocalculatethereflectedlightthatresultsfromtheimpactofthefromLightDirectionintothesurfacebytakingintoconsiderationitsnormal.There’saGLSLfunctionthatdoesthatnamedreflect.So,

Lettherebelight

105

Page 106: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

reflectedLight = reflect(fromLightSource,normal).

Wealsoneedavectorthatpointstothecamera,let’snameitcameraDirection,anditwillbecalculatedasthedifferencebetweenthecamerapositionandthevertexposition:cameraDirection = cameraPos− vPos.Thecamerapositionvectorandthevertexpositionneedtobeinthesamecoordinatesystemandtheresultingvectorneedstobenormalized.Thefollowingfiguresketchesthemaincomponentswehavecalculateduptonow.

NowweneedtocalculatethelightintensitythatweseewhichwewillcallspecularFactor.ThiscomponentwillbehigherifthecameraDirectionandthereflectedLightvectorsareparallelandpointinthesamedirectionandwilltakeitslowervalueiftheypointinoppositedirections.Inordertocalculatethisthedotproductcomestotherescueagain.SospecularFactor = cameraDirection ⋅ reflectedLight.Weonlywantthisvaluetobebetween0and1soifit’slowerthan0itwillbesetto0.

Wealsoneedtotakeintoconsiderationthatthislightmustbemoreintenseifthecameraispointingtothereflectedlightcone.ThiswillbeachievedbypoweringthespecularFactortoaparameternamedspecularPower.

specularFactor = specularFactor .

Finallyweneedtomodelthereflectivityofthematerial,whichwillalsomodulatetheintensityifthelightreflected,thiswillbedonewithanotherparameternamedreflectance.Sothecolourcomponentofthespecularcomponentwillbe:specularColour ∗ lColour ∗ reflectance ∗ specularFactor ∗ intensity.

Attenuation

specularPower

Lettherebelight

106

Page 107: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Wenowknowhowtocalculatethethreecomponentsthatwillserveustomodelapointlightwithanambientlight.Butourlightmodelisstillnotcomplete,thelightthatanobjectreflectsisindependentofthedistancethatthelightis,weneedtosimulatelightattenuation.

Attenuationisafunctionofthedistanceandlight.Theintensityoflightisinverselyproportionaltothesquareofdistance.Thatfactiseasytovisualize,aslightispropagatingitsenergyisdistributedalongthesurfaceofaspherewitharadiusthat’sequaltothedistancetraveledbythelight.Thesurfaceofasphereisproportionaltothesquareofitsradius.Wecancalculatetheattenuationfactorwiththisformula:

1.0/(atConstant+ atLinear ∗ dist+ atExponent ∗ dist ).

Inordertosimulateattenuationwejustneedtomultiplythatattenuationfactorbythefinalcolour.

ImplementationNowwecanstartcodingalltheconceptsdescribedabove,wewillstartwithourshaders.Mostoftheworkwillbedoneinthefragmentshaderbutweneedtopasssomedatafromthevertexshadertoit.Inpreviouschapterthefragmentshaderjustreceivedthetexturecoordinates,nowwearegoingtopassalsotwomoreparameters:

Thevertexnormal(normalized)transformedtomodelviewspacecoordinates.Thevertexpositiontransformedtomodelviewspacecoordinates.Thisisthecodeofthevertexshader.

2

Lettherebelight

107

Page 108: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

#version330

layout(location=0)invec3position;

layout(location=1)invec2texCoord;

layout(location=2)invec3vertexNormal;

outvec2outTexCoord;

outvec3mvVertexNormal;

outvec3mvVertexPos;

uniformmat4modelViewMatrix;

uniformmat4projectionMatrix;

voidmain()

{

vec4mvPos=modelViewMatrix*vec4(position,1.0);

gl_Position=projectionMatrix*mvPos;

outTexCoord=texCoord;

mvVertexNormal=normalize(modelViewMatrix*vec4(vertexNormal,0.0)).xyz;

mvVertexPos=mvPos.xyz;

}

Beforewecontinuewiththefragmentshaderthere’saveryimportantconceptthatmustbehighlighted.FromthecodeaboveyoucanseethatmvVertexNormal,thevariablecontainsthevertexnormal,istransformedintomodelviewspacecoordinates.ThisisdonebymultiplyingthevertexNormalbythemodelViewMatrixaswiththevertexposition.Butthere’sasubtledifference,thewcomponentofthatvertexnormalissetto0beforemultiplyingitbythematrix:vec4(vertexNormal,0.0).Whyarewedoingthis?Becausewedowantthenormaltoberotatedandscaledbutwedonotwantittobetranslated,weareonlyinterestedintoitsdirectionbutnotinitsposition.Thisisachievedbysettingiswcomponentto0andisoneoftheadvantagesofusinghomogeneouscoordinates,bysettingthewcomponentwecancontrolwhattransformationsareapplied.Youcandothematrixmultiplicationbyhandandseewhythishappens.

Nowwecanstarttodotherealworkinourfragmentshader,besidesdeclaringasinputparametersthevaluesthatcomefromthevertexshaderwearegoingtodefinesomeusefulstructurestomodellightandmaterialcharacteristic.Firstofall,wewilldefinethestructuresthatmodelthelight.

Lettherebelight

108

Page 109: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

structAttenuation

{

floatconstant;

floatlinear;

floatexponent;

};

structPointLight

{

vec3colour;

//Lightpositionisassumedtobeinviewcoordinates

vec3position;

floatintensity;

Attenuationatt;

};

Apointlightisdefinedbyacolour,aposition,anumberbetween0and1whichmodelsitsintensityandasetofparameterswhichwillmodeltheattenuationequation.

Thestructurethatmodelsamaterialcharacteristicsis:

structMaterial

{

vec4ambient;

vec4diffuse;

vec4specular;

inthasTexture;

floatreflectance;

};

Amaterialisdefinedbyaasetofcolours(ifwedon’tusetexturetocolourthefragments):

Thecoloutusedfortheambientcomponent.Thecolourusedforthediffusecomponent.Thecolourusedforthespecularcomponent.

Amaterialalsoisdefinedbyaflagthatcontrolsifithasanassociatedtextureornotandareflectanceindex.Wewillusethefollowinguniformsinourfragmentshader.

uniformsampler2Dtexture_sampler;

uniformvec3ambientLight;

uniformfloatspecularPower;

uniformMaterialmaterial;

uniformPointLightpointLight;

uniformvec3camera_pos;

Wearecreatingnewuniformstosetthefollowingvariables:

Lettherebelight

109

Page 110: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Theambientlight:whichwillcontainacolourthatwillaffecteveryfragmentinthesameway.Thespecularpower(theexponentusedintheequationthatwaspresentedwhentalkingaboutthespecularlight).Apointlight.Thematerialcharacteristics.Thecamerapositioninviewspacecoordinates.

Wewillalsodefinesomeglobalvariablesthatwillholdthematerialcolourcomponentstobeusedintheambient,diffuseandspecularcomponents.Weusethesevariablessince,ifthecomponenthasatexture,wewillusethesamecolourforallthecomponentsandwedonotwanttoperformredundanttexturelookups.Thevariablesaredefinedlikethis:

vec4ambientC;

vec4diffuseC;

vec4speculrC;

Wenowcandefineafunctionthatwillsetupthesevariablesaccodringtothematerialcharacteristics:

voidsetupColours(Materialmaterial,vec2textCoord)

{

if(material.hasTexture==1)

{

ambientC=texture(texture_sampler,textCoord);

diffuseC=ambientC;

speculrC=ambientC;

}

else

{

ambientC=material.ambient;

diffuseC=material.diffuse;

speculrC=material.specular;

}

}

Nowwearegoingtodefineafunctionthat,takingasitsinputapointlight,thevertexpositionanditsnormalreturnsthecolourcontributioncalculatedforthediffuseandspecularlightcomponentsdescribedpreviously.

Lettherebelight

110

Page 111: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

vec4calcPointLight(PointLightlight,vec3position,vec3normal)

{

vec4diffuseColour=vec4(0,0,0,0);

vec4specColour=vec4(0,0,0,0);

//DiffuseLight

vec3light_direction=light.position-position;

vec3to_light_source=normalize(light_direction);

floatdiffuseFactor=max(dot(normal,to_light_source),0.0);

diffuseColour=diffuseC*vec4(light.colour,1.0)*light.intensity*diffuseFact

or;

//SpecularLight

vec3camera_direction=normalize(-position);

vec3from_light_source=-to_light_source;

vec3reflected_light=normalize(reflect(from_light_source,normal));

floatspecularFactor=max(dot(camera_direction,reflected_light),0.0);

specularFactor=pow(specularFactor,specularPower);

specColour=speculrC*specularFactor*material.reflectance*vec4(light.colour,

1.0);

//Attenuation

floatdistance=length(light_direction);

floatattenuationInv=light.att.constant+light.att.linear*distance+

light.att.exponent*distance*distance;

return(diffuseColour+specColour)/attenuationInv;

}

Thepreviouscodeisrelativelystraightforward,itjustcalculatesacolourforthediffusecomponent,anotheroneforthespecularcomponentandmodulatesthembytheattenuationsufferedbythelightinitstraveltothevertexweareprocessing.

Pleasebeawarethatverticescoordinatesareinviewspace.Whencalculatingthespecularcomponent,wemustgetthedirecttiontothepointofview,thatisthecamera.This,couldbedonelikethis:

vec3camera_direction=normalize(camera_pos-position);

But,sincepositionisinviewspace,thecamerapositionisallwaysattheorigin,thatis,(0, 0, 0),sowecalculateitlikethis:

vec3camera_direction=normalize(vec3(0,0,0)-position);

Whichcanbesimplifiedlikethis:

Lettherebelight

111

Page 112: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

vec3camera_direction=normalize(-position);

Withthepreviousfunction,themainfunctionofthevertexfunctionisverysimple.

voidmain()

{

setupColours(material,outTexCoord);

vec4diffuseSpecularComp=calcPointLight(pointLight,mvVertexPos,mvVertexNormal)

;

fragColor=ambientC*vec4(ambientLight,1)+diffuseSpecularComp;

}

ThecalltothesetupColoursfunctionwillsetuptheambientC,diffuseCandspeculrCvariableswiththeappropriatecolours.Then,wecalculatethediffuseandspecularcomponents,takingintoconsiderationtheattennuation.Wedothisusingasinglefunctioncallforconvenience,asithasbeenexplainedabove.Finalcolouriscalculatedbyaddingtheambientcomponent(multiplyingambientCbytheambientlight).Asyoucanseeambientlightisnotaffectedbyattenuation.

Wehaveintroducedsomenewconceptsintoourshaderthatrequirefurtherexplanation,wearedefiningstructuresandusingthemasuniforms.Howdowepassthosestructures?Firstofallwewilldefinetwonewclassesthatmodelthepropertiesofapointlightandamaterial,namedohsurprise,PointLightandMaterial.TheyarejustplainPOJOssoyoucanchecktheminthesourcecodethataccompaniesthisbook.Then,weneedtocreatenewmethodsintheShaderProgramclass,firsttobeabletocreatetheuniformsforthepointlightandmaterialstructures.

publicvoidcreatePointLightUniform(StringuniformName)throwsException{

createUniform(uniformName+".colour");

createUniform(uniformName+".position");

createUniform(uniformName+".intensity");

createUniform(uniformName+".att.constant");

createUniform(uniformName+".att.linear");

createUniform(uniformName+".att.exponent");

}

publicvoidcreateMaterialUniform(StringuniformName)throwsException{

createUniform(uniformName+".ambient");

createUniform(uniformName+".diffuse");

createUniform(uniformName+".specular");

createUniform(uniformName+".hasTexture");

createUniform(uniformName+".reflectance");

}

Lettherebelight

112

Page 113: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Asyoucansee,it’sverysimple,wejustcreateaseparateuniformforalltheattributesthatcomposethestructure.NowweneedtocreateanothertwomethodstosetupthevaluesofthoseuniformsandthatwilltakeasparametersPointLightandMaterialinstances.

publicvoidsetUniform(StringuniformName,PointLightpointLight){

setUniform(uniformName+".colour",pointLight.getColor());

setUniform(uniformName+".position",pointLight.getPosition());

setUniform(uniformName+".intensity",pointLight.getIntensity());

PointLight.Attenuationatt=pointLight.getAttenuation();

setUniform(uniformName+".att.constant",att.getConstant());

setUniform(uniformName+".att.linear",att.getLinear());

setUniform(uniformName+".att.exponent",att.getExponent());

}

publicvoidsetUniform(StringuniformName,Materialmaterial){

setUniform(uniformName+".ambient",material.getAmbientColour());

setUniform(uniformName+".diffuse",material.getDiffuseColour());

setUniform(uniformName+".specular",material.getSpecularColour());

setUniform(uniformName+".hasTexture",material.isTextured()?1:0);

setUniform(uniformName+".reflectance",material.getReflectance());

}

InthischaptersourcecodeyouwillseealsothatwealsohavemodifiedtheMeshclasstoholdamaterialinstanceandthatwehavecreatedasimpleexamplethatcreatesapointlightthatcanbemovedbyusingthe“N”and“M”keysinordertoshowhowapointlightfocusingoverameshwithareflectancevaluehigherthan0lookslike.

Let'sgetbacktoourfragmentshader,aswehavesaidweneedanotheruniformwhichcontainsthecameraposition,camera_pos.Thesecoordinatesmustbeinviewspace.Usuallywewillsetuplightcoordinatesinworldspacecoordinates,soweneedtomultiplythembytheviewmatrixinordertobeabletousetheminourshader,soweneedtocreateanewmethodintheTransformationclassthatreturnstheviewmatrixsowetransformlightcoordinates.

//Getacopyofthelightobjectandtransformitspositiontoviewcoordinates

PointLightcurrPointLight=newPointLight(pointLight);

Vector3flightPos=currPointLight.getPosition();

Vector4faux=newVector4f(lightPos,1);

aux.mul(viewMatrix);

lightPos.x=aux.x;

lightPos.y=aux.y;

lightPos.z=aux.z;

shaderProgram.setUniform("pointLight",currPointLight);

Lettherebelight

113

Page 114: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Wewillnotincludethewholesourcecodebecausethischapterwouldbetoolonganditwouldnotcontributetoomuchtoclarifytheconceptsexplainedhere.Youcancheckitinthesourcecodethataccompaniesthisbook.

Lettherebelight

114

Page 115: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

LettherebeevenmorelightInthischapterwearegoingtoimplementotherlighttypesthatweintroducedinpreviouschapter.Wewillstartwithdirectionallightning.

DirectionalLightIfyourecall,directionallightinghitsalltheobjectsbyparallelraysallcomingfromthesamedirection.ItmodelslightsourcesthatarefarawaybuthaveahighintensitysuchustheSun.

Anothercharacteristicofdirectionallightisthatitisnotaffectedbyattenuation.ThinkagainaboutSunlight,allobjectsthatarehitbyraylightsareilluminatedwiththesameintensity,thedistancefromthesunissohugethatthepositionoftheobjectsisirrelevant.Infact,directionallightsaremodeledaslightsourcesplacedattheinfinity,ifitwasaffectedbyattenuationitwouldhavenoeffectinanyobject(it’scolourcontributionwouldbeequalto0).

Besidesthat,directionallightiscomposedalsobyadiffuseandspecularcomponents,theonlydifferenceswithpointlightsisthatitdoesnothaveapositionbutadirectionandthatitisnotaffectedbyattenuation.Let’sgetbacktothedirectionattributeofdirectionallight,andimaginewearemodelingthemovementofthesunacrossour3Dworld.Ifweareassumingthatthenorthisplacedtowardstheincreasingz-axis,thefollowingpictureshowsthedirectiontothelightsourceatdawn,midnightanddusk.

Lettherebeevenmorelight

115

Page 116: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Lightdirectionsfortheabovepositionsare:

Dawn:(-1,0,0)Midday:(0,1,0)Dusk:(1,0,0)

Sidenote:Youmaythinkthatabovecoordinatesareequaltopositionones,buttheymodelavector,adirection,notaposition.Fromthemathematicalpointofviewavectorandapositionarenotdistinguishablebuttheyhaveatotallydifferentmeaning.

But,howdowemodelthefactthatthislightislocatedattheinfinity?Theanswerisbyusingthewcoordinate,thatis,byusinghomogeneouscoordinatesandsettingthewcoordinateto0:

Dawn:(-1,0,0,0)Midday:(0,1,0,0)Dusk:(1,0,0,0)

Thisisthesamecaseaswhenwepassthenormals,fornormalswesetthewcomponentto0tostatethatwearenotinterestedindisplacements,justinthedirection.Also,whenwedealwithdirectionallightweneedtodothesame,cameratranslationsshouldnotaffectthedirectionofadirectionallight.

Solet’sstartcodingandmodelourdirectionallight.Thefirstthingthatwearegoingtodoistocreateaclassthatmodelsitsattributes.ItwillbeanotherPOJOwithacopyconstructorwhichstoresthedirection,thecolourandtheintensity.

Lettherebeevenmorelight

116

Page 117: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

packageorg.lwjglb.engine.graph;

importorg.joml.Vector3f;

publicclassDirectionalLight{

privateVector3fcolor;

privateVector3fdirection;

privatefloatintensity;

publicDirectionalLight(Vector3fcolor,Vector3fdirection,floatintensity){

this.color=color;

this.direction=direction;

this.intensity=intensity;

}

publicDirectionalLight(DirectionalLightlight){

this(newVector3f(light.getColor()),newVector3f(light.getDirection()),light

.getIntensity());

}

//Gettersandsettesbeyondthispoint...

Asyoucansee,wearestillusingaVector3ftomodelthedirection.Keepcalm,wewilldealwiththewcomponentwhenwetransferthedirectionallighttotheshader.Andbytheway,thenextthingthatwewilldoistoupdatetheShaderProgramtocreateandupdatetheuniformthatwillholdthedirectionallight.

Inourfragmentshaderwewilldefineastructurethatmodelsadirectionallight.

structDirectionalLight

{

vec3colour;

vec3direction;

floatintensity;

};

WiththatdefinitionthenewmethodsintheShaderProgramclassarestraightforward.

Lettherebeevenmorelight

117

Page 118: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

//...

publicvoidcreateDirectionalLightUniform(StringuniformName)throwsException{

createUniform(uniformName+".colour");

createUniform(uniformName+".direction");

createUniform(uniformName+".intensity");

}

//...

publicvoidsetUniform(StringuniformName,DirectionalLightdirLight){

setUniform(uniformName+".colour",dirLight.getColor());

setUniform(uniformName+".direction",dirLight.getDirection());

setUniform(uniformName+".intensity",dirLight.getIntensity());

}

Nowweneedtousethatuniform.WewillmodelhowthesunappearstomoveacrosstheskybycontrollingitsangleinourDummyGameclass.

Weneedtoupdatelightdirectionsowhenthesunit’satdawn(-90º)itsdirectionis(-1,0,0)anditsxcoordinateprogressivelyincreasesfrom-1to0andthe“y”coordinateincreasesto1asitapproachesmidday.Thenthe“x”coordinateincreasesto1andthe“y”coordinatesdecreasesto0again.Thiscanbedonebysettingthexcoordinatetothesineoftheangleandycoordinatetothecosineoftheangle.

Lettherebeevenmorelight

118

Page 119: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Wewillalsomodulatelightintensity,theintensitywillbeincreasingwhenit’sgettingawayfromdawnandwilldecreaseasitapproachestodusk.Wewillsimulatethenightbysettingtheintensityto0.Besidesthat,wewillalsomodulatethecoloursothelightgetsmoreredatdawnandatdusk.ThiswillbedoneintheupdatemethodoftheDummyGameclass.

//Updatedirectionallightdirection,intensityandcolour

lightAngle+=1.1f;

if(lightAngle>90){

directionalLight.setIntensity(0);

if(lightAngle>=360){

lightAngle=-90;

}

}elseif(lightAngle<=-80||lightAngle>=80){

floatfactor=1-(float)(Math.abs(lightAngle)-80)/10.0f;

directionalLight.setIntensity(factor);

directionalLight.getColor().y=Math.max(factor,0.9f);

directionalLight.getColor().z=Math.max(factor,0.5f);

}else{

directionalLight.setIntensity(1);

directionalLight.getColor().x=1;

directionalLight.getColor().y=1;

directionalLight.getColor().z=1;

}

doubleangRad=Math.toRadians(lightAngle);

directionalLight.getDirection().x=(float)Math.sin(angRad);

directionalLight.getDirection().y=(float)Math.cos(angRad);

ThenweneedtopassthedirectionallighttoourshadersintherendermethodoftheRendererclass.

Lettherebeevenmorelight

119

Page 120: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

//Getacopyofthedirectionallightobjectandtransformitspositiontoviewcoord

inates

DirectionalLightcurrDirLight=newDirectionalLight(directionalLight);

Vector4fdir=newVector4f(currDirLight.getDirection(),0);

dir.mul(viewMatrix);

currDirLight.setDirection(newVector3f(dir.x,dir.y,dir.z));

shaderProgram.setUniform("directionalLight",currDirLight);

Asyoucanseeweneedtotransformthelightdirectioncoordinatestoviewspace,butwesetthewcomponentto0sincewearenotinterestedinapplyingtranslations.

Nowwearereadytodotherealworkwhichwillbedoneinthefragmentshadersincethevertexshaderdoesnotbemodified.Wehaveyetstatedabovethatweneedtodefineanewstruct,namedDirectionalLight,tomodeladirectionallight,andwewillneedanewuniformformthat.

uniformDirectionalLightdirectionalLight;

Weneedtorefactorourcodealittlebit,inthepreviouschapterwehadafunctioncalledcalcPointLightthatcalculatethediffuseandspecularcomponentsandalsoappliedtheattenuation.Aswehaveexplaineddirectionallightalsocontributestothediffuseandspecularcomponentsbutisnotaffectedbyattenuation,sowewillcreateanewfunctionnamedcalcLightColourthatjustcalculatesthosecomponents.

Lettherebeevenmorelight

120

Page 121: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

vec4calcLightColour(vec3light_colour,floatlight_intensity,vec3position,vec3to_

light_dir,vec3normal)

{

vec4diffuseColour=vec4(0,0,0,0);

vec4specColour=vec4(0,0,0,0);

//DiffuseLight

floatdiffuseFactor=max(dot(normal,to_light_dir),0.0);

diffuseColour=diffuseC*vec4(light_colour,1.0)*light_intensity*diffuseFact

or;

//SpecularLight

vec3camera_direction=normalize(camera_pos-position);

vec3from_light_dir=-to_light_dir;

vec3reflected_light=normalize(reflect(from_light_dir,normal));

floatspecularFactor=max(dot(camera_direction,reflected_light),0.0);

specularFactor=pow(specularFactor,specularPower);

specColour=speculrC*light_intensity*specularFactor*material.reflectance*

vec4(light_colour,1.0);

return(diffuseColour+specColour);

}

ThenthemethodcalcPointLightappliesattenuationfactortothelightcolourcalculatedinthepreviousfunction.

vec4calcPointLight(PointLightlight,vec3position,vec3normal)

{

vec3light_direction=light.position-position;

vec3to_light_dir=normalize(light_direction);

vec4light_colour=calcLightColour(light.colour,light.intensity,position,to_li

ght_dir,normal);

//ApplyAttenuation

floatdistance=length(light_direction);

floatattenuationInv=light.att.constant+light.att.linear*distance+

light.att.exponent*distance*distance;

returnlight_colour/attenuationInv;

}

WewillcreatealsoanewfunctiontocalculatetheeffectofadirectionallightwhichjustinvokesthecalcLightColourfunctionwiththelightdirection.

Lettherebeevenmorelight

121

Page 122: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

vec4calcDirectionalLight(DirectionalLightlight,vec3position,vec3normal)

{

returncalcLightColour(light.colour,light.intensity,position,normalize(light.di

rection),normal);

}

Finally,ourmainmethodjustaggregatesthecolourcomponentsoftheambientpointanddirectionallightstocalculatethefragmentcolour.

voidmain()

{

setupColours(material,outTexCoord);

vec4diffuseSpecularComp=calcDirectionalLight(directionalLight,mvVertexPos,mvV

ertexNormal);

diffuseSpecularComp+=calcPointLight(pointLight,mvVertexPos,mvVertexNormal);

fragColor=ambientC*vec4(ambientLight,1)+diffuseSpecularComp;

}

Andthat’sit,wecannowsimulatethemovementofthe,artificial,sunacrosstheskyandgetsomethinglikethis(movementisacceleratedsoitcanbeviewedwithoutwaitingtoolong).

SpotLightNowwewillimplementspotlightswhichareverysimilartopointlightsbuttheemittedlightisrestrictedtoa3Dcone.Itmodelsthelightthatcomesoutfromfocusesoranyotherlightsourcethatdoesnotemitinalldirections.Aspotlighthasthesameattributesasapointlightbutaddstwonewparameters,theconeangleandtheconedirection.

Lettherebeevenmorelight

122

Page 123: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Spotlightcontributioniscalculatedinthesamewayasapointlightwithsomeexceptions.Thepointwhichthevectorthatpointsfromthevertexpositiontothelightsourceisnotcontainedinsidethelightconearenotaffectedbythepointlight.

Howdowecalculateifit’sinsidethelightconeornot?Weneedtodoadotproductagainbetweenthevectorthatpointsfromthelightsourceandtheconedirectionvector(bothofthemnormalized).

Lettherebeevenmorelight

123

Page 124: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

ThedotproductbetweenLandCvectorsisequalto: ⋅ = ∣ ∣ ⋅ ∣ ∣ ⋅ Cos(α).If,inourspotlightdefinitionwestorethecosineofthecutoffangle,ifthedotproductishigherthanthatvaluewewillknowthatitisinsidethelightcone(recallthecosinegraph,whenαangleis0,thecosinewillbe1,thesmallertheanglethehigherthecosine).

Theseconddifferenceisthatthepointsthatarefarawayfromtheconevectorwillreceivelesslight,thatis,theattenuationwillbehigher.Thereareseveralwaysofcalculatethis,wewillchoseasimpleapproachbymultiplyingtheattenuationbythefollowingfactor:

1 − (1 − Cos(α))/(1 − Cos(cutOffAngle)

(Inourfragmentshaderswewon’thavetheanglebutthecosineofthecutoffangle.Youcancheckthattheformulaaboveproducesvaluesfrom0to1,0whentheangleisequaltothecutoffangleand1whentheangleis0).

Theimplementationwillbeverysimilartotherestoflights.WeneedtocreateanewclassnamedSpotLight,setuptheappropriateuniforms,passittotheshaderandmodifythefragmentshadertogetit.Youcancheckthesourcecodeforthischapter.

Anotherimportantthingwhenpassingtheuniformsisthattranslationsshouldnotbeappliedtothelightconedirectionsinceweareonlyinterestedindirections.Soasinthecaseofthedirectionallight,whentransformingtoviewspacecoordinateswemustsetwcomponentto0.

L C L C

Lettherebeevenmorelight

124

Page 125: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

MultipleLightsSoatlastwehavefinallyimplementedallthefourtypesoflight,butcurrentlywecanonlyuseoneinstanceforeachtype.Thisisokforambientanddirectionallightbutwedefinitivelywanttouseseveralpointandspotlights.Weneedtosetupourfragmentshadertoreceivealistoflights,sowewillusearraystostorethatinformation.Let’sseehowthiscanbedone.

Beforewestart,it’simportanttonotethatinGLSLthelengthofthearraymustbesetatcompiletimesoitmustbebigenoughtoaccommodatealltheobjectsweneedlater,atruntime.Thefirstthingthatwewilldoisdefinesomeconstantstosetupthemaximumnumberofpointandspotlightsthatwearegoingtouse.

constintMAX_POINT_LIGHTS=5;

constintMAX_SPOT_LIGHTS=5;

Thenweneedtomodifytheuniformsthatpreviouslystorejustasinglepointandspotlighttouseanarray.

uniformPointLightpointLights[MAX_POINT_LIGHTS];

uniformSpotLightspotLights[MAX_SPOT_LIGHTS];

Inthemainfunctionwejustneedtoiterateoverthosearraystocalculatethecolourcontributionsofeachinstanceusingtheexistingfunctions.Wemaynotpassasmanylightsasthearraylengthsoweneedtocontrolit.Therearemanypossiblewaystodothis,oneistopassauniformwiththeactualarraylengthbutthismaynotworkwitholdergraphicscards.Insteadwewillcheckthelightintensity(emptypositionsinarraywillhavealightintensityequalto0).

Lettherebeevenmorelight

125

Page 126: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

for(inti=0;i<MAX_POINT_LIGHTS;i++)

{

if(pointLights[i].intensity>0)

{

diffuseSpecularComp+=calcPointLight(pointLights[i],mvVertexPos,mvVertexNor

mal);

}

}

for(inti=0;i<MAX_SPOT_LIGHTS;i++)

{

if(spotLights[i].pl.intensity>0)

{

diffuseSpecularComp+=calcSpotLight(spotLights[i],mvVertexPos,mvVertexNorma

l);

}

}

NowweneedtocreatethoseuniformsintheRenderclass.Whenweareusingarraysweneedtocreateauniformforeachelementofthelist.So,forinstance,forthepointLights

arrayweneedtocreateauniformnamedpointLights[0],pointLights[1],etc.Andofocurse,thistranslatesalsotothestructureattributes,sowewillhavepointLights[0].colour,pointLights[1],colour,etc.Themethodstocreatethoseuniformsareasfollows.

publicvoidcreatePointLightListUniform(StringuniformName,intsize)throwsException

{

for(inti=0;i<size;i++){

createPointLightUniform(uniformName+"["+i+"]");

}

}

publicvoidcreateSpotLightListUniform(StringuniformName,intsize)throwsException

{

for(inti=0;i<size;i++){

createSpotLightUniform(uniformName+"["+i+"]");

}

}

Wealsoneedmethodstosetupthevaluesofthoseuniforms.

Lettherebeevenmorelight

126

Page 127: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicvoidsetUniform(StringuniformName,PointLight[]pointLights){

intnumLights=pointLights!=null?pointLights.length:0;

for(inti=0;i<numLights;i++){

setUniform(uniformName,pointLights[i],i);

}

}

publicvoidsetUniform(StringuniformName,PointLightpointLight,intpos){

setUniform(uniformName+"["+pos+"]",pointLight);

}

publicvoidsetUniform(StringuniformName,SpotLight[]spotLights){

intnumLights=spotLights!=null?spotLights.length:0;

for(inti=0;i<numLights;i++){

setUniform(uniformName,spotLights[i],i);

}

}

publicvoidsetUniform(StringuniformName,SpotLightspotLight,intpos){

setUniform(uniformName+"["+pos+"]",spotLight);

}

FinallywejustneedtoupdatetheRenderclasstoreceivealistofpointandspotlights,andmodifyaccordinglytheDummyGameclasstocreatethoselisttoseesomethinglikethis.

Lettherebeevenmorelight

127

Page 128: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

GameHUDInthischapterwewillcreateaHUD(Heads-UpDisplay)forourgame.Thatis,asetof2Dshapesandtextthataredisplayedatanytimeoverthe3Dscenetoshowrelevantinformation.WewillcreateasimpleHUDthatwillserveustoshowsomebasictechniquesforrepresentingthatinformation.

Whenyouexaminethesourcecodeforthischpater,youwillseealsothatsomelittlerefactoringhasbeenappliedtothesourcecode.ThechangesaffectespeciallytheRendererclassinordertoprepareitfortheHUDrendering.

TextrenderingThefirstthingthatwewilldotocreateaHUDisrendertext.Inordertodothat,wearegoingtomapatexturethatcontainsalphabetcharactersintoaquad.Thatquadwillbedividedbyasetoftileswhichwillrepresentasingleletter.lateron,wewillusethattexturetodrawthetextinthescreen.Sothefirststepistocreatethetexturethatcontainsthealphabet.Youcanusemanyprogramsouttherethatcandothistask,suchas,CBG,F2IBuilder,etc.Inthiscase,WewilluseCodehead’sBitmapFontGenerator(CBFG).

CBGletsyouconfiguremanyoptionssuchasthetexturesize,thefonttype,theanti-aliasingtobeapplied,etc.Thefollowingfiguredepictstheconfigurationthatwewillusetogenerateatexturefile.InthischapterwewillassumethatwewillberenderingtextencodedinISO-8859-1format,ifyouneedtodealwithdifferentcharactersetsyouwillneedtotweakalittlebitthecode.

HUD

128

Page 129: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

WhenyouhavefinishedconfiguringallthesettingsinCBGyoucanexporttheresulttoseveralimageformats.InthiscasewewillexportitasaBMPfileandthentransformittoPNGsoitcanbeloadedasatexture.WhentransformingittoPNGwewillsetupalsotheblackbackgroundastransparent,thatis,wewillsettheblackcolourtohaveanalphavalueequalto0(YoucanusetoolslikeGIMPtodothat).Attheendyouwillhavesomethingsimilarasthefollowingpicture.

Asyoucansee,theimagehasallthecharactersdisplayedinrowsandcolumns.Inthiscasetheimageiscomposedby15columnsand17rows.Byusingthecharactercodeofaspecificletterwecancalculatetherowandthecolumnthatisenclosedintheimage.The

HUD

129

Page 130: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

columncanbecalculatedasfollows:column = codemodnumberOfColumns.Wheremod

isthemoduleoperator.Therowcanbecalculatedasfollows:row = code/numberOfCols,inthiscasewewilldoaintegerbyintegeroperationsowecanignorethedecimalpart.

WewillcreateanewclassnamedTextItemthatwillconstructallthegraphicalelementsneededtorendertext.Thisisasimplifiedversionthatdoesnotdealwithmultilinetexts,etc.butitwillallowustopresenttextualinformationintheHUD.Hereyoucanseethefirstlinesandtheconstructorofthisclass.

packageorg.lwjglb.engine;

importjava.nio.charset.Charset;

importjava.util.ArrayList;

importjava.util.List;

importorg.lwjglb.engine.graph.Material;

importorg.lwjglb.engine.graph.Mesh;

importorg.lwjglb.engine.graph.Texture;

publicclassTextItemextendsGameItem{

privatestaticfinalfloatZPOS=0.0f;

privatestaticfinalintVERTICES_PER_QUAD=4;

privateStringtext;

privatefinalintnumCols;

privatefinalintnumRows;

publicTextItem(Stringtext,StringfontFileName,intnumCols,intnumRows)throws

Exception{

super();

this.text=text;

this.numCols=numCols;

this.numRows=numRows;

Texturetexture=newTexture(fontFileName);

this.setMesh(buildMesh(texture,numCols,numRows));

}

AsyoucanseethisclassextendstheGameItemclass,thisisbecausewewillbeinterestedinchangingthetextpositioninthescreenandmayalsoneedtoscaleandrotateit.Theconstructorreceivesthetexttobedisplayedandtherelevantdataofthetexturefilethatwillbeusedtorenderit(thefilethatcontainstheimageandthenumberofcolumnsandrows).

IntheconstructorweloadthetextureimagefileandinvokeamethodthatwillcreateaMeshinstancethatmodelsourtext.Let’sexaminethebuildMeshmethod.

HUD

130

Page 131: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privateMeshbuildMesh(Texturetexture,intnumCols,intnumRows){

byte[]chars=text.getBytes(Charset.forName("ISO-8859-1"));

intnumChars=chars.length;

List<Float>positions=newArrayList();

List<Float>textCoords=newArrayList();

float[]normals=newfloat[0];

List<Integer>indices=newArrayList();

floattileWidth=(float)texture.getWidth()/(float)numCols;

floattileHeight=(float)texture.getHeight()/(float)numRows;

Thefirstlinesofcodecreatethedatastructuresthatwillbeusedtostorethepositions,texturecoordinates,normalsandindicesoftheMesh.Inthiscasewewillnotapplylightingsothenormalsarraywillbeempty.Whatwearegoingtodoisconstructaquadcomposedbyasetoftiles,eachofthemrepresentingasinglecharacter.Weneedtoassignalsotheappropriatetexturecoordinatesdependingonthecharactercodeassociatedtoeachtile.Thefollowingpictureshowsthedifferentelementsthatcomposethetilesandthequad.

So,foreachcharacterweneedtocreateatilewhichisformedbytwotriangleswhichcanbedefinedbyusingfourvertices(V1,V2,V3andV4).Theindiceswillbe(0,1,2)forthefirsttriangle(thelowerone)and(3,0,2)fortheothertriangle(theupperone).Texturecoordinatesarecalculatedbasedonthecolumnandtherowassociatedtoeachcharacterinthetextureimage.Texturecoordinatesneedtobeintherange[0,1]sowejustneedtodividethecurrentroworthecurrentcolumnbythetotalnumberofrowsorcolumnstogetthecoordinateassociatedtoV1.Fortherestofverticeswejustneedtoincreasethecurrentcolumnorrowbyoneinordertogettheappropriatecoordinate.

Thefollowingloopcreatesallthevertexposition,texturecoordinatesandindicesassociatedtothequadthatcontainsthetext.

HUD

131

Page 132: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

for(inti=0;i<numChars;i++){

bytecurrChar=chars[i];

intcol=currChar%numCols;

introw=currChar/numCols;

//Buildacharactertilecomposedbytwotriangles

//LeftTopvertex

positions.add((float)i*tileWidth);//x

positions.add(0.0f);//y

positions.add(ZPOS);//z

textCoords.add((float)col/(float)numCols);

textCoords.add((float)row/(float)numRows);

indices.add(i*VERTICES_PER_QUAD);

//LeftBottomvertex

positions.add((float)i*tileWidth);//x

positions.add(tileHeight);//y

positions.add(ZPOS);//z

textCoords.add((float)col/(float)numCols);

textCoords.add((float)(row+1)/(float)numRows);

indices.add(i*VERTICES_PER_QUAD+1);

//RightBottomvertex

positions.add((float)i*tileWidth+tileWidth);//x

positions.add(tileHeight);//y

positions.add(ZPOS);//z

textCoords.add((float)(col+1)/(float)numCols);

textCoords.add((float)(row+1)/(float)numRows);

indices.add(i*VERTICES_PER_QUAD+2);

//RightTopvertex

positions.add((float)i*tileWidth+tileWidth);//x

positions.add(0.0f);//y

positions.add(ZPOS);//z

textCoords.add((float)(col+1)/(float)numCols);

textCoords.add((float)row/(float)numRows);

indices.add(i*VERTICES_PER_QUAD+3);

//Addindicesporlefttopandbottomrightvertices

indices.add(i*VERTICES_PER_QUAD);

indices.add(i*VERTICES_PER_QUAD+2);

}

Thearesomeimportantthingstonoticeinthepreviousfragmentofcode:

Wewillrepresenttheverticesusingscreencoordinates(rememberthattheoriginofthescreencoordinatesislocatedatthetopleftcorner).Theycoordinateoftheverticesontopofthetrianglesislowerthantheycoordinateoftheverticesonthebottomofthetriangles.

HUD

132

Page 133: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Wedon’tscaletheshape,soeachtileisataxdistanceequaltoacharacterwidth.Theheightofthetriangleswillbetheheightofeachcharacter.Thisisbecausewewanttorepresentthetextassimilaraspossibleastheoriginaltexture.(AnywaywecanlaterscaletheresultsinceTextItemclassinheritsfromGameItem).Wesetafixedvalueforthezcoordinate,sinceitwillbeirrelevantinordertodrawthisobject.

Thenextfigureshowsthecoordinatesofsomevertices.

Whydoweusescreencoordinates?Firstofall,becausewewillberendering2DobjectsinourHUDandoftenismorehandytousethem,andsecondlybecausewewilluseanorthographicprojectioninordertodrawthem.Wewillexplainwhatisanorthographicprojectionlateron.

TheTextItemclassiscompletedwithothermethodstogetthetextandtochangeitatruntime.Wheneverthetextischanged,weneedtocleanupthepreviousVAOs(storedintheMeshinstance)andcreateanewone.Wedonotneedtodestroythetexture,sowehavecreatedanewmethodintheMeshclasstojustremovethatdata.

publicStringgetText(){

returntext;

}

publicvoidsetText(Stringtext){

this.text=text;

Texturetexture=this.getMesh().getMaterial().getTexture();

this.getMesh().deleteBuffers();

this.setMesh(buildMesh(texture,numCols,numRows));

}

HUD

133

Page 134: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Nowthatwehavesetuptheinfrastuctureneededtodarwtext,Howdowedoit?Thebasisisfirsttorenderthe3Dscene,asinthepreviouschapters,andthenrenderthe2DHUDoverit.InordertorendertheHUDwewilluseanorthographicprojection(alsonamedorthogonalprojection).AnOrthographicprojectionisa2Drepresentationofa3Dobject.Youmayalreadyhaveseensomesamplesinblueprintsof3Dobjectswhichshowtherepresentationofthoseobjectsfromthetoporfromsomesides.Thefollowingpictureshowstheorthographicprojectionofacylinderfromthetopandfromthefront.

Thisprojectionisveryconvenientinordertodraw2Dobjectsbecauseit"ignores"thevaluesofthezcoordinates,thatis,thedistancetotheview.Withthisprojectiontheobjectssizesdonotdecreasewiththedistance(asintheperspectiveprojection).Inordertoprojectanobjectusinganortographicprojectionwewillneedtouseanothermatrix,theorthographicmatrixwhichformulaisshownbelow.

Thismatrixalsocorrectsthedistortionsthatotherwisewillbegeneratedduetothefactthatourwindowisnotalwaysaperfectsquarebutarectangle.Therightandbottomparameterswillbethescreensize,theleftandthetoponeswillbetheorigin.Theorthographicprojectionmatrixisusedtotransformscreencoordinatesto3Dspacecoordinates.Thefollowingpictureshowshowthismappingisdone.

HUD

134

Page 135: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thepropertiesofthismatrix,willallowustousescreencoordinates.

WecannowcontinuewiththeeimplementationoftheHUD.Thenextthingthatweshoulddoiscreateanothersetofshaders,avertexandafragmentshaders,inordertodrawtheobjectsoftheHUD.Thevertexshaderisactuallyverysimple.

#version330

layout(location=0)invec3position;

layout(location=1)invec2texCoord;

layout(location=2)invec3vertexNormal;

outvec2outTexCoord;

uniformmat4projModelMatrix;

voidmain()

{

gl_Position=projModelMatrix*vec4(position,1.0);

outTexCoord=texCoord;

}

Itwilljustreceivetheverticespositions,thetexturecoordinates,theindicesandthenormalsandwilltransformthemtothe3Dspacecoordinatesusingamatrix.Thatmatrixisthemultiplicationoftheortographicprojectionmatrixandthemodelmatrix,projModelMatrix = ortographicMatrix ⋅modelMatrix.Sincewearenotdoinganythingwiththecoordinatesinmodelspace,it’smuchmoreefficienttomultiplybothmatricesinjavacodethanintheshadere.Bydoingsowewillbedoingthatmultipliaztiononceperiteminstedofdoingitforeachvertex.Rememberthatourverticesshouldbeexpressedinscreencoordinates.

Thefragmentshaderisalsoverysimple.

HUD

135

Page 136: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

#version330

invec2outTexCoord;

invec3mvPos;

outvec4fragColor;

uniformsampler2Dtexture_sampler;

uniformvec4colour;

voidmain()

{

fragColor=colour*texture(texture_sampler,outTexCoord);

}

Itjustusesthetexturecoordinatesandmultiplesthatcolourbyabasecolour.Thiscanbeusedtochangethecolourofthetexttoberenderedwithouttheneedofcreatingseveraltexturefiles.NowthatwehavecreatedthenewpairofshaderswecanusethemintheRendererclass.But,beforethat,wewillcreateanewinterfacenamedIHudthatwillcontainalltheelementsthataretobedisplayedintheHUD.Thisinterfacewillalsoprovideadefaultcleanupmethod.

packageorg.lwjglb.engine;

publicinterfaceIHud{

GameItem[]getGameItems();

defaultvoidcleanup(){

GameItem[]gameItems=getGameItems();

for(GameItemgameItem:gameItems){

gameItem.getMesh().cleanUp();

}

}

}

ByusingthatinterfaceourdifferentgamescandefinecustomHUDsbuttherenderingmechanismdoesnotneedtobechanged.NowwecangetbacktotheRendererclass,whichbythewayhasbeenmovedtotheenginegraphicspackagebecausenowit’sgenericenoughtonotbedependentonthespecificimplementationofeachgame.IntheRendererclasswehaveaddedanewmethodtocreate,linkandsetupanewShaderProgramthatusestheshadersdescribedabove.

HUD

136

Page 137: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatevoidsetupHudShader()throwsException{

hudShaderProgram=newShaderProgram();

hudShaderProgram.createVertexShader(Utils.loadResource("/shaders/hud_vertex.vs"));

hudShaderProgram.createFragmentShader(Utils.loadResource("/shaders/hud_fragment.fs"

));

hudShaderProgram.link();

//CreateuniformsforOrtographic-modelprojectionmatrixandbasecolour

hudShaderProgram.createUniform("projModelMatrix");

hudShaderProgram.createUniform("colour");

}

TherendermethodfirstinvokesthemethodrenderScenewhichcontainsthecodefrompreviouschapterthatrenderedthe3Dscene,andanewmethod,namedrenderHud,torendertheHUD.

publicvoidrender(Windowwindow,Cameracamera,GameItem[]gameItems,

SceneLightsceneLight,IHudhud){

clear();

if(window.isResized()){

glViewport(0,0,window.getWidth(),window.getHeight());

window.setResized(false);

}

renderScene(window,camera,gameItems,sceneLight);

renderHud(window,hud);

}

TherenderHudmethodisasfollows:

HUD

137

Page 138: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatevoidrenderHud(Windowwindow,IHudhud){

hudShaderProgram.bind();

Matrix4fortho=transformation.getOrthoProjectionMatrix(0,window.getWidth(),win

dow.getHeight(),0);

for(GameItemgameItem:hud.getGameItems()){

Meshmesh=gameItem.getMesh();

//SetortohtaphicandmodelmatrixforthisHUDitem

Matrix4fprojModelMatrix=transformation.getOrtoProjModelMatrix(gameItem,ort

ho);

hudShaderProgram.setUniform("projModelMatrix",projModelMatrix);

hudShaderProgram.setUniform("colour",gameItem.getMesh().getMaterial().getAmbi

entColour());

//RenderthemeshforthisHUDitem

mesh.render();

}

hudShaderProgram.unbind();

}

Thepreviousfragmentofcode,iteratesovertheelementsthatcomposetheHUDandmultipliestheorthographicprojectionmatrixbythemodelmatrixassociatedtoeachelement.Theorthographicprojectionmatrixisupdatedineachrendercall(becausethescreendimensionscanchange),andit’scalculatedinthefollowingway:

publicfinalMatrix4fgetOrthoProjectionMatrix(floatleft,floatright,floatbottom,

floattop){

orthoMatrix.identity();

orthoMatrix.setOrtho2D(left,right,bottom,top);

returnorthoMatrix;

}

InourgamepackagewewillcreateaHudclasswhichimplementstheIHudinterfaceandreceivesatextintheconstructorcreatinginternallyaTexIteminstance.

HUD

138

Page 139: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

packageorg.lwjglb.game;

importorg.joml.Vector4f;

importorg.lwjglb.engine.GameItem;

importorg.lwjglb.engine.IHud;

importorg.lwjglb.engine.TextItem;

publicclassHudimplementsIHud{

privatestaticfinalintFONT_COLS=15;

privatestaticfinalintFONT_ROWS=17;

privatestaticfinalStringFONT_TEXTURE="/textures/font_texture.png";

privatefinalGameItem[]gameItems;

privatefinalTextItemstatusTextItem;

publicHud(StringstatusText)throwsException{

this.statusTextItem=newTextItem(statusText,FONT_TEXTURE,FONT_COLS,FONT_R

OWS);

this.statusTextItem.getMesh().getMaterial().setColour(newVector4f(1,1,1,1)

);

gameItems=newGameItem[]{statusTextItem};

}

publicvoidsetStatusText(StringstatusText){

this.statusTextItem.setText(statusText);

}

@Override

publicGameItem[]getGameItems(){

returngameItems;

}

publicvoidupdateSize(Windowwindow){

this.statusTextItem.setPosition(10f,window.getHeight()-50f,0);

}

}

IntheDummyGameclasswecreateaninstanceofthatclassaninitializeitwithadefaulttext,andwewillgetsomethinglikethis.

HUD

139

Page 140: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

IntheTextureclassweneedtomodifythewaytexturesareinterpolatedtoimprovetextreadibility(youwillonlynoticeifyouplaywiththetextscaling).

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);

Butthesampleisnotfnishedyet.Ifyouplaywiththezoomsothetextoverlapswiththecubeyouwillseethiseffect.

Thetextisnotdrawnwithatransparentbackground.Inordertoachievethat,wemustexplicitlyenablesupportforblendingsothealphacomponentcanbeused.WewilldothisintheWindowclasswhenwesetuptheotherinitializationparameterswiththefollowingfragmentofcode.

//Supportfortransparencies

glEnable(GL_BLEND);

glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

Nowyouwillseethetextdrawnwithatransparentbackground.

HUD

140

Page 141: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

CompletetheHUDNowthatwehaverenderedatextwecanaddmoreelementstotheHUD.Wewilladdacompassthatrotatesdependingonthedirectionthecameraisfacing.Inthiscase,wewilladdanewGameItemtotheHudclassthatwillhaveameshthatmodelsacompass.

Thecompasswillbemodeledbyan.objfilebutwillnothaveatextureassociated,insteaditwillhavejustabackgroundcolour.SoweneedtochangethefragmentshaderfortheHUDalittlebittodetectifwehaveatextureornot.WewillbeabletodothisbysettinganewuniformnamedhasTexture.

HUD

141

Page 142: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

#version330

invec2outTexCoord;

invec3mvPos;

outvec4fragColor;

uniformsampler2Dtexture_sampler;

uniformvec4colour;

uniforminthasTexture;

voidmain()

{

if(hasTexture==1)

{

fragColor=colour*texture(texture_sampler,outTexCoord);

}

else

{

fragColor=colour;

}

}

ToaddthecompassthetheHUDwejustneedtocreateanewGameIteminstance,tntheHudclass,thatloadsthecompassmodelandaddsittothelistofitems.Inthiscasewewillneedtoscaleupthecompass.Rememberthatitneedstobeexpressedinscreencoordinates,sousuallyyouwillneedtoincreaseitssize.

//Createcompass

Meshmesh=OBJLoader.loadMesh("/models/compass.obj");

Materialmaterial=newMaterial();

material.setAmbientColour(newVector4f(1,0,0,1));

mesh.setMaterial(material);

compassItem=newGameItem(mesh);

compassItem.setScale(40.0f);

//Rotatetotransformittoscreencoordinates

compassItem.setRotation(0f,0f,180f);

//CreatelistthatholdstheitemsthatcomposetheHUD

gameItems=newGameItem[]{statusTextItem,compassItem};

Noticealsothat,inorderforthecompasstopointupwardsweneedtorotate180degreessincethemodelwilloftentendtouseOpenGLspacecoordinates.Ifweareexpectingscreencoordinatesitwouldpointingdownwards.TheHudclasswillalsoprovideamethodtoupdatetheangleofthecompassthatmusttakethisalsointoconsideration.

HUD

142

Page 143: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicvoidrotateCompass(floatangle){

this.compassItem.setRotation(0,0,180+angle);

}

IntheDummyGameclasswewillupdatetheanglewheneverthecameraismoved.Weneedtousetheyanglerotation.

//Updatecamerabasedonmouse

if(mouseInput.isRightButtonPressed()){

Vector2frotVec=mouseInput.getDisplVec();

camera.moveRotation(rotVec.x*MOUSE_SENSITIVITY,rotVec.y*MOUSE_SENSITIVITY,0)

;

//UpdateHUDcompass

hud.rotateCompass(camera.getRotation().y);

}

Wewillgetsomethinglikethis(rememberthatitisonlyasample,inarealgameyoumayprobablywanttousesometexturetogivethecompassadifferentlook).

TextrenderingrevisitedBeforereviewingothertopicslet’sgobacktothetextrenderingapproachwehavepresentedhere.ThesolutionisverysimpleandhandytointroducetheconceptsinvolvedinrenderingHUDelementsbutitpresentssomeproblems:

Itdoesnotsupportnonlatincharactersets.Ifyouwanttouseseveralfontsyouneedtocreateaseparatetexturefileforeachfont.Also,theonlywaytochangethetextsizeiseithertoscaleit,whichmayresultinapoorqualityrenderedtext,ortogenerateanothertexturefile.

HUD

143

Page 144: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Themostimportantone,charactersinmostofthefontsdonotoccupythesamesizebutwearedividingthefonttextureinequallysizedelements.Wehavecleverlyused“Consolas”fontwhichismonospaced(thatis,allthecharactersoccupythesameamountofhorizontalspace),butifyouuseanon-monospacedfontyouwillseeannoyingvariablewhitespacesbetweenthecharacters.

Weneedtochangeourapproachanprovideamoreflexiblewaytorendertext.Ifyouthinkaboutit,theoverallmechanismisok,thatis,thewayofrenderingtextbytexturingquadsforeachcharacter.Theissuehereishowwearegeneratingthetextures.WeneedtobeabletogeneratethosetexturedynamicallybyusingthefontsavailableintheSystem.

Thisiswherejava.awt.Fontcomestotherescue,wewillgeneratethetexturesbydrawingeachletterforaspecifiedfontfamilyandsizedynamically.Thattexturewillbeusedinthesamewayasdescribedpreviously,butitwillsolveperfectlyalltheissuesmentionedabove.WewillcreateanewclassnamedFontTexturethatwillreceiveaFontinstanceandacharsetnameandwilldynamicallycreateatexturethatcontainsalltheavailablecharacters.Thisistheconstructor.

publicFontTexture(Fontfont,StringcharSetName)throwsException{

this.font=font;

this.charSetName=charSetName;

charMap=newHashMap<>();

buildTexture();

}

Thefirststepistohandlethenonlatinissue,givenacharsetandafontwewillbuildaStringthatcontainsallthecharactersthatcanberendered.

privateStringgetAllAvailableChars(StringcharsetName){

CharsetEncoderce=Charset.forName(charsetName).newEncoder();

StringBuilderresult=newStringBuilder();

for(charc=0;c<Character.MAX_VALUE;c++){

if(ce.canEncode(c)){

result.append(c);

}

}

returnresult.toString();

}

Let’snowreviewthemethodthatactuallycreatesthetexture,namedbuildTexture.

HUD

144

Page 145: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatevoidbuildTexture()throwsException{

//Getthefontmetricsforeachcharacterfortheselectedfontbyusingimage

BufferedImageimg=newBufferedImage(1,1,BufferedImage.TYPE_INT_ARGB);

Graphics2Dg2D=img.createGraphics();

g2D.setFont(font);

FontMetricsfontMetrics=g2D.getFontMetrics();

StringallChars=getAllAvailableChars(charSetName);

this.width=0;

this.height=0;

for(charc:allChars.toCharArray()){

//Getthesizeforeachcharacterandupdateglobalimagesize

CharInfocharInfo=newCharInfo(width,fontMetrics.charWidth(c));

charMap.put(c,charInfo);

width+=charInfo.getWidth();

height=Math.max(height,fontMetrics.getHeight());

}

g2D.dispose();

Wefirstobtainthefontmetricsbycreatingatemporaryimage.ThenweiterateovertheStringthatcontainsalltheavailablecharactersandgetthewidth,withthehelpofthefontmetrics,ofeachofthem.Westorethatinformationonamap,charMap,whichwilluseasakeythecharacter.Withthatprocesswedeterminethesizeoftheimagethatwillhavethetexture(withaheightequaltothemaximumsizeofallthecharactersanditswithequaltothesumofeachcharacterwidth).CharSetisaninnerclassthatholdstheinformationaboutacharacter(itswidthandwhereitstarts,inthexcoordinate,inthetextureimage).

publicstaticclassCharInfo{

privatefinalintstartX;

privatefinalintwidth;

publicCharInfo(intstartX,intwidth){

this.startX=startX;

this.width=width;

}

publicintgetStartX(){

returnstartX;

}

publicintgetWidth(){

returnwidth;

}

}

HUD

145

Page 146: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thenwewillcreateanimagethatwillcontainalltheavailablecharacters.Inordertodothis,wejustdrawthestringoveraBufferedImage.

//Createtheimageassociatedtothecharset

img=newBufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);

g2D=img.createGraphics();

g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALI

AS_ON);

g2D.setFont(font);

fontMetrics=g2D.getFontMetrics();

g2D.setColor(Color.WHITE);

g2D.drawString(allChars,0,fontMetrics.getAscent());

g2D.dispose();

Wearegeneratinganimagewhichcontainsallthecharactersinasinglerow(wemaybearenotfulfillingthepremisethatthetextureshouldhaveasizeofapoweroftwo,butitshouldworkonmostmoderncards.Inanycaseyoucouldalwaysachievethatbyaddingsomeextraemptyspace).Youcanevenseetheimagethatwearegenerating,ifafterthatblockofcode,youputalinelikethis:

ImageIO.write(img,IMAGE_FORMAT,newjava.io.File("Temp.png"));

Theimagewillbewrittentoatemporaryfile.Thatfilewillcontainalongstripwithalltheavailablecharacters,drawninwhiteovertransparentbackgroundusingantialiasing.

Finally,wejustneedtocreateaTextureinstancefromthatimage,wejustdumptheimagebytesusingaPNGformat(whichiswhattheTextureclassexpects).

//Dumpimagetoabytebuffer

InputStreamis;

try(

ByteArrayOutputStreamout=newByteArrayOutputStream()){

ImageIO.write(img,IMAGE_FORMAT,out);

out.flush();

is=newByteArrayInputStream(out.toByteArray());

}

texture=newTexture(is);

}

HUD

146

Page 147: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

YoumaynoticethatwehavemodifiedalittlebittheTextureclasstohaveanotherconstructorthatreceivesanInputStream.NowwejustneedtochangetheTextItemclasstoreceiveaFontTextureinstanceinitsconstructor.

publicTextItem(Stringtext,FontTexturefontTexture)throwsException{

super();

this.text=text;

this.fontTexture=fontTexture;

setMesh(buildMesh());

}

ThebuildMeshmethodonlyneedstobechangedalittlebitwhensettingquadandtexturecoordinates,thisisasampleforoneofthevertices.

floatstartx=0;

for(inti=0;i<numChars;i++){

FontTexture.CharInfocharInfo=fontTexture.getCharInfo(characters[i]);

//Buildacharactertilecomposedbytwotriangles

//LeftTopvertex

positions.add(startx);//x

positions.add(0.0f);//y

positions.add(ZPOS);//z

textCoords.add((float)charInfo.getStartX()/(float)fontTexture.getWidth());

textCoords.add(0.0f);

indices.add(i*VERTICES_PER_QUAD);

//..Morecode

startx+=charInfo.getWidth();

}

Youcanchecktherestofthechangesdirectlyinthesourcecode.ThefollowingpictureshowswhatyouwillgetforanArialfontwithasizeof20:

HUD

147

Page 148: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Asyoucanseethequalityoftherenderedtexthasbeenimprovedalot,youcanplaywithdifferentfontsandsizesandcheckitbyyourown.There’sstillplentyofroomforimprovement(likesupportingmultilinetexts,effects,etc.),butthiswillbeleftasanexerciseforthereader.

Youmayalsonoticethatwearestillabletoapplyscalingtothetext(wepassamodelviewmatrixintheshader).ThismaynotbeneedednowfortextbutitmaybeusefulforotherHUDelements.

WehavesetupalltheinfrastructureneededinordertocreateaHUDforourgames.Nowitisjustamatterofcreatingalltheelementsthatrepresentrelevantinformationtotheuserandgivethemaprofessionallookandfeel.

OSXIfyoutrytorunthesamplesinthischapter,andthenextonesthatrendertext,youmayfindthattheapplicationblocksandnothingisshowninthescreen.ThisisduetothefactthatAWTandGLFWdogetalongverywellunderOSX.But,whatdoesithavetodowithAWT?WeareusingtheFontclass,whichbelongstoAWT,andjustbyinstantiatingit,AWTgetsinitializedalso.InOSXAWTtriestorununderthemainthread,whichisalsorequiredbyGLFW.Thisiswhatcausesthismess.

InordertobeabletousetheFontclass,GLFWmustbeinitializedbeforeAWTandthesamplesneedtoberuninheadlessmode.Youneedtosetupthispropertybeforeanythinggetsintialized:

System.setProperty("java.awt.headless","true");

Youmaygetawarning,butthesampleswillrun.

Amuchmorecleanapproachwouldbetousethestblibrarytorendertext.

HUD

148

Page 149: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

SkyBoxandsomeoptimizations

SkyboxAskyboxwillallowustosetabackgroundtogivetheillusionthatour3Dworldisbigger.Thatbackgroundiswrappedaroundthecamerapositionandcoversthewholespace.Thetechniquethatwearegoingtousehereistoconstructabigcubethatwillbedisplayedaroundthe3Dscene,thatis,thecentreofthecamerapositionwillbethecentreofthecube.Thesidesofthatcubewillbewrappedwithatexturewithhillsablueskyandcloudsthatwillbemappedinawaythattheimagelooksacontinuouslandscape.

Thefollowingpicturedepictstheskyboxconcept.

Theprocessofcreatingaskyboxcanbesummarizedinthefollowingsteps:

Createabigcube.Applyatexturetoitthatprovidestheillusionthatweareseeingagiantlandscapewithnoedges.Renderthecubesoitssidesareatafardistanceanditsoriginislocatedatthecentreofthecamera.

Then,let’sstartwiththetexture.Youwillfindthattherearelotsofpre-generatedtexturesforyoutouseintheinternet.Theoneusedinthesampleforthischapterhasbeendownloadedfromhere:http://www.custommapmakers.org/skyboxes.php.Theconcretesamplethatwe

SkyBoxandsomeoptimizations

149

Page 150: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

haveusedisthisone:http://www.custommapmakers.org/skyboxes/zips/ely_hills.zipandhasbeencreatedbyColinLowndes.

ThetexturesfromthatsitearecomposedbyseparateTGAfiles,oneforeachsideofthecube.ThetextureloaderthatwehavecreatedexpectsasinglefileinPNGformatsoweneedtocomposeasinglePNGimagewiththeimagesofeachface.Wecouldapplyothertechniques,suchuscubemapping,inordertoapplythetexturesautomatically.But,inordertokeepthischapterassimpleaspossible,youwillhavetomanuallayarrangethemintoasinglefile.Theresultimagewilllooklikethis.

Afterthat,weneedtocreatea.objfilewhichcontainsacubewiththecorrecttexturecoordinatesforeachface.Thepicturebelowshowsthetilesassociatedtoeachface(youcanfindthe.objfileusedinthischapterinthebook’ssourcecode).

SkyBoxandsomeoptimizations

150

Page 151: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Oncetheresoureshavebeensetup,wecanstartcoding.WewillstartbycreatinganewclassnamedSkyBoxwithaconstructorthatreceivesthepathtotheOBJmodelthatcontainstheskyboxcubeandthetexturefile.ThisclasswillinheritfromGameItemastheHUDclassfromthepreviouschapter.WhyitshouldinheritfromGameItem?Firstofall,forconvenience,wecanreusemostofthecodethatdealswithmeshesandtextures.Secondly,because,althoughtheskyboxwillnotmovewewillbeinterestedinapplyingrotationsandscalingtoit.IfyouthinkaboutitaSkyBoxisindeedagameitem.ThedefinitionoftheSkyBoxclassisasfollows.

packageorg.lwjglb.engine;

importorg.lwjglb.engine.graph.Material;

importorg.lwjglb.engine.graph.Mesh;

importorg.lwjglb.engine.graph.OBJLoader;

importorg.lwjglb.engine.graph.Texture;

publicclassSkyBoxextendsGameItem{

publicSkyBox(StringobjModel,StringtextureFile)throwsException{

super();

MeshskyBoxMesh=OBJLoader.loadMesh(objModel);

TextureskyBoxtexture=newTexture(textureFile);

skyBoxMesh.setMaterial(newMaterial(skyBoxtexture,0.0f));

setMesh(skyBoxMesh);

setPosition(0,0,0);

}

}

Ifyoucheckthesourcecodeforthischapteryouwillseethatwehavedonesomerefactoring.WehavecreatedaclassnamedScenewhichgroupsalltheinformationrelatedtothe3Dworld.ThisisthedefinitionandtheattributesoftheSceneclass,thatcontainsaninstanceoftheSkyBoxclass.

SkyBoxandsomeoptimizations

151

Page 152: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

packageorg.lwjglb.engine;

publicclassScene{

privateGameItem[]gameItems;

privateSkyBoxskyBox;

privateSceneLightsceneLight;

publicGameItem[]getGameItems(){

returngameItems;

}

//Morecodehere...

Thenextstepistocreateanothersetofvertexandfragmentshadersfortheskybox.But,whynotreusethesceneshadersthatwealreadyhave?Theansweristhat,actually,theshadersthatwewillneedareasimplifiedversionofthoseshaders,wewillnotbeapplyinglightstotheskybox(ortobemoreprecise,wewon’tneedpoint,spotordirectionallights).Belowyoucanseetheskyboxvertexshader.

#version330

layout(location=0)invec3position;

layout(location=1)invec2texCoord;

layout(location=2)invec3vertexNormal;

outvec2outTexCoord;

uniformmat4modelViewMatrix;

uniformmat4projectionMatrix;

voidmain()

{

gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);

outTexCoord=texCoord;

}

Youcanseethatwestillusethemodelviewmatrix.Atishasbeenexplainedbeorewewillscaletheskybox,soweneedthattransformationmatrix.Youmayseesomeotherimplementationsthatincreasethesizeofthecubethatmodelstheskyboxatstarttimeanddonotneedtomultiplythemodelandtheviewmatrix.Wehavechosenthisapproachbecauseit’smoreflexibleanditallowsustochangethesizeoftheskyboxatruntime,butyoucaneasilyswitchtotheotherapproachifyouwant.

Thefragmentshaderisalsoverysimple.

SkyBoxandsomeoptimizations

152

Page 153: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

#version330

invec2outTexCoord;

invec3mvPos;

outvec4fragColor;

uniformsampler2Dtexture_sampler;

uniformvec3ambientLight;

voidmain()

{

fragColor=vec4(ambientLight,1)*texture(texture_sampler,outTexCoord);

}

Asyoucansee,weaddedanambientlightuniformtotheshader.Thepurposeofthisuniformistomodifythecolouroftheskyboxtexturetosimulatedayandnight(Ifnot,theskyboxwilllooklikeifitwasmiddaywhentherestoftheworldisdark).

IntheRendererclasswejusthaveaddedanewmethodtousethoseshadersandsetuptheuniforms(nothingnewhere).

privatevoidsetupSkyBoxShader()throwsException{

skyBoxShaderProgram=newShaderProgram();

skyBoxShaderProgram.createVertexShader(Utils.loadResource("/shaders/sb_vertex.vs")

);

skyBoxShaderProgram.createFragmentShader(Utils.loadResource("/shaders/sb_fragment.

fs"));

skyBoxShaderProgram.link();

skyBoxShaderProgram.createUniform("projectionMatrix");

skyBoxShaderProgram.createUniform("modelViewMatrix");

skyBoxShaderProgram.createUniform("texture_sampler");

skyBoxShaderProgram.createUniform("ambientLight");

}

Andofcourse,weneedtocreateanewrendermethodfortheskyboxthatwillbeinvokedintheglobalrendermethod.

SkyBoxandsomeoptimizations

153

Page 154: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatevoidrenderSkyBox(Windowwindow,Cameracamera,Scenescene){

skyBoxShaderProgram.bind();

skyBoxShaderProgram.setUniform("texture_sampler",0);

//UpdateprojectionMatrix

Matrix4fprojectionMatrix=transformation.getProjectionMatrix(FOV,window.getWidt

h(),window.getHeight(),Z_NEAR,Z_FAR);

skyBoxShaderProgram.setUniform("projectionMatrix",projectionMatrix);

SkyBoxskyBox=scene.getSkyBox();

Matrix4fviewMatrix=transformation.getViewMatrix(camera);

viewMatrix.m30(0);

viewMatrix.m31(0);

viewMatrix.m32(0);

Matrix4fmodelViewMatrix=transformation.getModelViewMatrix(skyBox,viewMatrix);

skyBoxShaderProgram.setUniform("modelViewMatrix",modelViewMatrix);

skyBoxShaderProgram.setUniform("ambientLight",scene.getSceneLight().getAmbientLig

ht());

scene.getSkyBox().getMesh().render();

skyBoxShaderProgram.unbind();

}

Themethodaboveisquitesimilartotheotherrenderonesbutthere’sadifferencethatneedstobeexplained.Asyoucansee,wepasstheprojectionmatrixandthemodelviewmatrixasusual.But,whenwegettheviewmatrix,wesetsomeofthecomponentsto0.Whywedothis?Thereasonbehindthatisthatwedonotwanttranslationtobeappliedtotheskybox.

Rememberthatwhenwemovethecamera,whatweareactuallydoingismovingthewholeworld.Soifwejustmultiplytheviewmatrixasitis,theskyboxwillbedisplacedwhenthecameramovees.Butwedonotwantthis,wewanttostickitattheorigincoordinatesat(0,0,0).Thisisachievedbysettingto0thepartsoftheviewmatrixthatcontainthetranslationincrements(them30,m31andm32components).

Youmaythinkthatyoucouldavoidusingtheviewmatrixatallsincetheskyboxmustbefixedattheorigin.Inthatcasewhat,youwillseeisthattheskyboxwillnotrotatewiththecamera,whichisnotwhatwewant.Weneedittorotatebutnottranslate.

Andthat’sall,youcancheckinthesourcecodeforthischapterthatintheDummyGameclassthatwehavecreatedmoreblockinstancestosimulateagroundandtheskybox.Youcanalsocheckthatwenowchangetheambientlighttosimulatelightandday.Whatyouwillgetissomethinglikethis.

SkyBoxandsomeoptimizations

154

Page 155: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Theskyboxisasmallonesoyoucaneasilyseetheeffectofmovingthroughtheworld(inarealgameitshouldbemuchbigger).Youcanseealsothattheworldspaceobjects,theblocksthatformtheterrainarelargerthantheskybox,soasyoumovethroughityouwillseeblocksappearingthroughthemountains.Thisismoreevidentbecauseoftherelativesmallsizeoftheskyboxwehaveset.Butanywaywewillneedtoalleviatethatbyaddinganeffectthathidesorblurdistantobjects(forinstanceapplyingafogeffect).

Anotherreasonfornotcreatingabiggerskyboxisbecauseweneedtoapplyseveraloptimizationsinordertobemoreefficient(theywillbeexplainedlateron).

Youcanplaywiththerendermethodancommentthelinesthatpreventtheskyboxfromtranslating.Thenyouwillbeabletogetoutoftheboxandseesomethinglikethis.

SkyBoxandsomeoptimizations

155

Page 156: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Althoughitisnotwhataskyboxshoulddoitcanhelpyououttounderstandtheconceptbehindthistecnique.Rememberthatthisisasimpleexample,youcouldimproveitbyaddingothereffectssuchasthesunmovingthroughtheskyormovingclouds.Also,inordertocreatebiggerworldsyouwillneedtosplittheworldintofragmentsandonlyloadtheonesthatarecontiguoustothefragmentwheretheplayeriscurrentlyin.

Anotherpointthatisworthtomentioniswhenshouldwerendertheskybox,beforethesceneorafter?Renderingafterthescenehasbeendrawnismoreoptimal,sincemostofthefragmentswillbediscardedduetodepthtesting.Thatis,nonvisibleskyboxfragments,theones,thatwillbehiddenbysceneelementswillbediscarded.WhenOpenGLwilltrytorenderthem,anddepthtestisenabled,itwilldiscardtheoneswhicharebehindsomepreviouslyrenderedfragments,whichwillhavealowerdepthvalue.Sotheanswermightbeobvious,right?Justrendertheskyboxafterthescenehasbeenrendered.Theproblemwiththisapproachishandlingtransparenttextures.Ifwehave,inthescene,objectswithtransparenttextures,theywillbedrawnusingthe"background"colour,whichisnowblack.Ifwerendertheskyboxbefore,thetransparenteffectwillbeappliedcorrectly.So,shallwerenderitbeforethescenethen?Well,yesandno.Ifyourenderbeforethesceneisrenderedyouwillsolvetransparencyissuesbutyouwillimpactperformance.Infact,youstillmayfacetransparencyissueswithoutaskybox.Forinstance,let'ssaythatyouhaveatransparentitem,whichoverlapswithanobjectthatisfaraway.Ifthetransparentobjectisrenderedfirst,youwillfacealsotransperentissues.So,maybeanotherapproachcanbeto

SkyBoxandsomeoptimizations

156

Page 157: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

drawtransparentitems,seperately,afteralltheotheritemshavebeenrendered.Thisistheapproachusedbysomecommercialgames.So,bynow,wewillrendertheskyboxafterthescenehasbeenrendered,tryingtogetbetterperformance.

SomeoptimizationsFromthepreviousexample,thefactthattheskyboxisrelativesmallmakestheeffectalittlebitweird(youcanseeobjectsappearingmagicallybehindthehills).So,ok,let’sincreasetheskyboxsizeandthesizeofourworld.Let’sscalethesizeoftheskyboxbyafactorof50sotheworldwillbecomposedby40,000GameIteminstances(cubes).

Ifyouchangethescalefactorandreruntheexampleyouwillseethatperformanceproblemstartstoariseandthemovementthroughthe3Dworldisnotsmooth.It’stimetoputaneyeonperformance(youmayknowtheoldsayingthatstatesthat“prematureoptimizationistherootofallevil”,butsincethischapter13,Ihopenobodywillsaythatthispremature).

Let’sstartwithaconceptthatwillreducetheamountofdatathatisbeingrendered,whichisnamedfaceculling.Inourexampleswearerenderingthousandsofcubes,andacubeismadeofsixfaces.Wearerenderingthesixfacesforeachcubeeveniftheyarenotvisible.Youcancheckthisifyouzoominsideacube,youwillseeitsinteriorlikethis.

Facesthatcannotbeseenshouldbediscardedimmediatelyandthisiswhatfacecullingdoes.Infact,foracubeyoucanonlysee3facesatthesametime,sowecanjustdiscardhalfofthefaces(40,00032triangles)justbyapplyingfaceculling(thiswillonlybevalidifyourgamedoesnotrequireyoutodiveintotheinnersideofamodel,youcanseewhylateron).

Facecullingchecks,foreverytriangleifitsfacingtowardsusanddiscardstheonesthatarenotfacingthatdirection.But,howdoweknowifatriangleisfacingtowardsusornot?Well,thewaythatOpenGLdoesthisisbythewindingorderoftheverticesthatcomposeatriangle.

SkyBoxandsomeoptimizations

157

Page 158: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Rememberfromthefirstchaptersthatwemaydefinetheverticesofatriangleinclockwiseorcounter-clockwiseorder.InOpenGL,bydefault,trianglesthatareincounter-clockwiseorderarefacingtowardstheviewerandtrianglesthatareinclockwiseorderarefacingbackwards.Thekeythinghere,isthatthisorderischeckedwhilerenderingtakingintoconsiderationthepointofview.Soatrianglethathasbeendefinedincounter-clockwiseordercanbeinterpreted,atrenderingtime,asbeingdefinedlockwisebecauseofthepointofview.

Let’sputitinpractice,intheinitmethodoftheWindowclassaddthefollowinglines:

glEnable(GL_CULL_FACE);

glCullFace(GL_BACK);

Thefirstlinewillenablefacecullingandthesecondlinestatesthatfacesthatarefacingbackwardsshouldbeculled(removed).Withthatlineifyoulookupwardsyouwillseesomethinglikethis.

What’shappening?ifyoureviewtheverticesorderforthetopfaceyouwillseethatishasbeendefinedincounter-clockwiseorder.Well,itwas,butrememberthatthewindingreferstothepointofview.Infact,ifyouapplytranslationalsototheskyboxsoyouareabletoseeitformtheupsideyouwillseethatthetopfaceisrenderedagainonceyouareoutsideit.

SkyBoxandsomeoptimizations

158

Page 159: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Let’ssketchwhat’shappening.Thefollowingpictureshowsoneofthetrianglesofthetopfaceoftheskyboxcube,whichisdefinedbythreeverticesdefinedincounter-clockwiseorder.

Butrememberthatweareinsidetheskybox,ifwelookatthecubeformtheinterior,whatwewillseeisthattheverticesaredefinedinclockwiseorder.

Thisisbecause,theskyboxwasdefinedtobelookedfromtheoutside.Soweneedtoflipthedefinitionofsomeofthefacesinordertobeviewedcorrectlywhenfacecullingisenabled.

SkyBoxandsomeoptimizations

159

Page 160: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Butthere’sstillmoreroomforoptimization.Let’sreviewourrenderingprocess.IntherendermethodoftheRendererclasswhatwearedoingisiterateoveraGameItemarrayandrendertheassociatedMesh.ForeachGameItemwedothefollowing:

1. Setupthemodelviewmatrix(uniqueperGameItem).2. GettheMeshassociatedtotheGameItemandactivatethetexture,bindtheVAOand

enableitsattributes.3. Performacalltodrawthetriangles.4. DisablethetextureandtheVAOelements.

But,inourcurrentgame,wereusethesameMeshforthe40,000GameItems,wearerepeatingtheoperationsfrompoint2topoint4againandagain.Thisisnotveryefficient,keepinmindthateachcalltoanOpenGLfunctionisanativecallthatincursinsomeperformanceoverhead.Besidesthat,weshouldalwaystrytolimitthestatechangesinOpenGL(activatinganddeactivatingtextures,VAOsarestatechanges).

WeneedtochangethewaywedothingsandorganizeourstructuresaroundMeshessinceitwillbeveryfrequenttohavemanyGameItemswiththesameMesh.NowwehaveanarrayofGameItemseachofthempointingtothesameMesh.Wehavesomethinglikethis.

Instead,wewillcreateaMapofMesheswithalistoftheGamItemsthatsharethatMesh.

Therenderingstepswillbe,foreachMesh:

1. Setupthemodelviewmatrix(uniqueperGameItem).2. GettheMeshassociatedtotheGameItemandActivatetheMeshtexture,bindthe

VAOandenableitsattributes.3. ForeachGameItemassociated:a.Setupthemodelviewmatrix(uniqueperGame

Item).b.Performacalltodrawthetriangles.

SkyBoxandsomeoptimizations

160

Page 161: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

4. DisablethetextureandtheVAOelements.

IntheSceneclass,wewillstorethefollowingMap.

privateMap<Mesh,List<GameItem>>meshMap;

WestillhavethesetGameItemsmethod,butinsteadofjuststoringthearray,weconstructthemeshmap.

publicvoidsetGameItems(GameItem[]gameItems){

intnumGameItems=gameItems!=null?gameItems.length:0;

for(inti=0;i<numGameItems;i++){

GameItemgameItem=gameItems[i];

Meshmesh=gameItem.getMesh();

List<GameItem>list=meshMap.get(mesh);

if(list==null){

list=newArrayList<>();

meshMap.put(mesh,list);

}

list.add(gameItem);

}

}

TheMeshclassnowhasamethodtorenderalistoftheassociatedGameItemsandwehavesplittheactivatinganddeactivatingcodeintoseparatemethods.

SkyBoxandsomeoptimizations

161

Page 162: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatevoidinitRender(){

Texturetexture=material.getTexture();

if(texture!=null){

//Activatefirstexturebank

glActiveTexture(GL_TEXTURE0);

//Bindthetexture

glBindTexture(GL_TEXTURE_2D,texture.getId());

}

//Drawthemesh

glBindVertexArray(getVaoId());

glEnableVertexAttribArray(0);

glEnableVertexAttribArray(1);

glEnableVertexAttribArray(2);

}

privatevoidendRender(){

//Restorestate

glDisableVertexAttribArray(0);

glDisableVertexAttribArray(1);

glDisableVertexAttribArray(2);

glBindVertexArray(0);

glBindTexture(GL_TEXTURE_2D,0);

}

publicvoidrender(){

initRender();

glDrawElements(GL_TRIANGLES,getVertexCount(),GL_UNSIGNED_INT,0);

endRender();

}

publicvoidrenderList(List<GameItem>gameItems,Consumer<GameItem>consumer){

initRender();

for(GameItemgameItem:gameItems){

//SetupdatarequieredbygameItem

consumer.accept(gameItem);

//Renderthisgameitem

glDrawElements(GL_TRIANGLES,getVertexCount(),GL_UNSIGNED_INT,0);

}

endRender();

}

AsyoucanseewestillhavetheoldmethodthatrenderstheaMeshtakingintoconsiderationthatwehaveonlyoneGameItem(thismaybeusedinothercases,thisiswhyithasnotbeenremoved).ThenewmethodrendersalistofGameItemsandreceivesasa

SkyBoxandsomeoptimizations

162

Page 163: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

parameteraConsumer(afunction,thatusesthenewfunctionalprogrammingparadigmsintroducedinJava8),whichwillbeusedtosetupwhat’sspecificforeachGameItembeforedrawingthetriangles.Wewillusethistosetupthemodelviewmatrix,sincewedonotwanttheMeshclasstobecoupledwiththeuniformsnamesandtheparametersinvolvedwhensettingthesethingsup.

IntherenderScenemethodoftheRendererclassyoucanseethatwejustiterateovertheMeshmapandsetupthemodelviewmatrixuniformviaalambda.

for(Meshmesh:mapMeshes.keySet()){

sceneShaderProgram.setUniform("material",mesh.getMaterial());

mesh.renderList(mapMeshes.get(mesh),(GameItemgameItem)->{

Matrix4fmodelViewMatrix=transformation.buildModelViewMatrix(gameItem,viewM

atrix);

sceneShaderProgram.setUniform("modelViewMatrix",modelViewMatrix);

}

);

}

Anothersetofoptimizationsthatwecandoisthatwearecreatingtonsofobjectsintherendercycle.Inparticular,wewerecreatingtoomanyMatrix4finstancesthatholdsacopyathemodelviewmatrixforeachGameIteminstance.WewillcreatespecificmatricesforthatintheTransformationclass,andreusethesameinstance.Ifyoucheckthecodeyouwillseealsothatwehavechangedthenamesofthemethods,thegetXXmethodsjustreturnthestorematrixinstanceandanymethodthatchangesthevalueofamatrixiscalledbuildXXtoclarifyitspurpose.

WehavealsoavoidedtheconstructionofnewFloatBufferinstanceseachtimewesetauniformforaMatrixandremovedsomeotheruselessinstantiations.Withallthatinplaceyoucanseenowthattherenderingissmootherandmoreagile.

Youcancheckallthedetailsinthesourcecode.

SkyBoxandsomeoptimizations

163

Page 164: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

HeightMapsInthischapterwewilllearnhowtocreatecomplexterrainsusingheightmaps.Beforewestart,youwillnoticethatsomerefactoringhasbeendone.Wehavecreatedsomenewpackagesandmovedsomeoftheclassestobetterorganizethem.Youcancheckthechangesinthesourcecode.

Sowhat’saheightmap?Aheightmapisanimagewhichisusedtogeneratea3Dterrainwhichusesthepixelcolourstogetsurfaceelevationdata.HeightmapsimagesuseusuallygrayscaleandcanbegeneratedbyprogramslikeTerragen.Aheightmapimagelookslikethis.

Theimageaboveit’slikeifyouwerelookingatafragmentoflandfromabove.Withthatimagewewillbuildameshcomposedbytrianglesformedbyvertices.Thealtitudeofeachvertexwillbecalculateddependingonthecolourofeachoftheimagepixels.Blackcolourwillrepresentthelowestvalueandwhitethehighestone.

Wewillbecreatingagridofvertices,oneforeachpixeloftheimage.Thoseverticeswillbeusedtoformtrianglesthatwillcomposethemeshasshowninthenextfigure.

HeightMaps

164

Page 165: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thatmeshwillformagiantquadthatwillberenderedacrossxandzaxisusingthepixelcolourstochangetheelevationintheyaxis.

Theprocessofcreatinga3Dterrainfromaheightmapcanbesummarizedasfollows:

Loadtheimagethatcontainstheheightmap.(WewilluseaBufferedImageinstancetogetaccesstoeachpixel).Foreachimagepixelcreateavertexwithitsheightisbasedonthepixelcolour.Assignthecorrecttexturecoordinatetothevertex.Setuptheindicestodrawthetrianglesassociatedtothevertex.

HeightMaps

165

Page 166: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

WewillcreateaclassnamedHeightMapMeshthatwillcreateaMeshbasedonaheightmapimageperformingthestepsdescribedabove.Let’sfirstreviewtheconstantsdefinedforthatclass:

privatestaticfinalintMAX_COLOUR=255*255*255;

Aswehaveexplainedabove,wewillcalculatetheheightofeachvertexbasedonthecolourofeachpixeloftheimageusedasheightmap.Imagesareusuallygreyscale,foraPNGimagethatmeansthateachRGBcomponentforeachpixelcanvaryfrom0to255,sowehave256discretevaluestodefinedifferentheights.Thismaybeenoughprecisionforyou,butifit’snotwecanusethethreeRGBcomponentstohavemoreintermediatevalues,in

thiscasetheheightcanbecalculatedformarangethatgetsfrom0to255 .Wewillchoosethesecondapproachsowearenotlimitedtogreyscaleimages.

Thenextconstantsare:

privatestaticfinalfloatSTARTX=-0.5f;

privatestaticfinalfloatSTARTZ=-0.5f;

Themeshwillbeformedbyasetofvertices(oneperpixel)whosexandzcoordinateswillbeintherangefollowingrange:

[-0.5,0.5],thatis,[STARTX,-STARTX]forthexaxis.[-0.5,0.5],thatis,[STARTZ,-STARTZ]forthezaxis.

Don'tworrytoomuchaboutthosevalues,laterontheresultingmeshcanbescaledtoaccommodateitssizeintheworld.Regardingyaxis,wewillsetuptwoparameters,minYandmaxY,forsettingthelowestandhighestvaluethattheycoordinatecanhave.Theseparametersarenotconstantbecausewemaywanttochangethematruntime,independentlyofthescalingapplied.Attheend,theterrainwillbecontainedinacubeintherange[STARTX,-STARTX],[minY,maxY]and[STARTZ,-STARTZ].

ThemeshwillbecreatedintheconstructoroftheHeightMapMeshclass,whichisdefinedlikethis.

publicHeightMapMesh(floatminY,floatmaxY,StringheightMapFile,StringtextureFile,

inttextInc)throwsException{

3

HeightMaps

166

Page 167: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Itreceivestheminimumandmaximumvalefortheyaxis,thenameofthefilethatcontainstheimagetobeusedasheightmapandthetexturefiletobeused.ItalsoreceivesanintegernamedtextIncthatwewilldiscusslateron.

ThefirstthingthatwedointheconstructoristoloadtheheightmapimageintoaBufferedImageinstance.

this.minY=minY;

this.maxY=maxY;

PNGDecoderdecoder=newPNGDecoder(getClass().getResourceAsStream(heightMapFile));

intheight=decoder.getHeight();

intwidth=decoder.getWidth();

ByteBufferbuf=ByteBuffer.allocateDirect(

4*decoder.getWidth()*decoder.getHeight());

decoder.decode(buf,decoder.getWidth()*4,PNGDecoder.Format.RGBA);

buf.flip();

Then,weloadthetexturefileintoaByteBufferandsetupthevariablesthatwewillneedtoconstructtheMesh.TheincxandinczvariableswillhavetheincrementtobeappliedtoeachvertexinthexandzcoordinatessotheMeshcoverstherangestatedabove.

Texturetexture=newTexture(textureFile);

floatincx=getWidth()/(width-1);

floatincz=Math.abs(STARTZ*2)/(height-1);

List<Float>positions=newArrayList();

List<Float>textCoords=newArrayList();

List<Integer>indices=newArrayList();

Afterthatwearereadytoiterateovertheimage,creatingavertexpereachpixel,settingupitstexturecoordinatesandsettinguptheindicestodefinecorrectlythetrianglesthatcomposetheMesh.

HeightMaps

167

Page 168: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

for(introw=0;row<height;row++){

for(intcol=0;col<width;col++){

//Createvertexforcurrentposition

positions.add(STARTX+col*incx);//x

positions.add(getHeight(col,row,width,buf));//y

positions.add(STARTZ+row*incz);//z

//Settexturecoordinates

textCoords.add((float)textInc*(float)col/(float)width);

textCoords.add((float)textInc*(float)row/(float)height);

//Createindices

if(col<width-1&&row<height-1){

intleftTop=row*width+col;

intleftBottom=(row+1)*width+col;

intrightBottom=(row+1)*width+col+1;

intrightTop=row*width+col+1;

indices.add(rightTop);

indices.add(leftBottom);

indices.add(leftTop);

indices.add(rightBottom);

indices.add(leftBottom);

indices.add(rightTop);

}

}

}

Theprocessofcreatingthevertexcoordinatesisselfexplanatory.Let’signoreatthismomentwhywemultiplythetexturecoordinatesbyanumberandhowtheheightiscalculated.Youcanseethatforeachvertexwedefinetheindicesoftwotriangles(exceptifweareinthelastroworcolumn).Let’svisualizeitwitha3 × 3imagetovisualizehowtheyareconstructed.A3 × 3imagecontains9vertices,andthus4quadsformedby2 × 4triangles.Thefollowingpictureshowsthatgrid,namingeachvertexintheformVrc(r:row,c:column).

HeightMaps

168

Page 169: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Whenweareprocessingthefirstvertex(V00),wedefinetheindicesofthetwotrianglesshadedinred.

Whenweareprocessingthesecondvertex(V01),wedefinetheindicesofthetwotrianglesshadedinred.But,whenweareprocessingthethethirdvertex(V02)wedonotneedtodefinemoreindices,thetrianglesforthatrowhavealreadybeendefined.

HeightMaps

169

Page 170: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Youcaneasilyseehowtheprocesscontinuesfortherestofvertices.Now,oncewehavecreatedalltheverticespositions,thetexturecoordinatesandtheindiceswejustneedtocreateaMeshandtheassociatedMaterialwithallthatdata.

float[]posArr=Utils.listToArray(positions);

int[]indicesArr=indices.stream().mapToInt(i->i).toArray();

float[]textCoordsArr=Utils.listToArray(textCoords);

float[]normalsArr=calcNormals(posArr,width,height);

this.mesh=newMesh(posArr,textCoordsArr,normalsArr,indicesArr);

Materialmaterial=newMaterial(texture,0.0f);

mesh.setMaterial(material);

Youcanseethatwecalculatethenormalstakingasaninputthevertexpositions.Beforeweseehownormalscanbecalculated,let’sseehowheightsareobtained.WehavecreatedamethodnamedgetHeightwhichcalculatestheheightforavertex.

privatefloatgetHeight(intx,intz,intwidth,ByteBufferbuffer){

byter=buffer.get(x*4+0+z*4*width);

byteg=buffer.get(x*4+1+z*4*width);

byteb=buffer.get(x*4+2+z*4*width);

bytea=buffer.get(x*4+3+z*4*width);

intargb=((0xFF&a)<<24)|((0xFF&r)<<16)

|((0xFF&g)<<8)|(0xFF&b);

returnthis.minY+Math.abs(this.maxY-this.minY)*((float)argb/(float)MAX_C

OLOUR);

}

Themethodreceivesthexanzcoordinatesforapixel,thewidthoftheimageandtheByteBufferthatconatinsitandreturnstheRGBcolour(thesumoftheindividualR,GandBcomponents)andassignsavaluecontainedbetweenminYandmaxY(minYforblackcolourandmaxYforwhitecolour).

YoumaydevelopasimplerversionusingaBufferedImagewhichcontainshandyethodsdorgettingRGBvalues,butwewouldbeusingAWT.RememberthatAWTdoesnotmixwellwithOSXsotrytoavoidusingtheirclasses.

Let’sviewnowhowtexturecoordinatesarecalculated.Thefirstoptionistowrapthetexturealongthewholemesh,thetopleftvertexwouldhave(0,0)texturecoordinatesandthebottomrightvertexwouldhave(1,1)texturecoordinates.Theproblemwiththisapproachisthatthetextureshouldbehugeinordertoprovidegoodresults,ifnot,itwouldbestretchedtoomuch.

Butwecanstilluseasmalltexturewithverygoodresultsbyemployingaveryefficienttechnique.Ifwesettexturecoordinatesthatarebeyondthe[1, 1]range,wegetbacktooriginandstartcountingagainfromthestart.Thefollowingpictureshowsthisbehaviortiling

HeightMaps

170

Page 171: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

thesametextureinseveralquadsthatextendbeyondthe[1, 1]range.

Thisiswhatwewilldowhensettingthetexturecoordinates.Wewillbemultiplyingthetexturecoordinates(calculatedasifthetexturejustwaswrappedcoveringthewholemesh)byafactor,thetextIncparameter,toincreasethenumberofpixelsofthetexturetobeusedbetweenadjacentvertices.

Theonlythingthat’spendingnowisnormalcalculation.Rememberthatweneednormalssolightcanbeappliedtotheterraincorrectly.Withoutnormalsourterrainwillberenderedwiththesamecolournomatterhowlighthitseachpoint.Themethodthatwewilluseheremaynotbethemostefficientforheightmapsbutitwillhelpyouunderstandhownormalscanbeauto-calculated.Ifyousearchforothersolutionsyoumayfindmoreefficientapproachesthat

HeightMaps

171

Page 172: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

onlyusetheheightsofadjacentpointswithoutperformingcrossproductoperations.Neverthelesssincethiswillonlybedoneatstartup,themethodpresentedherewillnothurtperformancesomuch.

Let’sgraphicallyexplainhowanormalcanbecalculated.Imaginethatwehaveavertex

named .Wefirstcalculate,foreachofthesurroundingvertices( , , and ),the

vectorsthataretangenttothesurfacethatconnectsthesepoints.Thesevectors,( , ,

and ),arecalculatedbysubtractingeachadjacentpointfrom ( = − ,etc.)

Then,wecalculatethenormalforeachoftheplanesthatconnectstheadjacentpoints.Thisisdonebyperformingthecrossproductbetweenthepreviouscalculatedvectors.For

instance,thenormalofthesurfacethatconnects and (shadedinblue)iscalculated

asthecrossproductbetween and , = × .

P0 P1 P02 P3 P4

V 1 V 2

V 3 V 4 P0 V 1 P1 P0

P1 P2

V 1 V 2 V 12 V 1 V 2

HeightMaps

172

Page 173: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Ifwecalculatetherestofthenormalsfortherestofthesurfaces( = × ,

= × and = × ),thenormalfor willbethesum(normalized)ofall

thenormalsofthesurroundingsurfaces: = + + + .

Theimplementationofthemethodthatcalculatesthenormalsisasfollows.

privatefloat[]calcNormals(float[]posArr,intwidth,intheight){

Vector3fv0=newVector3f();

Vector3fv1=newVector3f();

Vector3fv2=newVector3f();

Vector3fv3=newVector3f();

Vector3fv4=newVector3f();

Vector3fv12=newVector3f();

Vector3fv23=newVector3f();

V 23 V 2 V 3

V 34 V 3 V 4 V 41 V 4 V 1 P0

N0^ V 12^ V 23^ V 34^ V 41^

HeightMaps

173

Page 174: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Vector3fv34=newVector3f();

Vector3fv41=newVector3f();

List<Float>normals=newArrayList<>();

Vector3fnormal=newVector3f();

for(introw=0;row<height;row++){

for(intcol=0;col<width;col++){

if(row>0&&row<height-1&&col>0&&col<width-1){

inti0=row*width*3+col*3;

v0.x=posArr[i0];

v0.y=posArr[i0+1];

v0.z=posArr[i0+2];

inti1=row*width*3+(col-1)*3;

v1.x=posArr[i1];

v1.y=posArr[i1+1];

v1.z=posArr[i1+2];

v1=v1.sub(v0);

inti2=(row+1)*width*3+col*3;

v2.x=posArr[i2];

v2.y=posArr[i2+1];

v2.z=posArr[i2+2];

v2=v2.sub(v0);

inti3=(row)*width*3+(col+1)*3;

v3.x=posArr[i3];

v3.y=posArr[i3+1];

v3.z=posArr[i3+2];

v3=v3.sub(v0);

inti4=(row-1)*width*3+col*3;

v4.x=posArr[i4];

v4.y=posArr[i4+1];

v4.z=posArr[i4+2];

v4=v4.sub(v0);

v1.cross(v2,v12);

v12.normalize();

v2.cross(v3,v23);

v23.normalize();

v3.cross(v4,v34);

v34.normalize();

v4.cross(v1,v41);

v41.normalize();

normal=v12.add(v23).add(v34).add(v41);

normal.normalize();

}else{

normal.x=0;

normal.y=1;

HeightMaps

174

Page 175: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

normal.z=0;

}

normal.normalize();

normals.add(normal.x);

normals.add(normal.y);

normals.add(normal.z);

}

}

returnUtils.listToArray(normals);

}

Finally,inordertobuildlargerterrains,wehavetwooptions:

Createalargerheightmap.Reuseaheightmapandtileitthroughthe3Dspace.Theheightmapwillbelikeaterrainblockthatcouldbetranslatedacrosstheworldliketiles.Inordertodoso,thepixelsoftheedgeoftheheightmapmustbethesame(theleftedgemustbeequaltotherightsideandthetopedgemustbeequaltothebottomone)toavoidgapsbetweenthetiles.

Wewillusethesecondapproach(andselectanappropriateheightmap).Tosupportthis,wewillcreateaclassnamedTerrainthatwillcreateasquareofheightmaptiles,definedlikethis.

HeightMaps

175

Page 176: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

packageorg.lwjglb.engine.items;

importorg.lwjglb.engine.graph.HeightMapMesh;

publicclassTerrain{

privatefinalGameItem[]gameItems;

publicTerrain(intblocksPerRow,floatscale,floatminY,floatmaxY,Stringheigh

tMap,StringtextureFile,inttextInc)throwsException{

gameItems=newGameItem[blocksPerRow*blocksPerRow];

HeightMapMeshheightMapMesh=newHeightMapMesh(minY,maxY,heightMap,texture

File,textInc);

for(introw=0;row<blocksPerRow;row++){

for(intcol=0;col<blocksPerRow;col++){

floatxDisplacement=(col-((float)blocksPerRow-1)/(float)2)*

scale*HeightMapMesh.getXLength();

floatzDisplacement=(row-((float)blocksPerRow-1)/(float)2)*

scale*HeightMapMesh.getZLength();

GameItemterrainBlock=newGameItem(heightMapMesh.getMesh());

terrainBlock.setScale(scale);

terrainBlock.setPosition(xDisplacement,0,zDisplacement);

gameItems[row*blocksPerRow+col]=terrainBlock;

}

}

}

publicGameItem[]getGameItems(){

returngameItems;

}

}

Let'sexplaintheoverallprocess,wehaveblocksthathavethefollowingcoordinates(forxandzandwiththeconstantsdefinedabove).

HeightMaps

176

Page 177: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Let'ssupposethatwearecreate¡ingaterrainformedbya3x3blocksgrid.Let'sassumealsothatwewont'scaletheterrainblocks(thatis,thevariableblocksPerRowwillbe3andthevariablescalewillbe1).Wewantthegridtobecenteredat(0,0)coordinates.

Weneedtotranslatetheblockssotheverticeswillhavethefollowingcoordinates.

ThetranslationisachievedbycallingsetPositionmethod,butrememberthatwhatwesetisadisplacementnotaposition.Ifyoureviewthefigureaboveyouwillseethatthecentralblockdoesnotrequireanydisplacement,it'salreadypositionedintheadequatecoordinates.

HeightMaps

177

Page 178: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thevertexdrawningreenneedsadisplacement,forthexcoordinate,of−1andthevertexdrawninblueneedsadisplacementof+1.Theformulatocalculatethexdisplacement,takingintoconsiderationthescaleandtheblockwidth,isthisone:

xDisplacement = (col − (blocksPerRow − 1)/2) × scale× width

Andtheequivalentformulaforzdisplacementis:

zDisplacement = (row − (blocksPerRow − 1)/2) × scale× height

IfwecreateaTerraininstanceintheDummyGameclass,wecangetsomethinglikethis.

Youcanmovethecameraaroundtheterrainandseehowit’srendered.Sincewestilldonothaveimplementedcollisiondetectionyoucanpassthroughitandlookitfromabove.Becausewehavefacecullingenabled,somepartsoftheterrainarenotrenderedwhenlookingfrombelow.

HeightMaps

178

Page 179: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

TerrainCollisionsOncewehavecreatedaterrainthenextstepistodetectcollisionstoavoidtraversingthroughit.Ifyourecallfrompreviouschapter,aterrainiscomposedbyblocks,andeachofthoseblocksisconstructedfromaheightmap.Theheightmapisusedtosettheheightoftheverticesthatcomposethetrianglesthatformtheterrain.

Inordertodetectacollisionwemustcomparecurrentpositionyvaluewiththeyvalueofthepointoftheterrainwearecurrentlyin.Ifweareaboveterrain’syvaluethere’snocollision,ifnot,weneedtogetback.Simpleconcept,doesit?Indeeditisbutweneedtoperformseveralcalculationsbeforeweareabletodothatcomparison.

Thefirstthingweneedtodefineiswhatweunderstandfortheterm"currentposition".Sincewedonothaveyetaplayerconcepttheansweriseasy,thecurrentpositionwillbethecameraposition.Sowealreadyhaveoneofthecomponentsofthecomparison,thus,thenextthingtocalculateisterrainheightatcurrentposition.

Asit'sbeensaidbefore,theterrainiscomposedbyagridofterrainblocksasshowninthenextfigure.

Eachterrainblockisconstructedfromthesameheightmapmesh,butisscaledanddisplacedpreciselytoformaterraingridthatlookslikeacontinuouslandscape.

So,whatweneedtodofirstisdetermineinwhichterrainblockthecurrentposition,thecamera,isin.Inordertodothat,wewillcalculatetheboundingboxofeachterrainblocktakingintoconsiderationthedisplacementandthescaling.Sincetheterrainwillnotbedisplacedorscaledatruntime,wecanperformthosecalculationsintheTerrainclassconstructor.Bydoingthiswayweaccessthemlateratanytimewithoutrepeatingthoseoperationsineachgameloopcycle.

Wewillcreateanewmethodthatcalculatestheboundingboxofaterrainblock,namedgetBoundingBox.

TerrainCollisions

179

Page 180: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privateBox2DgetBoundingBox(GameItemterrainBlock){

floatscale=terrainBlock.getScale();

Vector3fposition=terrainBlock.getPosition();

floattopLeftX=HeightMapMesh.STARTX*scale+position.x;

floattopLeftZ=HeightMapMesh.STARTZ*scale+position.z;

floatwidth=Math.abs(HeightMapMesh.STARTX*2)*scale;

floatheight=Math.abs(HeightMapMesh.STARTZ*2)*scale;

Box2DboundingBox=newBox2D(topLeftX,topLeftZ,width,height);

returnboundingBox;

}

TheBox2Dclassisasimplifiedversionofthejava.awt.Rectangle2D.Floatclass;createdtoavoidusingAWT.

Nowweneedtocalculatetheworldcoordinatesoftheterrainblocks.Inthepreviouschapteryousawthatallofourterrainmesheswerecreatedinsideaquadwithitsoriginsetto[STARTX,STARTZ].Thus,weneedtotransformthosecoordinatestotheworldcoordinatestakingintoconsiderationthescaleandthedisplacementasshowninthenextfigure.

Asit’sbeensaidabove,thiscanbedoneintheTerrainclassconstructorsinceitwon'tchangeatruntime.Soweneedtoaddanewattributewhichwillholdtheboundingboxes:

privatefinalBox2D[][]boundingBoxes;

IntheTerrainconstructor,whilewearecreatingtheterrainblocks,wejustneedtoinvokethemethodthatcalculatestheboundingbox.

TerrainCollisions

180

Page 181: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicTerrain(intterrainSize,floatscale,floatminY,floatmaxY,StringheightMapF

ile,StringtextureFile,inttextInc)throwsException{

this.terrainSize=terrainSize;

gameItems=newGameItem[terrainSize*terrainSize];

PNGDecoderdecoder=newPNGDecoder(getClass().getResourceAsStream(heightMapFile))

;

intheight=decoder.getHeight();

intwidth=decoder.getWidth();

ByteBufferbuf=ByteBuffer.allocateDirect(

4*decoder.getWidth()*decoder.getHeight());

decoder.decode(buf,decoder.getWidth()*4,PNGDecoder.Format.RGBA);

buf.flip();

//Thenumberofverticespercolumnandrow

verticesPerCol=heightMapImage.getWidth();

verticesPerRow=heightMapImage.getHeight();

heightMapMesh=newHeightMapMesh(minY,maxY,buf,width,textureFile,textInc);

boundingBoxes=newBox2D[terrainSize][terrainSize];

for(introw=0;row<terrainSize;row++){

for(intcol=0;col<terrainSize;col++){

floatxDisplacement=(col-((float)terrainSize-1)/(float)2)*scal

e*HeightMapMesh.getXLength();

floatzDisplacement=(row-((float)terrainSize-1)/(float)2)*scal

e*HeightMapMesh.getZLength();

GameItemterrainBlock=newGameItem(heightMapMesh.getMesh());

terrainBlock.setScale(scale);

terrainBlock.setPosition(xDisplacement,0,zDisplacement);

gameItems[row*terrainSize+col]=terrainBlock;

boundingBoxes[row][col]=getBoundingBox(terrainBlock);

}

}

}

So,withalltheboundingboxespre-calculated,wearereadytocreateanewmethodthatwillreturntheheightoftheterraintakingasaparameterthecurrentposition.ThismethodwillbenamedgetHeightVectoranditsdefinedlikethis.

TerrainCollisions

181

Page 182: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicfloatgetHeight(Vector3fposition){

floatresult=Float.MIN_VALUE;

//Foreachterrainblockwegettheboundingbox,translateittoviewcoodinates

//andcheckifthepositioniscontainedinthatboundingbox

Box2DboundingBox=null;

booleanfound=false;

GameItemterrainBlock=null;

for(introw=0;row<terrainSize&&!found;row++){

for(intcol=0;col<terrainSize&&!found;col++){

terrainBlock=gameItems[row*terrainSize+col];

boundingBox=boundingBoxes[row][col];

found=boundingBox.contains(position.x,position.z);

}

}

//Ifwehavefoundaterrainblockthatcontainsthepositionweneed

//tocalculatetheheightoftheterrainonthatposition

if(found){

Vector3f[]triangle=getTriangle(position,boundingBox,terrainBlock);

result=interpolateHeight(triangle[0],triangle[1],triangle[2],position.x,

position.z);

}

returnresult;

}

Thefirstthingthattowedointhatmethodistodeterminetheterrainblockthatwearein.Sincewealreadyhavetheboundingboxforeachterrainblock,thealgorithmissimple.Wejustsimplyneedtoiterateoverthearrayofboundingboxesandcheckifthecurrentpositionisinside(theclassBox2Dprovidesamethodforthis).

Oncewehavefoundtheterrainblock,weneedtocalculatethetrianglewhichwearein.ThisisdoneinthegetTrianglemethodthatwillbedescribedlateron.Afterthat,wehavethecoordinatesofthetrianglethatwearein,includingitsheight.But,weneedtheheightofapointthatisnotlocatedatanyofthoseverticesbutinaplaceinbetween.ThisisdoneintheinterpolateHeightmethod.Wewillalsoexplainhowthisisdonelateron.

Let’sfirststartwiththeprocessofdeterminingthetrianglethatwearein.ThequadthatformsaterrainblockcanbeseenasagridinwhicheachcellisformedbytwotrianglesLet’sdefinesomevariablesfirst:

boundingBox.xisthexcoordinateoftheoriginoftheboundingboxassociatedtothequad.boundingBox.yisthezcoordinatesoftheoriginoftheboundingboxassociatedtothequad(Althoughyouseea“y”,itmodelsthezaxis).boundingBox.widthisthewidthofthequadboundingBox.heightistheheightofthequad.

TerrainCollisions

182

Page 183: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

cellWidthisthewidthofacell.cellHeightistheheightofacell.

Allofthevariablesdefinedaboveareexpressedinworldcoordinates.Tocalculatethewidthofacellwejustneedtodividetheboundingboxwidthbythenumberofverticespercolumn:

cellWidth =

AndthevariablecellHeightiscalculatedanalogous

cellHeight =

Oncewehavethosevariableswecancalculatetherowandthecolumnofthecellwearecurrentlyinwidthisquitestraightforward:

col =

row =

Thefollowingpictureshowsallthevariablesdescribedaboveforasampleterrainblock.

verticesPerColboundingBox.width

verticesPerRowboundingBox.height

boundingBox.widthposition.x−boundingBox.x

boundingBox.heightposition.z−boundingBox.y

TerrainCollisions

183

Page 184: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Withallthatinformationweareabletocalculatethepositionsoftheverticesofthetrianglescontainedinthecell.Howwecandothis?Let’sexaminethetrianglesthatformasinglecell.

Youcanseethatthecellisdividedbyadiagonalthatseparatesthetwotriangles.Thewaytodeterminethetriangleassociatedtothecurrentposition,isbycheckingifthezcoordinateisaboveorbelowthatdiagonal.Inourcase,ifcurrentpositionzvalueislessthanthez

TerrainCollisions

184

Page 185: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

valueofthediagonalsettingthexvaluetothexvalueofcurrentpositionweareinT1.Ifit'sgreaterthanthatweareinT2.

Wecandeterminethatbycalculatingthelineequationthatmatchesthediagonal.

Ifyourememberyourschoolmathclasses,theequationofalinethatpassesfromtwopoints(in2D)is:

y − y1 = m ⋅ (x− x1)

Wheremisthelineslope,thatis,howmuchtheheightchangeswhenmovingthroughthexaxis.Notethat,inourcase,theycoordinatesarethezones.Alsonote,thatweareusing2Dcoordinatesbecausewearenotcalculatingheightshere.Wejustwanttoselectthepropertriangleandtodothatxanzcoordinatesareenough.So,inourcasethelineequationshouldberewrittenlikethis.

z − z1 = m ⋅ (z − z1)

Theslopecanbecalculateinthefollowingway:

m =

Sotheequationofthediagonaltogetthezvaluegivenaxpositionislikethis:

z = m ⋅ (xpos− x1) + z1 = ⋅ (zpos− x1) + z1

Wherex1,x2,z1andz2arethexandzcoordinatesoftheverticesV 1andV 2respectively.

Sothemethodtogetthetrianglethatthecurrentpositionisin,namedgetTriangle,applyingallthecalculationsdescribedabovecanbeimplementedlikethis:

x1−x2z1−z2

x1−x2z1−z2

TerrainCollisions

185

Page 186: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

protectedVector3f[]getTriangle(Vector3fposition,Box2DboundingBox,GameItemterrai

nBlock){

//Getthecolumnandrowoftheheightmapassociatedtothecurrentposition

floatcellWidth=boundingBox.width/(float)verticesPerCol;

floatcellHeight=boundingBox.height/(float)verticesPerRow;

intcol=(int)((position.x-boundingBox.x)/cellWidth);

introw=(int)((position.z-boundingBox.y)/cellHeight);

Vector3f[]triangle=newVector3f[3];

triangle[1]=newVector3f(

boundingBox.x+col*cellWidth,

getWorldHeight(row+1,col,terrainBlock),

boundingBox.y+(row+1)*cellHeight);

triangle[2]=newVector3f(

boundingBox.x+(col+1)*cellWidth,

getWorldHeight(row,col+1,terrainBlock),

boundingBox.y+row*cellHeight);

if(position.z<getDiagonalZCoord(triangle[1].x,triangle[1].z,triangle[2].x,tr

iangle[2].z,position.x)){

triangle[0]=newVector3f(

boundingBox.x+col*cellWidth,

getWorldHeight(row,col,terrainBlock),

boundingBox.y+row*cellHeight);

}else{

triangle[0]=newVector3f(

boundingBox.x+(col+1)*cellWidth,

getWorldHeight(row+2,col+1,terrainBlock),

boundingBox.y+(row+1)*cellHeight);

}

returntriangle;

}

protectedfloatgetDiagonalZCoord(floatx1,floatz1,floatx2,floatz2,floatx){

floatz=((z1-z2)/(x1-x2))*(x-x1)+z1;

returnz;

}

protectedfloatgetWorldHeight(introw,intcol,GameItemgameItem){

floaty=heightMapMesh.getHeight(row,col);

returny*gameItem.getScale()+gameItem.getPosition().y;

}

Youcanseethatwehavetwoadditionalmethods.Thefirstone,namedgetDiagonalZCoord,calculatesthezcoordinateofthediagonalgivenaxpositionandtwovertices.Theotherone,namedgetWorldHeight,isusedtoretrievetheheightofthetrianglevertices,theycoordinate.Whentheterrainmeshisconstructedtheheightofeachvertexispre-calculatedandstored,weonlyneedtotranslateittoworldcoordinates.

TerrainCollisions

186

Page 187: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Ok,sowehavethetrianglecoordinatesthatthecurrentpositionisin.Finally,wearereadytocalculateterrainheightatcurrentposition.Howcanwedothis?Well,ourtriangleiscontainedinaplane,andaplanecanbedefinedbythreepoints,inthiscase,thethreeverticesthatdefineatriangle.

Theplaneequationisasfollows:a ⋅ x+ b ⋅ y + c ⋅ z + d = 0

Thevaluesoftheconstantsofthepreviousequationare:

a = (B −A ) ⋅ (C −A ) − (C −A ) ⋅ (B −A )

b = (B −A ) ⋅ (C −A ) − (C −A ) ⋅ (B −A )

c = (B −A ) ⋅ (C −A ) − (C −A ) ⋅ (B −A )

WhereA,BandCarethethreeverticesneededtodefinetheplane.

Then,withpreviousequationsandthevaluesofthexandzcoordinatesforthecurrentpositionweareabletocalculatetheyvalue,thatistheheightoftheterrainatthecurrentposition:

y = (−d− a ⋅ x− c ⋅ z)/b

Themethodthatperformsthepreviouscalculationsisthefollowing:

protectedfloatinterpolateHeight(Vector3fpA,Vector3fpB,Vector3fpC,floatx,float

z){

//Planeequationax+by+cz+d=0

floata=(pB.y-pA.y)*(pC.z-pA.z)-(pC.y-pA.y)*(pB.z-pA.z);

floatb=(pB.z-pA.z)*(pC.x-pA.x)-(pC.z-pA.z)*(pB.x-pA.x);

floatc=(pB.x-pA.x)*(pC.y-pA.y)-(pC.x-pA.x)*(pB.y-pA.y);

floatd=-(a*pA.x+b*pA.y+c*pA.z);

//y=(-d-ax-cz)/b

floaty=(-d-a*x-c*z)/b;

returny;

}

Andthat’sall!wearenowabletodetectthecollisions,sointheDummyGameclasswecanchangethefollowinglineswhenweupdatethecameraposition:

y y z z y y z z

z z x x z z z z

x x y y x x y y

TerrainCollisions

187

Page 188: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

//Updatecameraposition

Vector3fprevPos=newVector3f(camera.getPosition());

camera.movePosition(cameraInc.x*CAMERA_POS_STEP,cameraInc.y*CAMERA_POS_STEP,came

raInc.z*CAMERA_POS_STEP);

//Checkiftherehasbeenacollision.Iftrue,settheypositionto

//themaximumheight

floatheight=terrain.getHeight(camera.getPosition());

if(camera.getPosition().y<=height){

camera.setPosition(prevPos.x,prevPos.y,prevPos.z);

}

Asyoucanseetheconceptofdetectingterraincollisionsiseasytounderstandbutweneedtocarefullyperformasetofcalculationsandbeawareofthedifferentcoordinatesystemswearedealingwith.

Besidesthat,althoughthealgorithmpresentedhereisvalidinmostofthecases,therearestillsituationsthatneedtobehandledcarefully.Oneeffectthatyoumayobserveistheonecalledtunnelling.Imaginethefollowingsituation,wearetravellingatafastspeedthroughourterrainandbecauseofthat,thepositionincrementgetsahighvalue.Thisvaluecangetsohighthat,sincewearedetectingcollisionswiththefinalposition,wemayhaveskippedobstaclesthatlayinbetween.

Therearemanypossiblesolutionstoavoidthateffect,thesimplestoneistosplitthecalculationtobeperformedinsmallerincrements.

TerrainCollisions

188

Page 189: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

FogBeforewedealwithmorecomplextopicswewillreviewhowtocreateafogeffectinourgameengine.Withthateffectwewillsimulatehowdistantobjectsgetdimmedandseemtovanishintoadensefog.

Letusfirstexaminewhataretheattributesthatdefinefog.Thefirstoneisthefogcolour.Intherealworldthefoghasagraycolour,butwecanusethiseffecttosimulatewideareasinvadedbyafogwithdifferentcolours.Theattributeisthefog'sdensity.

Thus,inordertoapplythefogeffectweneedtofindawaytofadeour3Dsceneobjectsintothefogcolouraslongastheygetfarawayfromthecamera.Objectsthatareclosetothecamerawillnotbeaffectedbythefog,butobjectsthatarefarawaywillnotbedistinguishable.Soweneedtobeabletocalculateafactorthatcanbeusedtoblendthefogcolourandeachfragmentcolourinordertosimulatethateffect.Thatfactorwillneedtobedependentonthedistancetothecamera.

Let’snamethatfactorasfogFactor,andsetitsrangefrom0to1.WhenfogFactortakesthe1value,itmeansthattheobjectwillnotbeaffectedbyfog,thatis,it’sanearbyobject.WhenfogFactortakesthe0value,itmeansthattheobjectswillbecompletelyhiddeninthefog.

Then,theequationneededtocalculatethefogcolourwillbe:

finalColour = (1 − fogFactor) ⋅ fogColour + fogFactor ⋅ framentColour

finalColouristhecolourthatresultsfromapplyingthefogeffect.fogFactoristheparametersthatcontrolshowthefogcolourandthefragmentcolourareblended.Itbasicallycontrolstheobjectvisibility.fogColouristhecolourofthefog.fragmentColour,isthecolourofthefragmentwithoutapplyinganyfogeffectonit.

NowweneedtofindawaytocalculatefogFactordependingonthedistance.Wecanchosedifferentmodels,andthefirstonecouldbetousealinearmodel.Thatisamodelthat,givenadistance,changesthefogFactorvalueinalinearway.

Thelinearmodelcanbedefinedbythefollowingparameters:

fogStart:Thedistanceatwherefogeffectsstartstobeapplied.fogF inish:Thedistanceatwherefogeffectsreachesitsmaximumvalue.distance:Distancetothecamera.

Withthoseparameters,theequationtobeappliedwouldbe:

Fog

189

Page 190: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

fogFactor =

ForobjectsatdistancelowerthanfogStartwejustsimplysetthefogFactorto1.ThefollowinggraphshowshowthefogFactorchangeswiththedistance.

Thelinearmodeliseasytocalculatebutitisnotveryrealisticanditdoesnottakeintoconsiderationthefogdensity.Inrealityfogtendstogrowinasmootherway.Sothenextsuitablemodelisaexponentialone.Theequationforthatmodelisasfollows:

focFactor = e =

Thenewvariablesthatcomeintoplayare:

fogDensitywhichmodelsthethicknessordensityofthefog.exponentwhichisusedtocontrolhowfastthefogincreaseswithdistance

Thefollowingpictureshowstwographsfortheequationabovefordifferentvaluesoftheexponent(2forthebluelineand4fortheredone)

(fogF inish− fogStart)(fogF inish− distance)

−(distance⋅fogDensity)exponent

e(distance⋅fogDensity)exponent1

Fog

190

Page 191: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Inourcodewewilluseaformulawhichsetsavalueoftwofortheexponent(youcaneasilymodifytheexampletousedifferentvalues).

Nowthatthetheoryhasbeenexplainedwecanputitintopractice.Wewillimplementtheeffectinthescenefragmentshadersincewehavethereallthevariablesweneed.Wewillstartbydefiningastructthatmodelsthefogattributes.

structFog

{

intactive;

vec3colour;

floatdensity;

};

Theactiveattributewillbeusedtoactivateordeactivatethefogeffect.Thefogwillbepassedtotheshaderthroughanotheruniformnamedfog.

uniformFogfog;

WewillcreatealsoanewclassnamedFogwhichisanotherPOJO(PlainOldJavaObject)whichcontainsthefogattributes.

Fog

191

Page 192: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

packageorg.lwjglb.engine.graph.weather;

importorg.joml.Vector3f;

publicclassFog{

privatebooleanactive;

privateVector3fcolour;

privatefloatdensity;

publicstaticFogNOFOG=newFog();

publicFog(){

active=false;

this.colour=newVector3f(0,0,0);

this.density=0;

}

publicFog(booleanactive,Vector3fcolour,floatdensity){

this.colour=colour;

this.density=density;

this.active=active;

}

//Gettersandsettershere….

WewilladdaFoginstanceintheSceneclass.Asadefault,theSceneclasswillinitializetheFoginstancetotheconstantNOFOGwhichmodelsadeactivatedinstance.

SinceweaddedanewuniformtypeweneedtomodifytheShaderProgramclasstocreateandinitializethefoguniform.

publicvoidcreateFogUniform(StringuniformName)throwsException{

createUniform(uniformName+".active");

createUniform(uniformName+".colour");

createUniform(uniformName+".density");

}

publicvoidsetUniform(StringuniformName,Fogfog){

setUniform(uniformName+".activeFog",fog.isActive()?1:0);

setUniform(uniformName+".colour",fog.getColour());

setUniform(uniformName+".density",fog.getDensity());

}

IntheRendererclasswejustneedtocreatetheuniforminthesetupSceneShadermethod:

Fog

192

Page 193: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

sceneShaderProgram.createFogUniform("fog");

AnduseitintherenderScenemethod:

sceneShaderProgram.setUniform("fog",scene.getFog());

Wearenowabletodefinefogcharacteristicsinourgame,butweneedtogetbacktothefragmentshaderinordertoapplythefogeffect.WewillcreateafunctionnamedcalcFogwhichisdefinedlikethis.

vec4calcFog(vec3pos,vec4colour,Fogfog)

{

floatdistance=length(pos);

floatfogFactor=1.0/exp((distance*fog.density)*(distance*fog.density));

fogFactor=clamp(fogFactor,0.0,1.0);

vec3resultColour=mix(fog.colour,colour.xyz,fogFactor);

returnvec4(resultColour.xyz,colour.w);

}

Asyoucanseewefirstcalculatethedistancetothevertex.Thevertexcoordinatesaredefinedintheposvariableandwejustneedtocalculatethelength.Thenwecalculatethefogfactorusingtheexponentialmodelwithanexponentoftwo(whichisequivalenttomultiplyittwice).WeclampthefogFactortoarangebetween0and1andusethemixfunction.InGLSL,themixfunctionisusedtoblendthefogcolourandthefragmentcolour(definedbyvariablecolour).It'sequivalenttoapplythisequation:

resultColour = (1 − fogFactor) ⋅ fog.colour + fogFactor ⋅ colour

Wealsopreservethewcomponente,thetransparency,oftheoriginalcolour.Wedon'twantthiscomponenttobeaffected,thefragmentshouldmantainitstransparencylevel.

Attheendofthefragmentshaderafterapplyingallthelighteffectswejustsimplyassignthereturnedvaluetothefragmentcolourifthefogisactive.

if(fog.activeFog==1)

{

fragColor=calcFog(mvVertexPos,fragColor,fog);

}

Withallthatcodecompleted,wecansetupaFogwiththefollowingdata:

scene.setFog(newFog(true,newVector3f(0.5f,0.5f,0.5f),0.15f));

Fog

193

Page 194: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Andwewillgetaneffectlikethis:

Youwillseethatdistantobjectsgetfadedinthedistanceandthatfogstartstodisappearwhenyouapproachtothem.There’saproblem,thoughwiththeskybox,itlooksalittlebitweirdthatthehorizonisnotaffectedbythefog.Thereareseveralwaystosolvethis:

Useadifferentskyboxinwhichyouonlyseeasky.Removetheskybox,sinceyouhaveadensefog,youshouldnotbeabletoseeabackground.

Maybenoneofthetwosolutionsfitsyou,andyoucantrytomatchthefogcolourtotheskyboxbackgroundbutyouwillendupdoingcomplexcalculationsandtheresultwillnotbemuchbetter.

Ifyoulettheexamplerunyouwillseehowdirectionallightgetsdimmedandthescenedarkens,butthere’saproblemwiththefog,itisnotaffectedbylightandyouwillgetsomethinglikethis.

Fog

194

Page 195: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Distantobjectsaresettothefogcolourwhichisaconstantandnotaffectedbylight.Thisfactproduceslikeaglowinginthedarkeffect(whichmaybeokforyouornot).Weneedtochangethefuncionthatcalculatesthefogtotakeintoconsiderationthelight.Thefunctionwillreceivetheambientlightandthedirectionallighttomodulatethefogcolour.

vec4calcFog(vec3pos,vec4colour,Fogfog,vec3ambientLight,DirectionalLightdirLi

ght)

{

vec3fogColor=fog.colour*(ambientLight+dirLight.colour*dirLight.intensity)

;

floatdistance=length(pos);

floatfogFactor=1.0/exp((distance*fog.density)*(distance*fog.density));

fogFactor=clamp(fogFactor,0.0,1.0);

vec3resultColour=mix(fogColor,colour.xyz,fogFactor);

returnvec4(resultColour.xyz,1);

}

Asyoucanseewiththedirectionallightwejustusethecolourandtheintensity,wearenotinterestedinthedirection.Withthatmodificationwejustneedtoslightlymodifythecalltothefunctionlikethis:

if(fog.active==1)

{

fragColor=calcFog(mvVertexPos,fragColor,fog,ambientLight,directionalLight);

}

Fog

195

Page 196: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Andwewillgetsomethinglikethiswhenthenightfalls.

Oneimportantthingtohighlightisthatwemustwiselychoosethefogcolour.Thisisevenmoreimportantwhenwehavenoskyboxbutafixedcolourbackground.Weshouldsetupthefogcolourtobeequaltotheclearcolour.Ifyouuncommentthecodethatrendertheskyboxandreruntheexampleyouwillgetsomethinglikethis.

Butifwemodifytheclearcolourtobeequalto(0.5,0.5,0.5)theresultwillbelikethis.

Fog

196

Page 197: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Fog

197

Page 198: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

NormalMappingInthischapterwewillexplainatechniquethatwilldramaticallyimprovehowour3Dmodelslooklike.Bynow,wearenowabletoapplytexturestocomplex3Dmodels,butwearestillfarawayfromwhatrealobjectslooklike.Surfacesintherealworldarenotperfectlyplain,theyhaveimperfectionsthatour3Dmodelscurrentlydonothave.

Inordertorendermorerealisticsceneswearegoingtousenormalmaps.Ifyoulookataflatsurfaceintherealwordyouwillseethatthoseimperfectionscanbeseenevenatdistancebythewaythatthelightreflectsonit.Ina3Dsceneaflatsurfacewillhavenoimperfections,wecanapplyatexturetoitbutwewon’tchangethewaythatlightreflectsonit.That’sthethingthatmakesthedifference.

Wemaythinkinincreasingthedetailofourmodelsbyincreasingthenumberoftrianglesandreflectthoseimperfectionsbutperformancewilldegrade.Whatweneedisawaytochangethewaylightreflectsonsurfacestoincreasetherealism.Thisisachievedwiththenormalmappingtechnique.

Let’sgobacktotheplainsurfaceexample,aplanecanebedefinedbytwotriangleswhichformaquad.Ifyourememberformthelightningchapters,theelementthatmodelshowlightreflectsaresurfacenormals.Inthiscase,wehaveasinglenormalforthewholesurface,eachfragmentofthesurfaceusesthesamenormalwhencalculatinghowlightaffectsthem.Thisisshowninthenextfigure.

Ifwecouldchangethenormalsforeachfragmentofthesurfacewecouldmodelsurfaceimperfectionstorendertheminamorerealisticway.Thisisshowninthenextfigure.

NormalMapping

198

Page 199: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thewaywearegoingtoachievethisisbyloadinganothertexturewhichstoresthenormalsforthesurface.Eachpixelofthenormaltexturewillcontainthevaluesofthex,yandzcoordinatesofthenormalstoredasanRGBvalue.

Let’susethefollowingtexturetodrawaquad.

Anexampleofanormalmaptexturefortheimageabovecouldbethefollowing.

Asyoucanseeisiflikewehadappliedacolourtransformationtotheoriginaltexture.Eachpixelstoresnormalinformationusingcolourcomponents.Onethingthatyouwillusuallyseewhenviewingnormalmapsisthatthedominantcolourstendtoblue.Thisisduetothefact

NormalMapping

199

Page 200: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

thatnormalspointtothepositivezaxis.Thezcomponentwillusuallyhaveamuchhighervaluethanthexandyonesforplainsurfacesasthenormalpointsoutofthesurface.Sincex,y,zcoordinatesaremappedtoRGB,thebluecomponentwillhavealsoahighervalue.

So,torenderanobjectusingnormalmapswejustneedanextratextureanduseitwhilerenderingfragmentstogettheappropriatenormalvalue.

Let’sstartchangingourcodeinordertosupportnormalmaps.WewilladdanewtextureinstancetotheMaterialclasssowecanattachanormalmaptexturetoourgameitems.Thisinstancewillhaveitsowngettersandsettersandmethodtocheckifthematerialhasanormalmapornot.

publicclassMaterial{

privatestaticfinalVector4fDEFAULT_COLOUR=newVector3f(1.0f,1.0f,1.0f,10.f

);

privateVector3fambientColour;

privateVector3fdiffuseColour;

privateVector3fspecularColour;

privatefloatreflectance;

privateTexturetexture;

privateTexturenormalMap;

//…Previouscodehere

publicbooleanhasNormalMap(){

returnthis.normalMap!=null;

}

publicTexturegetNormalMap(){

returnnormalMap;

}

publicvoidsetNormalMap(TexturenormalMap){

this.normalMap=normalMap;

}

}

Wewillusethenormalmaptextureinthescenefragmentshader.But,sinceweareworkinginviewcoordinatesspaceweneedtopassthemodelviewmatrixinordertodothepropertransformation.Thus,weneedtomodifythescenevertexshader.

NormalMapping

200

Page 201: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

#version330

layout(location=0)invec3position;

layout(location=1)invec2texCoord;

layout(location=2)invec3vertexNormal;

outvec2outTexCoord;

outvec3mvVertexNormal;

outvec3mvVertexPos;

outmat4outModelViewMatrix;

uniformmat4modelViewMatrix;

uniformmat4projectionMatrix;

voidmain()

{

vec4mvPos=modelViewMatrix*vec4(position,1.0);

gl_Position=projectionMatrix*mvPos;

outTexCoord=texCoord;

mvVertexNormal=normalize(modelViewMatrix*vec4(vertexNormal,0.0)).xyz;

mvVertexPos=mvPos.xyz;

outModelViewMatrix=modelViewMatrix;

}

Inthescenefragmentshaderweneedtoaddanotherinputparameter.

inmat4outModelViewMatrix;

Inthefragmentshader,wewillneedtopassanewuniformforthenormalmaptexturesampler:

uniformsampler2Dtexture_sampler;

Also,inthefragmentshader,wewillcreateanewfunctionthatcalculatesthenormalforthecurrentfragment.

vec3calcNormal(Materialmaterial,vec3normal,vec2text_coord,mat4modelViewMatrix)

{

vec3newNormal=normal;

if(material.hasNormalMap==1)

{

newNormal=texture(normalMap,text_coord).rgb;

newNormal=normalize(newNormal*2-1);

newNormal=normalize(modelViewMatrix*vec4(newNormal,0.0)).xyz;

}

returnnewNormal;

}

NormalMapping

201

Page 202: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thefunctiontakesthefollowingparameters:

Thematerialinstance.Thevertexnormal.Thetexturecoordinates.Themodelviewmatrix.

Thefirstthingwedointhatfunctionistocheckifthismaterialhasanormalmapassociatedornot.Ifnot,wejustsimplyusethevertexnormalasusual.Ifithasanormalmap,weusethenormaldatastoredinthenormaltexturemapassociatedtothecurrenttexturecoordinates.

Rememberthatthecolourwegetarethenormalcoordinates,butsincetheyarestoredasRGBvaluestheyarecontainedintherange[0,1].Weneedtotransformthemtobeintherange[-1,1],sowejustmultiplybytwoandsubtract1.Then,wenormalizethatvalueandtransformittoviewmodelcoordinatespace(aswiththevertexnormal).

Andthat’sall,wecanusethereturnedvalueasthenormalforthatfragmentinallthelightningcalculations.

IntheRendererclassweneedtocreatethenormalmapuniform,andintherenderScenemethodweneedtosetituplikethis:

...

sceneShaderProgram.setUniform("fog",scene.getFog());

sceneShaderProgram.setUniform("texture_sampler",0);

sceneShaderProgram.setUniform("normalMap",1);

...

Youmaynoticesomeinterestingthinginthecodeabove.Wearesetting0forthematerialtextureuniform(texture_sampler)and1forthenormalmaptexture(normalMap).Ifyourecallfromthetexturechapter.Weareusingmorethanonetexture,sowemustsetupthetextureunitforeachseparatetexture.

WeneedtotakethisalsointoconsiderationwhenwearerenderingaMesh.

NormalMapping

202

Page 203: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatevoidinitRender(){

Texturetexture=material.getTexture();

if(texture!=null){

//Activatefirsttexturebank

glActiveTexture(GL_TEXTURE0);

//Bindthetexture

glBindTexture(GL_TEXTURE_2D,texture.getId());

}

TexturenormalMap=material.getNormalMap();

if(normalMap!=null){

//Activatefirsttexturebank

glActiveTexture(GL_TEXTURE1);

//Bindthetexture

glBindTexture(GL_TEXTURE_2D,normalMap.getId());

}

//Drawthemesh

glBindVertexArray(getVaoId());

glEnableVertexAttribArray(0);

glEnableVertexAttribArray(1);

glEnableVertexAttribArray(2);

}

Asyoucanseeweneedtobindtoeachofthetexturesavailableandactivatetheassociatedtextureunitinordertobeabletoworkwithmorethanonetexture.IntherenderScenemethodintheRendererclasswedonotneedtoexplicitlysetuptheuniformofthetexturesinceit’salreadycontainedintheMaterial.

Inordertoshowtheimprovementsthatnormalmapsprovide,wehavecreatedanexamplethatshowstwoquadssidebyside.Therightquadhasatexturemapappliedandtheleftonenot.Wealsohaveremovedtheterrain,theskyboxandtheHUDandsetupadirectionallightwithcanbechangedwiththeleftandrightcursorkeyssoyoucanseetheeffect.Wehavemodifiedthebasesourcecodeabitinordertosupportnothavingaskyboxoraterrain.Wehavealsoclampedthelighteffectinthefragmentshaderintherang[0,1]toavoidoverexposingeffectoftheimage.

Theresultisshowninthenextfigure.

NormalMapping

203

Page 204: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Asyoucanseethequadthathasanormaltextureappliedgivestheimpressionofhavingmorevolume.Althoughitis,inessence,aplainsurfaceliketheotherquad,youcanseehowthelightreflects.But,althoughthecodewehavesetup,worksperfectlywiththisexampleyouneedtobeawareofitslimitations.Thecodeonlyworksfornormalmaptexturesthatarecreatedusingobjectspacecoordinates.Ifthisisthecasewecanapplythemodelviewmatrixtransformationstotranslatethenormalcoordinatestotheviewspace.

But,usuallynormalmapsarenotdefinedinthatway.Theyusuallyaredefinedinthecalledtangentspace.Thetangentspaceisacoordinatesystemthatislocaltoeachtriangleofthemodel.Inthatcoordinatespacethezaxisalwayspointsoutofthesurface.Thisisthereasonwhywhenyoulookatanormalmapitsusuallybluish,evenforcomplexmodelswithopposingfaces.

Wewillstickwiththissimpleimplementationbynow,butkeepinmindthatyoumustalwaysusenormalmapsdefinedinobjectspace.Ifyouusemapsdefinedintangentspaceyouwillgetweirdresults.Inordertobeabletoworkwiththemweneedtosetupspecificmatricestotransformcoordinatestothetangentspace.

NormalMapping

204

Page 205: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Shadows

ShadowMappingCurrentlyweareabletorepresenthowlightaffectstheobjectsina3Dscene.Objectsthatgetmorelightareshownbrighterthenobjectsthatdonotreceivelight.Howeverwearestillnotabletocastshadows.Shadowswillincreasethedegreeofrealismthat3Dscenewouldhavesowewilladdsupportforitinthischapter.

WewilluseatechniquenamedShadowmappingwhichiswidelyusedingamesanddoesnotaffectseverelytheengineperformance.Shadowmappingmayseemsimpletounderstandbutit’ssomehowdifficulttoimplementitright.Or,tobemoreprecise,it’sverydifficulttoimplementitinagenericwaysthatcoverallthepotentialcasesandproducesconsistentresults.

Wewillexplainhereanapproachwhichwillserveyoutoaddshadowsformostofthecases,butwhatit’smoreimportantitwillserveyoutounderstanditslimitations.ThecodepresentedhereisfarfrombeingperfectbutIthinkitwillbeeasytounderstand.Itisalsodesignedtosupportdirectionallights(whichinmyopinionisthemorecomplexcase)butyouwilllearnhowitcanbeextendedtosupportothertypeoflights(suchuspointlights).IfyouwanttoachievemoreadvancedresultsyoushouldusemoreadvancetechniquessuchasCascadedShadowMaps.Inanycasetheconceptsexplainedherewillserveyouasabasis.

Solet’sstartbythinkinginhowwecouldcheckifaspecificarea(indeedafragment)isinshadowornot.Whiledrawingthatareaifwecancastraystothelightsource,ifwecanreachthelightsourcewithoutanycollisionthenthatpixelisinlight.Ifnot,thenthepixelisinshadow.

Thefollowingpictureshowsthecaseforapointlight,thepointPAcanreachthesourcelight,butpointsPBandPCcan’tsotheyareinshadow.

Shadows

205

Page 206: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

So,howwecancheckifwecancastthatraywithoutcollisionsinanefficientmanner?Alightsourcecan,theoreticallycastinfinitelyraylights,sohowdowecheckifaraylightisblockedornot?Whatwecandoinsteadofcastingraylightsistolookatthe3Dscenefromthelight’spersèctiveandrenderthescenefromthatlocation.Wecansetthecameraatthelightpositionandrenderthescenesowecanstorethedepthforeachfragment.Thisisequivalenttocalculatethedistanceofeachfragmenttothelightsource.Attheend,whatwearedoingisstoringtheminimumdistanceasseenfromthelightsourceasashadowmap.

Thefollowingpictureshowsacubefloatingoveraplaneandaperpendicularlight.

Thesceneasseenfromthelightperspectivewouldbesomethinglikethis(thedarkerthecolour,theclosertothelightsource).

Shadows

206

Page 207: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Withthatinformationwecanrenderthe3Dsceneasusualandcheckthedistanceforeachfragmenttothelightsourcewiththeminimumstoreddistance.Ifthedistanceislessthatthevaluestoredintheshadowmap,thentheobjectisinlight,otherwiseisinshadow.Wecanhaveseveralobjectsthatcouldbehitbythesameraylight.Butwestoretheminimumdistance.

Thus,shadowmappingisatwostepprocess:

Firstwerenderthescenefromthelightspaceintoashadowmaptogettheminimumdistances.Secondwerenderthescenefromthecamerapointofviewandusethatdepthmaptocalculateifobjectsareinshadowornot.

Inordertorenderthedepthmapweneedtotalkaboutthedepthbuffer.Whenwerenderasceneallthedepthinformationisstoredinabuffernamed,obviously,depth-buffer(alsoz-buffer).Thatdepthinformationisthezvalueofeachofthefragmentthatisrendered.Ifyourecallfromthefirstchapterswhatwearedoingwhilerenderingasceneistransformingfromworldcoordinatestoscreencoordinates.Wearedrawingtoacoordinatespacewhichrangesfrom0to1forxandyaxis.Ifanobjectismoredistantthanother,wemustcalculatehowthisaffectstheirxandycoordinatesthroughtheperspectiveprojectionmatrix.Thisisnotcalculatedautomaticallydependingonthezvalue,thismustbedoneus.Whatisactuallystoredinthezcoordinateitsthedepthofthatfragment,nothinglessbutnothingmore.

Besidesthat,inoursourcecodeweareenablingdepthtesting.IntheWindowclasswehavesetthefollowingline:

glEnable(GL_DEPTH_TEST);

Bysettingthislinewepreventfragmentsthatcannotbeseen,becausetheyarebehindotherobjects,tobedrawn.Beforeafragmentisdrawnitszvalueiscomparedwiththezvalueofthez-buffer.Ifithasahigherzvalue(it’sfaraway)thanthezvalueofthebufferit’sdiscarded.Rememberthatthisisdoneinscreenspace,sowearecomparingthezvalueofafragmentgivenapairofxandycoordinatesinscreenspace,thatisintherange[0, 1].Thus,thezvalueisalsointhatrange.

Shadows

207

Page 208: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thepresenceofthedepthbufferisthereasonwhyweneedtoclearthescreenbeforeperforminganyrenderoperation.Weneedtoclearnotonlythecolourbutthedepthinformationalso:

publicvoidclear(){

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

}

Inordertostartbuildingthedepthmapwewanttogetthatdepthinformationasviewedfromthelightperspective.Weneedtosetupacamerainthelightposition,renderthesceneandstorethatdepthinformationintoatexturesowecanaccesstoitlater.

Therefore,thefirstthingweneedtodoisaddsupportforcreatingthosetextures.WewillmodifytheTextureclasstosupportthecreationofemptytexturesbyaddinganewconstructor.Thisconstructorexpectsthedimensionsofthetextureandtheformatofthepixelsitstores.

publicTexture(intwidth,intheight,intpixelFormat)throwsException{

this.id=glGenTextures();

this.width=width;

this.height=height;

glBindTexture(GL_TEXTURE_2D,this.id);

glTexImage2D(GL_TEXTURE_2D,0,GL_DEPTH_COMPONENT,this.width,this.height,0,pix

elFormat,GL_FLOAT,(ByteBuffer)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_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);

}

WesetthetexturewrappingmodetoGL_CLAMP_TO_EDGEsincewedonotwantthetexturetorepeatincaseweexceedthe[0, 1]range.

Sonowthatweareabletocreateemptytextures,weneedtobeabletorenderasceneintoit.InordertodothatweneedtouseFrameBuffersObjects(orFBOs).AFrameBufferisacollectionofbuffersthatcanbeusedasadestinationforrendering.WhenwehavebeenrenderingtothescreenwehaveusingOpenGL’sdefaultbuffer.OpenGLallowsustorendertouserdefinedbuffersbyusingFBOs.WewillisolatetherestofthecodeoftheprocessofcreatingFBOsforshadowmappingbycreatinganewclassnamedShadowMap.Thisisthedefinitionofthatclass.

packageorg.lwjglb.engine.graph;

importstaticorg.lwjgl.opengl.GL11.*;

importstaticorg.lwjgl.opengl.GL30.*;

Shadows

208

Page 209: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicclassShadowMap{

publicstaticfinalintSHADOW_MAP_WIDTH=1024;

publicstaticfinalintSHADOW_MAP_HEIGHT=1024;

privatefinalintdepthMapFBO;

privatefinalTexturedepthMap;

publicShadowMap()throwsException{

//CreateaFBOtorenderthedepthmap

depthMapFBO=glGenFramebuffers();

//Createthedepthmaptexture

depthMap=newTexture(SHADOW_MAP_WIDTH,SHADOW_MAP_HEIGHT,GL_DEPTH_COMPONENT

);

//AttachthethedepthmaptexturetotheFBO

glBindFramebuffer(GL_FRAMEBUFFER,depthMapFBO);

glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,dep

thMap.getId(),0);

//Setonlydepth

glDrawBuffer(GL_NONE);

glReadBuffer(GL_NONE);

if(glCheckFramebufferStatus(GL_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE){

thrownewException("CouldnotcreateFrameBuffer");

}

//Unbind

glBindFramebuffer(GL_FRAMEBUFFER,0);

}

publicTexturegetDepthMapTexture(){

returndepthMap;

}

publicintgetDepthMapFBO(){

returndepthMapFBO;

}

publicvoidcleanup(){

glDeleteFramebuffers(depthMapFBO);

depthMap.cleanup();

}

}

Shadows

209

Page 210: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

TheShadowMapclassdefinestwoconstantsthatdeterminethesizeofthetexturethatwillholdthedepthmap.Italsodefinestwoattributes,onefortheFBOandoneforthetexture.Intheconstructor,wecreateanewFBOandanewTexture.FortheFBOwewilluseasthepixelformattheconstantGL_DEPTH_COMPONENTsinceweareonlyinterestedinstoringdepthvalues.ThenweattachtheFBOtothetextureinstance.

ThefollowinglinesexplicitlysettheFBOtonotrenderanycolour.AFBOneedsacolourbuffer,butwearenotgoingtoneeded.ThisiswhywesetthecolourbufferstobeusedasGL_NONE.

glDrawBuffer(GL_NONE);

glReadBuffer(GL_NONE);

NowwearereadytorenderthescenefromthelightperspectiveintoFBOintheRendererclass.Inordertodothat,wewillcreateaspecificsetofvertexandfragmentsshaders.

Thevertexshader,nameddepth_vertex.fs,isdefinedlikethis.

#version330

layout(location=0)invec3position;

layout(location=1)invec2texCoord;

layout(location=2)invec3vertexNormal;

uniformmat4modelLightViewMatrix;

uniformmat4orthoProjectionMatrix;

voidmain()

{

gl_Position=orthoProjectionMatrix*modelLightViewMatrix*vec4(position,1.0f);

}

Weexpecttoreceivethesameinputdataasthesceneshader.Infact,weonlyneedtheposition,buttoreuseasmuchascodeaspossiblewewillpassitanyway.Wealsoneedapairofmatrices.Rememberthatwemustrenderthescenefromthelightpointofview,soweneedtotransformourmodelstolight'scoordinatespace.ThisisdonethroughthemodelLightViewMatrixmatrix,whichisanalogoustoviewmodelmatrixusedforacamera.Thelightisourcameranow.

Thenweneedtotransformthosecoordinatestoscreenspace,thatis,weneedtoprojectthem.Andthisisoneofthedifferenceswhilecalculatingshadowmapsfordirectionallightsversuspointlights.Forpointlightswewoulduseaperspectiveprojectionmatrixasifwewererenderingthescenenormally.Directionallights,instead,affectallobjectsinthesame

Shadows

210

Page 211: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

wayindependentlyofthedistance.Directionallightsarelocatedataninfinitepointanddonothaveapositionbutadirection.Anorthographicprojectiondoesnotrenderdistantobjectssmaller,andbecauseofthischaracteristicisthemostsuitablefordirectionallights.

Thefragmentshaderisevensimpler.Itjustoutputsthezcoordinateasthedepthvalue.

#version330

voidmain()

{

gl_FragDepth=gl_FragCoord.z;

}

Infact,youcanremovethatline,sinceweareonlygeneratingdepthvalues,thedepthvalueitwillbeautomaticallyreturned.

OncewehavedefinedthenewshadersfordepthrenderingwecanusethemintheRendererclass.Wedefineanewmethodforsettingupthoseshaders,namedsetupDepthShader,whichwillbeinvokedwheretheothersshadersareinitialized.

privatevoidsetupDepthShader()throwsException{

depthShaderProgram=newShaderProgram();

depthShaderProgram.createVertexShader(Utils.loadResource("/shaders/depth_vertex.vs"

));

depthShaderProgram.createFragmentShader(Utils.loadResource("/shaders/depth_fragmen

t.fs"));

depthShaderProgram.link();

depthShaderProgram.createUniform("orthoProjectionMatrix");

depthShaderProgram.createUniform("modelLightViewMatrix");

}

NowweneedtocreateanewmethodthatusesthoseshaderswhichwillbenamedrenderDepthMap.Thismethodwillbeinvokedintheprincipalrendermethod.

publicvoidrender(Windowwindow,Cameracamera,Scenescene,IHudhud){

clear();

//Renderdepthmapbeforeviewportshasbeensetup

renderDepthMap(window,camera,scene);

glViewport(0,0,window.getWidth(),window.getHeight());

//Restofthecodehere....

Shadows

211

Page 212: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Ifyoulookattheabovecodeyouwillseethatthenewmethodisinvokedattheverybeginning,beforewehavesettheviewport.Thisisduetothefactthatthisnewmethodwillchangetheviewporttomatchthedimensionsofthetexturethatholdsthedepthmap.Becauseofthat,wewillalwaysneedtoset,aftertherenderDepthMaphasbeenfinished,theviewporttothescreendimensions(withoutcheckingifthewindowhasbeenresized).

Let’sdefinenowtherenderDepthMapmethod.ThefirstthingthatwewilldoistobindtotheFBOwehavecreatedintheShadowMapclassandsettheviewporttomatchthetexturedimensions.

glBindFramebuffer(GL_FRAMEBUFFER,shadowMap.getDepthMapFBO());

glViewport(0,0,ShadowMap.SHADOW_MAP_WIDTH,ShadowMap.SHADOW_MAP_HEIGHT);

Thenweclearthedepthbuffercontentsandbindthedepthshaders.Sinceweareonlydealingwithdepthvalueswedonotneedtoclearcolourinformation.

glClear(GL_DEPTH_BUFFER_BIT);

depthShaderProgram.bind();

Nowweneedtosetupthematrices,andherecomesthetrickypart.Weusethelightasacamerasoweneedtocreateaviewmatrixwhichneedsapositionandthreeangles.Asithasbeensaidatthebeginningofthechapterwewillsupportonlydirectionallights,andthattypeoflightsdoesnotdefineapositionbutadirection.Ifwewereusingpointlightsthiswouldbeeasy,thepositionofthelightwouldbethepositionoftheviewmatrix,butwedonothavethat.

Wewilltakeasimpleapproachtocalculatethelightposition.Directionallightsaredefinedbyavector,usually,normalized,whichpointstothedirectionwherethelightis.Wewillmultiplythatdirectionvectorbyaconfigurablefactorsoitdefinesapointatareasonabledistanceforthescenewewanttodraw.Wewillusethatdirectioninordertocalculatetherotationangleforthatviewmatrix.

Shadows

212

Page 213: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thisisthefragmentthatcalculatesthelightpositionandtherotationangles

floatlightAngleX=(float)Math.toDegrees(Math.acos(lightDirection.z));

floatlightAngleY=(float)Math.toDegrees(Math.asin(lightDirection.x));

floatlightAngleZ=0;

Matrix4flightViewMatrix=transformation.updateLightViewMatrix(newVector3f(lightDire

ction).mul(light.getShadowPosMult()),newVector3f(lightAngleX,lightAngleY,lightAngl

eZ));

Nextweneedtocalculatetheorthographicprojectionmatrix.

Matrix4forthoProjMatrix=transformation.updateOrthoProjectionMatrix(orthCoords.left,

orthCoords.right,orthCoords.bottom,orthCoords.top,orthCoords.near,orthCoords.far)

;

WehavemodifiedtheTransformationclasstoincludethelightviewmatrixandtheorthographicprojectionmatrix.Previouslywehadaorthographic2Dprojectionmatrix,sowehaverenamedthepreviousmethodsandattributes.Youcancheckthedefinitioninthesourcecodewhichisstraightforward.

ThenwerenderthesceneobjectsasintherenderScenemethodbutusingthepreviousmatricestoworkinlightspacecoordinatesystem.

Shadows

213

Page 214: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

depthShaderProgram.setUniform("orthoProjectionMatrix",orthoProjMatrix);

Map<Mesh,List<GameItem>>mapMeshes=scene.getGameMeshes();

for(Meshmesh:mapMeshes.keySet()){

mesh.renderList(mapMeshes.get(mesh),(GameItemgameItem)->{

Matrix4fmodelLightViewMatrix=transformation.buildModelViewMatrix(gameItem,

lightViewMatrix);

depthShaderProgram.setUniform("modelLightViewMatrix",modelLightViewMatrix);

}

);

}

//Unbind

depthShaderProgram.unbind();

glBindFramebuffer(GL_FRAMEBUFFER,0);

TheparameterizationoftheorthographicprojectionmatrixisdefinedinthedirectionalLight.Thinkoftheorthographicprojectionmatrixasaboundingboxthatcontainsalltheobjectsthatwewanttorender.Whenprojectingonlytheobjectsthatfitintothatboundingboxwillbebevisible.Thatboundingboxisdefinedby6parameters:left,right,bottom,top,near,far.Since,thelightpositionisnowtheorigin,theseparametersdefinethedistancefromthatorigintotheleftorright(x-axis)upordown(y-axis)andtothenearestorfarthestplane(z-axis).

Oneofthetrickiestpointsingettingshadowsmaptoworkisdeterminethelightpositionandtheorthographicprojectionmatrixparameters.ThisiswayalltheseparametersarenowdefinedintheDirectionalLightclasssoitcanbesetproperlyaccordingtoeachscene.

Youcanimplementamoreautomaticapproach,bycalculatingthecentreofthecamerafrustum,getbackinthelightdirectionandbuildaorthographicprojectionthatcontainsalltheobjectsinthescene.Thefollowingfigureshowsa3Dsceneaslookedformabove,thecamerapositionanditsfrustum(inblue)andtheoptimallightpositionandboundingboxinred.

Shadows

214

Page 215: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Theproblemwiththeapproachaboveisthatisdifficulttocalculateandifyouhavesmallobjectsandtheboundingboxisbigyoumaygetstrangeresults.Theapproachpresentedhereissimplerforsmallscenesandyoucantweakittomatchyourmodels(evenyoucanchosetoexplicitlysetlight’spositiontoavoidstrangeeffectsifcameramovesfarawayfromtheorigin).Ifyouwantamoregenericmodelthatcanbeappliedtoanysceneyoushouldextendittosupportcascadingshadowmaps.

Let'scontinue.Beforeweusethedepthmapstoactuallycalculateshadows,youcouldrenderaquadwiththegeneratedtexturetoseehowarealdepthmaplookslike.Youcouldgetsomethinglikethisforascenecomposedbyarotatingcubefloatingoveraplanewithaperpendiculardirectionallight.

Shadows

215

Page 216: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Asit'sbeensaidbefore,thedarkerthecolour,theclosertothelightposition.What’stheeffectofthelightpositioninthedepthmap?Youcanplaywiththemultiplicationfactorofthedirectionallightandyouwillseethatthesizeoftheobjectsrenderedinthetexturedonotdecrease.Rememberthatweareusinganorthographicprojectionmatrixandobjectsdonotgetsmallerwithdistance.Whatyouwillseeisthatallcoloursgetbrighterasseeninthenextpicture.

Doesthatmeanthatwecanchooseahighdistanceforthelightpositionwithoutconsequences?Theanswerisno.Iflightistoofarawayfromtheobjectswewanttorender,theseobjectscanbeoutoftheboundingboxthatdefinestheorthographicprojectionmatrix.Inthiscaseyouwillgetanicewhitetexturewhichwouldbeuselessforshadowmapping.Ok,thenwesimplyincreasetheboundingboxsizeandeverythingwillbeok,right?Theanswerisagainno.Ifyouchosehugedimensionsfortheorthographicprojectionmatrixyourobjectswillbedrawnverysmallinthetexture,andthedepthvaluescanevenoverlapleadingtostrangeresults.Ok,soyoucanthinkinincreasingthetexturesize,but,againinthiscaseyouarelimitedandtexturescannotgrowindefinitelytousehugeboundingboxes.

Soasyoucanseeselectingthelightpositionandtheorthographicprojectionparametersisacomplexequilibriumwhichmakesdifficulttogetrightresultsusingshadowmapping.

Let’sgobacktotherenderingprocess,oncewehavecalculatedthedepthmapwecanuseitwhilerenderingthescene.Firstweneedtomodifythescenevertexshader.Uptonow,thevertexshaderprojectedthevertexcoordinatesfrommodelviewspacetothescreenspaceusingaperspectivematrix.Nowweneedtoprojectalsothevertexcoordinatesfromlightspacecoordinatesusingaprojectionmatrixtobeusedinthefragmentshadertocalculatetheshadows.

Thevertexshaderismodifiedlikethis.

Shadows

216

Page 217: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

#version330

layout(location=0)invec3position;

layout(location=1)invec2texCoord;

layout(location=2)invec3vertexNormal;

outvec2outTexCoord;

outvec3mvVertexNormal;

outvec3mvVertexPos;

outvec4mlightviewVertexPos;

outmat4outModelViewMatrix;

uniformmat4modelViewMatrix;

uniformmat4projectionMatrix;

uniformmat4modelLightViewMatrix;

uniformmat4orthoProjectionMatrix;

voidmain()

{

vec4mvPos=modelViewMatrix*vec4(position,1.0);

gl_Position=projectionMatrix*mvPos;

outTexCoord=texCoord;

mvVertexNormal=normalize(modelViewMatrix*vec4(vertexNormal,0.0)).xyz;

mvVertexPos=mvPos.xyz;

mlightviewVertexPos=orthoProjectionMatrix*modelLightViewMatrix*vec4(position

,1.0);

outModelViewMatrix=modelViewMatrix;

}

Weusenewuniformsforthelightviewmatrixandtheorthographicprojectionmatrix.

Inthefragmentshaderwewillcreateanewfunctiontocalculatetheshadowsthatisdefinedlikethis.

floatcalcShadow(vec4position)

{

floatshadowFactor=1.0;

vec3projCoords=position.xyz;

//Transformfromscreencoordinatestotexturecoordinates

projCoords=projCoords*0.5+0.5;

if(projCoords.z<texture(shadowMap,projCoords.xy).r)

{

//Currentfragmentisnotinshade

shadowFactor=0;

}

return1-shadowFactor;

}

Shadows

217

Page 218: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thefunctionreceivesthepositioninlightviewspaceprojectedusingtheorthographicprojectionmatrix.Itreturns0ifthepositionisinshadowand1ifit’snot.First,thecoordinatesaretransformedtotexturecoordinates.Screencoordinatesareintherange[−1, 1],buttexturecoordinatesareintherange[0, 1].Withthatcoordinateswegetthedepthvaluefromthetextureandcompareitwiththezvalueofthefragmentcoordinates.Ifthezvalueifthefragmenthasalowervaluethantheonestoredinthetexturethatmeansthatthefragmentisnotinshade.

Inthefragmentshader,thereturnvaluefromthecalcShadowfunctiontomodulatethelightcolourcontributionsfrompoint,spotanddirectionallights.Theambientlightisnotaffectedbytheshadow.

floatshadow=calcShadow(mlightviewVertexPos);

fragColor=clamp(ambientC*vec4(ambientLight,1)+diffuseSpecularComp*shadow,0,1

);

IntherenderScenemethodoftheRendererclasswejustneedtopasstheuniformfortheorthographicprojectionandlightviewmatrices(weneedtomodifyalsothemethodthatinitializestheshadertocreatethenewuniforms).Youcanconsultthisinthebook’ssourcecode.

IftoruntheDummyGameclass,whichhasbeenmodifiedtosetupafloatingcubeoveraplanewithadirectionallightwhichanglecanbechangedbyusingupanddownkeys,youshouldseesomethinglikethis.

Althoughshadowsareworking(youcancheckthatbymovinglightdirection),theimplementationpresentssomeproblems.Firstofall,therearestrangelinesintheobjectsthatarelightenedup.Thiseffectiscalledshadowacne,andit’sproducedbythelimited

Shadows

218

Page 219: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

resolutionofthetexturethatstoresthedepthmap.Thesecondproblemisthatthebordersoftheshadowarenotsmoothandlookblocky.Thecauseisthesameagain,thetextureresolution.Wewillsolvetheseproblemsinordertoimproveshadowquality.

ShadowMappingimprovementsNowthatwehavetheshadowmappingmechanismworking,let’ssolvetheproblemswehave.Let’sfirststartwiththeshadowacneproblem.Thedepthmaptextureislimitedinsize,andbecauseofthat,severalfragmentscanbemappedtothesamepixelinthattexturedepth.Thetexturedepthstorestheminimumdepth,soattheend,wehaveseveralfragmentsthatsharethesamedepthinthattexturealthoughtheyareatdifferentdistances.

Wecansolvethisbyincreasing,byalittlebitthedepthcomparisoninthefragmentshader,weaddabias.

floatbias=0.05;

if(projCoords.z-bias<texture(shadowMap,projCoords.xy).r)

{

//Currentfragmentisnotinshade

shadowFactor=0;

}

Now,theshadowacnehasdisappeared.

Shadows

219

Page 220: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Nowwearegoingtosolvedeshadowedgesproblem,whichisalsocausedbythetextureresolution.Foreachfragmentwearegoingtosamplethedepthtexturewiththefragment’spositionvalueandthesurroundingvalues.Thenwewillcalculatetheaverageandassignthatvalueastheshadowvalue.Inthiscasehisvaluewon’tbe0or1butcantakevaluesinbetweeninordertogetsmootheredges.

Thesurroundingvaluesmustbeatonepixeldistanceofthecurrentfragmentpositionintexturecoordinates.Soweneedtocalculatetheincrementofonepixelintexturecoordinateswhichisequalto1/textureSize.

InthefragmentShaderwejustneedtomodifytheshadowfactorcalculationtogetanaveragevalue.

floatshadowFactor=0.0;

vec2inc=1.0/textureSize(shadowMap,0);

for(introw=-1;row<=1;++row)

{

for(intcol=-1;col<=1;++col)

{

floattextDepth=texture(shadowMap,projCoords.xy+vec2(row,col)*inc).r;

shadowFactor+=projCoords.z-bias>textDepth?1.0:0.0;

}

}

shadowFactor/=9.0;

Theresultlooksnowsmoother.

Shadows

220

Page 221: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Nowoursamplelooksmuchbetter.Nevertheless,theshadowmappingtechniquepresentedherecanstillbeimprovedalot.Youcancheckaboutsolvingpeterpanningeffect(causedbythebiasfactor)andothertechniquestoimprovetheshadowedges.Inanycase,withtheconceptsexplainedhereyouhaveagoodbasistostartmodifyingthesample.

Inordertorendermultiplelightsyoujustneedtorenderaseparatedepthmapforeachlightsource.Whilerenderingthesceneyouwillneedtosampleallthosedepthmapstocalculatetheappropriateshadowfactor.

Shadows

221

Page 222: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Animations

IntroductionBynowwehavejustloadedstatic3Dmodels,inthischapterwewilllearnhowtoanimatethem.Whenthinkingaboutanimationsthefirstapproachistocreatedifferentmeshesforeachmodelpositions,loadthemupintotheGPUanddrawthemsequentiallytocreatetheillusionofanimation.Althoughthisapproachisperfectforsomegamesit'snotveryefficient(intermsofmemoryconsumption).

Thiswhereskeletalanimationcomestoplay.Inskeletalanimationthewayamodelanimatesisdefinedbyitsunderlyingskeleton.Askeletonisdefinedbyahierarchyofspecialpointscalledjoints.Thosejointsaredefinedbytheirpositionandrotation.Wehavesaidalsothatit'sahierarchy,thismeansthatthefinalpositionforeachjointisaffectedbytheirparents.Forinstance,thinkonawrist,thepositionofawristismodifiedifacharactermovestheelbowandalsoifitmovestheshoulder.

Jointsdonotneedtorepresentaphysicalboneorarticulation,theyareartifactsthatallowsthecreativestomodelananimation.Inadditiontojointswestillhavevertices,thepointsthatdefinethetrianglesthatcomposea3Dmodel.But,inskeletalanimation,verticesaredrawnbasedonthepositionofthejointsitisrelatedto.

InthischapterwewilluseMD5formattoloadanimatedmodels.MD5formatwascreatebyIDSoftware,thecreatorsofDoom,andit’sbasicallyatextbasedfileformatwhichiswellunderstood.AnotherapproachwouldbetousetheColladaformat,whichisapublicstandardsupportedbymanytools.ColladaisanXMLbasedformatbutasadownsideit’sverycomplex(Thespecificationforthe1.5versionhasmorethan500pages).So,wewillsticktoamuchmoresimpleformat,MD5,thatwillallowustofocusintheconceptsoftheskeletalanimationandtocreateaworkingsample.

YoucanalsoexportsomemodelsfromBlendertoMD5formatviaspecificaddonsthatyoucanfindontheInternet(http://www.katsbits.com/tools/#md5)

InthischapterI’veconsultedmanydifferentsources,butIhavefoundtwothatprovideaverygoodexplanationabouthowtocreateananimatedmodelusingMD5files.Thesessourcescanbeconsultedat:

http://www.3dgep.com/gpu-skinning-of-md5-models-in-opengl-and-cg/http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html

Animations

222

Page 223: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Solet’sstartbywritingthecodethatparsesMD5files.TheMD5formatdefinestwotypesoffiles:

Themeshdefinitionfile:whichdefinesthejointsandtheverticesandtexturesthatcomposethesetofmeshesthatformthe3Dmodel.Thisfileusuallyhasaextensionnamed“.md5mesh”.Theanimationdefinitionfile:whichdefinestheanimationsthatcanbeappliedtothemodel.Thisfileusuallyhasaextensionnamed“.md5anim”.

AnMD5fileiscomposedbyaheaderandifferentsectionscontainedbetweenbraces.Let’sstartexaminingthemeshdefinitionfile.IntheresourcesfolderyouwillfindseveralmodelsinMD5format.Ifyouopenoneofthemyoucanseeastructuresimilarlikethis.

Thefirststructurethatyoucanfindinthemeshdefinitionfileistheheader.Youcanseebelowheader’scontentfromoneofthesamplesprovided:

Animations

223

Page 224: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

MD5Version10

commandline""

numJoints33

numMeshes6

Theheaderdefinesthefollowingattributes:

TheversionoftheMD5specificationthatitcompliesto.Thecommandusedtogeneratethisfile(froma3Dmodellingtool).ThenumberofJointsthataredefinedinthejointssectionThenumberofMeshes(thenumberofmeshessectionsexpected).

TheJointssectionsdefinesthejoints,asitnamesstates,theirpositionsandtheirrelationships.Afragmentofthejointssectionofoneofthesamplemodelsisshownbelow.

joints{

"origin"-1(-0.0000000.016430-0.006044)(0.7071070.0000000.707107)

//

"sheath"0(11.004813-3.17713831.702473)(0.307041-0.5786140.354181)

//origin

"sword"1(9.809593-9.36154940.753730)(0.305557-0.5781550.353505)

//sheath

"pubis"0(0.0140762.06444226.144581)(-0.466932-0.531013-0.466932)

//origin

……

}

AJointisdefinedbythefollowingattributes:

Jointname,atextualattributebetweenquotes.Jointparent,usinganindexwhichpointstotheparentjointusingitspositioninthejointslist.Therootjointhasaparentequalsto-1.Jointposition,definedinmodelspacecoordinatesystem.Jointorientation,definedalsoinmodelspacecoordinatesystem.Theorientationinfactisaquaternionwhosew-componentisnotincluded.

Beforecontinuingexplainingtherestofthefilelet’stalkaboutquaternions.Quaternionsarefourcomponentelementsthatareusedtorepresentrotation.Uptonow,wehavebeenusingEulerangles(yaw,pitchandroll)todefinerotations,whichbasicallydefinerotationaroundthex,yandzangles.But,Euleranglespresentsomeproblemswhenworkingwithrotations,specificallyyoumustbeawareofthecorrectordertoapplyderotationsandsomeoperationscangetverycomplex.

Animations

224

Page 225: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thiswherequaternionscometohelpinordertosolvethiscomplexity.Asithasbeensaidbeforeaquaternionisdefinedasasetof4numbers(x,y,z,w).Quaternionsdefinearotationaxisandtherotationanglearoundthataxis.

YoucancheckinthewebthemathematicaldefinitionofeachofthecomponentsbutthegoodnewsisthatJOML,themathlibraryweareusing,providessupportforthem.Wecanconstructrotationmatricesbasedonquaternionsandperformsometransformationtovectorswiththem.

Let’sgetbacktothejointsdefinition,thewcomponentismissingbutitcanbeeasilycalculatedwiththehelpoftherestofthevalues.Youcancheckthesourcecodetoseehowit'sdone.

Afterthejointsdefinitionyoucanfindthedefinitionofthedifferentmeshesthatcomposeamodel.BelowyoucanfindafragmentofaMeshdefinitionfromoneofthesamples.

mesh{

shader"/textures/bob/guard1_body.png"

numverts494

vert0(0.3945310.513672)01

vert1(0.4472660.449219)12

...

vert493(0.6835940.455078)8643

numtris628

tri0021

tri1013

...

tri627471479493

numweights867

weight051.000000(6.1757748.105262-0.023020)

weight150.500000(4.88017312.8052514.196980)

...

weight86660.333333(1.266308-0.3027018.949338)

}

Animations

225

Page 226: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Let’sreviewthestructurepresentedabove:

AMeshstartsbydefiningatexturefile.Keepinmindthatthepaththatyouwillfindhereistheoneusedbythetoolthatcreatedthatmodel.Thatpathmaynotmatchtheonethatisusedtoloadthosefiles.Youhavetwoapproacheshere,eitheryouchangethebasepathdynamicallyoreitheryouchangethatpathbyhand.I’vechosenthelatterone,thesimplerone.Nextyoucanfindtheverticesdefinition.Avertexisdefinedbythefollowingattributes:

Vertexindex.

Texturecoordinates.

Theindexofthefirstweightdefinitionthataffectsthisvertex.

Thenumberofweightstoconsider.Afterthevertices,thetrianglesthatformthismesharedefined.Thetrianglesdefinethewaythatverticesareorganizedusingtheirindices.Finally,theweightsaredefined.AWeightdefinitioniscomposedby:

AWeightindex.

AJointindex,whichpointstothejointrelatedtothisweight.

Abiasfactor,whichisusedtomodulatetheeffectofthisweight.

Apositionofthisweight.

Thefollowingpicturedepictstherelationbetweenthecomponentsdescribedaboveusingsampledata.

Ok,sonowthatweunderstandthemeshmodelfilewecanparseit.Ifyoulookatthesourcecodeyouwillseethatanewpackagehasbeencreatedtohostparsersformodelformats.There’soneforOBJfilesunderorg.lwjglb.engine.loaders.objandthecodeforMD5files

Animations

226

Page 227: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

isunderorg.lwjglb.engine.loaders.md5.

AlltheparsingcodeisbasedonregularexpressionstoextracttheinformationfromtheMD5textfiles.TheparserswillcreateahierarchyofobjectsthatmimicthestructureoftheinformationcomponentscontainedintheMD5files.ItmaynotbethemostefficientparserintheworldbutIthinkitwillservetobetterunderstandtheprocess.

ThestartingclasstoparseaMD5modelfileisMD5Modelclass.ThisclassreceivesasaparameterinitsparsemethodthecontentsofaMD5fileancreatesahierarchythatcontainstheheader,thelistofjointsandthelistofmesheswithallthesubelements.Thecodeisverystraightforwardso,Iwon’tincludeithere.

Afewcommentsabouttheparsingcode:

ThesubelementsofaMesharedefinedasinnerclassesinsidetheMD5Meshclass.YoucancheckhowthefourthcomponentofthejointsorientationarecalculatedinthecalculateQuaternionmethodformtheMD5Utilsclass.

NowthatwehaveparsedafilewemusttransformthatobjecthierarchyintosomethingthatcanbeprocessedbythegameEngine,wemustcreateaGameIteminstance.InordertodothatwewillcreateanewclassnamedMD5LoaderthatwilltakeaMD5ModelinstanceandwillconstructaGameItem.

Beforewestart,asyounoticed,aMD5modelhasseveralMeshes,butourGameItemclassonlysupportsasingleMesh.Weneedtochangethisfirst,theclassGameItemnowlookslikethis.

packageorg.lwjglb.engine.items;

importorg.joml.Vector3f;

importorg.lwjglb.engine.graph.Mesh;

publicclassGameItem{

privateMesh[]meshes;

privatefinalVector3fposition;

privatefloatscale;

privatefinalVector3frotation;

publicGameItem(){

position=newVector3f(0,0,0);

scale=1;

rotation=newVector3f(0,0,0);

}

Animations

227

Page 228: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicGameItem(Meshmesh){

this();

this.meshes=newMesh[]{mesh};

}

publicGameItem(Mesh[]meshes){

this();

this.meshes=meshes;

}

publicVector3fgetPosition(){

returnposition;

}

publicvoidsetPosition(floatx,floaty,floatz){

this.position.x=x;

this.position.y=y;

this.position.z=z;

}

publicfloatgetScale(){

returnscale;

}

publicvoidsetScale(floatscale){

this.scale=scale;

}

publicVector3fgetRotation(){

returnrotation;

}

publicvoidsetRotation(floatx,floaty,floatz){

this.rotation.x=x;

this.rotation.y=y;

this.rotation.z=z;

}

publicMeshgetMesh(){

returnmeshes[0];

}

publicMesh[]getMeshes(){

returnmeshes;

}

publicvoidsetMeshes(Mesh[]meshes){

this.meshes=meshes;

}

publicvoidsetMesh(Meshmesh){

if(this.meshes!=null){

for(MeshcurrMesh:meshes){

Animations

228

Page 229: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

currMesh.cleanUp();

}

}

this.meshes=newMesh[]{mesh};

}

}

WiththemodificationabovewecannowdefinethecontentsfortheMD5Loaderclass.ThisclasswillhaveamethodnamedprocessthatwillreceiveaMD5Modelinstanceandadefaultcolour(forthemeshesthatdonotdefineatexture)andwillreturnaGameIteminstance.Thebodyofthatmethodisshownbelow.

publicstaticGameItemprocess(MD5Modelmd5Model,Vector4fdefaultColour)throwsExcep

tion{

List<MD5Mesh>md5MeshList=md5Model.getMeshes();

List<Mesh>list=newArrayList<>();

for(MD5Meshmd5Mesh:md5Model.getMeshes()){

Meshmesh=generateMesh(md5Model,md5Mesh,defaultColour);

handleTexture(mesh,md5Mesh,defaultColour);

list.add(mesh);

}

Mesh[]meshes=newMesh[list.size()];

meshes=list.toArray(meshes);

GameItemgameItem=newGameItem(meshes);

returngameItem;

}

AsyoucanseewejustiterateoverthemeshesdefinedintotheMD5Modelclassandtransformthemintoinstancesoftheclassorg.lwjglb.engine.graph.MeshbyusingthegenerateMeshmethodwhichistheonethatreallydoesthework.Beforeweexaminethatmethodwewillcreateaninnerclassthatwillserveustobuildthepositionsandnormalsarray.

Animations

229

Page 230: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatestaticclassVertexInfo{

publicVector3fposition;

publicVector3fnormal;

publicVertexInfo(Vector3fposition){

this.position=position;

normal=newVector3f(0,0,0);

}

publicVertexInfo(){

position=newVector3f();

normal=newVector3f();

}

publicstaticfloat[]toPositionsArr(List<VertexInfo>list){

intlength=list!=null?list.size()*3:0;

float[]result=newfloat[length];

inti=0;

for(VertexInfov:list){

result[i]=v.position.x;

result[i+1]=v.position.y;

result[i+2]=v.position.z;

i+=3;

}

returnresult;

}

publicstaticfloat[]toNormalArr(List<VertexInfo>list){

intlength=list!=null?list.size()*3:0;

float[]result=newfloat[length];

inti=0;

for(VertexInfov:list){

result[i]=v.normal.x;

result[i+1]=v.normal.y;

result[i+2]=v.normal.z;

i+=3;

}

returnresult;

}

}

Let’sgetbacktothegenerateMeshmethod,thefirstwedoisgetthemeshverticesinformation,theweightsandthestructureofthejoints.

Animations

230

Page 231: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatestaticMeshgenerateMesh(MD5Modelmd5Model,MD5Meshmd5Mesh,Vector4fdefaultC

olour)throwsException{

List<VertexInfo>vertexInfoList=newArrayList<>();

List<Float>textCoords=newArrayList<>();

List<Integer>indices=newArrayList<>();

List<MD5Mesh.MD5Vertex>vertices=md5Mesh.getVertices();

List<MD5Mesh.MD5Weight>weights=md5Mesh.getWeights();

List<MD5JointInfo.MD5JointData>joints=md5Model.getJointInfo().getJoints();

Thenweneedtocalculatetheverticespositionbasedontheinformationcontainedintheweightsandjoints.Thisisdoneinthefollowingblock

for(MD5Mesh.MD5Vertexvertex:vertices){

Vector3fvertexPos=newVector3f();

Vector2fvertexTextCoords=vertex.getTextCoords();

textCoords.add(vertexTextCoords.x);

textCoords.add(vertexTextCoords.y);

intstartWeight=vertex.getStartWeight();

intnumWeights=vertex.getWeightCount();

for(inti=startWeight;i<startWeight+numWeights;i++){

MD5Mesh.MD5Weightweight=weights.get(i);

MD5JointInfo.MD5JointDatajoint=joints.get(weight.getJointIndex());

Vector3frotatedPos=newVector3f(weight.getPosition()).rotate(joint.getO

rientation());

Vector3facumPos=newVector3f(joint.getPosition()).add(rotatedPos);

acumPos.mul(weight.getBias());

vertexPos.add(acumPos);

}

vertexInfoList.add(newVertexInfo(vertexPos));

}

Let’sexaminewhatwearedoinghere.Weiterateovertheverticesinformationandstorethetexturecoordinatesinalist,noneedtoapplyanytransformationhere.Thenwegetthestartingandtotalnumberofweightstoconsidertocalculatethevertexposition.

Thevertexpositioniscalculatedbyusingalltheweightsthatisrelatedto.Eachweightshasapositionandabias.Thesumofallbiasoftheweightsassociatedtoeachvertexmustbeequalto1.0.Eachweightalsohasapositionwhichisdefinedinjoint’slocalspace,soweneedtotransformittomodelspacecoordinatesusingthejoint’sorientationandposition(likeifitwereatransformationmatrix)towhichitrefersto.

Tosumup,thevertexpositioncanbeexpressedbythisformula:

Animations

231

Page 232: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

V pos = (Jt ×Wp ) b

Where:

Thesummationstartsfromws(Weightstart)uptowc(Weightcount)weights.

Jt isthejoint’stransformationmatrixassociatedtotheweightW .

Wp istheweightposition.

Wb istheweightbias.

Thisequationiswhatweimplementinthebodyoftheloop(wedonothavethetransformationmatrixsincewehavethejointpositionandrotationseparatelybuttheresultisthesame).

Withthecodeabovewewillbeabletoconstructthepositionsandtexturecoordinatesdatabutwestillneedtobuilduptheindicesandthenormals.Indicescanbecalculatedbyusingthetrianglesinformation,justbyiteratingthroughthelistthatholdsthosetriangles.

Normalscanbecalculatedalsousingtrianglesinformation.LetV ,V andV bethetrianglevertices(inobject’smodelspace).Thenormalforthetrianglecanbecalculateaccordingtothisformula:

N = (V − V ) × (V − V )

WhereNshouldbenormalizedafter.Thefollowingfigureshowsthegeometricinterpretationoftheformulaabove.

i=ws∑

ws+wc

i i W i

i i

i

i

0 1 2

2 0 1 0

Animations

232

Page 233: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Foreachvertexwecomputeitsnormalbythenormalizedsumofallthenormalsofthetrianglesitbelongsto.Thecodethatperformsthosecalculationsisshownbelow.

for(MD5Mesh.MD5Triangletri:md5Mesh.getTriangles()){

indices.add(tri.getVertex0());

indices.add(tri.getVertex1());

indices.add(tri.getVertex2());

//Normals

VertexInfov0=vertexInfoList.get(tri.getVertex0());

VertexInfov1=vertexInfoList.get(tri.getVertex1());

VertexInfov2=vertexInfoList.get(tri.getVertex2());

Vector3fpos0=v0.position;

Vector3fpos1=v1.position;

Vector3fpos2=v2.position;

Vector3fnormal=(newVector3f(pos2).sub(pos0)).cross(newVector3f(pos1).sub(

pos0));

v0.normal.add(normal);

v1.normal.add(normal);

v2.normal.add(normal);

}

//Oncethecontributionshavebeenadded,normalizetheresult

for(VertexInfov:vertexInfoList){

v.normal.normalize();

}

ThenwejustneedtotransformtheListstoarraysandprocessthetextureinformation.

float[]positionsArr=VertexInfo.toPositionsArr(vertexInfoList);

float[]textCoordsArr=Utils.listToArray(textCoords);

float[]normalsArr=VertexInfo.toNormalArr(vertexInfoList);

int[]indicesArr=indices.stream().mapToInt(i->i).toArray();

Meshmesh=newMesh(positionsArr,textCoordsArr,normalsArr,indicesArr);

returnmesh;

}

Goingbacktotheprocessmethodyoucanseethatthere'samethodnamedhandleTexture,whichisresponsibleforloadingtextures.Thisisthedefinitionofthatmethod:

Animations

233

Page 234: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatestaticvoidhandleTexture(Meshmesh,MD5Meshmd5Mesh,Vector4fdefaultColour)

throwsException{

StringtexturePath=md5Mesh.getTexture();

if(texturePath!=null&&texturePath.length()>0){

Texturetexture=newTexture(texturePath);

Materialmaterial=newMaterial(texture);

//HandlenormalMaps;

intpos=texturePath.lastIndexOf(".");

if(pos>0){

StringbasePath=texturePath.substring(0,pos);

Stringextension=texturePath.substring(pos,texturePath.length());

StringnormalMapFileName=basePath+NORMAL_FILE_SUFFIX+extension;

if(Utils.existsResourceFile(normalMapFileName)){

TexturenormalMap=newTexture(normalMapFileName);

material.setNormalMap(normalMap);

}

}

mesh.setMaterial(material);

}else{

mesh.setMaterial(newMaterial(defaultColour,1));

}

}

Theimplementationisverystraightforward.Theonlypeculiarityisthatifameshdefinesatexturenamed“texture.png”itsnormaltexturemapwillbedefinedinafile“texture_normal.png”.Weneedtocheckifthatfileexistsandloaditaccordingly.

WecannowloadaMD5fileandrenderitaswerenderotherGameItems,butbeforedoingthatweneedtodisablecullfaceinordertorenderitproperlysincenotallthetriangleswillbedrawninthecorrectdirection.WewilladdsupporttotheWindowclasstosettheseparametersatruntime(youcancheckitinthesourcecodethechanges).

Ifyouloadsomeofthesamplemodelsyouwillgetsomethinglikethis.

Animations

234

Page 235: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Whatyouseehereisthebindingpose,it’sthestaticrepresentationoftheMD5modelusedfortheanimatorstomodelthemeasily.Inordertogetanimationtoworkwemustprocesstheanimationdefinitionfile.

AnimatethemodelAMD5animationdefinitionfile,likethemodeldefinitionone,iscomposedbyaheaderandifferentsectionscontainedbetweenbraces.Ifyouopenoneofthosefilesyoucanseeastructuresimilarlikethis.

Animations

235

Page 236: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thefirststructurethatyoucanfindintheanimationfile,asinthecaseofthemeshdefinitionfile,istheheader.Youcanseebelowheader’scontentfromoneofthesamplesprovided:

Animations

236

Page 237: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

MD5Version10

commandline""

numFrames140

numJoints33

frameRate24

numAnimatedComponents198

Theheaderdefinesthefollowingattributes:

TheversionoftheMD5specificationthatitcompliesto.Thecommandusedtogeneratethisfile(froma3Dmodellingtool).Thenumberframesdefinedinthefile.Thenumberofjointsdefinedinthehierarchysection.Theframerate,framespersecond,thatwasusedwhilecreatingthisanimation.Thisparametercanbeusedtocalculatethetimebetweenframes.Thenumberofcomponentsthateachframedefines.

Thehierarchysectionistheonethatcomesfirstanddefinesthejointsforthisanimation.Youcanseeafragmentbelow:

hierarchy{

"origin"-100//

"body"0630//origin(TxTyTzQxQyQz)

"body2"100//body

"SPINNER"2566//body2(QxQyQz)

....

}

Ajoint.Inthehierarchysection,isdefinedbythefollowingattributes:

Jointname,atextualattributebetweenquotes.Jointparent,usinganindexwhichpointstotheparentjointusingitspositioninthejointslist.Therootjointhasaparentequalsto-1.Jointflags,whichsethowthisjoint'spositionandorientationwillbechangedaccordingtothedatadefinedineachanimationframe.Thestartindex,insidetheanimationdataofeachframethatisusedwhenapplyingtheflags.

Thenextsectionistheboundsone.Thissectiondefinesaboundingboxwhichcontainsthemodelforeachanimationframe.Itwillcontainalineforeachoftheanimationframesanditlooklikethis:

Animations

237

Page 238: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

bounds{

(-24.3102264404-44.2608566284-0.181215778)(31.086198806838.7131576538117.7

417449951)

(-24.3102283478-44.1887664795-0.1794649214)(31.180028915438.7173080444117.

7729110718)

(-24.3102359772-44.1144447327-0.1794776917)(31.204278945938.7091217041117.

8352737427)

....

}

Eachboundingboxisdefinedbytwo3componentvectorsinmodelspacecoordinates.Thefirstvectordefinestheminimumboundandthesecondonethemaximum.

Thenextsectionisthebaseframedata.Inthissection,thepositionandorientationofeachjointissetupbeforethedeformationsofeachanimationframeareapplied.Youcanseeafragmentbelow:

baseframe{

(000)(-0.5-0.5-0.5)

(-0.894733607870.7142486572-6.5027675629)(-0.3258574307-0.00830373540.0313

780755)

(0.00000014620.0539700091-0.0137935728)(000)

....

}

Eachlineisassociatedtoajointanddefinethefollowingattributes:

Positionofthejoint,asathreecomponentsvector.Orientationofthejoint,asthethreecomponentsofaquaternion(asinthemodelfile).

Afterthatyouwillfindseveralframedefinitions,asmanyasthevalueassignedtothenumFramesheaderattribute.Eachframesectionislikeahugearrayoffloatsthatwillbeusedbythejointswhenapplyingthetransformationsforeachframe.Youcanseeafragmentbelow.

frame1{

-0.927910089570.682762146-6.3709330559-0.3259022534-0.01005017380.0320306309

0.32590225340.0100501738-0.0320306309

-0.1038384438-0.1639953405-0.01525534880.0299418624

....

}

ThebaseclassthatparsesaMD5animationfileisnamedMD5AnimModel.Thisclasscreatesalltheobjectshierarchythatmapsthecontentsofthatfileandyoucancheckthesourcecodeforthedetails.ThestructureissimilartotheMD5modeldefinitionfile.Nowthatweare

Animations

238

Page 239: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

abletoloadthatinformationwewilluseittogenerateananimation.

Wewillgeneratetheanimationintheshader,soinsteadofpre-calculatingallthepositionsforeachframeweneedtopreparethedataweneedsointhevertexshaderwecancomputethefinalpositions.Let’sgetbacktotheprocessmethodintheMD5Loaderclass,weneedtomodifyittotakeintoconsiderationtheanimationinformation.Thenewdefinitionforthatmethodisshownbelow:

publicstaticAnimGameItemprocess(MD5Modelmd5Model,MD5AnimModelanimModel,Vector4f

defaultColour)throwsException{

List<Matrix4f>invJointMatrices=calcInJointMatrices(md5Model);

List<AnimatedFrame>animatedFrames=processAnimationFrames(md5Model,animModel,i

nvJointMatrices);

List<Mesh>list=newArrayList<>();

for(MD5Meshmd5Mesh:md5Model.getMeshes()){

Meshmesh=generateMesh(md5Model,md5Mesh);

handleTexture(mesh,md5Mesh,defaultColour);

list.add(mesh);

}

Mesh[]meshes=newMesh[list.size()];

meshes=list.toArray(meshes);

AnimGameItemresult=newAnimGameItem(meshes,animatedFrames,invJointMatrices);

returnresult;

}

Therearesomechangeshere,themostobviousisthatthemethodnowreceivesaMD5AnimModelinstance.ThenextoneisthatwedonotreturnaGameIteminstancebutandAnimGameItemone.ThisclassinheritsfromtheGameItemclassbutaddssupportforanimations.Wewillseewhythiswasdonethiswaylater.

Ifwecontinuewiththeprocessmethod,thefirstthingwedoiscallthecalcInJointMatricesmethod,whichisdefinedlikethis:

Animations

239

Page 240: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatestaticList<Matrix4f>calcInJointMatrices(MD5Modelmd5Model){

List<Matrix4f>result=newArrayList<>();

List<MD5JointInfo.MD5JointData>joints=md5Model.getJointInfo().getJoints();

for(MD5JointInfo.MD5JointDatajoint:joints){

Matrix4ftranslateMat=newMatrix4f().translate(joint.getPosition());

Matrix4frotationMat=newMatrix4f().rotate(joint.getOrientation());

Matrix4fmat=translateMat.mul(rotationMat);

mat.invert();

result.add(mat);

}

returnresult;

}

ThismethoditeratesoverthejointscontainedintheMD5modeldefinitionfile,calculatesthetransformationmatrixassociatedtoeachjointandthenitgetstheinverseofthosematrices.ThisinformationisusedtoconstructtheAnimationGameIteminstance.

Let’scontinuewiththeprocessmethod,thenextthingwedoisprocesstheanimationframesbycallingtheprocessAnimationFramesmethod:

privatestaticList<AnimatedFrame>processAnimationFrames(MD5Modelmd5Model,MD5AnimMo

delanimModel,List<Matrix4f>invJointMatrices){

List<AnimatedFrame>animatedFrames=newArrayList<>();

List<MD5Frame>frames=animModel.getFrames();

for(MD5Frameframe:frames){

AnimatedFramedata=processAnimationFrame(md5Model,animModel,frame,invJoin

tMatrices);

animatedFrames.add(data);

}

returnanimatedFrames;

}

Thismethodprocesseachanimationframe,definedintheMD5animationdefinitionfile,andreturnsalistofAnimatedFrameinstances.TherealworkisdoneintheprocessAnimationFramemethod.Let’sexplainwhatthismethodwilldo.

Wefirst,iterateoverthejointsdefinedinthehierarchysectionintheMD5animatonfile.

Animations

240

Page 241: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatestaticAnimatedFrameprocessAnimationFrame(MD5Modelmd5Model,MD5AnimModelani

mModel,MD5Frameframe,List<Matrix4f>invJointMatrices){

AnimatedFrameresult=newAnimatedFrame();

MD5BaseFramebaseFrame=animModel.getBaseFrame();

List<MD5Hierarchy.MD5HierarchyData>hierarchyList=animModel.getHierarchy().getHi

erarchyDataList();

List<MD5JointInfo.MD5JointData>joints=md5Model.getJointInfo().getJoints();

intnumJoints=joints.size();

float[]frameData=frame.getFrameData();

for(inti=0;i<numJoints;i++){

MD5JointInfo.MD5JointDatajoint=joints.get(i);

Wegetthepositionandorientationofthebaseframeelementassociatedtoeachjoint.

MD5BaseFrame.MD5BaseFrameDatabaseFrameData=baseFrame.getFrameDataList().get

(i);

Vector3fposition=baseFrameData.getPosition();

Quaternionforientation=baseFrameData.getOrientation();

Inprinciple,thatinformationshouldbeassignedtothethejoint’spositionandorientation,butitneedstobetransformedaccordingtothejoint’sflag.Ifyourecall,whenthestructureoftheanimationfilewaspresented,eachjointinthehierarchysectiondefinesaflag.Thatflagmodelshowthepositionandorientationinformationshouldbechangedaccordingtotheinformationdefinedineachanimationframe.

Ifthefirstbitofthatflagfieldisequalto1,weshouldchangethexcomponentofthebaseframepositionwiththedatacontainedintheanimationframeweareprocessing.Thatanimationfarmedefinesabugafloatarray,sowhichIelementsshouldwetake.TheanswerisalsointhejointsdefinitionwhichincludesastartIndexattribute.Ifthesecondbitofthegalisequalto1,weshouldchangetheycomponentofthebaseframepositionwiththevalueatstartIndex+1,andsoon.Thenextbitsareforthezposition,andthex,yandzcomponentsoftheorientation.

Animations

241

Page 242: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

intflags=hierarchyList.get(i).getFlags();

intstartIndex=hierarchyList.get(i).getStartIndex();

if((flags&1)>0){

position.x=frameData[startIndex++];

}

if((flags&2)>0){

position.y=frameData[startIndex++];

}

if((flags&4)>0){

position.z=frameData[startIndex++];

}

if((flags&8)>0){

orientation.x=frameData[startIndex++];

}

if((flags&16)>0){

orientation.y=frameData[startIndex++];

}

if((flags&32)>0){

orientation.z=frameData[startIndex++];

}

//UpdateQuaternion'swcomponent

orientation=MD5Utils.calculateQuaternion(orientation.x,orientation.y,orien

tation.z);

Nowwehaveallinformationneededtocalculatethetransformationmatricestogetthefinalpositionforeachjointforthecurrentanimationframe.Butthere’sanotherthingthatwemustconsider,thepositionofeachjointisrelativetoitsparentposition,soweneedtogetthetransformationmatrixassociatedtoeachparentanduseitinordertogetatransformationmatrixthatisinmodelspacecoordinates.

Animations

242

Page 243: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

//Calculatetranslationandrotationmatricesforthisjoint

Matrix4ftranslateMat=newMatrix4f().translate(position);

Matrix4frotationMat=newMatrix4f().rotate(orientation);

Matrix4fjointMat=translateMat.mul(rotationMat);

//Jointpositionisrelativetojoint'sparentindexposition.Useparentmat

rices

//totransformittomodelspace

if(joint.getParentIndex()>-1){

Matrix4fparentMatrix=result.getLocalJointMatrices()[joint.getParentInde

x()];

jointMat=newMatrix4f(parentMatrix).mul(jointMat);

}

result.setMatrix(i,jointMat,invJointMatrices.get(i));

}

returnresult;

}

YoucanseethatwecreateaninstanceoftheAnimatedFrameclassthatholdstheinformationthatwillbeuseduringanimation.Thisclassalsousestheinversematrices,wewillseelateronwhythisdonethisway.AnimportantthingtonoteisthatthesetMatrixmethodoftheAnimatedFrameisdefinedlikethis.

publicvoidsetMatrix(intpos,Matrix4flocalJointMatrix,Matrix4finvJointMatrix){

localJointMatrices[pos]=localJointMatrix;

Matrix4fmat=newMatrix4f(localJointMatrix);

mat.mul(invJointMatrix);

jointMatrices[pos]=mat;

}

ThevariablelocalJointMatrixstoresthetransformationmatrixforthejointthatoccupiestheposition“i”forthecurrentframe.TheinvJointMatrixholdstheinversetransformationmatrixforthejointthatoccupiestheposition“i”forthebindingpose.WestoretheresultofmultiplyingthelocalJointMatrixbytheinvJointMatrix.Thisresultwillbeusedlatertocomputethefinalpositions.Westorealsotheoriginaljointtransformationmatrix,thevariablelocalJointMatrix,sowecanuseittocalculatethisjointchildstransformationmatrices.

Let'sgetbacktotheMD5Loaderclass.ThegenerateMeshmethodalsohaschanged,wecalculatethepositionsofthebindingposeasithasbeenexplainedbefore,butforeachvertexwestoretwoarrays:

Anarraythatholdstheweightbiasassociatedtothisvertex.Anarraythatholdthejointindicesassociatedtothisvertex(throughtheweights).

Animations

243

Page 244: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Welimitthesizeofthosearraystoavalueof4.TheMeshclasshasalsobeenmodifiedtoreceivethoseparametersandincludeitintheVAOinformationprocessedbytheshaders.Youcancheckthedetailsinthesourcecode,butSolet’srecapwhatwehavedone:

Wearestillloadingthebindingposewiththeirfinalpositionscalculatedasthesumofthejointspositionsandorientationsthroughtheweightsinformation.ThatinformationisloadedintheshadersasVBOsbutit’scomplementedbythebiasoftheweightsassociatedtoeachvertexandtheindicesofthejointsthataffectit.Thisinformationiscommontoalltheanimationframes,sinceit’sdefinedintheMD5definitionfile.Thisisthereasonwhywelimitthesizeofthebiasandjointindicesarrays,theywillbeloadedasVBOsoncewhenthemodelissenttotheGPU.Foreachanimationframewestorethetransformationmatricestobeappliedtoeachjointaccordingtothepositionsandorientationsdefinedinthebaseframe.Wealsohavecalculatedtheinversematricesofthetransformationmatricesassociatedtothejointsthatdefinethebindingpose.Thatis,weknowhowtoundothetransformationsdoneinthebindingpose.Wewillseehowthiswillbeappliedlater.

Nowthatwehaveallthepiecestosolvethepuzzlewejustneedtousethemintheshader.Wefirstneedtomodifytheinputdatatoreceivetheweightsandthejointindices.

#version330

constintMAX_WEIGHTS=4;

constintMAX_JOINTS=150;

layout(location=0)invec3position;

layout(location=1)invec2texCoord;

layout(location=2)invec3vertexNormal;

layout(location=3)invec4jointWeights;

layout(location=4)inivec4jointIndices;

Wehavedefinedtwoconstants:

MAX_WEIGHTS,definesthemaximumnumberofweightsthatcomeintheweightsVBO

Animations

244

Page 245: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

(ansolothejointindices)MAX_JOINTS,definesthemaximumnumberofjointswearegoingtosupport(moreonthislater).

Thenwedefinetheoutputdataandtheuniforms.

outvec2outTexCoord;

outvec3mvVertexNormal;

outvec3mvVertexPos;

outvec4mlightviewVertexPos;

outmat4outModelViewMatrix;

uniformmat4jointsMatrix[MAX_JOINTS];

uniformmat4modelViewMatrix;

uniformmat4projectionMatrix;

uniformmat4modelLightViewMatrix;

uniformmat4orthoProjectionMatrix;

YoucanseethatwehaveanewuniformnamedjointsMatrixwhichisanarrayofmatrices(withamaximumlengthsetbytheMAX_JOINTSconstant).Thatarrayofmatricesholdsthejointmatricescalculatedforallthejointsinthepresentframe,andwascalculatedintheMD5Loaderclasswhenprocessingaframe.Thus,thatarrayholdsthetransformationsthatneedtobeappliedtoajointinthepresentanimationframeandwillserveasthebasisforcalculatingthevertexfinalposition.

WiththenewdataintheVBOsandthisuniformwewilltransformthebindingposeposition.Thisisdoneinthefollowingblock.

vec4initPos=vec4(0,0,0,0);

intcount=0;

for(inti=0;i<MAX_WEIGHTS;i++)

{

floatweight=jointWeights[i];

if(weight>0){

count++;

intjointIndex=jointIndices[i];

vec4tmpPos=jointsMatrix[jointIndex]*vec4(position,1.0);

initPos+=weight*tmpPos;

}

}

if(count==0)

{

initPos=vec4(position,1.0);

}

Animations

245

Page 246: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Firstofall,wegetthebindingposeposition,weiterateovertheweightsassociatedtothisvertexandmodifythepositionusingtheweightsandthejointmatricesforthisframe(storedinthejointsMatrixuniform)byusingtheindexthatisstoredintheinput.

So,givenavertexposition,wearecalculatingit’sframepositionas

V fp = Wb Jfp × Jt ) × V pos

Where:

WfvpisthevertexfinalpositionWbisthevertexweightJfpisthejointmatrixtransformationmatrixforthisframe

Jt istheinverseofthejointtransformationmatrixforthebindingpose.ThemultiplicationofthismatrixandJfpiswhat'scontainedinthejointsMatrixuniform.V posisthevertexpositioninthebindingposition.

V posiscalcualtedbyusintheJtmatrix,whichisthematrixofthejointtransformationmatrixforthebindingpose.So,attheendwearesomehowundoingthemodificicationsofthebindingposetoapplythetransformationsforthisframe.Thisisthereasonwhyweneedtheinversebindingposematrix.

Theshadersupportsverticeswithvariablenumberofweights,uptoamaximumof4,andalsosupportstherenderingofnonanimateditems.Inthiscase,theweightswillbeequalto0andwewillgettheoriginalposition.

i=0∑

MAXWEIGTHS

i ( i i−1

−1

Animations

246

Page 247: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Therestoftheshaderstaysmoreorlessthesame,wejustusetheupdatedpositiona

ndpassthecorrectvaluestobeusedbythefragmentshader.

vec4mvPos=modelViewMatrix*initPos;

gl_Position=projectionMatrix*mvPos;

outTexCoord=texCoord;

mvVertexNormal=normalize(modelViewMatrix*vec4(vertexNormal,0.0)).xyz;

mvVertexPos=mvPos.xyz;

mlightviewVertexPos=orthoProjectionMatrix*modelLightViewMatrix*vec4(position

,1.0);

outModelViewMatrix=modelViewMatrix;

}

So,inordertotesttheanimationwejustneedtopassthejointsMatrixtotheshader.SincethisinformationisstoredonlyininstancesoftheAnimGameItemclass,thecodeisverysimple.IntheloopthatrenderstheMeshes,weaddthisfragment.

if(gameIteminstanceofAnimGameItem){

AnimGameItemanimGameItem=(AnimGameItem)gameItem;

AnimatedFrameframe=animGameItem.getCurrentFrame();

sceneShaderProgram.setUniform("jointsMatrix",frame.getJointMatrices());

}

Ofcourse,yowillneedtocreatetheuniformbeforeusingit,youcancheckthesourcecodeforthat.Ifyouruntheexampleyouwillbeabletoseehowthemodelanimatesbypressingthespacebar(eachtimethekeyispressedanewframeissetandthejointsMatrixuniformchanges).

Youwillseesomethinglikethis.

Animations

247

Page 248: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Althoughtheanimationissmooth,thesamplepresentssomeproblems.Firstofall,lightisnotcorrectlyappliedandtheshadowrepresentsthebindingposebutnotthecurrentframe.Wewillsolvealltheseproblemsnow.

CorrectinganimationissuesThefirstissuethatwilladdressisthelightningproblem.Youmayhavealreadynoticedthecase,itsduetothefactthatwearenottransformingnormals.Thus,thenormalsthatareusedinthefragmentshaderaretheonesthatcorrespondtothebindingpose.Weneedtotransformtheminthesamewayasthepositions.

Thisissueiseasytosolve,wejustneedtoincludethenormalsintheloopthatiteratesovertheweightsinthevertexshader.

Animations

248

Page 249: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

vec4initPos=vec4(0,0,0,0);

vec4initNormal=vec4(0,0,0,0);

intcount=0;

for(inti=0;i<MAX_WEIGHTS;i++)

{

floatweight=jointWeights[i];

if(weight>0){

count++;

intjointIndex=jointIndices[i];

vec4tmpPos=jointsMatrix[jointIndex]*vec4(position,1.0);

initPos+=weight*tmpPos;

vec4tmpNormal=jointsMatrix[jointIndex]*vec4(vertexNormal,0.0);

initNormal+=weight*tmpNormal;

}

}

if(count==0)

{

initPos=vec4(position,1.0);

initNormal=vec4(vertexNormal,0.0);

}

Thenwejustcalculatetheoutputvertexnormalasalways:

mvVertexNormal=normalize(modelViewMatrix*initNormal).xyz;

Thenextissueistheshadowproblem.Ifyourecallfromtheshadowschapter,weareusingshadowmapstodrawshadows.Wearerenderingthescenefromthelightperspectiveinordertocreateadepthmapthattellsusifapointisinshadowornot.But,asinthecaseofthenormals,wearejustpassingthebindingposecoordinatesandnotchangingthemaccordingtothecurrentframe.Thisisthereasonwhytheshadowdoesnotcorrespondstothecurrentposition.

Thesolutioniseasy,wejustneedtomodifythedepthvertexshadertousethejointsMatrixandtheweightsandjointindicestotransformtheposition.Thisishowthedepthvertexshaderlookslike.

Animations

249

Page 250: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

#version330

constintMAX_WEIGHTS=4;

constintMAX_JOINTS=150;

layout(location=0)invec3position;

layout(location=1)invec2texCoord;

layout(location=2)invec3vertexNormal;

layout(location=3)invec4jointWeights;

layout(location=4)inivec4jointIndices;

uniformmat4jointsMatrix[MAX_JOINTS];

uniformmat4modelLightViewMatrix;

uniformmat4orthoProjectionMatrix;

voidmain()

{

vec4initPos=vec4(0,0,0,0);

intcount=0;

for(inti=0;i<MAX_WEIGHTS;i++)

{

floatweight=jointWeights[i];

if(weight>0){

count++;

intjointIndex=jointIndices[i];

vec4tmpPos=jointsMatrix[jointIndex]*vec4(position,1.0);

initPos+=weight*tmpPos;

}

}

if(count==0)

{

initPos=vec4(position,1.0);

}

gl_Position=orthoProjectionMatrix*modelLightViewMatrix*initPos;

}

YouneedtomodifytheRendererclasstosetupthenewuniformsforthisshader,andthefinalresultwillbemuchbetter.Thelightwillbeappliedcorrectlyandtheshadowwillchangeforeachanimationframeasshowninthenextfigure.

Animations

250

Page 251: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Andthat'sall,youhavenowaworkingexamplethatanimatesMD5models.Thesourcecodecanstillbeimprovedandyoucanmodifythematricesthatareloadedineachrendercycletointerpolatebetweeenframespositions.Youcancheckthesourcesusedforthischaptertoseehowthiscanbedone.

Animations

251

Page 252: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Particles

ThebasicsInthischapterwewilladdparticleeffectstothegameengine.Withthiseffectwewillbeabletosimulaterays,fire,dustandclouds.It’sasimpleeffecttoimplementthatwillimprovethegraphicalaspectofanygame.

Beforewestartit'sworthtomentionthattherearemanywaystoimplementparticleeffectswithdifferent,results.Inthiscasewewillusebillboardparticles.Thistechniqueusesmovingtexturequadstorepresentaparticlewiththepeculiaritythattheyarealwaysalwaysfacingtheobserver,inourcase,thecamera.YoucanalsousebillboardingtechniquetoshowinformationpanelsovergameitemslikeaminiHUDs.

Let’sstartbydefiningwhatisaparticle.Aparticlecandedefinedbythefollowingattributes:

1. Ameshthatrepresentsthequadvertices.2. Atexture.3. Apositionatagiveninstant.4. Ascalefactor.5. Speed.6. Amovementdirection.7. Alifetimeortimetolive.Oncethistimehasexpiredtheparticleceasestoexist.

ThefirstfouritemsarepartoftheGameItemclass,butthelastthreearenot.Thus,wewillcreateanewclassnamedParticlethatextendsaGameIteminstanceandthatisdefinedlikethis.

packageorg.lwjglb.engine.graph.particles;

importorg.joml.Vector3f;

importorg.lwjglb.engine.graph.Mesh;

importorg.lwjglb.engine.items.GameItem;

publicclassParticleextendsGameItem{

privateVector3fspeed;

/**

*Timetoliveforparticleinmilliseconds.

*/

privatelongttl;

Particles

252

Page 253: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicParticle(Meshmesh,Vector3fspeed,longttl){

super(mesh);

this.speed=newVector3f(speed);

this.ttl=ttl;

}

publicParticle(ParticlebaseParticle){

super(baseParticle.getMesh());

Vector3faux=baseParticle.getPosition();

setPosition(aux.x,aux.y,aux.z);

aux=baseParticle.getRotation();

setRotation(aux.x,aux.y,aux.z);

setScale(baseParticle.getScale());

this.speed=newVector3f(baseParticle.speed);

this.ttl=baseParticle.geTtl();

}

publicVector3fgetSpeed(){

returnspeed;

}

publicvoidsetSpeed(Vector3fspeed){

this.speed=speed;

}

publiclonggeTtl(){

returnttl;

}

publicvoidsetTtl(longttl){

this.ttl=ttl;

}

/**

*UpdatestheParticle'sTTL

*@paramelapsedTimeElapsedTimeinmilliseconds

*@returnTheParticle'sTTL

*/

publiclongupdateTtl(longelapsedTime){

this.ttl-=elapsedTime;

returnthis.ttl;

}

}

Asyoucanseefromthecodeabove,particle'sspeedandmovementdirectioncanbeexpressedasasinglevector.Thedirectionofthatvectormodelsthemovementdirectionanditsmodulethespeed.TheParticleTimeToLive(TTL)ismodelledasmillisecondscounterthatwillbedecreasedwheneverthegamestateisupdated.Theclasshasalsoacopyconstructor,thatis,aconstructorthattakesaninstanceofanotherParticletomakeacopy.

Particles

253

Page 254: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Now,weneedtocreateaparticlegeneratororparticleemitter,thatis,aclassthatgeneratestheparticlesdynamically,controlstheirlifecycleandupdatestheirpositionaccordingtoaspecificmodel.Wecancreatemanyimplementationsthatvaryinhowparticlesandcreatedandhowtheirpositionsareupdated(forinstance,takingintoconsiderationthegravityornot).So,inordertokeepourgameenginegeneric,wewillcreateaninterfacethatalltheParticleemittersmustimplement.Thisinterface,namedIParticleEmitter,isdefinedlikethis:

packageorg.lwjglb.engine.graph.particles;

importjava.util.List;

importorg.lwjglb.engine.items.GameItem;

publicinterfaceIParticleEmitter{

voidcleanup();

ParticlegetBaseParticle();

List<GameItem>getParticles();

}

TheIParticleEmitterinterfacehasamethodtocleanupresources,namedcleanup,andamethodtogetthelistofParticles,namedgetParticles.ItalsoasamethodnamedgetBaseParticle,butWhat’sthismethodfor?Aparticleemitterwillcreatemanyparticlesdynamically.Wheneveraparticleexpires,newoneswillbecreated.Thatparticlerenewalcyclewilluseabaseparticle,likeapattern,tocreatenewinstances.Thisiswhatthisbaseparticleisusedfor,ThisisalsothereasonwhytheParticleclassdefinesacopyconstructor.

InthegameenginecodewewillreferonlytotheIParticleEmitterinterfacesothebasecodewillnotbedependentonthespecificimplementations.Neverthelesswecancreateaimplementationthatsimulatesaflowofparticlesthatarenotaffectedbygravity.ThisimplementationcanbeusedtosimulateraysorfireandisnamedFlowParticleEmitter.

Thebehaviourofthisclasscanbetunedwiththefollowingattributes:

Amaximumnumberofparticlesthatcanbealiveatatime.Aminimumperiodtocreateparticles.Particleswillbecreatedonebyonewithaminimumperiodtoavoidcreatingparticlesinbursts.Asetofrangestorandomizeparticlesspeedandstartingposition.Newparticleswillusebaseparticlepositionandspeedwhichcanberandomizedwithvaluesbetweenthoserangestospreadthebeam.

Theimplementationofthisclassisasfollows:

Particles

254

Page 255: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

packageorg.lwjglb.engine.graph.particles;

importjava.util.ArrayList;

importjava.util.Iterator;

importjava.util.List;

importorg.joml.Vector3f;

importorg.lwjglb.engine.items.GameItem;

publicclassFlowParticleEmitterimplementsIParticleEmitter{

privateintmaxParticles;

privatebooleanactive;

privatefinalList<GameItem>particles;

privatefinalParticlebaseParticle;

privatelongcreationPeriodMillis;

privatelonglastCreationTime;

privatefloatspeedRndRange;

privatefloatpositionRndRange;

privatefloatscaleRndRange;

publicFlowParticleEmitter(ParticlebaseParticle,intmaxParticles,longcreationP

eriodMillis){

particles=newArrayList<>();

this.baseParticle=baseParticle;

this.maxParticles=maxParticles;

this.active=false;

this.lastCreationTime=0;

this.creationPeriodMillis=creationPeriodMillis;

}

@Override

publicParticlegetBaseParticle(){

returnbaseParticle;

}

publiclonggetCreationPeriodMillis(){

returncreationPeriodMillis;

}

publicintgetMaxParticles(){

returnmaxParticles;

}

@Override

Particles

255

Page 256: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicList<GameItem>getParticles(){

returnparticles;

}

publicfloatgetPositionRndRange(){

returnpositionRndRange;

}

publicfloatgetScaleRndRange(){

returnscaleRndRange;

}

publicfloatgetSpeedRndRange(){

returnspeedRndRange;

}

publicvoidsetCreationPeriodMillis(longcreationPeriodMillis){

this.creationPeriodMillis=creationPeriodMillis;

}

publicvoidsetMaxParticles(intmaxParticles){

this.maxParticles=maxParticles;

}

publicvoidsetPositionRndRange(floatpositionRndRange){

this.positionRndRange=positionRndRange;

}

publicvoidsetScaleRndRange(floatscaleRndRange){

this.scaleRndRange=scaleRndRange;

}

publicbooleanisActive(){

returnactive;

}

publicvoidsetActive(booleanactive){

this.active=active;

}

publicvoidsetSpeedRndRange(floatspeedRndRange){

this.speedRndRange=speedRndRange;

}

publicvoidupdate(longellapsedTime){

longnow=System.currentTimeMillis();

if(lastCreationTime==0){

lastCreationTime=now;

}

Iterator<?extendsGameItem>it=particles.iterator();

while(it.hasNext()){

Particleparticle=(Particle)it.next();

if(particle.updateTtl(ellapsedTime)<0){

Particles

256

Page 257: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

it.remove();

}else{

updatePosition(particle,ellapsedTime);

}

}

intlength=this.getParticles().size();

if(now-lastCreationTime>=this.creationPeriodMillis&&length<maxParticl

es){

createParticle();

this.lastCreationTime=now;

}

}

privatevoidcreateParticle(){

Particleparticle=newParticle(this.getBaseParticle());

//Addalittlebitofrandomnessoftheparrticle

floatsign=Math.random()>0.5d?-1.0f:1.0f;

floatspeedInc=sign*(float)Math.random()*this.speedRndRange;

floatposInc=sign*(float)Math.random()*this.positionRndRange;

floatscaleInc=sign*(float)Math.random()*this.scaleRndRange;

particle.getPosition().add(posInc,posInc,posInc);

particle.getSpeed().add(speedInc,speedInc,speedInc);

particle.setScale(particle.getScale()+scaleInc);

particles.add(particle);

}

/**

*Updatesaparticleposition

*@paramparticleTheparticletoupdate

*@paramelapsedTimeElapsedtimeinmilliseconds

*/

publicvoidupdatePosition(Particleparticle,longelapsedTime){

Vector3fspeed=particle.getSpeed();

floatdelta=elapsedTime/1000.0f;

floatdx=speed.x*delta;

floatdy=speed.y*delta;

floatdz=speed.z*delta;

Vector3fpos=particle.getPosition();

particle.setPosition(pos.x+dx,pos.y+dy,pos.z+dz);

}

@Override

publicvoidcleanup(){

for(GameItemparticle:getParticles()){

particle.cleanup();

}

}

}

Nowwecanextendtheinformationthat’scontainedintheSceneclasstoincludeanarrayofParticleEmitterinstances.

Particles

257

Page 258: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

packageorg.lwjglb.engine;

//Importshere

publicclassScene{

//Moreattributeshere

privateIParticleEmitter[]particleEmitters;

Atthisstagewecanstartrenderingtheparticles.Particleswillnotbeaffectedbylightsandwillnotcastanyshadow.Theywillnothaveanyskeletalanimation,soitmakessensetohavespecificshaderstorenderthem.Theshaderswillbeverysimple,theywilljustrendertheverticesusingtheprojectionandmodelviewmatricesanduseatexturetosetthecolours.

Thevertexshaderisdefinedlikethis.

#version330

layout(location=0)invec3position;

layout(location=1)invec2texCoord;

layout(location=2)invec3vertexNormal;

outvec2outTexCoord;

uniformmat4modelViewMatrix;

uniformmat4projectionMatrix;

voidmain()

{

gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);

outTexCoord=texCoord;

}

Thefragmentshaderisdefinedlikethis:

Particles

258

Page 259: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

#version330

invec2outTexCoord;

invec3mvPos;

outvec4fragColor;

uniformsampler2Dtexture_sampler;

voidmain()

{

fragColor=texture(texture_sampler,outTexCoord);

}

Asyoucanseetheyareverysimple,theyresemblethepairofshadersusedinthefirstchapters.Now,asinotherchapters,weneedtosetupandusethoseshadersintheRendererclass.TheshaderssetupwillbedoneinamethodnamedsetupParticlesShaderwhichisdefinedlikethis:

privatevoidsetupParticlesShader()throwsException{

particlesShaderProgram=newShaderProgram();

particlesShaderProgram.createVertexShader(Utils.loadResource("/shaders/particles_v

ertex.vs"));

particlesShaderProgram.createFragmentShader(Utils.loadResource("/shaders/particles

_fragment.fs"));

particlesShaderProgram.link();

particlesShaderProgram.createUniform("projectionMatrix");

particlesShaderProgram.createUniform("modelViewMatrix");

particlesShaderProgram.createUniform("texture_sampler");

}

AndnowwecancreatetherendermethodnamedrenderParticlesintheRendererclasswhichisdefinedlikethis:

Particles

259

Page 260: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatevoidrenderParticles(Windowwindow,Cameracamera,Scenescene){

particlesShaderProgram.bind();

particlesShaderProgram.setUniform("texture_sampler",0);

Matrix4fprojectionMatrix=transformation.getProjectionMatrix();

particlesShaderProgram.setUniform("projectionMatrix",projectionMatrix);

Matrix4fviewMatrix=transformation.getViewMatrix();

IParticleEmitter[]emitters=scene.getParticleEmitters();

intnumEmitters=emitters!=null?emitters.length:0;

for(inti=0;i<numEmitters;i++){

IParticleEmitteremitter=emitters[i];

Meshmesh=emitter.getBaseParticle().getMesh();

mesh.renderList((emitter.getParticles()),(GameItemgameItem)->{

Matrix4fmodelViewMatrix=transformation.buildModelViewMatrix(gameItem,v

iewMatrix);

particlesShaderProgram.setUniform("modelViewMatrix",modelViewMatrix);

}

);

}

particlesShaderProgram.unbind();

}

Thefragmentaboveshouldbeselfexplanatoryifyoumanagedtogettothispoint,itjustrenderseachparticlesettinguptherequireduniforms.Wehavenowcreatedallthemethodsweneedtotesttheimplementationoftheparticleeffect.WejustneedtomodifytheDummyGameclasswecansetupaparticleemitterandthecharacteristicsofthebaseparticle.

Vector3fparticleSpeed=newVector3f(0,1,0);

particleSpeed.mul(2.5f);

longttl=4000;

intmaxParticles=200;

longcreationPeriodMillis=300;

floatrange=0.2f;

floatscale=0.5f;

MeshpartMesh=OBJLoader.loadMesh("/models/particle.obj");

Texturetexture=newTexture("/textures/particle_tmp.png");

MaterialpartMaterial=newMaterial(texture,reflectance);

partMesh.setMaterial(partMaterial);

Particleparticle=newParticle(partMesh,particleSpeed,ttl);

particle.setScale(scale);

particleEmitter=newFlowParticleEmitter(particle,maxParticles,creationPeriodMillis

);

particleEmitter.setActive(true);

particleEmitter.setPositionRndRange(range);

particleEmitter.setSpeedRndRange(range);

this.scene.setParticleEmitters(newFlowParticleEmitter[]{particleEmitter});

Particles

260

Page 261: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Weareusingaplainfilledcircleastheparticle’stexturebynow,tobetterunderstandwhat’shappening.Ifyouexecuteityouwillseesomethinglikethis.

Whysomeparticlesseemtobecutoff?Whythetransparentbackgrounddoesnotsolvethis?Thereasonforthatisdepthtesting.Somefragmentsoftheparticlesgetdiscardedbecausetheyhaveadepthbuffervaluehigherthanthecurrentvalueofthedepthbufferforthatzone.Wecansolvethisbyorderingtheparticledrawingsdependingintheirdistancetothecameraorwecanjustdisablethedepthwriting.

Beforewedrawtheparticleswejustneedtoinsertthisline:

glDepthMask(false);

Andwhenwearedonewithrenderingwerestorethepreviousvalue:

glDepthMask(true);

Thenwewillgetsomethinglikethis.

Particles

261

Page 262: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Ok,problemsolved.Nevertheless,westillwantanothereffecttobeapplied,wewouldwantthatcoloursgetblendedsocolourswillbeaddedtocreatebettereffects.Thisisachievedwithbyaddingthislinebeforerenderingtosetupadditiveblending.

glBlendFunc(GL_SRC_ALPHA,GL_ONE);

Asinthedepthcase,afterwehaverenderedalltheparticleswerestoretheblendingfunctionto:

glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

Nowwegetsomethinglikethis.

Particles

262

Page 263: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Butwehavenotfinishedyet.Ifyouhavemovedthecameraoverthebluesquarelookingdownyoumayhavegotsomethinglikethis.

Theparticlesdonotlookverygood,theyshouldlookroundbuttheyresembleasheetofpaper.Atthispointsiswhereweshouldbeapplyingthebillboardtechnique.Thequadthatisusedtorendertheparticleshouldalwaysbefacingthecamera,totallyperpendiculartoit

Particles

263

Page 264: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

asifittherewasnorotationatall.Thecameramatrixappliestranslationandrotationtoeveryobjectinthescene,wewanttoskiptherotationtobeapplied.

Warning:Mathsahead,youcanskipitifyoudon'tfeelcomfortablewiththis.Let’sreviewthatviewmatrixonceagain.Thatmatrixcanberepresentedlikethis(withoutanyscaleappliedtoit).

Theredelementsrepresentthecamerarotationwhiletheblueonesrepresentthetranslation.Weneedtocanceltheeffectoftheupperleft3x3matrixcontainedintheviewmatrixsoitgetstosomethinglikethis.

So,wehavea3x3matrix,theupperleftredfragment,let'snameitM andwewantittotransformittotheidentifymatrix:I .Anymatrixmultipliedbyitsinversewillgivetheidentify

matrix:M ×M = I .Sowejustneedtogettheupperleft3x3matrixfromtheviewmatrix,andmultiplyitbyitsinverse,butwecanevenoptimizethis.Arotationmatrixhasaninterestingcharacteristic,itsinversecoincideswithitstransposematrix.Thatis:

M ×M =M ×M = I.Andatransposematrixismuchmoreeasiertocalculatethantheinverse.Thetransposeofamatrixislikeifweflipit,wechangerowspercolumns.

=

Ok,let'ssummarize.Wehavethistransformation:V ×M ,whereV istheviewmatrixandM isthemodelmatrix.Wecanexpressthatexpressionlikethis:

×

Wewanttocanceltherotationoftheviewmatrix,togetsomethinglikethis:

⎣⎢⎢⎡r00r01r020

r10r11r120

r20r21r220

dx

dy

dz

1 ⎦⎥⎥⎤

⎣⎢⎢⎡1000

0100

0010

dx

dy

dz

1 ⎦⎥⎥⎤

r

r r−1

r r−1

r rT

⎣⎡r00r01r02

r10r11r12

r20r21r22⎦⎤T

⎣⎡r00r10r20

r01r11r21

r02r12r22⎦⎤

⎣⎢⎢⎡v00v01v02v03

v10v11v12v13

v20v21v22v23

v30v31v32v33⎦⎥⎥⎤

⎣⎢⎢⎡m00m01m02m03

m10m11m12m13

m20m21m22m23

m30m31m32m33⎦

⎥⎥⎤

⎣⎢⎢⎡100

mv03

010

mv13

001

mv23

mv30mv31mv32mv33⎦

⎥⎥⎤

Particles

264

Page 265: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Sowejustneedtosettheupperleft3x3matrixforthemodelmatrixasthetransposematrixofthe3x3upperpartoftheviewmatrix:

×

But,afterdoingthis,wehaveremovedthescalingfactor,indeedwhatwedoreallywhanttoachieveissomethinglikethis:

Wheresx,syandszarethescalingfactor.Thus,afterwehavesetsettheupperleft3x3matrixforthemodelmatrixasthetransposematrixoftheviewmatrix,weneedtoapplyscalingagain.

Andthat'sall,wejustneedtochangethisintherenderParticlesMethodlikethis:

for(inti=0;i<numEmitters;i++){

IParticleEmitteremitter=emitters[i];

Meshmesh=emitter.getBaseParticle().getMesh();

mesh.renderList((emitter.getParticles()),(GameItemgameItem)->{

Matrix4fmodelMatrix=transformation.buildModelMatrix(gameItem);

viewMatrix.transpose3x3(modelMatrix);

Matrix4fmodelViewMatrix=transformation.buildModelViewMatrix(modelMa

trix,viewMatrix);

modelViewMatrix.scale(gameItem.getScale());

particlesShaderProgram.setUniform("modelViewMatrix",modelViewMatrix);

}

);

}

WealsohaveaddedanothermethodtotheTransformationclasstoconstructamodelviewmatrixusingtwomatricesinsteadofaGameItemandtheviewmatrix.

Withthatchange,whenwelooktheparticlesfromabovewegetsomethinglikethis.

⎣ ⎦

⎣⎢⎢⎡v00v01v02v03

v10v11v12v13

v20v21v22v23

v30v31v32v33⎦⎥⎥⎤

⎣⎢⎢⎡v00v10v20m03

v01v11v21m13

v02v12v22m23

m30m31m32m33⎦

⎥⎥⎤

⎣⎢⎢⎡

sx

00

mv03

0sy

0mv13

00sz

mv23

mv30mv31mv32mv33⎦

⎥⎥⎤

Particles

265

Page 266: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Nowwehaveeverythingweneedtocreateamorerealisticparticleeffectsolet'schangethetexturetosomethingmoreelaborated.Wewillusethisimage(itwascreatedwithGIMPwiththelightsandshadowsfilters).

Withthistexture,wewillgetsomethinglikethis.

Particles

266

Page 267: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Muchbetter!Youmaynoticethatweneedtoadjustthescale,sinceparticlesarenowalwaysfacingthecamerathedisplayedareaisalwaysthemaximum.

Finally,anotherconclusion,togetperfectresultswhichcanbeusedinanysceneyouwillneedtoimplementparticleorderingandactivatedepthbuffer.Inanycase,youhavehereasampletoincludethiseffectinyourgames.

TextureAtlasNowthatwehavesetthebasicinfrastructureforparticleeffectwecanaddsomeanimationeffectstoit.Inordertoachievethat,wearegoingtosupporttextureatlas.Atextureatlasisalargeimagethatcontainsallthetexturesthatwillbeused.Withatextureatlasweneedonlytoloadalargeimageandthenwhiledrawingthegameitemsweselecttheportionsofthatimagetobeusedasourtexture.Thistechniquecanbeappliedforinstancewhenwewanttorepresentthesamemodelmanytimeswithdifferenttextures(thinkforinstanceabouttrees,orrocks).Insteadofhavingmanytextureinstancesandswitchingbetweenthem(rememberthatswitchingstatesarealwaysslow)wecanusethesametextureatlasandjustselecttheappropriatecoordinates.

Inthiscase,wearegoingtousetexturecoordinatestoanimateparticles.Wewilliterateoverdifferenttexturestomodelaparticleanimation.Allthosetextureswillbegroupedintoatextureatlaswhichlookslikethis.

Particles

267

Page 268: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thetextureatlascanbedividedintoquadtiles.Wewillassignatilepositiontoaparticleandwillchangeitovertimetorepresentanimation.Solet’sgetonit.ThefirstthingthatwearegoingtodoismodifyingtheTextureclasstospecifythenumberofrowsandcolumnsthatatextureatlascanhave.

packageorg.lwjglb.engine.graph;

//..Importshera

publicclassTexture{

//Moreattributeshere

privateintnumRows=1;

privateintnumCols=1;

//Morecodehere

publicTexture(StringfileName,intnumCols,intnumRows)throwsException{

this(fileName);

this.numCols=numCols;

this.numRows=numRows;

}

Particles

268

Page 269: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thedefaultcaseistohaveatexturewithanumberofcolumnsandrowsequalto1,thatis,thetextureswehavedealingwith.Wealsoaddanotherconstructortobeabletospecifytherowsandcolumns.

ThenweneedtokeeptrackthepositioninthetextureatlasforaGameItem,sowejustaddanotherattributetothatclasswithadefaultvalueequalto0.

packageorg.lwjglb.engine.items;

importorg.joml.Vector3f;

importorg.lwjglb.engine.graph.Mesh;

publicclassGameItem{

//Moreattributeshere

privateinttextPos;

ThenwewillmodifytheParticleclasstobeabletoiterateautomaticallythroughatextureatlas.

packageorg.lwjglb.engine.graph.particles;

importorg.joml.Vector3f;

importorg.lwjglb.engine.graph.Mesh;

importorg.lwjglb.engine.graph.Texture;

importorg.lwjglb.engine.items.GameItem;

publicclassParticleextendsGameItem{

privatelongupdateTextureMillis;

privatelongcurrentAnimTimeMillis;

TheupdateTextureMillisattributemodelstheperiodoftime(inmilliseconds)tomovetothenextpositioninthetextureatlas.Thelowestthevaluethefastesttheparticlewillrolloverthetextures.ThecurrentAnimTimeMillisattributejustkeepstrackofthetimethattheparticlehasmaintainedatextureposition.

Thus,weneedtomodifytheParticleclassconstructortosetupthosevalues.Alsowecalculatethenumberoftilesofthetextureatlas,whichismodelledbytheattributeanimFrames.

Particles

269

Page 270: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicParticle(Meshmesh,Vector3fspeed,longttl,longupdateTextureMillis){

super(mesh);

this.speed=newVector3f(speed);

this.ttl=ttl;

this.updateTextureMills=updateTextureMills;

this.currentAnimTimeMillis=0;

Texturetexture=this.getMesh().getMaterial().getTexture();

this.animFrames=texture.getNumCols()*texture.getNumRows();

}

Now,wejustneedtomodifythemethodthatchecksiftheparticlehasexpiredtocheckalsoifweneedtoupdatethetextureposition.

publiclongupdateTtl(longelapsedTime){

this.ttl-=elapsedTime;

this.currentAnimTimeMillis+=elapsedTime;

if(this.currentAnimTimeMillis>=this.getUpdateTextureMillis()&&this.animFrame

s>0){

this.currentAnimTimeMillis=0;

intpos=this.getTextPos();

pos++;

if(pos<this.animFrames){

this.setTextPos(pos);

}else{

this.setTextPos(0);

}

}

returnthis.ttl;

}

Besidesthat,wealsohavemodifiedtheFlowRangeEmitterclasstoaddsomerandomnesstotheperiodoftimewhenweshouldchangetheaparticle’stextureposition.Youcancheckitinthesourcecode.

Nowwecanusethatinformationtosetupappropriatetexturecoordinates.Wewilldothisinthevertexfragmentsinceitoutputsthosevaluestobeusedinthefragmentshader.Thenewversionofthatshaderisdefinedlikethis.

Particles

270

Page 271: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

#version330

layout(location=0)invec3position;

layout(location=1)invec2texCoord;

layout(location=2)invec3vertexNormal;

outvec2outTexCoord;

uniformmat4modelViewMatrix;

uniformmat4projectionMatrix;

uniformfloattexXOffset;

uniformfloattexYOffset;

uniformintnumCols;

uniformintnumRows;

voidmain()

{

gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);

//Supportfortextureatlas,updatetexturecoordinates

floatx=(texCoord.x/numCols+texXOffset);

floaty=(texCoord.y/numRows+texYOffset);

outTexCoord=vec2(x,y);

}

Asyoucanseewehavenowthreenewuniforms.TheuniformsnumColsandnumRowsjustcontainthenumberofcolumnsandrowsofthetextureatlas.Inordertocalculatethetexturecoordinates,wefirstmustscaledowntheseparameters.Eachtilewillhaveawidthwhichisequalto1/numColsandaheightwhichisequalto1/numRowsasshowninthenextfigure.

Particles

271

Page 272: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thenwejustneedtoapplyandoffsetdependingontherowandcolumn,thisiswhatismodelledbythetexXOffsetandtexYOffsetuniforms.

WewillcalculatetheseoffsetsintheRendererclassasshowninthenextfragment.Wecalculatetherowandcolumnthateachparticleisinaccordingtoitspositionandcalculatetheoffsetaccordinglyasamultipleoftile’swidthandheight.

mesh.renderList((emitter.getParticles()),(GameItemgameItem)->{

intcol=gameItem.getTextPos()%text.getNumCols();

introw=gameItem.getTextPos()/text.getNumCols();

floattextXOffset=(float)col/text.getNumCols();

floattextYOffset=(float)row/text.getNumRows();

particlesShaderProgram.setUniform("texXOffset",textXOffset);

particlesShaderProgram.setUniform("texYOffset",textYOffset);

Notethatifyouonlyneedtosupportperfectlysquaretextureatlas,youwillonlyneedtwouniforms.Thefinalresultlookslikethis.

Particles

272

Page 273: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Nowwehaveanimatedparticlesworking.Inthenextchapterwewilllearnhowtooptimizetherenderingprocess.Wearerenderingmultipleelementsthathavethesamemeshandweareperformingadrawingcallforeachofthem.Inthenextchapterwewilllearnhowtodoitinasinglecall.Thattechniqueisusefulforparticlesbutalsoforrenderingsceneswheremultipleelementssharethesamemodelbutareplacedindifferentlocationsorhavedifferenttextures.

Particles

273

Page 274: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

InstancedRendering

LotsofInstancesWhendrawinga3Dsceneisfrequenttohavemanymodelsrepresentedbythesamemeshbutwithdifferenttransformations.Inthiscase,althoughtheymaybesimpleobjectswithjustafewtriangles,performancecansuffer.Thecausebehindthisisthewaywearerenderingthem.

WearebasicallyiteratingthroughallthegameitemsinsidealoopandperformingacalltothefunctionglDrawElements.Asithasbeensaidinpreviouschapters,callstoOpenGLlibraryshouldbeminimized.EachcalltotheglDrawElementsfunctionimposesanoverheadthatisrepeatedagainandagainforeachGameIteminstance.

Whendealingwithlotsofsimilarobjectsitwouldbemoreefficienttorenderallofthemusingasinglecall.Thistechniqueiscalledinstancedrendering.InordertoacomplishthatOpenGLprovidesasetoffunctionsnamedglDrawXXXInstancedtorenderasetofelementsatonce.Inourcase,sincewearedrawingelementswewillusethefunctionnamedglDrawElementsInstanced.ThisfunctionreceivesthesameargumentsastheglDrawElementsplusoneadditionalparameterwhichsetsthenumberofinstancestobedrawn.

ThisisasampleofhowtheglDrawElementsisused.

glDrawElements(GL_TRIANGLES,numVertices,GL_UNSIGNED_INT,0)

Andthisishowtheinstancedversioncanbeused:

glDrawElementsInstanced(GL_TRIANGLES,numVertices,GL_UNSIGNED_INT,0,numInstances);

Butyoumaybewonderingnowhowcanyousetthedifferenttransformationsforeachofthoseinstances.Now,beforewedraweachinstancewepassthedifferenttransformationsandinstancerelateddatausinguniforms.Beforearendercallismadeweneedtosetupthespecificdataforeachitem.Howcanwedothiswhenrenderingallofthematonce?

Whenusinginstancedrendering,inthevertexshaderwecanuseaninputvariablethatholdstheindexoftheinstancethatiscurrentlybeingdrawn.Withthatbuilt-invariablewecan,forinstance,passanarrayofuniformscontainingthetransformationstobeappliedtoeachinstanceanduseasinglerendercall.

InstancedRendering

274

Page 275: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Theproblemwiththisapproachisthatitstillimposestoomuchoverhead.Inadditiontothat,thenumberofuniformsthatwecanpassislimited.Thus,weneedtoemplyanotherapproach,insteadofusinglistsofuniformswewilluseinstancedarrays.

Ifyourecallfromthefirstchapters,thedataforeachMeshisdefinedbyasetofarraysofdatanamedVBOs.ThedatastoreinthoseVBOsisuniqueperMeshinstance.

WithstandardVBOs,insideashader,wecanaccessthedataassociatedtoeachvertex(itsposition,colour,textue,etc.).Whenevertheshaderisrun,theinputvariablesaresettopointtothespecificdataassociatedtoeachvertex.Withinstancedarrayswecansetupdatathatischangedperinstanceinsteadofpervertex.IfwecombinebothtypeswecanuseregularVBOstostorepervertexinformation(position,texturecoordinates)andVBOsthatcontainperinstancedatasuchasmodelviewmatrices.

ThenextfigureshowsaMeshcomposedbythreepervertexVBOsdefinigthepositions,texturesandnormals.Thefirstindexofeachofthoseelementsistheinstancethatitbelongsto(inbluecolour).Thesecondindexrepresentsthevertexpositioninsideainstance.

TheMeshisalsodefinedbytwoperinstanceVBOs.Oneforthemodelviewmatrixandtheotheroneforthelightviewmatrix.Whenrenderingtheverticesforthefirsinstance(the1X,ones),themodelviewandlightviewmatriceswillbethesame(the1).Whenverticesofthesecondinstancearetoberenderedthesecondmodelviewandlightviewmatriceswillbeused.

InstancedRendering

275

Page 276: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thus,whenrenderingthefirstvertexofthefirstinstance,V11,T11andN11wouldbeusedforposition,textureandnormaldataandMV1wouldbeusedasamodelviewmatrix.Whenrenderingthesecondvertexofthesamefirstinstance,V12,T12andN12wouldbeusedforposition,textureandnormaldaraandMV1woulsstillbeusedasamodelviewmatrix.MV2andLV2wouldnotbeuseduntilsecondinstanceisrendered.

InordertodefineperinstancedataweneedtocallthefunctionglVertexAttribDivisorafterdefiningvertexattributes.Thisfunctionreceivestwoparameters:

index:Theindexofthevertexattribute(asissuedintheglVertexAttribPointerfunction).

Divisor:Ifthisvalecontainszero,thedataischangedforeachvertexwhilerendering.Ifitissettoone,thedatachangesonceperinstance.Ifit’ssettotwoitchangeseverytwoinstances,etc.

So,inordertosetdataforainstanceweneedtoperformthiscallaftereveryattributedefinition:

glVertexAttribDivisor(index,1);

Let’sstartchangingourcodebasetosupportinstancedrendering.ThefirststepistocreateanewclassnamedInstancedMeshthatinheritsfromtheMeshclass.TheconstructorofthisclasswillbesimilartothesimilartotheMeshonebutwithanextraparameter,thenumberofinstances.

Intheconstructor,besidesrelyinginsuper’sconstructor,wewillcreatetwonewVBOs,oneforthemodelviewmatrixandanotheroneforthelightviewmatrix.Thecodeforcreatingthemodelviewmatrixispresentedbelow.

InstancedRendering

276

Page 277: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

modelViewVBO=glGenBuffers();

vboIdList.add(modelViewVBO);

this.modelViewBuffer=MemoryUtil.memAllocFloat(numInstances*MATRIX_SIZE_FLOATS);

glBindBuffer(GL_ARRAY_BUFFER,modelViewVBO);

intstart=5;

for(inti=0;i<4;i++){

glVertexAttribPointer(start,4,GL_FLOAT,false,MATRIX_SIZE_BYTES,i*VECTOR4F_S

IZE_BYTES);

glVertexAttribDivisor(start,1);

start++;

}

ThefirstthingthatwedoiscreateanewVBOandanewFloatBuffertostorethedataonit.Thesizeofthatbufferismeasuredinfloats,soitwillbeequaltothenumberofinstancesmultipliedbythesizeinfloatsofa4x4matrix,whichisequalto16.

OncetheVBOhasbeenbondwestartdefiningtheattributesforit.Youcanseethatthisisdoneinaforloopthatiteratesfourtimes.Eachturnoftheloopdefinesonevectorthematrix.Whynotsimplydefiningasingleattributeforthewholematrix?Thereasonforthatisthatavertexattributecannotcontainmorethanfourfloats.Thus,weneedtosplitthematrixdefinitionintofourpieces.Let’srefreshtheparametersoftheglVertexAttribPointer:

Index:Theindexoftheelementtobedefined.Size:Thenumberofcomponentsforthisattribute.Inthiscaseit’s4,4floats,whichisthemaximumacceptedvalue.Type:Thetypeofdata(floatsinourcase).Normalize:Iffixed-pointdatashouldbenormalizedornot.Stride:Thisisimportanttounderstandhere,thissetsthebyteoffsetsbetweenconsecutiveattributes.Inthiscase,weneedtosetittothewholesizeofamatrixinbytes.Thisactslikeamarkthatpacksthedatasoitcanbechangedbetweenvertexorinstances.Pointer:Theoffsetthatthisattributedefinitionappliesto.Inourcase,weneedtosplitthematrixdefinitionintofourcalls.Eachvectorofthematrixincrementstheoffset.

Afterdefiningthevertexattribute,weneedtocalltheglVertexAttribDivisorusingthesameindex.

Thedefinitionofthelightviewmatrixissimilartothepreviousone,youcancheckitinthesourcecode.ContinuingwiththeInstancedMeshclassdefinitionit’simportanttooverridethemethodsthatenablethevertexattributesbeforerendering(andtheonethatdisablesthemafter).

InstancedRendering

277

Page 278: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

@Override

protectedvoidinitRender(){

super.initRender();

intstart=5;

intnumElements=4*2;

for(inti=0;i<numElements;i++){

glEnableVertexAttribArray(start+i);

}

}

@Override

protectedvoidendRender(){

intstart=5;

intnumElements=4*2;

for(inti=0;i<numElements;i++){

glDisableVertexAttribArray(start+i);

}

super.endRender();

}

TheInstancedMeshclassdefinesapublicmethod,namedrenderListInstanced,thatrendersalistofgameitems,thismethodsplitsthelistofgameitemsintochunksofsizeequaltothenumberofinstancesusedtocreatetheInstancedMesh.TherealrenderingmethodiscalledrenderChunkInstancedandisdefinedlikethis.

InstancedRendering

278

Page 279: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatevoidrenderChunkInstanced(List<GameItem>gameItems,booleandepthMap,Transfor

mationtransformation,Matrix4fviewMatrix,Matrix4flightViewMatrix){

this.modelViewBuffer.clear();

this.modelLightViewBuffer.clear();

inti=0;

for(GameItemgameItem:gameItems){

Matrix4fmodelMatrix=transformation.buildModelMatrix(gameItem);

if(!depthMap){

Matrix4fmodelViewMatrix=transformation.buildModelViewMatrix(modelMatrix

,viewMatrix);

modelViewMatrix.get(MATRIX_SIZE_FLOATS*i,modelViewBuffer);

}

Matrix4fmodelLightViewMatrix=transformation.buildModelLightViewMatrix(model

Matrix,lightViewMatrix);

modelLightViewMatrix.get(MATRIX_SIZE_FLOATS*i,this.modelLightViewBuffer);

i++;

}

glBindBuffer(GL_ARRAY_BUFFER,modelViewVBO);

glBufferData(GL_ARRAY_BUFFER,modelViewBuffer,GL_DYNAMIC_DRAW);

glBindBuffer(GL_ARRAY_BUFFER,modelLightViewVBO);

glBufferData(GL_ARRAY_BUFFER,modelLightViewBuffer,GL_DYNAMIC_DRAW);

glDrawElementsInstanced(GL_TRIANGLES,getVertexCount(),GL_UNSIGNED_INT,0,gameIt

ems.size());

glBindBuffer(GL_ARRAY_BUFFER,0);

}

Themethodisquitesimple,webasicallyiterateoverthegameitemsandcalculatethemodelviewandlightviewmatrices.Thesematricesaredumpedintotheirrespectivebuffers.ThecontentsofthosebuffersaresenttototheGPUandfinallywerenderallofthemwithasinglecalltotheglDrawElementsInstancedmethod.

Goingbacktotheshaders,weneedtomodifythevertexshadertosupportinstancedrendering.Wewillfirstaddnewinputparametersforthemodelandviewmatricesthatwillbepassedwhenusinginstancedrendering.

layout(location=5)inmat4modelViewInstancedMatrix;

layout(location=9)inmat4modelLightViewInstancedMatrix;

Asyoucansee,themodelviewmatrixstartsatlocation5.Sinceamatrixisdefinedbyasetoffourattributes(eachonecontainingavector),thelightviewmatrixstartsatlocation9.Sincewewanttouseasingleshaderforbothnoninstancedandinstancedrendering,wewillmaintaintheuniformsformodelandlightviewmatrices.Weonlyneedtochangetheirnames.

InstancedRendering

279

Page 280: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

uniformintisInstanced;

uniformmat4modelViewNonInstancedMatrix;

uniformmat4modelLightViewNonInstancedMatrix;

Wehavecreatedanotheruniformtospecifyifweareusinginstancedrenderingornot.Inthecaseweareusinginstancedrenderingthecodeisverysimple,wejustusethematricesfromtheinputparameters.

voidmain()

{

vec4initPos=vec4(0,0,0,0);

vec4initNormal=vec4(0,0,0,0);

mat4modelViewMatrix;

mat4lightViewMatrix;

if(isInstanced>0)

{

modelViewMatrix=modelViewInstancedMatrix;

lightViewMatrix=modelLightViewInstancedMatrix;

initPos=vec4(position,1.0);

initNormal=vec4(vertexNormal,0.0);

}

Wedon’tsupportanimationsforinstancedrenderingtosimplifytheexample,butthistechniquecanbeperfectlyusedforthis.Finally,theshaderjustsetupappropriatevaluesasusual.

vec4mvPos=modelViewMatrix*initPos;

gl_Position=projectionMatrix*mvPos;

outTexCoord=texCoord;

mvVertexNormal=normalize(modelViewMatrix*initNormal).xyz;

mvVertexPos=mvPos.xyz;

mlightviewVertexPos=orthoProjectionMatrix*lightViewMatrix*initPos;

outModelViewMatrix=modelViewMatrix;

}

Ofcourse,theRendererhasbeenmodifiedtosupporttheuniformschangesandtoseparatetherenderingofnoninstancedmeshesfromtheinstancedones.Youcancheckthechangesinthesourcecode.

Inadditiontothat,someoptimizationshavebeenaddedtothesourcecodebytheJOMLauthorKaiBurjack.TheseoptimizationshavebeenappliedtotheTransformationclassandaresummarizedinthefollowinglist:

Removedredundantcallstosetupmatriceswithidentityvalues.

InstancedRendering

280

Page 281: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Usequaternionsforrotationswhicharemoreefficient.Usespecificmethodsforrotatingandtranslatingmatriceswhichareoptimizedforthoseoperations.

ParticlesrevisitedWiththesupportofinstancedrenderingwecanalsoimprovetheperformancefortheparticlesrendering.Particlesarethebestusecaseforthis.

Inordertoapplyinstancerenderingtoparticleswemustprovidesupportfortextureatlas.ThiscanbeachievedbyaddinganewVBOwithtextureoffsetsforinstancedrendering.Thetextureoffsetscanbemodeledbyasinglevectoroftowfloats,sothere'snoneedtosplitthedefinitionasinthematricescase.

//Textureoffsets

glVertexAttribPointer(start,2,GL_FLOAT,false,INSTANCE_SIZE_BYTES,strideStart);

glVertexAttribDivisor(start,1);

InstancedRendering

281

Page 282: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

But,insteadofaddinganewVBOwewillsetalltheinstanceattributesinsideasingleVBO.Thenextfigureshowstheconcept.WearepackingupalltheattributesinsideasingleVBO.Thevalueswillchangepereachinstance.

InordertouseasingleVBOweneedtomodifytheattributesizeforalltheattributesinsideaninstance.Asyoucanseefromthecodeabove,thedefinitionofthetextureoffsetsusesaconstantnamedINSTANCE_SIZE_BYTES.Thisconstantisequaltothesizeinbytesoftwomatrices(onefortheviewmodelandtheotheroneforthelightviewmodel)plustwofloats(textureoffesets),whichintotalis136.Thestridealsoneedstobemodifiedproperly.

Youcancheckthemodificationsinthesourcecode.

TheRendererclassneedsalsotobemodifiedtouseinstancedrenderingforparticlesandsupporttextureatlasinscenerendering.Inthiscase,there'snosenseinsupportbothtypesofrendering(noninstanceandinstanced),sothemodificationsaresimpler.

Thevertexshaderforparticlesisalsostraightfroward.

#version330

layout(location=0)invec3position;

layout(location=1)invec2texCoord;

layout(location=2)invec3vertexNormal;

layout(location=5)inmat4modelViewMatrix;

layout(location=13)invec2texOffset;

outvec2outTexCoord;

uniformmat4projectionMatrix;

uniformintnumCols;

uniformintnumRows;

voidmain()

{

gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);

//Supportfortextureatlas,updatetexturecoordinates

floatx=(texCoord.x/numCols+texOffset.x);

floaty=(texCoord.y/numRows+texOffset.y);

outTexCoord=vec2(x,y);}

Theresultsofthischanges,lookexactlythesameaswhenrenderingnoninstancedparticlesbuttheperformanceismuchhigher.AFPScounterhasbeenaddedtothewindowtitle,asanoption.Youcanplaywithinstancedandnoninstancedrenderingtoseetheimprovementsbyyourself.

InstancedRendering

282

Page 283: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

ExtrabonusWithalltheinfrastructurethatwehaverightnow,I'vemodifiedtherenderingcubescodetouseaheightmapasabase,usingalsotextureatlastousedifferenttextures.Italsocombinesparticlesrendering.Itlookslikethis.

InstancedRendering

283

Page 284: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Pleasekeepinmindthatthere'sstillmuchroomforoptimization,buttheaimofthebookisguidingyouinlearningLWJGLandOpenGLconceptsandtechniques.Thegoalisnottocreateafullblowngameengine(andefinitelynotavoxelengine,whichrequireadifferentapproachandmoreoptimizations).

InstancedRendering

284

Page 285: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

AudioUntilthismomentwehavebeendealingwithgraphics,butanotherkeyaspectofeverygameisaudio.ThiscapabilityisgoingtobeaddressedinthischapterwiththehelpofOpenAL(OpenAudioLibrary).OpenAListheOpenGLcounterpartforaudio,itallowsustoplaysoundsthroughandabstractionlayer.Thatlayerisolatesusfromtheunderlyingcomplexitiesoftheaudiosubsystem.Besidesthat,itallowsusto“render”soundsina3Dscene,wheresoundscanbesetupinspecificlocations,attenuatedwiththedistanceandmodifiedaccordingtotheirvelocity(simulatingDopplereffect)

LWJGLsupportsOpenALwithoutrequiringanyadditionaldownload,it’sjustreadytouse.ButbeforestartcodingweneedtopresentthemainelementsinvolvedwhendealingwithOpenAL,whichare:

Buffers.Sources.Listener.

Buffersstoreaudiodata,thatis,musicorsoundeffects.TheyaresimilartothetexturesintheOpenGLdomain.OpenALexpectsaudiodatatobeinPCM(PulseCodedModulation)format(eitherinmonoorinstereo),sowecannotjustdumpMP3orOGGfileswithoutconvertingthemfirsttoPCM.

Thenextelementaresources,whichrepresentalocationina3Dspace(apoint)thatemitssound.Asourceisassociatedtoabuffer(onlyoneattime)andcanbedefinedbythefollowingattributes:

Aposition,thelocationofthesource(x,yandzcoordinates).Bytheway,OpenALusesarighthandedCartesiancoordinatesystemasOpenGL,soyoucanassume(tosimplifythings)thatyourworldcoordinatesareequivalenttotheonesinthesoundspacecoordinatesystem.Avelocity,whichspecifieshowfastthesourceismoving.ThisisusedtosimulateDopplereffect.Again,whichisusedtomodifytheintensityofthesound(it’slikeanamplifierfactor).

Asourcehasadditionalattibuteswhichwillbeshownlaterwhendescribingthesourcecode.

Andlast,butnoleast,alistenerwhichiswherethegeneratedsoundsaresupposedtobeheard.TheListenerrepresentswerethemicrophoneissetina3Daudioscenetoreceivethesounds.Thereisonlyonelistener.Thus,it’softensaidthataudiorenderingisdoneform

Audio

285

Page 286: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

thelistener’sperspective.Alistenersharessometheattributesbutithassomeadditionalonessuchastheorientation.Theorientationrepresentswherethelistenerisfacing.

Soanaudio3Dsceneiscomposedbyasetofsoundsourceswhichemitsoundandalistenerthatreceivesthem.Thefinalperceivedsoundwilldependonthedistanceofthelistenertothedifferentsources,theirrelativespeedandtheselectedpropagationmodels.Sourcescansharebuffersandplaythesamedata.Thefollowingfiguredepictsasample3Dscenewiththedifferentelementtypesinvolved.

So,let'sstartcoding,wewillcreateanewpackageunderthenameorg.lwjglb.engine.soundthatwillhostalltheclassesresponsibleforhandlingaudio.Wewillfirststartwithaclass,namedSoundBufferthatwillrepresentanOpenALbuffer.Afragmentofthedefinitionofthatclassisshownbelow.

Audio

286

Page 287: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

packageorg.lwjglb.engine.sound;

//...Someinportshere

publicclassSoundBuffer{

privatefinalintbufferId;

publicSoundBuffer(Stringfile)throwsException{

this.bufferId=alGenBuffers();

try(STBVorbisInfoinfo=STBVorbisInfo.malloc()){

ShortBufferpcm=readVorbis(file,32*1024,info);

//Copytobuffer

alBufferData(buffer,info.channels()==1?AL_FORMAT_MONO16:AL_FORMAT_S

TEREO16,pcm,info.sample_rate());

}

}

publicintgetBufferId(){

returnthis.bufferId;

}

publicvoidcleanup(){

alDeleteBuffers(this.bufferId);

}

//....

}

Theconstructoroftheclassexpectsasoundfile(whichmaybeintheclasspathastherestofresources)andcreatesanewbufferfromit.ThefirstthingthatwedoiscreateanOpenALbufferwiththecalltoalGenBuffers.Attheendoursoundbufferwillbeidentifiedbyanintegerwhichislikeapointertothedataitholds.Oncethebufferhasbeencreatedwedumptheaudiodatainit.TheconstructorexpectsafileinOGGformat,soweneedtotransformittoPCMformat.Youcancheckhowthat'sdoneintesourcecode,anyway,thesourcecodehasbeenextractedfromtheLWJGLOpenALtests.

PreviousversionsofLWJGLhadahelperclassnamedWaveDatawhichwasusedtoloadaudiofilesinWAVformat.ThisclassisnolongerpresentinLWJGL3.Nevertheless,youmaygetthesourcecodefromthatclassanduseitinyourgames(maybewithoutrequiringanychanges).

TheSoundBufferclassalsoprovidesacleanupmethodtofreetheresourceswhenwearedonewithit.

Let'scontinuebymodellinganOpenAl,whichwillbeimplementedbyclassnamedSounSource.Theclassisdefinedbelow.

Audio

287

Page 288: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

packageorg.lwjglb.engine.sound;

importorg.joml.Vector3f;

importstaticorg.lwjgl.openal.AL10.*;

publicclassSoundSource{

privatefinalintsourceId;

publicSoundSource(booleanloop,booleanrelative){

this.sourceId=alGenSources();

if(loop){

alSourcei(sourceId,AL_LOOPING,AL_TRUE);

}

if(relative){

alSourcei(sourceId,AL_SOURCE_RELATIVE,AL_TRUE);

}

}

publicvoidsetBuffer(intbufferId){

stop();

alSourcei(sourceId,AL_BUFFER,bufferId);

}

publicvoidsetPosition(Vector3fposition){

alSource3f(sourceId,AL_POSITION,position.x,position.y,position.z);

}

publicvoidsetSpeed(Vector3fspeed){

alSource3f(sourceId,AL_VELOCITY,speed.x,speed.y,speed.z);

}

publicvoidsetGain(floatgain){

alSourcef(sourceId,AL_GAIN,gain);

}

publicvoidsetProperty(intparam,floatvalue){

alSourcef(sourceId,param,value);

}

publicvoidplay(){

alSourcePlay(sourceId);

}

publicbooleanisPlaying(){

returnalGetSourcei(sourceId,AL_SOURCE_STATE)==AL_PLAYING;

}

publicvoidpause(){

alSourcePause(sourceId);

}

Audio

288

Page 289: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicvoidstop(){

alSourceStop(sourceId);

}

publicvoidcleanup(){

stop();

alDeleteSources(sourceId);

}

}

Thesoundsourceclassprovidessomemethodstosetupitsposition,thegain,andcontrolmethodsforplayingstoppingandpausingit.Keepinmindthatsoundcontrolactionsaremadeoverasource(notoverthebuffer),rememberthatseveralsourcescansharethesamebuffer.AsintheSoundBufferclass,aSoundSourceisidentifiedbyanidentifier,whichisusedineachoperation.Thisclassalsoprovidesacleanupmethodtofreethereservedresources.Butlet’sexaminetheconstructor.ThefirstthingthatwedoistocreatethesourcewiththealGenSourcescall.Then,wesetupsomeinterestingpropertiesusingtheconstructorparameters.

Thefirstparameter,loop,indicatesifthesoundtobeplayedshouldbeinloopmodeornot.Bydefault,whenaplayactionisinvokedoverasourcetheplayingstopswhentheaudiodataisconsumed.Thisisfineforsomesounds,butsomeothers,likebackgroundmusic,needtobeplayedoverandoveragain.Insteadofmanuallycontrollingwhentheaudiohasstoppedandre-launchtheplayprocess,wejustsimplysettheloopingpropertytotrue:“alSourcei(sourceId,AL_LOOPING,AL_TRUE);”.

Theotherparameter,relative,controlsifthepositionofthesourceisrelativetothelistenerornot.Inthiscase,whenwesetthepositionforasource,webasicallyaredefiningthedistance(withavector)tothelistener,notthepositionintheOpenAL3Dscene,nottheworldposition.Thisactivatedbythe“alSourcei(sourceId,AL_SOURCE_RELATIVE,AL_TRUE);”call.But,Whatcanweusethisfor?Thispropertyisinterestingforinstanceforbackgroundsoundsthatshouldbeaffected(attenuated)bythedistancetothelistener.Think,forinstance,inbackgroundmusicorsoundeffectsrelatedtoplayercontrols.Ifwesetthesesourcesasrelative,andsettheirpositionto(0, 0, 0)theywillnotbeattenuated.

Nowit’sturnforthelistenerwhich,surprise,ismodelledbyaclassnamedSoundListener.Here’sthedefinitionforthatclass.

Audio

289

Page 290: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

packageorg.lwjglb.engine.sound;

importorg.joml.Vector3f;

importstaticorg.lwjgl.openal.AL10.*;

publicclassSoundListener{

publicSoundListener(){

this(newVector3f(0,0,0));

}

publicSoundListener(Vector3fposition){

alListener3f(AL_POSITION,position.x,position.y,position.z);

alListener3f(AL_VELOCITY,0,0,0);

}

publicvoidsetSpeed(Vector3fspeed){

alListener3f(AL_VELOCITY,speed.x,speed.y,speed.z);

}

publicvoidsetPosition(Vector3fposition){

alListener3f(AL_POSITION,position.x,position.y,position.z);

}

publicvoidsetOrientation(Vector3fat,Vector3fup){

float[]data=newfloat[6];

data[0]=at.x;

data[1]=at.y;

data[2]=at.z;

data[3]=up.x;

data[4]=up.y;

data[5]=up.z;

alListenerfv(AL_ORIENTATION,data);

}

}

Adifferenceyouwillnoticefromthepreviousclassesisthatthere’snoneedtocreatealistener.Therewillalwaysbeonelistener,sononeedtocreateone,it’salreadythereforus.Thus,intheconstructorwejustsimplysetitsinitialposition.Forthesamereasonthere’snoneedforacleanupmethod.Theclasshasmethodsalsoforsettinglistenerpositionandvelocity,asintheSoundSourceclass,butwehaveanextramethodforchangingthelistenerorientation.Let’sreviewwhatorientationisallabout.Listenerorientationisdefinedbytwovectors,“at”vectorand“up”one,whichareshowninthenextfigure.

Audio

290

Page 291: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

The“at”vectorbasicallypointswherethelistenerisfacing,bydefaultitscoordinatesare(0, 0,−1).The“up”vectordetermineswhichdirectionisupforthelistenerand,bydefaultitpointsto(0, 1, 0).SothetreecomponentsofeachofthosetwovectorsarewhataresetinthealListenerfvmethodcall.Thismethodisusedtotransferasetoffloats(avariablenumberoffloats)toaproperty,inthiscase,theorientation.

Beforecontinuingit'snecessarytostressoutsomeconceptsinrelationtosourceandlistenerspeeds.TherelativespeedbetweensourcesandlistenerwillcauseOpenALtosimulateDopplereffect.Incaseyoudon’tknow,Dopplereffectiswhatcausesthatamovingobjectthatisgettingclosertoyouseemstoemitinahigherfrequencythanitseemstoemitwheniswalkingaway.Thething,isthat,simplybysettingasourceorlistenervelocity,OpenALwillnotupdatetheirpositionforyou.ItwillusetherelativevelocitytocalculatetheDopplereffect,butthepositionswon’tbemodified.So,ifyouwanttosimulateamovingsourceorlisteneryoumusttakecareofupdatingtheirpositionsinthegameloop.

Nowthatwehavemodelledthekeyelementswecansetthemuptowork,weneedtoinitializeOpenALlibrary,sowewillcreateanewclassnamedSoundManagerthatwillhandlethis.Here’safragmentofthedefinitionofthisclass.

Audio

291

Page 292: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

packageorg.lwjglb.engine.sound;

//Importshere

publicclassSoundManager{

privatelongdevice;

privatelongcontext;

privateSoundListenerlistener;

privatefinalList<SoundBuffer>soundBufferList;

privatefinalMap<String,SoundSource>soundSourceMap;

privatefinalMatrix4fcameraMatrix;

publicSoundManager(){

soundBufferList=newArrayList<>();

soundSourceMap=newHashMap<>();

cameraMatrix=newMatrix4f();

}

publicvoidinit()throwsException{

this.device=alcOpenDevice((ByteBuffer)null);

if(device==NULL){

thrownewIllegalStateException("FailedtoopenthedefaultOpenALdevice."

);

}

ALCCapabilitiesdeviceCaps=ALC.createCapabilities(device);

this.context=alcCreateContext(device,(IntBuffer)null);

if(context==NULL){

thrownewIllegalStateException("FailedtocreateOpenALcontext.");

}

alcMakeContextCurrent(context);

AL.createCapabilities(deviceCaps);

}

ThisclassholdsreferencestotheSoundBufferandSoundSourceinstancestotrackandlatercleanupthemproperly.SoundBuffersarestoredinaListbutSoundSourcesarestoredininaMapsotheycanberetrievedbyaname.TheinitmethodinitializestheOpenALsubsystem:

Opensthedefaultdevice.Createthecapabilitiesforthatdevice.Createasoundcontext,liketheOpenGLone,andsetitasthecurrentone.

Audio

292

Page 293: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

TheSoundManagerclassalsohasamethodtoupdatethelistenerorientationgivenacameraposition.Inourcase,thelistenerwillbeplacedwheneverthecamerais.So,givencamerapositionandrotationinformation,howdowecalculatethe“at”and“up”vectors?Theanswerisbyusingtheviewmatrixassociatedtothecamera.Weneedtotransformthe“at”(0, 0,−1)and“up”(0, 1, 0)vectorstakingintoconsiderationcamerarotation.LetcameraMatrixbetheviewmatrixassociatedtothecamera.Thecodetoaccomplishthatwouldbe:

Matrix4finvCam=newMatrix4f(cameraMatrix).invert();

Vector3fat=newVector3f(0,0,-1);

invCam.transformDirection(at);

Vector3fup=newVector3f(0,1,0);

invCam.transformDirection(up);

Thefirstthingthatwedoisinvertthecameraviewmatrix.Whywedothis?Thinkaboutitthisway,theviewmatrixtransformsfromworldspacecoordinatestoviewspace.Whatwewantisjusttheopposite,wewanttotransformfromviewspacecoordinates(theviewmatrix)tospacecoordinates,whichiswherethelistenershouldbepositioned.Withmatrices,theoppositeusuallymeanstheinverse.Oncewehavethatmatrixwejusttransformthe“default”“at”and“up”vectorsusingthatmatrixtocalculatethenewdirections.

But,ifyoucheckthesourcecodeyouwillseethattheimplementationisslightlydifferent,whatwedoisthis:

Vector3fat=newVector3f();

cameraMatrix.positiveZ(at).negate();

Vector3fup=newVector3f();

cameraMatrix.positiveY(up);

listener.setOrientation(at,up);

Thecodeaboveisequivalenttothefirstapproach,it’sjustamoreefficientapproach.Itusesafastermethod,availableinJOMLlibrary,thatjustdoesnotneedtocalculatethefullinversematrixbutachievesthesameresults.ThismethodwasprovidedbytheJOMLauthorinaLWJGLforum,soyoucancheckmoredetailsthere.IfyoucheckthesourcecodeyouwillseethattheSoundManagerclasscalculatesitsowncopyoftheviewmatrix.ThisisalreadydoneintheRendererclass.Inordertokeepthecodesimple,andtoavoidrefactoring,I’vepreferredtokeepthisthatway.

Andthat’sall.Wehavealltheinfrastructureweneedinordertoplaysounds.Youcancheckinthesourcecodehowallthepiecesareused.Youcanseehowmusicisplayedandthedifferenteffectssound(ThesefileswereobtainedfromFreesound,propercreditsareina

Audio

293

Page 294: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

filenameCREDITS.txt).Ifyougetsomeotherfiles,youmaynoticethatsoundattenuationwithdistanceorlistenerorientationwillnotwork.Pleasecheckthatthefilesareinmono,notinstereo.OpenALcanonlyperformthosecomputationswithmonosounds.

Afinalnote.OpenALalsoallowsyoutochangetheattenuationmodelbyusingthealDistanceModelandpassingthemodelyouwant('``AL11.AL_EXPONENT_DISTANCE,AL_EXPONENT_DISTANCE_CLAMP```,etc.).Youcanplaywiththemandchecktheresults.

Audio

294

Page 295: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

3DObjectPicking

CameraSelectionOneofthekeyaspectsofeverygameistheabilitytointeractwiththeenvironment.Thiscapabilityrequirestobeabletoselectobjectsinthe3Dscene.Inthischapterwewillexplorehowthiscanbeachieved.

But,beforewestarttalkingaboutthestepstobeperformedtoselectobjects,weneedawaytorepresentselectedobjects.Thus,thefirstthingthatwemustdo,isaddanotherattributetotheGameItemclass,whichwillallowustotagselectedobjects:

privatebooleanselected;

Then,weneedtobeabletousethatvalueinthesceneshaders.Let’sstartwiththefragmentshader(scene_fragment.fs).Inthiscase,wewillassumethatwewillreceiveaflag,fromthevertexshader,thatwilldetermineifthefragmenttoberenderedbelongstoaselectedobjectornot.

infloatoutSelected;

Then,attheendofthefragmentshader,wewillmodifythefinalfragmentcolour,bysettingthebluecomponentto1ifit’sselected.

if(outSelected>0){

fragColor=vec4(fragColor.x,fragColor.y,1,1);

}

Then,weneedtobeabletosetthatvalueforeach.Ifyourecallfrompreviouschapterswehavetwoscenarios:

Renderingofnoninstancedmeshes.Renderingofinstancedmeshes.

Inthefirstcase,thedataforeachGameItemispassedthroughuniforms,sowejustneedtoaddanewuniformforthatinthevertexshader.Inthesecondcase,weneedtocreateanewinstancedattribute.Youcanseebellowtheadditionsthatneedtobeintegratedintothevertexshaderforbothcases.

3DObjectpicking

295

Page 296: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

layout(location=14)infloatselectedInstanced;

...

uniformfloatselectedNonInstanced;

...

if(isInstanced>0)

{

outSelected=selectedInstanced;

...

}

else

{

outSelected=selectedNonInstanced;

...

Nowthattheinfrastructurehasbeenset-upwejustneedtodefinehowobjectswillbeselected.Beforewecontinueyoumaynotice,ifyoulookatthesourcecode,thattheViewmatrixisnowstoredintheCameraclass.Thisisduetothefactthatwewerrecalculatingtheviewmatrixsinseveralclassesinthesourcecode.Previously,itwasstoredintheTransformationandintheSoundManagerclasses.Inordertocalculateintersectionswewouldneedtocerateanotherreplica.Insteadofthat,wecentralizethatintheCameraclass.Thischangealso,requiresthattheviewmatrixisupdatedinourmaingameloop.

Let’scontinuewiththepickingdiscussion.Inthissample,wewillfollowasimpleapproach,selectionwillbedoneautomaticallyusingthecamera.Theclosestobjecttowherethecameraisfacingwillbeselected.Let’sdiscusshowthiscanbedone.

Thefollowingpicturedepictsthesituationweneedtosolve.

3DObjectpicking

296

Page 297: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Wehavethecamera,placedinsomecoordinatesinworld-space,facingaspecificdirection.Anyobjectthatintersectswitharaycastfromcamera’spositionfollowingcamera’sforwarddirectionwillbeacandidate.Betweenallthecandidateswejustneedtochosetheclosestone.

Inoursample,gameitemsarecubes,soweneedtocalculatetheintersectionofthecamera’sforwardvectorwithcubes.Itmayseemtobeaveryspecificcase,butindeedisveryfrequent.Inmanygames,thegameitemshaveassociatedwhat’scalledaboundingbox.Aboundingboxisarectanglebox,thatcontainsalltheverticesforthatobject.Thisboundingboxisusedalso,forinstance,forcollisiondetection.Infact,intheanimationchapter,yousawthateachanimationframedefinedaboundingbox,thathelpstosettheboundariesatanygiventime.

So,let’sstartcoding.Wewillcreateanewclassnamed CameraBoxSelectionDetector,whichwillhaveamethodnamedselectGameItem```whichwillreceivealistofgameitemsandareferencetothecamera.Themethodisdefinedlikethis.

publicvoidselectGameItem(GameItem[]gameItems,Cameracamera){

GameItemselectedGameItem=null;

floatclosestDistance=Float.POSITIVE_INFINITY;

dir=camera.getViewMatrix().positiveZ(dir).negate();

for(GameItemgameItem:gameItems){

gameItem.setSelected(false);

min.set(gameItem.getPosition());

max.set(gameItem.getPosition());

min.add(-gameItem.getScale(),-gameItem.getScale(),-gameItem.getScale());

max.add(gameItem.getScale(),gameItem.getScale(),gameItem.getScale());

if(Intersectionf.intersectRayAab(camera.getPosition(),dir,min,max,nearFar

)&&nearFar.x<closestDistance){

closestDistance=nearFar.x;

selectedGameItem=gameItem;

}

}

if(selectedGameItem!=null){

selectedGameItem.setSelected(true);

}

}

Themethod,iteratesoverthegameitemstryingtogettheonesthatinteresectwiththeraycastformthecamera.ItfirstdefinesavariablenamedclosestDistance.Thisvariablewillholdtheclosestdistance.Forgameitemsthatintersect,thedistancefromthecameratotheintersectionpointwillbecalculated,Ifit’slowerthanthevaluestoredinclosestDistance,thenthisitemwillbethenewcandidate.

3DObjectpicking

297

Page 298: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Beforeenteringintotheloop,weneedtogetthedirectionvectorthatpointswherethecameraisfacing.Thisiseasy,justusetheviewmatrixtogetthezdirectiontakingintoconsiderationcamera’srotation.Rememberthatpositivezpointsoutofthescreen,soweneedtheoppositedirectionvector,thisiswhywenegateit.

InthegameloopintersectioncalculationsaredonepereachGameItem.But,howdowedothis?ThisiswherethegloriousJOMLlibrarycomestotherescue.WeareusingJOML’sIntersectionfclass,whichprovidesseveralmethodstocalculateintersectionsin2Dand3D.Specifically,weareusingtheintersectRayAabmethod.

ThismethodimplementsthealgorithmthattestintersectionforAxisAlignedBoxes.Youcancheckthedetails,aspointedoutintheJOMLdocumentation,here.

Themethodtestsifaray,definedbyanoriginandadirection,intersectsabox,definedbyminimumandmaximumcorner.Thisalgorithmisvalid,becauseourcubes,arealignedwiththeaxis,iftheywererotated,thismethodwouldnotwork.Thus,themethodreceivesthefollowingparameters:

Anorigin:Inourcase,thiswillbeourcameraposition.Adirection:Thisiswherethecameraisfacing,theforwardvector.Theminimumcornerofthebox.Inourcase,thecubesarecenteredaroundtheGameItemposition,theminimumcornerwillbethosecoordinatesminusthescale.(Initsoriginalsize,cubeshavealengthof2andasacleof1).Themaximumcorner.Selfexplanatory.Aresultvector.Thiswillcontainthenearandfardistancesoftheintersectionpoints.

Themethodwillreturntrueifthereisanintersection.Iftrue,wechecktheclosesdistanceandupdateitifneeded,andstoreareferenceofthecandidateselectedGameItem.Thenextfigureshowsalltheelementsinvolvedinthismethod.

3DObjectpicking

298

Page 299: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Oncetheloophasfinished,thecandidateGameItemismarkedasselected.

Andthat’s,all.TheselectGameItemwillbeinvokedintheupdatemethodoftheDummyGameclass,alongwiththeviewmatrixupdate.

//Updateviewmatrix

camera.updateViewMatrix();

//Updatesoundlistenerposition;

soundMgr.updateListenerPosition(camera);

this.selectDetector.selectGameItem(gameItems,camera);

Besidesthat,across-hairhasbeenaddedtotherenderingprocesstocheckthateverythingisworkingproperly.Theresultisshowninthenextfigure.

3DObjectpicking

299

Page 300: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Obviously,themethodpresentedhereisfarfromoptimalbutitwillgiveyouthebasicstodevelopmoresophisticatedmethodsonyourown.Somepartsofthescenecouldbeeasilydiscarded,likeobjectsbehindthecamera,sincetheyarenotgoingtobeintersected.Besidesthat,youmaywanttoorderyouritemsaccordingtothedistancetothecameratospeedupcalculations.Inadditiontothat,calculationsonlyneedtobedoneifthecamerahasmovedor.rotatedfrompreviousupdate.

MouseSelectionObjectpickingwiththecameraisgreat,butwhatifwewanttobeabletofreelyselectobjectswiththemouse?Inthiscase,wewantthat,whenevertheuserclicksonthescreen,theclosestobjectisautomaticallyselected.

Thewaytoachievethisissimilartothemethoddescribedabove.Inthepreviousmethodwehadthecamerapositionandgeneratedraysfromitusingthe“forward”directionaccordingtocamera’scurrentorientation.Inthiscase,westillneedtocastrays,butthedirectionpointstoapointfarawayfromthecamera,wheretheclickhasbeenmade.Inthiscase,weneedtocalculatethatdirectionvectorusingtheclickcoordinates.

But,howdowepassfroma(x, y)coordinatesinviewportspacetoworldspace?Let’sreviewhowwepassfrommodelspacecoordinatestoviewspace.Thedifferentcoordinatetransformationsthatareappliedinordertoachievethatare:

Wepassfrommodelcoordinatestoworldcoordinatesusingthemodelmatrix.

3DObjectpicking

300

Page 301: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Wepassfromworldcoordinatestoviewspacecoordinatesusingtheviewmatrix(thatprovidesthecameraeffect)-Wepassfromviewcoordinatestohomogeneousclipspacebyapplyingtheperspectiveprojectionmatrix.FinalscreencoordinatesarecalculateautomaticallybyOpenGLforus.Beforedointthat,itpassestonormalizeddevicespace(bydividingthex, y, zcoordinatesbythewcomponent)andthentox, yscreencoordinates.

Soweneedjusttoperformthetraversetheinevrsepathtogetfromscreencoordinates(x, y),toworldcoordinates.

Thefirststepistotransformfromscreencoordinatestonormalizeddevicespace.The(x, y)coordinatesintheviewportspaceareintherange[0, screenwith][0, screenheight].Theupperleftcornerofthescreenhasavalueof[0, 0].Weneedtotransformthatintocoordinatesintherange[−1, 1].

Themathsaresimple:

x = 2 ⋅ screen /screenwidth− 1

y = 1 − 2 ∗ screen /screenheight

But,howdowecalculatethezcoordinate?Theanswerissimple,wesimplyassignitthe−1value,sothattheraypointstothefarthestvisibledistance(RememberthatinOpenGL,−1pointstothescreen).Nowwehavethecoordinatesinnormaliseddevicespace.

Inordertocontinuewiththetransformationsweneedtoconvertthemtothehomogeneousclipspace.Weneedtohavethewcomponent,thatisusehomogeneouscoordinates.Althoughthisconceptwaspresentedinthepreviouschapters,let’sgetbacktoit.Inorderto

x

y

3DObjectpicking

301

Page 302: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

representa3Dpointwejustneedthex, yandzcoordinates,butwearecontinuouslyworkingwithanadditionalcoordinate,thewcomponent.Weneedthisextracomponentinordertousematricestoperformthedifferenttransformations.Sometransformationsdonotneedthatextracomponentbutotherdo.Forinstance,thetranslationmatrixdoesnotworkifweonlyhavex, yandzcomponents.Thus,wehaveaddedthewcomponentandassignedthemavalueof1sowecanworkwith4by4matrices.

Besidesthat,mostoftransformations,ortobemoreprecise,mostofthetransformationmatricesdonotalterthewcomponent.Anexceptiontothisistheprojectionmatrix.Thismatrixchangesthewvaluetobeproportionaltothezcomponent.

Transformingfromhomogeneousclipspacetonormalizeddevicecoordinatesisachievedbydividingthex,yandzcoordinatesbyw.Asthiscomponentisproportionaltothezcomponent,thisimpliesthatdistantobjectsaredrawnsmaller.Inourcaseweneedtodothereverse,weneedtounproject,butsincewhatwearecalculatingit’saraywejustsimplycanignorethatstep,wejustsetthewcomponentto1andleavetherestofthecomponentswiththeiroriginalvalue.

Nowweneedtogobackyoviewspace.Thisiseasy,wejustneedtocalculatetheinverseoftheprojectionmatrixandmultiplyitbyour4componentsvector.Oncewehavedonethat,weneedtotransformthemtoworldspace.Again,wejustneedtousetheviewmatrix,calculateit’sinverseandmultiplyitbyourvector.

Rememberthatweareonlyinterestedindirections,so,inthiscasewesetthewcomponentto0.Alsowecansetthezcomponentagainto−1,sincewewantittopointtowardsthescreen.Oncewehavedonethatandappliedtheinverseviewmatrixwehaveourvectorinworldspace.Wehaveourraycalculatedandcanapplythesamealgorithmasinthecaseofthecamerapicking.

WehavecreatedanewclassnamedMouseBoxSelectionDetectorthatimplementsthesetpsdescribedabove.Besidesthat,wehavemovedtheprojectionmatrixtotheWindowclasssowecanusetheminseveralplacesofthesourcecodeandrefactroedalittlebittheCameraBoxSelectionDetectorsotheMouseBoxSelectionDetectorcaninheitfromitandusethecollisiondetectionmethod.Youcancheckthesourcecodedirectly,sincetheimplemenattionit’sverysimple.

Theresultnowlookslikethis.

3DObjectpicking

302

Page 303: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Youjustneedtoclickovertheblockwiththemouseleftbuttontoperformtheselection.

Inanycase,ifyoucanconsultthedetailsbehindthestepsexplainedhereinanexcellentarticlewithverydetailedsketechsofthedifferentstepsinvolved.

3DObjectpicking

303

Page 304: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

HUDRevisited-NanoVGInpreviouschaptersweexplainedhowaHUDcanbecreatedrenderingsshapesandtexturesoverthetopofthesceneusinganorthographicprojection.InthischapterwewilllearnhowtousetheNanoVGlibrarytobeabletorenderantialiasedvectorgraphicstoconstructmorecomplexHUDsinaneasyway.

Therearemanyotherlibrariesouttherethatyoucanusetoaccomplishthistask,suchasNiftyGUI,Nuklear,etc.InthischapterwewillfocusonNanovgsinceit’sverysimpletouse,butifyou’relookingfordevelopingcomplexGUIinteractionswithbuttons,menusandwindowsyoushouldprobablylookforNiftyGUI.

ThefirststepinordertostartusingNanoVGisaddingthedependencesinthepom.xmlfile(oneforthedependenciesrequiredatcompiletimeandtheotheroneforthenativesrequiredatruntime).

...

<dependency>

<groupId>org.lwjgl</groupId>

<artifactId>lwjgl-nanovg</artifactId>

<version>${lwjgl.version}</version>

</dependency>

...

<dependency>

<groupId>org.lwjgl</groupId>

<artifactId>lwjgl-nanovg</artifactId>

<version>${lwjgl.version}</version>

<classifier>${native.target}</classifier>

<scope>runtime</scope>

</dependency>

BeforewestartusingNanoVGwemustsetupsomethingsintheOpenGLsidesothesamplescanworkcorrectly.Weneedtoenablesupportforstencilbuffertest.Untilnowwehavetalkedaboutcolouranddepthbuffers,butwehavenotmentionedthestencilbuffer.Thisbufferstoresavalue(aninteger)foreverypixelwhichisusedtocontrolwhichpixelsshouldbedrawn.Thisbufferisusedtomaskordiscarddrawingareasaccordingtothevaluesitstores.Itcanbeused,forinstance,tocutoutsomepartsofthesceneinaneasyway.WeenablestencilbuffertestbyaddingthislinetotheWindowclass(afterweenabledepthtesting):

glEnable(GL_STENCIL_TEST);

Hudrevisited-NanoVG

304

Page 305: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Sinceweareusinganotherbufferwemusttakecarealsoofremovingitsvaluesbeforeeachrendercall.Thus,weneedtomodifytheclearmethodoftheRendererclass:

publicvoidclear(){

glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

}

Wewillalsoaddanewwindowoptionforactivatingantialiasing.Thus,intheWindowclasswewillenableitbythisway:

if(opts.antialiasing){

glfwWindowHint(GLFW_SAMPLES,4);

}

NowwearereadytousetheNanoVGlibrary.ThefirsthingthatwewilldoisgetridofftheHUDartefactswehavecreated,thatistheshaderstheIHudinterface,thehudrenderingmethodsintheRendererclass,etc.Yocancheckthisoutinthesourcecode.

Inthiscase,thenewHudclasswilltakecareofitsrendering,sowedonotneedtodelegateittotheRendererclass.Let’startbydefiningthatclass,ItwillhaveaninitmethodthatsetsupthelibraryandtheresourcesneededtobuildtheHUD.Themethodisdefinedlikethis:

publicvoidinit(Windowwindow)throwsException{

this.vg=window.getOptions().antialiasing?nvgCreate(NVG_ANTIALIAS|NVG_STENCIL

_STROKES):nvgCreate(NVG_STENCIL_STROKES);

if(this.vg==NULL){

thrownewException("Couldnotinitnanovg");

}

fontBuffer=Utils.ioResourceToByteBuffer("/fonts/OpenSans-Bold.ttf",150*1024);

intfont=nvgCreateFontMem(vg,FONT_NAME,fontBuffer,0);

if(font==-1){

thrownewException("Couldnotaddfont");

}

colour=NVGColor.create();

posx=MemoryUtil.memAllocDouble(1);

posy=MemoryUtil.memAllocDouble(1);

counter=0;

}

Hudrevisited-NanoVG

305

Page 306: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

ThefirstthingwedoiscreateaNanoVGcontext.InthiscaseweareusinganOpenGL3.0backendsincewearereferringtotheorg.lwjgl.nanovg.NanoVGGL3namespace.IfantialiasingisactivatedwesetuptheflagNVG_ANTIALIAS.

Next,wecreateafontbyusingaTrueTypefontpreviouslyloadedintoaByteBuffer.Weassignitanamesowecanlateronuseitwhilerenderingtext.OneimportantthingaboutthisisthattheByteBufferusedtoloadthefontmustbekeptinmemorywhilethefontisused.Thatis,itcannotbegarbagecollected,otherwiseyouwillgetanicecoredump.Thisiswhyitisstoredasaclassattribute.

Then,wecreateacolourinstanceandsomehelpfulvariablesthatwillbeusedwhilerendering.Thatmethodiscalledinthegameinitmethod,justbeforetherenderedisinitialized:

@Override

publicvoidinit(Windowwindow)throwsException{

hud.init(window);

renderer.init(window);

...

TheHudclassalsodefinesarendermethod,whichshouldbecalledafterthescenehasbeenrenderedsotheHUDisdrawnontopofit.

@Override

publicvoidrender(Windowwindow){

renderer.render(window,camera,scene);

hud.render(window);

}

TherendermethodoftheHudclassstartslikethis:

publicvoidrender(Windowwindow){

nvgBeginFrame(vg,window.getWidth(),window.getHeight(),1);

ThefirstthingthatwemustdoiscallthenvgBeginFrame``method.AlltheNanoVGrenderingoperationsmustbeenclosedbetweenanvgBeginFrameandnvgEndFramecalls.ThenvgBeginFrame```acceptsthefollowingparameters:

TheNanoVGcontext.Thesizeofthewindowtorender(widthanheight).Thepixelratio.IfyouneedtosupportHi-DPIyoucanchangethisvalue.Forthissamplewejustsetitto1.

Hudrevisited-NanoVG

306

Page 307: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thenwecreateseveralribbonsthatoccupythewholescreenwith.Thefirstoneisdrawnlikethis:

//Upperribbon

nvgBeginPath(vg);

nvgRect(vg,0,window.getHeight()-100,window.getWidth(),50);

nvgFillColor(vg,rgba(0x23,0xa1,0xf1,200,colour));

nvgFill(vg);

Whilerenderingashape,thefirstmethodthatshallbeinvokedisnvgBeginPath,thatinstructsNanoVGtostartdrawinganewshape.Thenwedefinewhattodraw,arect,thefillcolourandbyinvokingthenvgFillwedrawit.

Youcanchecktherestofthesourcecodetoseehowtherestoftheshapesaredrawn.WhenrenderingtextisnotnecessarytocallnvgBeginPathbeforerenderingit.

Afterwehavefinisheddrawingalltheshapes,wejustcallthenvgEndFrametoendrendering,butthere’soneimportantthingtobedonebeforeleavingthemethod.WemustrestoretheOpenGLstate.NanoVGmodifiesOpenGLstateinordertoperformtheiroperations,ifthestateisnotcorrectlyrestoredyoumayseethatthesceneisnotcorrectlyrenderedoreventhatit'sbeenwipedout.Thus,weneedtorestoretherelevantOpenGLstatusthatweneedforourrendering.ThisisdelegatedintheWindowclass:

//Restorestate

window.restoreState();

Themethodisdefinedlikethis:

publicvoidrestoreState(){

glEnable(GL_DEPTH_TEST);

glEnable(GL_STENCIL_TEST);

glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

if(opts.cullFace){

glEnable(GL_CULL_FACE);

glCullFace(GL_BACK);

}

}

Andthat’sall(besidessomeadditionalmethodstoclearthingsup),thecodeiscompleted.Whenyouexecutethesampleyouwillgetsomethinglikethis:

Hudrevisited-NanoVG

307

Page 308: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Hudrevisited-NanoVG

308

Page 309: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Optimizations-FrustumCulling(I)Atthismomentweareusingmanydifferentgraphiceffects,suchaslights,particles,etc.Inadditiontothat,wehavelearnedhowtoinstancedrenderingtoreducetheoverheadofdrawingmanysimilarobjects.However,westillhaveplentyofroomforapplyingsimpleoptimizationtechniquesthatwillincreasetheFramesPerSecond(FPS)thatwecanachieve.

YoumayhavewonderedwhyarewedrawingthewholelistofGameItemseveryframeevenifsomeofthemwillnotbevisible(becausetheyarebehindthecameraortoofaraway).YoumayeventhinkthatthisishandledautomaticallyhandledbyOpenGL,andsomewayyouaretrue.OpenGLwilldiscardtherenderingofverticesthatfalloffthevisiblearea.Thiscalledclipping.Theissuewithclippingisthatit’sdonepervertex,afterthevertexshaderhasbeenexecuted.Hence,eventhisoperationsavesresources,wecanbemoreefficientbynottryingtorenderobjectsthatwillnotbevisible.WewouldnotbewastingresourcesbysendingthedatatotheGPUandbyperformingtransformationsforeveryvertexthatispartofthoseobjects.Weneedtoremovetheobjectsthatdoarenotcontainedintotheviewfrustum,thatis,weneedtoperformfrustumculling.

But,firstlet’sreviewwhatistheviewfrustum.Theviewfrustumisavolumethatcontainseveryobjectthatmaybevisibletakingintoconsiderationthecamerapositionandrotationandtheprojectionthatweareusing.Typically,theviewfrustumisarectangularpyramidlikeshowninthenextfigure.

Optimizations

309

Page 310: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Asyoucansee,theviewfrustumisdefinedbysixplanes,anythingthatliesoutsidetheviewfrustumwillnotberendering.So,frustumcullingistheprocessofremovingobjectsthatareoutsidetheviewfrustum.

Thus,inordertoperformfrustumcullingweneedto:

Calculatefrustumplanesusingthedatacontainedintheviewandprojectionmatrices.

ForeveryGameItem,checkifitscontainedinsidethatviewfrustum,thatis,comatinedbetweenthesizefrustumplanes,andeliminatetheonesthatlieoutsidefromtherenderingprocess.

Solet’sstartbycalculatingthefrustumplanes.Aplane,isdefinedbyapointcontainedinitandavectororthogonaltothatplane,asshowninthenextfigure:

Theequationofaplaneisdefinedlikethis:

Ax+By + Cz +D = 0

Optimizations

310

Page 311: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Hence,weneedtocalculatethesixplaneequationsforthesixsidesofourviewfrustum.Inordertotothatyoubasicallyhavetwooptions.Youcanperformtediouscalculationsthatwillgetyouthesixplaneequations,thatthis,thefourconstants(A,B,CandD)fromthepreviousequation.TheotheroptionistoletJOMLlibrarytocalculatethisforyou.Inthiscase,wewillchosethelastoption.

Solet’sstartcoding.WewillcreateanewclassnamedFrustumCullingFilterwhichwillperform,asitsnamestates,filteringoperationsaccordingtotheviewfrustum.

publicclassFrustumCullingFilter{

privatestaticfinalintNUM_PLANES=6;

privatefinalMatrix4fprjViewMatrix;

privatefinalVector4f[]frustumPlanes;

publicFrustumCullingFilter(){

prjViewMatrix=newMatrix4f();

frustumPlanes=newVector4f[NUM_PLANES];

for(inti=0;i<NUM_PLANES;i++){

frustumPlanes[i]=newVector4f();

}

}

publicvoidupdateFrustum(Matrix4fprojMatrix,Matrix4fviewMatrix){

//Calculateprojectionviewmatrix

prjViewMatrix.set(projMatrix);

prjViewMatrix.mul(viewMatrix);

//Getfrustumplanes

for(inti=0;i<NUM_PLANES;i++){

prjViewMatrix.frustumPlane(i,frustumPlanes[i]);

}

}

TheFrustumCullingFilterclasswillalsohaveamethodtocalculatetheplaneequationscalledupdateFrustumwhichwillbecalledbeforerendering.Themethodisdefinedlikethis:

publicvoidupdateFrustum(Matrix4fprojMatrix,Matrix4fviewMatrix){

//Calculateprojectionviewmatrix

prjViewMatrix.set(projMatrix);

prjViewMatrix.mul(viewMatrix);

//Getfrustumplanes

for(inti=0;i<NUM_PLANES;i++){

prjViewMatrix.frustumPlane(i,frustumPlanes[i]);

}

}

Optimizations

311

Page 312: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

First,westoreacopyoftheprojectionmatrixandmultiplyitbytheviewmatrixtogettheprojectionviewmatrix.Then,withthattransformationmatrixwejustsimplyneedtoinvokethefrustumPlanemethodforeachofthefrustumplanes.It’simportanttonotethattheseplaneequationsareexpressedinworldcoordinates,soallthecalculationsneedtobedoneinthatspace.

NowthatwehavealltheplanescalculatedwejustneedtocheckiftheGameIteminstancesareinsidethefrustumornot.Howcanwedothis?Let’sfirstexaminehowwecancheckifapointisinsidethefrustum.Wecanachievethatbycalculatingthesigneddistanceofthepointtoeachoftheplanes.Ifthedistanceofthepointtotheplaneispositive,thismeansthatthepointisinfrontoftheplane(accordingtoitsnormal).Ifit’snegative,thismeansthatthepointisbehindtheplane.

Therefore,apointwillbeinsidetheviewfrustumifthedistancetoalltheplanesofthefrustumispositive.Thedistanceofapointtotheplaneisdefinedlikethis:

dist = Ax +By + Cz +D,wherex ,y andz arethecoordinatesofthepoint.

So,apointisbehindtheplaneifAx +By + Cz +D <= 0.

But,wedonothavepoints,wehavecomplexmeshes,wecannotjustuseapointtocheckifanobjectisinsideafrustumornot.YoumaythinkincheckingeveryvertexoftheGameItemandseeifit’sinsidethefrustumornot.Ifanyofthepointsisinside,theGameItemshouldbedrawn.ButthiswhatOpenGLdoesinfactwhenclipping,thisiswhatwearetyingtoavoid.Rememberthatfrustumcullingbenefitswillbemorenoticeablethemorecomplexthemeshestoberenderedare.

WeneedtoenclsoeeveyGameItemintoasimplevolumethatiseasytocheck.Herewehavebasicallytwooptions:

0 0 0 0 0 0

0 0 0

Optimizations

312

Page 313: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Boundingboxes.

BoundingSpheres.

Inthiscase,wewillusespheres,sinceisthemostsimpleapproach.WewillencloseeveryGameItemsintoasphereandwillcheckifthesphereisinsidetheviewfrustumornot.Inordertodothat,wejustneedthecenterandtheradiusofthesphere.Thechecksarealmostequaltothepointcase,exceptthatweneedtotaketheradiusintoconsideration.Aspherewillbeoutsidethefrustimifitthefollowingconditionismet:dist = Ax0 +By0 + Cz0 <= −radius.

So,wewilladdanewmethodtotheFrustumCullingFilterclasstocheckifaspphereisinsidethefrustumornot.Themethodisdefinedlikethis.

publicbooleaninsideFrustum(floatx0,floaty0,floatz0,floatboundingRadius){

booleanresult=true;

for(inti=0;i<NUM_PLANES;i++){

Vector4fplane=frustumPlanes[i];

if(plane.x*x0+plane.y*y0+plane.z*z0+plane.w<=-boundingRadius){

result=false;returnresult;

}

}

returnresult;

}

Then,wewilladdmethodthatfilterstheGameItemsthatoutsidetheviewfrustum:

Optimizations

313

Page 314: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicvoidfilter(List<GameItem>gameItems,floatmeshBoundingRadius){

floatboundingRadius;

Vector3fpos;

for(GameItemgameItem:gameItems){

boundingRadius=gameItem.getScale()*meshBoundingRadius;

pos=gameItem.getPosition();

gameItem.setInsideFrustum(insideFrustum(pos.x,pos.y,pos.z,boundingRadius));

}

}

Wehaveaddedanewattribute,insideFrustum,totheGameItemclass,totrackthevisibility.Asyoucansee,theradiusoftheboundingsphereispassedasparameterThisisduetothefactthattheboundingsphereisassociatedtotheMesh,it’snotapropertyoftheGameItem.But,rememberthatwemustoperateinworldcoordinates,andtheradiosoftheboundingspherewillbeinmodelspace.WewilltransformittoworldspacebyapplyingthescalethathasbeensetupfortheGameItem,WeareassumigalsothatthepositionoftheGameItemisthecentreofthespehere(inworldspacecoordinates).

Thelastmethod,isjustautilityone,thatacceptsthemapofmeshesandfiltersalltheGameIteminstancescontainedinit.

publicvoidfilter(Map<?extendsMesh,List<GameItem>>mapMesh){

for(Map.Entry<?extendsMesh,List<GameItem>>entry:mapMesh.entrySet()){

List<GameItem>gameItems=entry.getValue();

filter(gameItems,entry.getKey().getBoundingRadius());

}

}

Andthat’sit.Wecanusethatclassinsidetherenderingprocess.Wejustneedtoupdatethefrustumplanes,calculatewhichGameItemsarevisibleandfilterthemoutwhendrawinginstancedandnoninstancedmeshes.

frustumFilter.updateFrustum(window.getProjectionMatrix(),camera.getViewMatrix());

frustumFilter.filter(scene.getGameMeshes());

frustumFilter.filter(scene.getGameInstancedMeshes());

YoucanplaywithactivatinganddeactivatingthefilteringandcanchecktheincreaseanddecreaseintheFPSthatyoucanachieve.Particlesarenotconsideredinthefiltering,butitstrivialtoaddit.Inanycase,forparticles,itmaybebettertojustcheckthepositionoftheemitterinsteadofcheckingeveryparticle.

Optimizations-FrustumCulling(II)

Optimizations

314

Page 315: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Oncethebasisoffrustumcullinghasbeenexplained,wecangetadvatangeofmorerefinedmethodsthattheJOMLlibraryprovides.Inparticular,itprovdiesaclassnamedFrustumIntersectionwhichextractstheplanesoftheveiwfrustuminamoreefficientwayasdescribedinthispaper.Besidesthat,thisclassalsoprovidesmethodsfortestingboundingboxes,pointsandspheres.

So,let'schangetheFrustumCullingFilterclass.Theattributesandconstructoraresimplifiedlikethis:

publicclassFrustumCullingFilter{

privatefinalMatrix4fprjViewMatrix;

privateFrustumIntersectionfrustumInt;

publicFrustumCullingFilter(){

prjViewMatrix=newMatrix4f();

frustumInt=newFrustumIntersection();

}

TheupdateFrustummethodjustdelegatestheplaneextractiontotheFrustumIntersectioninstance.

publicvoidupdateFrustum(Matrix4fprojMatrix,Matrix4fviewMatrix){

//Calculateprojectionviewmatrix

prjViewMatrix.set(projMatrix);

prjViewMatrix.mul(viewMatrix);

//Updatefrustumintersectionclass

frustumInt.set(prjViewMatrix);

}

AndthemethodthatinsideFrustummethodisvenemoresimple:

publicbooleaninsideFrustum(floatx0,floaty0,floatz0,floatboundingRadius){

returnfrustumInt.testSphere(x0,y0,z0,boundingRadius);

}

WiththisapproachyouwillbeabletovenegetafewmoreFPS.Besidesthat,aglobalflaghasbeenaddedtotheWindowclasstoenable/disbalefrustumculling.TheGameItemclassalsohasaflagforenabling/disablingthefiltering,becausetheremaybesomeitemsforwhichfrustumcullingfilteringdoesnotmakesense.

Optimizations

315

Page 316: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

CascadedShadowMapsIntheshadowschapterwepresentedtheshadowmaptechniquetobeabletodisplayshadowsusingdirectionallightswhenrenderinga3Dscene.Thesolutionpresentedthere,requiredyoutomanuallytweaksomeoftheparametersinordertoimprovetheresults.Inthischapterwearegoingtochangethattechniquetoautomatealltheprocessandtoimprovetherresultsforopenspaces.InordertoachievethatgoalwearegoingtouseatechniquecalledCascadedShadowMaps(CSM).

Let’sfirststartbyexamininghowwecanautomatetheconstructionofthelightviewmatrixandtheorthographicprojectionmatrixusedtorendertheshadows.Ifyourecallfromtheshadowschapter,weneedtodrawthesceneformthelight’sperspective.Thisimpliesthecreationofalightviewmatrix,whichactslikeacameraforlightandaprojectionmatrix.Sincelightisdirectional,andissupposedtobelocatedattheinfinity,wechoseanorthographicprojection.

Wewantallthevisibleobjectstofitintothelightviewprojectionmatrix.Hence,weneedtofittheviewfrustumintothelightfrustum.Thefollowingpicturedepictswhatwewanttoachieve.

CascadedShadowMaps

316

Page 317: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Howcanweconstructthat?Thefirststepistocalculatethefrustumcornersoftheviewprojectionmatrix.Wegetthecoordinatesinworldspace.Thenwecalculatethecentreofthatfrustum.Thiscanbecalculatingbyaddingthecoordinatesforallthecornersanddividingtheresultbythenumberofcorners.

Withthatinformationwecansetthepositionofthelight.Thatpositionanditsdirectionwillbeusedtoconstructthelightviewmatrix.Inordertocalculatetheposition,westartformthecentreoftheviewfrustumobtainedbefore.Wethengobacktothedirectionoflightanamountequaltothedistancebetweenthenearandfarzplanesoftheviewfrustum.

CascadedShadowMaps

317

Page 318: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Oncewehaveconstructedthelightviewmatrix,weneedtosetuptheorthographicprojectionmatrix.Inordertocalculatethemwetransformthefrustumcornerstolightviewspace,justbymultiplyingthembythelightviewmatrixwehavejustconstructed.Thedimensionsofthatprojectionmatrixwillbeminimumandmaximumxandyvalues.Thenearzplanecanbesetuptothesamevalueusedbyourstandardprojectionmatricesandthefarvaluewillbethedistancebetweenthemaximumandminimumzvaluesofthefrustumcornersinlightviewspace.

However,ifyouimplementthealgorithmdescribedaboveovertheshadowssample,youmaybedisappointedbytheshadowsquality.

CascadedShadowMaps

318

Page 319: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thereasonforthatisthatshadowsresolutionislimitedbythetexturesize.Wearecoveringnowapotentiallyhugearea,andtexturesweareusingtostoredepthinformationhavenotenoughresolutioninordertogetgoodresults.Youmaythinkthatthesolutionisjusttoincreasetextureresolution,butthisisnotsufficienttocompletelyfixtheproblem.Youwouldneedhugetexturesforthat.

There’sasmartersolutionforthat.Thekeyconceptisthat,shadowsofobjectsthatareclosertothecameraneedtohaveahigherqualitythanshadowsfordistantobjects.Oneapproachcouldbetojustrendershadowsforobjectsclosetothecamera,butthiswouldcauseshadowstoappear/disappearaslongaswemovethroughthescene.

TheapproachthatCascadedShadowMaps(CSMs)useistodividetheviewfrustumintoseveralsplits.Splitsclosertothecameracoverasmalleramountspaceswhilstdistantregionscoveramuchwiderregionofspace.Thenextfigureshowsaviewfrustumdividedintothreesplits.

CascadedShadowMaps

319

Page 320: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Foreachofthesesplits,thedepthmapisrendered,adjustingthelightviewandprojectionmatricestocoverfittoeachsplit.Thus,thetexturethatstoresthedepthmapcoversareducedareaoftheviewfrustum.And,sincethesplitclosesttothecameracoverslessspace,thedepthresolutionisincreased.

Asitcanbededucedformexplanationabove,Wewillneedasmanydepthtexturesassplits,andwewillalsochangethelightviewandprojectionmatricesforeachofthe,Hence,thestepstobedoneinordertoapplyCSMsare:

Dividetheviewfrustumintonsplits.

Whilerenderingthedepthmap,foreachsplit:

Calculatelightviewandprojectionmatrices.

Renderthescenefromlight’sperspectiveintoaseparatedepthmap

Whilerenderingthescene:

Usethedepthsmapscalculatedabove.

Determinethesplitthatthefragmenttobedrawnbelongsto.

Calculateshadowfactorasinshadowmaps.

CascadedShadowMaps

320

Page 321: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Asyoucansee,themaindrawbackofCSMsisthatweneedtorenderthescene,fromlight’sperspective,foreachsplit.Thisiswhyisoftenonlyusedforopenspaces.Anyway,wewillseehowwecaneasilyreducethatoverhead.

Solet’sstartexaminingthecode,butbeforewecontinuealittlewarning,Iwillnotincludethefullsourcecodeheresinceitwouldbeverytedioustored.Instead,Iwillpresentthemainclassestheirresponsibilitiesandthefragmentsthatmayrequirefurtherexplanationinordertogetagoodunderstanding.Alltheshadingrelatedclasseshavebeenmovedtoanewpackagecalledorg.lwjglb.engine.graph.shadow.

Thecodethatrendersshadows,thatis,thescenefromlight’sperspectivehasbeenmovedtotheShadowRendererclass.(ThatcodewaspreviouslycontainedintheRendererclass).

Theclassdefinesthefollowingconstants:

publicstaticfinalintNUM_CASCADES=3;

publicstaticfinalfloat[]CASCADE_SPLITS=newfloat[]{Window.Z_FAR/20.0f,Window.

Z_FAR/10.0f,Window.Z_FAR};

Thefirstoneisthenumberofcascadesorsplits.Thesecondonedefineswherethefarzplaneislocatedforeachofthesesplits.Asyoucanseetheyarenotequallyspaced.Thesplitthatisclosertothecamerahastheshortestdistanceinthezplane.

Theclassalsostoresthereferencetotheshaderprogramusedtorenderthedepthmap,alistwiththeinformationassociatedtoeachsplit,modelledbytheShadowCascadeclass,andareferencetotheobjectthatwhillhostthedepthmapthinformation(tetxures),modelledbytheShadowBufferclass.

TheShadowRendererclasshasmethodsforsettinguptheshadersandtherequiredattributesandarendermethod.Therendermethodisdefinedlikethis.

CascadedShadowMaps

321

Page 322: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicvoidrender(Windowwindow,Scenescene,Cameracamera,Transformationtransform

ation,Rendererrenderer){

update(window,camera.getViewMatrix(),scene);

//Setupviewporttomatchthetexturesize

glBindFramebuffer(GL_FRAMEBUFFER,shadowBuffer.getDepthMapFBO());

glViewport(0,0,ShadowBuffer.SHADOW_MAP_WIDTH,ShadowBuffer.SHADOW_MAP_HEIGHT);

glClear(GL_DEPTH_BUFFER_BIT);

depthShaderProgram.bind();

//Rendersceneforeachcascademap

for(inti=0;i<NUM_CASCADES;i++){

ShadowCascadeshadowCascade=shadowCascades.get(i);

depthShaderProgram.setUniform("orthoProjectionMatrix",shadowCascade.getOrthoP

rojMatrix());

depthShaderProgram.setUniform("lightViewMatrix",shadowCascade.getLightViewMat

rix());

glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,sha

dowBuffer.getDepthMapTexture().getIds()[i],0);

glClear(GL_DEPTH_BUFFER_BIT);

renderNonInstancedMeshes(scene,transformation);

renderInstancedMeshes(scene,transformation);

}

//Unbind

depthShaderProgram.unbind();

glBindFramebuffer(GL_FRAMEBUFFER,0);

}

Asyoucansee,Isimilartothepreviousrendermethodforshadowmaps,exceptthatweareperformingseveralrenderingpasses,onepersplit.IneachpasswechangethelightviewmatrixandtheorthographicprojectionmatrixwiththeinformationcontainedintheassociatedShadowCascadeinstande.

Also,ineachpass,weneedtochangethetextureweareusing.Eachpasswillrenderthedepthinformationtoadifferenttexture.ThisinformationisstoredintheShadowBufferclass,andissetuptobeusedbytheFBOwiththisline:

glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_TEXTURE_2D,shadowBuffe

r.getDepthMapTexture().getIds()[i],0);

CascadedShadowMaps

322

Page 323: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Asit’sjusthavebeenmentioned,theShadowBufferclassstorestheinformationrelatedtothetexturesusedtostoredepthinformation.Thecodeitsverysimilartothecodeusedintheshadowschapter,exceptthatweareusingtexturearrays.Thus,wehavecreatedanewclass,ArrTexture,thatcreatesanarrayoftextureswiththesameattributes.Thisclassalsoprovidesabindmethodthatbindsallthetexturearraysforusingtheminthesceneshader.Themethodreceivesaparameter,withthetextureunittostartwith.

publicvoidbindTextures(intstart){

for(inti=0;i<ShadowRenderer.NUM_CASCADES;i++){

glActiveTexture(start+i);

glBindTexture(GL_TEXTURE_2D,depthMap.getIds()[i]);

}

}

ShadowCascadeclass,storesthelightviewandorthographicprojectionmatricesassociatedtoonesplit.Eachsplitisdefinedbyanearandazfarplandistance,andwiththatinformationthematricesarecalculatedaccordingly.

Theclassprovidedandupdatemethodwhich,takingasaninputtheviewnatrixandthelightdirection.Themethodcalculatestheviewfrustumcornersinworldspaceandthencalculatesthelightposition.Thatpositioniscalculatedgoingback,suingthelightdirection,fromthefrustumcentretoadistanceequaltothedistancebetweenthefarandnearzplanes.

CascadedShadowMaps

323

Page 324: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicvoidupdate(Windowwindow,Matrix4fviewMatrix,DirectionalLightlight){

//Buildprojectionviewmatrixforthiscascade

floataspectRatio=(float)window.getWidth()/(float)window.getHeight();

projViewMatrix.setPerspective(Window.FOV,aspectRatio,zNear,zFar);

projViewMatrix.mul(viewMatrix);

//Calculatefrustumcornersinworldspace

floatmaxZ=-Float.MAX_VALUE;

floatminZ=Float.MAX_VALUE;

for(inti=0;i<FRUSTUM_CORNERS;i++){

Vector3fcorner=frustumCorners[i];

corner.set(0,0,0);

projViewMatrix.frustumCorner(i,corner);

centroid.add(corner);

centroid.div(8.0f);

minZ=Math.min(minZ,corner.z);

maxZ=Math.max(maxZ,corner.z);

}

//Gobackfromthecentroiduptomax.z-min.zinthedirectionoflight

Vector3flightDirection=light.getDirection();

Vector3flightPosInc=newVector3f().set(lightDirection);

floatdistance=maxZ-minZ;

lightPosInc.mul(distance);

Vector3flightPosition=newVector3f();

lightPosition.set(centroid);

lightPosition.add(lightPosInc);

updateLightViewMatrix(lightDirection,lightPosition);

updateLightProjectionMatrix();

}

Withthelightpositionandthelightdirection.wecanconstructthelightviewmatrix.ThisisdoneintheupdateLightViewMatrix:

privatevoidupdateLightViewMatrix(Vector3flightDirection,Vector3flightPosition){

floatlightAngleX=(float)Math.toDegrees(Math.acos(lightDirection.z));

floatlightAngleY=(float)Math.toDegrees(Math.asin(lightDirection.x));

floatlightAngleZ=0;

Transformation.updateGenericViewMatrix(lightPosition,newVector3f(lightAngleX,li

ghtAngleY,lightAngleZ),lightViewMatrix);

}

Finally,weneedtoconstructtheorthographicprojectionmatrix.ThisisdoneintheupdateLightProjectionMatrixmethod.Themethodistotransformtheviewfrustumcoordinatesintolightspace.Wethengettheminimumandmaximumvaluesforthex,y

CascadedShadowMaps

324

Page 325: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

coordinatestoconstructtheboundingboxthatenclosestheviewfrustum.Nearzplanecanbesetto0andthefarzplanetothedistancebetweenthemaximumandminimumvalueofthecoordinates.

privatevoidupdateLightProjectionMatrix(){

//Nowcalculatefrustumdimensionsinlightspace

floatminX=Float.MAX_VALUE;

floatmaxX=-Float.MAX_VALUE;

floatminY=Float.MAX_VALUE;

floatmaxY=-Float.MAX_VALUE;

floatminZ=Float.MAX_VALUE;

floatmaxZ=-Float.MAX_VALUE;

for(inti=0;i<FRUSTUM_CORNERS;i++){

Vector3fcorner=frustumCorners[i];

tmpVec.set(corner,1);

tmpVec.mul(lightViewMatrix);

minX=Math.min(tmpVec.x,minX);

maxX=Math.max(tmpVec.x,maxX);

minY=Math.min(tmpVec.y,minY);

maxY=Math.max(tmpVec.y,maxY);

minZ=Math.min(tmpVec.z,minZ);

maxZ=Math.max(tmpVec.z,maxZ);

}

floatdistz=maxZ-minZ;

orthoProjMatrix.setOrtho(minX,maxX,minY,maxY,0,distz);

}

Rememberthattheorthographicprojectionislikeaboundingboxthatshouldenclosealltheobjectsthatwillberendered.Thatboundingboxisexpressedinlightviewcoordinatesspace.Thus,whatwearedoingiscalculatetheminimumboundingbox,axisalignedwiththelightposition,hatenclosestheviewfrustum.

TheRendererclasshasbeenmodifiedtousetheclassesintheviewpackageandalsotomodifytheinformationthatispassedtotherenderers.Intherendererweneedtodealwiththemodel,themodelview,andthemodellightmatrices.Inpreviouschaptersweusedthemodel–view/light–viewmatrices,toreducethenumberofoperations.Inthiscase,weoptedtosimplifythenumberofelementstobepassedandnowwearepassingjustthemodel,viewandlightmatricestotheshaders.Also,forparticles,weneedtopreservethescale,sincewenolongerpassthemodelviewmatrix,thatinformationislostnow.Wereusetheattributeusedtomarkselecteditemstosetthatscaleinformation.Intheparticlesshaderwewillusethatvaluetosetthescalingagain.

Inthescenevertexshader,wecalculatemodellightviewmatrixforeachsplit,andpassitasanoutputtothefragmentshader.

CascadedShadowMaps

325

Page 326: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

mvVertexPos=mvPos.xyz;

for(inti=0;i<NUM_CASCADES;i++){

mlightviewVertexPos[i]=orthoProjectionMatrix[i]*lightViewMatrix[i]*modelMatr

ix*vec4(position,1.0);

}

Inthefragmentshaderweusethosevaluestoquerytheappropriatedepthmapdependingonthesplitthatthefragmentis.Thisneedstobedoneinthefragmentshadersince,foraspecificitem,theirfragmentsmayresideindifferentsplits.

Also,inthefragmentshaderwemustdecidewhichsplitweareinto.Inordertodothat,weusethezvalueofthefragmentandcompareitwiththemaximumzvalueforeachsplit.Thatis,thezfarplanevalue.Thatinformationispassedasanewuniform:

uniformfloatcascadeFarPlanes[NUM_CASCADES];

Wecalculatedesplitlikethis.Thevariableidxwillhavethesplittobeused:

intidx;

for(inti=0;i<NUM_CASCADES;i++)

{

if(abs(mvVertexPos.z)<cascadeFarPlanes[i])

{

idx=i;

break;

}

}

Also,inthesceneshadersweneedtopassanarrayoftextures,anarrayofsampler2D's,tousethedepthmap,thetexture,associatedtothesplitweareinto.Thesourcecode,insteadofusinganarrayusesalistofuniformsthatwillholdthetextureunitthatisusedtorefertothedepthmapassociatedtoeachsplit.

uniformsampler2DnormalMap;

uniformsampler2DshadowMap_0;

uniformsampler2DshadowMap_1;

uniformsampler2DshadowMap_2;

Changingittoanarrayofuniformscausesproblemswithothertexturesthataredifficulttotrackforthissample.Inanycase,youcantrychangingitinyourcode.

Therestofthechangesinthesourcecode,andtheshadersarejustadaptationsrequiredbythechangesdescribedabove.Youcancheckitdirectlyoverthesourcecode.

CascadedShadowMaps

326

Page 327: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Finally,whenintroducingthesechangesyoumayseethatperformancehasdropped.Thisisduetothefactthatwearerenderingthreetimesthedepthmap.Wecanmitigatethiseffectbyavoidingrenderingatallwhenthescenehasnotchanged.Ifthecamerahasnotbeenmovedorthesceneitemshavenotchangedwedonotneedtorenderagainandagainthedepthmap.Thedepthmapsarestoredintextures,sotheyarenotwipedoutforeachrendercall.Thus,wehaveaddedanewvariabletotherendermethodthatindicatesifthishaschanged,avoidingupdatingthedepthmapsitremainsthesame.ThisincreasestheFPSdramatically.Attheend,youwillgetsomethinglikethis:

CascadedShadowMaps

327

Page 328: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Assimp

StaticMeshesThecapabilityofloadingcomplex3dmodelsindifferentformatsiscrucialinordertowriteagame.Thetaskofwritingparsersforsomeofthemwouldrequirelotsofwork.Evenjustsupportingasingleformatcanbetimeconsuming.Forinstance,thewavefrontloaderdescribedinchapter9,onlyparsesasmallsubsetofthespecification(materialsarenothandledatall).

Fortunately,theAssimplibraryalreadycanbeusedtoparsemanycommon3Dformats.It’saC++librarywhichcanloadstaticandanimatedmodelsinavarietyofformats.LWJGLprovidesthebindingstousethemfromJavacode.Inthischapter,wewillexplainhowitcanbeused.

Thefirstthingisaddingassimpmavendependenciestotheprojectpom.xml.Weneedtoaddcompiletimeandruntimedependencies.

<dependency>

<groupId>org.lwjgl</groupId>

<artifactId>lwjgl-assimp</artifactId>

<version>${lwjgl.version}</version>

</dependency>

<dependency>

<groupId>org.lwjgl</groupId>

<artifactId>lwjgl-assimp</artifactId>

<version>${lwjgl.version}</version>

<classifier>${native.target}</classifier>

<scope>runtime</scope>

</dependency>

Oncethedependencieshasbeenset,wewillcerateanewclassnamedStaticMeshesLoaderthatwillbeusedtoloadmesheswithnoanimations.Theclassdefinestwostaticpublicmethods:

Assimp

328

Page 329: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicstaticMesh[]load(StringresourcePath,StringtexturesDir)throwsException{

returnload(resourcePath,texturesDir,aiProcess_JoinIdenticalVertices|aiProcess

_Triangulate|aiProcess_FixInfacingNormals);

}

publicstaticMesh[]load(StringresourcePath,StringtexturesDir,intflags)throwsE

xception{

//....

Bothmethodshavethefollowingarguments:

resourcePath:Thepathtothefilewherethemodelfileislocated.Thisisanabsolutepath,becauseAssimpmayneedtoloadadditionalfilesandmayusethesamebasepathastheresourcepath(Forinstance,materialfilesforwavefront,OBJ,files).

texturesDir:Thepathtothedirectorythatwillholdthetexturesforthismodel.ThisaCLASSPATHrelativepath.Forinstance,awavefrontmaterialfilemaydefineseveraltexturefiles.Thecode,expectthisfilestobelocatedinthetexturesDirdirectory.Ifyoufindtextureloadingerrorsyoumayneedtomanuallytweakthesepathsinthemodelfile.

Thesecondmethodhasanextraargumentnamedflags.Thisparameterallowstotunetheloadingprocess.Thefirstmethodsjustinvokesthesecondoneandpassessomevaluesthatareusefulinmostofthesituations:

aiProcess_JoinIdenticalVertices:Thisflagreducesthenumberofverticesthatareused,identifiyingthosethatcanbereusedbetweenfaces.

aiProcess_Triangulate:Themodelmayusequadsorothergeometriestodefinetheirelements.Sinceweareonlydealingwithtriangles,wemustusethisflagtosplitallhefacesintotriangles(ifneeded).

aiProcess_FixInfacingNormals:Thisflagstrytoreversenormalsthatmaypointinwards.

Therearemanyotherflagsthatcanbeused,youchancheckthemintheLWJGLJavadocdocumentation.

Let’sgobacktothesecondconstructor.ThefirstthingwedoisinvoketheaiImportFilemethodtoloadthemodelwiththeselectedflags.

AISceneaiScene=aiImportFile(resourcePath,flags);

if(aiScene==null){

thrownewException("Errorloadingmodel");

}

Therestofthecodefortheconstructorisaasfollows:

Assimp

329

Page 330: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

intnumMaterials=aiScene.mNumMaterials();

PointerBufferaiMaterials=aiScene.mMaterials();

List<Material>materials=newArrayList<>();

for(inti=0;i<numMaterials;i++){

AIMaterialaiMaterial=AIMaterial.create(aiMaterials.get(i));

processMaterial(aiMaterial,materials,texturesDir);

}

intnumMeshes=aiScene.mNumMeshes();

PointerBufferaiMeshes=aiScene.mMeshes();

Mesh[]meshes=newMesh[numMeshes];

for(inti=0;i<numMeshes;i++){

AIMeshaiMesh=AIMesh.create(aiMeshes.get(i));

Meshmesh=processMesh(aiMesh,materials);

meshes[i]=mesh;

}

returnmeshes;

Weprocessthematerialscontainedinthemodel.Materialsdefinecolourandtexturestobeusedbythemeshesthatcomposethemodel.Thenweprocessthedifferentmeshes.Amodelcandefineseveralmeshesandeachofthemcanuseoneofthematerialsdefinedforthemodel.

IfyouexaminethecodeaboveyoumayseethatmanyofthecallstotheAssimplibraryreturnPointerBufferinstances.YoucanthinkaboutthemlikeCpointers,theyjustpointtoamemoryregionwhichcontaindata.Youneedtoknowinadvancethetypeofdatathattheyholdinordertoprocessthem.Inthecaseofmaterials,weiterateoverthatbuffercreatinginstancesoftheAIMaterialclass.Inthesecondcase,weiterateoverthebufferthatholdsmeshdatacreatinginstanceoftheAIMeshclass.

Let’sexaminetheprocessMaterialmethod.

Assimp

330

Page 331: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatestaticvoidprocessMaterial(AIMaterialaiMaterial,List<Material>materials,S

tringtexturesDir)throwsException{

AIColor4Dcolour=AIColor4D.create();

AIStringpath=AIString.calloc();

Assimp.aiGetMaterialTexture(aiMaterial,aiTextureType_DIFFUSE,0,path,(IntBuffer

)null,null,null,null,null,null);

StringtextPath=path.dataString();

Texturetexture=null;

if(textPath!=null&&textPath.length()>0){

TextureCachetextCache=TextureCache.getInstance();

texture=textCache.getTexture(texturesDir+"/"+textPath);

}

Vector4fambient=Material.DEFAULT_COLOUR;

intresult=aiGetMaterialColor(aiMaterial,AI_MATKEY_COLOR_AMBIENT,aiTextureType

_NONE,0,colour);

if(result==0){

ambient=newVector4f(colour.r(),colour.g(),colour.b(),colour.a());

}

Vector4fdiffuse=Material.DEFAULT_COLOUR;

result=aiGetMaterialColor(aiMaterial,AI_MATKEY_COLOR_DIFFUSE,aiTextureType_NON

E,0,colour);

if(result==0){

diffuse=newVector4f(colour.r(),colour.g(),colour.b(),colour.a());

}

Vector4fspecular=Material.DEFAULT_COLOUR;

result=aiGetMaterialColor(aiMaterial,AI_MATKEY_COLOR_SPECULAR,aiTextureType_NO

NE,0,colour);

if(result==0){

specular=newVector4f(colour.r(),colour.g(),colour.b(),colour.a());

}

Materialmaterial=newMaterial(ambient,diffuse,specular,1.0f);

material.setTexture(texture);

materials.add(material);

}

Wecheckifthematerialdefinesatextureornot.Ifso,weloadthetexture.WehavecreatedanewclassnamedTextureCachewhichcachestextures.Thisisduetothefactthatseveralmeshesmaysharethesametextureandwedonotwanttowastespaceloadingagainandagainthesamedata.Thenwetrytogetthecoloursofthematerialfortheambient,diffuseandspecularcomponents.Fortunately,thedefinitionthatwehadforamaterialalreadycontainedthatinformation.

Assimp

331

Page 332: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

TheTextureCachedefinitionisverysimpleisjustamapthatindexesthedifferenttexturesbythepathtothetexturefile(Youcancheckdirectlyinthesourcecode).Duetothefact,thatnowtexturesmayusedifferentimageformats(PNG,JPEG,etc.),wehavemodifiedthewaythattexturesareloaded.InsteadofusingthePNGlibrary,wenowusetheSTBlibrarytobeabletoloadmoreformats.

Let’sgobacktotheStaticMeshesLoaderclass.TheprocessMeshisdefinedlikethis.

privatestaticMeshprocessMesh(AIMeshaiMesh,List<Material>materials){

List<Float>vertices=newArrayList<>();

List<Float>textures=newArrayList<>();

List<Float>normals=newArrayList<>();

List<Integer>indices=newArrayList();

processVertices(aiMesh,vertices);

processNormals(aiMesh,normals);

processTextCoords(aiMesh,textures);

processIndices(aiMesh,indices);

Meshmesh=newMesh(Utils.listToArray(vertices),

Utils.listToArray(textures),

Utils.listToArray(normals),

Utils.listIntToArray(indices)

);

Materialmaterial;

intmaterialIdx=aiMesh.mMaterialIndex();

if(materialIdx>=0&&materialIdx<materials.size()){

material=materials.get(materialIdx);

}else{

material=newMaterial();

}

mesh.setMaterial(material);

returnmesh;

}

AMeshisdefinedbyasetofverticesposition,normalsdirections,texturecoordinatesandindices.EachoftheseelementsareprocessedintheprocessVertices,processNormals,processTextCoordsandprocessIndicesmethod.AMeshalsomaypointtoamaterial,usingitsindex.IftheindexcorrespondstothepreviouslyprocessedmaterialswejustsimplyassociatethemtotheMesh.

TheprocessXXXmethodsareverysimple,theyjustinvokethecorrespondingmethodovertheAIMeshinstancethatreturnsthedesireddata.Forinstance,theprocessprocessVerticesisdefinedlikethis:

Assimp

332

Page 333: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatestaticvoidprocessVertices(AIMeshaiMesh,List<Float>vertices){

AIVector3D.BufferaiVertices=aiMesh.mVertices();

while(aiVertices.remaining()>0){

AIVector3DaiVertex=aiVertices.get();

vertices.add(aiVertex.x());

vertices.add(aiVertex.y());

vertices.add(aiVertex.z());

}

}

YoucanseethatgetgetabuffertotheverticesbyinvokingthemVerticesmethod.WejustsimplyprocessthemtocreateaListoffloatsthatcontaintheverticespositions.Since,themethodretyrnsjusstabufferyoucouldpassthatinformationdirectlytotheOpenGLmethodsthatcreatevertices.Wedonotdoitthatwayfortworeasons.Thefirstoneistrytoreduceasmuchaspossiblethemodificationsoverthecodebase.Secondoneisthatbyloadingintoanintermediatestructureyoumaybeabletoperformsomepros-processingtasksandevendebugtheloadingprocess.

Ifyouwantasampleofthemuchmoreefficientapproach,thatis,directlypassingthebufferstoOpenGL,youcancheckthissample.

TheStaticMeshesLoadermakestheOBJLoaderclassobsolete,soithasbeenremovedformthebasesourcecode.AmorecomplexOBJfileisprovidedasasample,ifyourunityouwillseesomethinglikethis:

Animations

Assimp

333

Page 334: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Nowthatwehaveusedassimpforloadingstaticmesheswecanproceedwithanimations.Ifyourecallformtheanimationschapter,theVAOassociatedtoameshcontainstheverticespositions,thetexturecoordinates,theindicesandalistofweightsthatshouldbeappliedtojointpositionstomodulatefinalvertexposition.

Eachvertexpositionhasassociatedalistoffourweightsthatchangethefinalposition,referringthebonesindicesthatwillbecombinedtodetermineitsfinalposition.Eachframealistoftransformationmatricesareloaded,asuniforms,foreachjoint.Withthatinformationthefinalpositioniscalculated.

Intheanimationchapter,wedevelopedaMD5parsertoloadanimatedmeshes.Inthischapterwewilluseassimplibrary.ThiswillallowustoloadmanymoreformatsbesidesMD5,suchasCOLLADA,FBX,etc.

Beforewestartcodinglet’sclarifysometerminology.Inthischapterwewillrefertobonesandjointsindistinguishably.Ajoint/boneisarejustelementsthataffectvertices,andthathaveaparentformingahierarchy.MD5formatusesthetermjoint,butassimpusesthetermbone.

Let’sreviewfirstthestructureshandledbyassimpthatcontainanimationinformation.Wewillstartfirstwiththebonesandweightsinformation.ForeachMesh,wecanaccesstheverticespositions,texturecoordinatesandindices.Meshesstorealsoalistofbones.Eachboneisdefinedbythefollowingattributes:

Aname.Anoffsetmatrix:Thiswillusedlatertocomputethefinaltransformationsthatshouldbeusedbyeachbone.

Assimp

334

Page 335: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Bonesalsopointtoalistofweights,eachweights.Eachweightsisdefinedbythefollowingattributes:

Aweightfactor,thatis,thenumberthatwillbeusedtomodulatetheinfluenceofthebone’stransformationassociatedtoeachvertex.Avertexidentifier,thatis,thevertexassociatedtothecurrentbone.

Thefollowingpictureshowstherelationshipsbetweenalltheseelements.

Hence,thefirstthingthatwemustdoistoconstructthelistofverticespositions,thebones/joints/indicesandtheassociatedweightsfromthestructureabove.Oncewehavedonethat,weneedtopre-calculatethetransformationmatricesforeachbone/jointforalltheanimationframesdefinedinthemodel.

AssimpsceneobjectdefinesaNode’shierarchy.EachNodeisdefinedbyanamealistofchildrennode.Animationsusethesenodestodefinethetransformationsthatshouldbeappliedto.Thishierarchyisdefinedindeedthebones’hierarchy.Everyboneisanode,andhasaparent,excepttherootnode,andpossibleasetofchildren.Therearespecialnodesthatarenotbones,theyareusedtogrouptransformations,andshouldbehandledwhencalculatingthetransformations.AnotherissueisthattheseNode’shierarchyisdefinedfrothewholemodel,wedonothaveseparatehierarchiesforeachmesh.

Ascenealsodefinesasetofanimations.Asinglemodelcanhavemorethanoneanimation.Youcanhaveanimationsforamodeltowalk,runetc.Eachoftheseanimationsdefinedifferenttransformations.Ananimationhasthefollowingattributes:

Aname.Aduration.Thatis,thedurationintimeoftheanimation.namemayseemconfusing

Assimp

335

Page 336: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

sinceananimationisthelistoftransformationsthatshouldbeappliedtoeachnodeforeachdifferentframe.Alistofanimationchannels.Ananimationchannel,contains,foraspecificinstantintimethetranslation,rotationandscalinginformationsthatshouldbeappliedtoeachnode.TheclassthatmodelsthedatacontainedintheanimationchannelsistheAINodeAnim.

Thefollowingfigureshowstherelationshipsbetweenalltheelementsdescribedabove.

Foraspecificinstantoftime,foraframe,thetransformationtobeappliedtoaboneisthetransformationdefinedintheanimationchannelforthatinstant,multipliedbythetransformationsofalltheparentnodesuptotherootnode.Hence,weneedtoreordertheinformationstoredinthescene,theprocessisasfollows:

Constructthenodehierarchy.Foreachanimation,iterateovereachanimationchannel(foreachanimationnode):Constructthetransformationmatricesforalltheframes.Thetransformationmmatrixisthecompositionofthetranslation,rotationandscalematrix.Reorderthatinformationforeachframe:ConstructthefinaltransformationstobeappliedforeachboneintheMesh.Thisisachievedbymultiplyingthetransformationmatrixofthebone(oftheassociatednode)bythetransformationmatricesofalltheparentnodesuptotherootnode.

Solet’sstartcoding.WewillcreatefirsaclassnamedAnimMeshesLoaderwhichextendsfromStaticMeshesLoader,butinsteadofreturninganarrayofMeshes,itreturnsanAnimGameIteminstance.Itdefinestwopublicmethodsforthat:

Assimp

336

Page 337: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

publicstaticAnimGameItemloadAnimGameItem(StringresourcePath,StringtexturesDir)

throwsException{

returnloadAnimGameItem(resourcePath,texturesDir,

aiProcess_GenSmoothNormals|aiProcess_JoinIdenticalVertices|aiProcess_T

riangulate

|aiProcess_FixInfacingNormals|aiProcess_LimitBoneWeights);

}

publicstaticAnimGameItemloadAnimGameItem(StringresourcePath,StringtexturesDir,i

ntflags)

throwsException{

AISceneaiScene=aiImportFile(resourcePath,flags);

if(aiScene==null){

thrownewException("Errorloadingmodel");

}

intnumMaterials=aiScene.mNumMaterials();

PointerBufferaiMaterials=aiScene.mMaterials();

List<Material>materials=newArrayList<>();

for(inti=0;i<numMaterials;i++){

AIMaterialaiMaterial=AIMaterial.create(aiMaterials.get(i));

processMaterial(aiMaterial,materials,texturesDir);

}

List<Bone>boneList=newArrayList<>();

intnumMeshes=aiScene.mNumMeshes();

PointerBufferaiMeshes=aiScene.mMeshes();

Mesh[]meshes=newMesh[numMeshes];

for(inti=0;i<numMeshes;i++){

AIMeshaiMesh=AIMesh.create(aiMeshes.get(i));

Meshmesh=processMesh(aiMesh,materials,boneList);

meshes[i]=mesh;

}

AINodeaiRootNode=aiScene.mRootNode();

Matrix4frootTransfromation=AnimMeshesLoader.toMatrix(aiRootNode.mTransformation

());

NoderootNode=processNodesHierarchy(aiRootNode,null);

Map<String,Animation>animations=processAnimations(aiScene,boneList,rootNode,

rootTransfromation);

AnimGameItemitem=newAnimGameItem(meshes,animations);

returnitem;

}

ThemethodsarequitesimilartotheonesdefinedintheStaticMeshesLoaderwiththefollowingdifferences:

Themethodthatpassesadefaultsetofloadingflags,usesthisnewparameter:aiProcess_LimitBoneWeights.Thiswilllimitthemaximumnumberofweightsthataffecta

Assimp

337

Page 338: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

vertextofour(Thisisalsothemaximumvaluethatwearecurrentlysupportingfromtheanimationschapter).ThemethodthatactuallyloadsthemodeljustloadsthedifferentmeshesbutitfirstcalculatesthenodehierarchyandthencallstotheprocessAnimationsattheendtobuildanAnimGameIteminstance.

TheprocessMeshmethodisquitesimilartotheoneintheStaticMeshesLoaderwiththeexceptionthatitcreatesMeshespassingjointindicesandweightsasaparameter:

processBones(aiMesh,boneList,boneIds,weights);

Meshmesh=newMesh(Utils.listToArray(vertices),Utils.listToArray(textures),

Utils.listToArray(normals),Utils.listIntToArray(indices),

Utils.listIntToArray(boneIds),Utils.listToArray(weights));

ThejointindicesandweightsarecalculatedintheprocessBonesmethod:

Assimp

338

Page 339: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatestaticvoidprocessBones(AIMeshaiMesh,List<Bone>boneList,List<Integer>bon

eIds,

List<Float>weights){

Map<Integer,List<VertexWeight>>weightSet=newHashMap<>();

intnumBones=aiMesh.mNumBones();

PointerBufferaiBones=aiMesh.mBones();

for(inti=0;i<numBones;i++){

AIBoneaiBone=AIBone.create(aiBones.get(i));

intid=boneList.size();

Bonebone=newBone(id,aiBone.mName().dataString(),toMatrix(aiBone.mOffsetM

atrix()));

boneList.add(bone);

intnumWeights=aiBone.mNumWeights();

AIVertexWeight.BufferaiWeights=aiBone.mWeights();

for(intj=0;j<numWeights;j++){

AIVertexWeightaiWeight=aiWeights.get(j);

VertexWeightvw=newVertexWeight(bone.getBoneId(),aiWeight.mVertexId(),

aiWeight.mWeight());

List<VertexWeight>vertexWeightList=weightSet.get(vw.getVertexId());

if(vertexWeightList==null){

vertexWeightList=newArrayList<>();

weightSet.put(vw.getVertexId(),vertexWeightList);

}

vertexWeightList.add(vw);

}

}

intnumVertices=aiMesh.mNumVertices();

for(inti=0;i<numVertices;i++){

List<VertexWeight>vertexWeightList=weightSet.get(i);

intsize=vertexWeightList!=null?vertexWeightList.size():0;

for(intj=0;j<Mesh.MAX_WEIGHTS;j++){

if(j<size){

VertexWeightvw=vertexWeightList.get(j);

weights.add(vw.getWeight());

boneIds.add(vw.getBoneId());

}else{

weights.add(0.0f);

boneIds.add(0);

}

}

}

}

Thismethodtraversesthebonedefinitionforaspecificmesh,gettingtheirweightsandgeneratingfillingupthreelists:

boneList:Itcontainsalistofnodes,withtheiroffsetmatrices.Itwilluseslaterontocalculatenodestransformations.AnewclassnamedBonehasbeencreatedtoholdthatinformation.Thislistwillcontainthebonesforallthemeshes.

Assimp

339

Page 340: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

boneIds:ItcontainsjusttheidentifiersofthebonesforeachvertexoftheMesh.Bonesareidentifiedbyitspositionwhenrendering.ThislistonlycontainsthebonesforaspecificMesh.weights:ItcontainstheweightsforeachvertexoftheMeshtobeappliedfortheassociatedbones.

TheinformationcontainedintheweightsandboneIdsisusedtoconstructtheMeshdata.TheinformationcontainedintheboneListwillbeusedlaterwhencalculatinganimationdata.

Let’sgobacktotheloadAnimGameItemmethod.OncewehavecreatedtheMeshes,wealsogetthetransformationwhichisappliedtotherootnodewhichwillbeusedalsotocalculatethefinaltransformatio.Afterthat,weneedtoprocessthehierarchyofnodes,whichisdoneintheprocessNodesHierarchymethod.Thismethodisquitesimple,Itjusttraversesthenodeshierarchystartingfromtherootnodeconstructingatreeofnodes.

privatestaticNodeprocessNodesHierarchy(AINodeaiNode,NodeparentNode){

StringnodeName=aiNode.mName().dataString();

Nodenode=newNode(nodeName,parentNode);

intnumChildren=aiNode.mNumChildren();

PointerBufferaiChildren=aiNode.mChildren();

for(inti=0;i<numChildren;i++){

AINodeaiChildNode=AINode.create(aiChildren.get(i));

NodechildNode=processNodesHierarchy(aiChildNode,node);

node.addChild(childNode);

}

returnnode;

}

WehavecreatedanewNodeclassthatwillcontaintherelevantinformationofAINodeinstances,andprovidesfindmethodstolocatethenodeshierarchytofindanodebyitsname.BackintheloadAnimGameItemmethod,wejustusethatinformationtocalculatetheanimationsintheprocessAnimationsmethod.ThismethodreturnsaMapofAnimationinstances.Rememberthatamodelcanhavemorethanoneanimation,sotheyarestoredindexedbytheirnames.WiththatinformationwecanfinallyconstructanAnimAgameIteminstance.

TheprocessAnimationsmethodisdefinedlikethis

Assimp

340

Page 341: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatestaticMap<String,Animation>processAnimations(AISceneaiScene,List<Bone>bo

neList,

NoderootNode,Matrix4frootTransformation){

Map<String,Animation>animations=newHashMap<>();

//Processallanimations

intnumAnimations=aiScene.mNumAnimations();

PointerBufferaiAnimations=aiScene.mAnimations();

for(inti=0;i<numAnimations;i++){

AIAnimationaiAnimation=AIAnimation.create(aiAnimations.get(i));

//Calculatetransformationmatricesforeachnode

intnumChanels=aiAnimation.mNumChannels();

PointerBufferaiChannels=aiAnimation.mChannels();

for(intj=0;j<numChanels;j++){

AINodeAnimaiNodeAnim=AINodeAnim.create(aiChannels.get(j));

StringnodeName=aiNodeAnim.mNodeName().dataString();

Nodenode=rootNode.findByName(nodeName);

buildTransFormationMatrices(aiNodeAnim,node);

}

List<AnimatedFrame>frames=buildAnimationFrames(boneList,rootNode,rootTran

sformation);

Animationanimation=newAnimation(aiAnimation.mName().dataString(),frames,

aiAnimation.mDuration());

animations.put(animation.getName(),animation);

}

returnanimations;

}

Foreachanimation,animationchannelsareprocessed.Eachchanneldefinesthedifferenttransformationsthatshouldbeappliedovertimeforanode.ThetransformationsdefinedforeachnodearedefinedinthebuildTransFormationMatricesmethod.Thesematricesarestoreforeachnode.Oncethenodeshierarchyisfilledupwiththatinformationwecanconstructtheanimationframes.

Let’sfirstreviewthebuildTransFormationMatricesmethod:

Assimp

341

Page 342: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatestaticvoidbuildTransFormationMatrices(AINodeAnimaiNodeAnim,Nodenode){

intnumFrames=aiNodeAnim.mNumPositionKeys();

AIVectorKey.BufferpositionKeys=aiNodeAnim.mPositionKeys();

AIVectorKey.BufferscalingKeys=aiNodeAnim.mScalingKeys();

AIQuatKey.BufferrotationKeys=aiNodeAnim.mRotationKeys();

for(inti=0;i<numFrames;i++){

AIVectorKeyaiVecKey=positionKeys.get(i);

AIVector3Dvec=aiVecKey.mValue();

Matrix4ftransfMat=newMatrix4f().translate(vec.x(),vec.y(),vec.z());

AIQuatKeyquatKey=rotationKeys.get(i);

AIQuaternionaiQuat=quatKey.mValue();

Quaternionfquat=newQuaternionf(aiQuat.x(),aiQuat.y(),aiQuat.z(),aiQuat.

w());

transfMat.rotate(quat);

if(i<aiNodeAnim.mNumScalingKeys()){

aiVecKey=scalingKeys.get(i);

vec=aiVecKey.mValue();

transfMat.scale(vec.x(),vec.y(),vec.z());

}

node.addTransformation(transfMat);

}

}

Asyoucansee,anAINodeAniminstancedefinesasetofkeysthatcontaintranslation,rotationandscalinginformation.Thesekeysarereferredtospecificinstantoftimes.Weassumethatinformationisorderedintime,andconstructalistofmatricesthatcontainthetransformationtobeappliedforeachframe.ThatfinalcalculationisdoneinthebuildAnimationFramesmethod:

Assimp

342

Page 343: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

privatestaticList<AnimatedFrame>buildAnimationFrames(List<Bone>boneList,Noderoot

Node,

Matrix4frootTransformation){

intnumFrames=rootNode.getAnimationFrames();

List<AnimatedFrame>frameList=newArrayList<>();

for(inti=0;i<numFrames;i++){

AnimatedFrameframe=newAnimatedFrame();

frameList.add(frame);

intnumBones=boneList.size();

for(intj=0;j<numBones;j++){

Bonebone=boneList.get(j);

Nodenode=rootNode.findByName(bone.getBoneName());

Matrix4fboneMatrix=Node.getParentTransforms(node,i);

boneMatrix.mul(bone.getOffsetMatrix());

boneMatrix=newMatrix4f(rootTransformation).mul(boneMatrix);

frame.setMatrix(j,boneMatrix);

}

}

returnframeList;

}

ThismethodreturnsalistofAnimatedFrameinstances.EachAnimatedFrameinstancewillcontainthelistoftransformationstobeappliedforeachboneforaspecificframe.Thismethodjustiteratesoverthelistthatcontainsallthebones.Foreachbone:

Getstheassociatednode.BuildsatransformationmatrixbymultiplyingthetransformationoftheassociatedNodewithallthetransformationsoftheirparentsuptotherootnode.ThisisdoneintheNode.getParentTransformsmethod.Itmultipliesthatmatrixwiththebone’soffsetmatrix.Thefinaltransformationiscalculatedbymultiplyingtheroot’snodetransformationwiththematrixcalculatedinthestepabove.

Therestofthechangesinthesourcecodeareminorchangestoadaptsomestructures.Attheendyouwillbeabletoloadanimationslikethisone(youneedyopressspacepartochangetheframe).

Assimp

343

Page 344: Table of Contents - FITstaff.fit.ac.cy/.../3d-game-development-with-lwjgl.pdf · 2017-09-28 · This game loop is simple and could be used for some games but it also presents some

Thecomplexityofthissampleresidesmoreintheadaptationsoftheassimpstructurestoadaptittotheengineusedinthebookandtopre-calculatethedataforeachframe.Beyondthat,theconceptsaresimilartotheonespresentedintheanimationschapter.Youmaytryalsotomodifythesourcecodetointerpolatebetweenframestogetsmootheranimations.

Assimp

344