What we eventually want is to transform that data to a format that OpenGL understands so that we can render the objects. We learned from the previous chapter ...
Ifyou'rerunningAdBlock,pleaseconsiderwhitelistingthissiteifyou'dliketosupportLearnOpenGL;andnoworries,Iwon'tbemadifyoudon't:)
IntroductionGettingstartedOpenGLCreatingawindowHelloWindowHelloTriangleShadersTexturesTransformationsCoordinateSystemsCameraReviewLightingColorsBasicLightingMaterialsLightingmapsLightcastersMultiplelightsReviewModelLoadingAssimpMeshModelAdvancedOpenGLDepthtestingStenciltestingBlendingFacecullingFramebuffersCubemapsAdvancedDataAdvancedGLSLGeometryShaderInstancingAntiAliasingAdvancedLightingAdvancedLightingGammaCorrectionShadowsShadowMappingPointShadowsNormalMappingParallaxMappingHDRBloomDeferredShadingSSAOPBRTheoryLightingIBLDiffuseirradianceSpecularIBLInPracticeDebuggingTextRendering2DGameBreakoutSettingupRenderingSpritesLevelsCollisionsBallCollisiondetectionCollisionresolutionParticlesPostprocessingPowerupsAudioRendertextFinalthoughtsGuestArticlesHowtopublish2020OITIntroductionWeightedBlendedSkeletalAnimation2021CSMSceneSceneGraphFrustumCullingTessellationHeightmapTessellationDSACoderepositoryTranslationsAbout
BTC
1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa
ETH/ERC20
0x1de59bd9e52521a46309474f8372531533bd7c43
Mesh
Model-Loading/Mesh
WithAssimpwecanloadmanydifferentmodelsintotheapplication,butonceloadedthey'reallstoredinAssimp'sdatastructures.WhatweeventuallywantistotransformthatdatatoaformatthatOpenGLunderstandssothatwecanrendertheobjects.Welearnedfromthepreviouschapterthatameshrepresentsasingledrawableentity,solet'sstartbydefiningameshclassofourown.
Let'sreviewabitofwhatwe'velearnedsofartothinkaboutwhatameshshouldminimallyhaveasitsdata.Ameshshouldatleastneedasetofvertices,whereeachvertexcontainsapositionvector,anormalvector,andatexturecoordinatevector.Ameshshouldalsocontainindicesforindexeddrawing,andmaterialdataintheformoftextures(diffuse/specularmaps).
NowthatwesettheminimalrequirementsforameshclasswecandefineavertexinOpenGL:
structVertex{
glm::vec3Position;
glm::vec3Normal;
glm::vec2TexCoords;
};
WestoreeachoftherequiredvertexattributesinastructcalledVertex.NexttoaVertexstructwealsowanttoorganizethetexturedatainaTexturestruct:
structTexture{
unsignedintid;
stringtype;
};
Westoretheidofthetextureanditstypee.g.adiffuseorspeculartexture.
Knowingtheactualrepresentationofavertexandatexturewecanstartdefiningthestructureofthemeshclass:
classMesh{
public:
//meshdata
vectorvertices;
vectorindices;
vectortextures;
Mesh(vectorvertices,vectorindices,vectortextures);
voidDraw(Shader&shader);
private:
//renderdata
unsignedintVAO,VBO,EBO;
voidsetupMesh();
};
Asyoucansee,theclassisn'ttoocomplicated.Intheconstructorwegivethemeshallthenecessarydata,weinitializethebuffersinthesetupMeshfunction,andfinallydrawthemeshviatheDrawfunction.NotethatwegiveashadertotheDrawfunction;bypassingtheshadertothemeshwecansetseveraluniformsbeforedrawing(likelinkingsamplerstotextureunits).
Thefunctioncontentoftheconstructorisprettystraightforward.Wesimplysettheclass'spublicvariableswiththeconstructor'scorrespondingargumentvariables.WealsocallthesetupMeshfunctionintheconstructor:
Mesh(vectorvertices,vectorindices,vectortextures)
{
this->vertices=vertices;
this->indices=indices;
this->textures=textures;
setupMesh();
}
Nothingspecialgoingonhere.Let'sdelverightintothesetupMeshfunctionnow.
Initialization
Thankstotheconstructorwenowhavelargelistsofmeshdatathatwecanuseforrendering.Wedoneedtosetuptheappropriatebuffersandspecifythevertexshaderlayoutviavertexattributepointers.Bynowyoushouldhavenotroublewiththeseconcepts,butwe'vespiceditupabitthistimewiththeintroductionofvertexdatainstructs:
voidsetupMesh()
{
glGenVertexArrays(1,&VAO);
glGenBuffers(1,&VBO);
glGenBuffers(1,&EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER,VBO);
glBufferData(GL_ARRAY_BUFFER,vertices.size()*sizeof(Vertex),&vertices[0],GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,indices.size()*sizeof(unsignedint),
&indices[0],GL_STATIC_DRAW);
//vertexpositions
glEnableVertexAttribArray(0);
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,sizeof(Vertex),(void*)0);
//vertexnormals
glEnableVertexAttribArray(1);
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(Vertex),(void*)offsetof(Vertex,Normal));
//vertextexturecoords
glEnableVertexAttribArray(2);
glVertexAttribPointer(2,2,GL_FLOAT,GL_FALSE,sizeof(Vertex),(void*)offsetof(Vertex,TexCoords));
glBindVertexArray(0);
}
Thecodeisnotmuchdifferentfromwhatyou'dexpect,butafewlittletrickswereusedwiththehelpoftheVertexstruct.
StructshaveagreatpropertyinC++thattheirmemorylayoutissequential.Thatis,ifweweretorepresentastructasanarrayofdata,itwouldonlycontainthestruct'svariablesinsequentialorderwhichdirectlytranslatestoafloat(actuallybyte)arraythatwewantforanarraybuffer.Forexample,ifwehaveafilledVertexstruct,itsmemorylayoutwouldbeequalto:
Vertexvertex;
vertex.Position=glm::vec3(0.2f,0.4f,0.6f);
vertex.Normal=glm::vec3(0.0f,1.0f,0.0f);
vertex.TexCoords=glm::vec2(1.0f,0.0f);
//=[0.2f,0.4f,0.6f,0.0f,1.0f,0.0f,1.0f,0.0f];
ThankstothisusefulpropertywecandirectlypassapointertoalargelistofVertexstructsasthebuffer'sdataandtheytranslateperfectlytowhatglBufferDataexpectsasitsargument:
glBufferData(GL_ARRAY_BUFFER,vertices.size()*sizeof(Vertex),vertices[0],GL_STATIC_DRAW);
Naturallythesizeofoperatorcanalsobeusedonthestructfortheappropriatesizeinbytes.Thisshouldbe32bytes(8floats*4byteseach).
Anothergreatuseofstructsisapreprocessordirectivecalledoffsetof(s,m)thattakesasitsfirstargumentastructandasitssecondargumentavariablenameofthestruct.Themacroreturnsthebyteoffsetofthatvariablefromthestartofthestruct.ThisisperfectfordefiningtheoffsetparameteroftheglVertexAttribPointerfunction:
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(Vertex),(void*)offsetof(Vertex,Normal));
Theoffsetisnowdefinedusingtheoffsetofmacrothat,inthiscase,setsthebyteoffsetofthenormalvectorequaltothebyteoffsetofthenormalattributeinthestructwhichis3floatsandthus12bytes.
Usingastructlikethisdoesn'tonlygetusmorereadablecode,butalsoallowsustoeasilyextendthestructure.Ifwewantanothervertexattributewecansimplyaddittothestructandduetoitsflexiblenature,therenderingcodewon'tbreak.
Rendering
ThelastfunctionweneedtodefinefortheMeshclasstobecompleteisitsDrawfunction.Beforerenderingthemesh,wefirstwanttobindtheappropriatetexturesbeforecallingglDrawElements.However,thisissomewhatdifficultsincewedon'tknowfromthestarthowmany(ifany)texturesthemeshhasandwhattypetheymayhave.Sohowdowesetthetextureunitsandsamplersintheshaders?
Tosolvetheissuewe'regoingtoassumeacertainnamingconvention:eachdiffusetextureisnamedtexture_diffuseN,andeachspeculartextureshouldbenamedtexture_specularNwhereNisanynumberrangingfrom1tothemaximumnumberoftexturesamplersallowed.Let'ssaywehave3diffusetexturesand2speculartexturesforaparticularmesh,theirtexturesamplersshouldthenbecalled:
uniformsampler2Dtexture_diffuse1;
uniformsampler2Dtexture_diffuse2;
uniformsampler2Dtexture_diffuse3;
uniformsampler2Dtexture_specular1;
uniformsampler2Dtexture_specular2;
Bythisconventionwecandefineasmanytexturesamplersaswewantintheshaders(uptoOpenGL'smaximum)andifameshactuallydoescontain(somany)textures,weknowwhattheirnamesaregoingtobe.Bythisconventionwecanprocessanyamountoftexturesonasinglemeshandtheshaderdeveloperisfreetouseasmanyofthoseashewantsbydefiningthepropersamplers.
Therearemanysolutionstoproblemslikethisandifyoudon'tlikethisparticularsolutionitisuptoyoutogetcreativeandcomeupwithyourownapproach.
Theresultingdrawingcodethenbecomes:
voidDraw(Shader&shader)
{
unsignedintdiffuseNr=1;
unsignedintspecularNr=1;
for(unsignedinti=0;i