@@ -911,13 +911,376 @@ public class RenderBuffers {
911911}
912912```
913913
914- ShadowRender
915- shadow.vert
916- Render
917- SkyBoxRender
918- SkyBox
919- TextureCache
920- Engine
921- Main
914+ We will change also the shadow render process to use indirect drawing. The changes in the vertex shader (` shadow.vert ` ) are quite similar, we will not be using animation information and we need to access the proper model matrices using the combination of ` gl_BaseInstance ` and ` gl_InstanceID ` built-in variables. In this case, we do not need material information so the fragment shader (` shadow.frag ` ) is not changed.
915+
916+ ``` glsl
917+ #version 460
918+
919+ const int MAX_DRAW_ELEMENTS = 100;
920+ const int MAX_ENTITIES = 50;
921+
922+ layout (location=0) in vec3 position;
923+ layout (location=1) in vec3 normal;
924+ layout (location=2) in vec3 tangent;
925+ layout (location=3) in vec3 bitangent;
926+ layout (location=4) in vec2 texCoord;
927+
928+ struct DrawElement
929+ {
930+ int modelMatrixIdx;
931+ };
932+
933+ uniform mat4 modelMatrix;
934+ uniform mat4 projViewMatrix;
935+ uniform DrawElement drawElements[MAX_DRAW_ELEMENTS];
936+ uniform mat4 modelMatrices[MAX_ENTITIES];
937+
938+ void main()
939+ {
940+ vec4 initPos = vec4(position, 1.0);
941+ uint idx = gl_BaseInstance + gl_InstanceID;
942+ int modelMatrixIdx = drawElements[idx].modelMatrixIdx;
943+ mat4 modelMatrix = modelMatrices[modelMatrixIdx];
944+ gl_Position = projViewMatrix * modelMatrix * initPos;
945+ }
946+ ```
947+
948+ Changes in ` ShadowRender ` are also pretty similar as the ones in the ` ScenRender ` class:
949+
950+ ``` java
951+ public class ShadowRender {
952+
953+ private static final int COMMAND_SIZE = 5 * 4 ;
954+ ...
955+ private int staticRenderBufferHandle;
956+ ...
957+ public void cleanup () {
958+ shaderProgram. cleanup();
959+ shadowBuffer. cleanup();
960+ glDeleteBuffers(staticRenderBufferHandle);
961+ }
962+
963+ private void createUniforms () {
964+ ...
965+ for (int i = 0 ; i < SceneRender . MAX_DRAW_ELEMENTS ; i++ ) {
966+ String name = " drawElements[" + i + " ]" ;
967+ uniformsMap. createUniform(name + " .modelMatrixIdx" );
968+ }
969+
970+ for (int i = 0 ; i < SceneRender . MAX_ENTITIES ; i++ ) {
971+ uniformsMap. createUniform(" modelMatrices[" + i + " ]" );
972+ }
973+ }
974+ ...
975+ }
976+ ```
977+
978+ The ` createUniforms ` method needs to be update to use the new uniforms and the ` cleanup ` one needs to free the indirect draw buffer. The ` render ` methdod will use now the ` glMultiDrawElementsIndirect ` instead of submitting individal draw commands for meshes and entities:
979+
980+ ``` java
981+ public class ShadowRender {
982+ ...
983+ public void render (Scene scene , RenderBuffers renderBuffers ) {
984+ CascadeShadow . updateCascadeShadows(cascadeShadows, scene);
985+
986+ glBindFramebuffer(GL_FRAMEBUFFER , shadowBuffer. getDepthMapFBO());
987+ glViewport(0 , 0 , ShadowBuffer . SHADOW_MAP_WIDTH , ShadowBuffer . SHADOW_MAP_HEIGHT );
988+
989+ shaderProgram. bind();
990+
991+ int entityIdx = 0 ;
992+ for (Model model : scene. getModelMap(). values()) {
993+ List<Entity > entities = model. getEntitiesList();
994+ for (Entity entity : entities) {
995+ uniformsMap. setUniform(" modelMatrices[" + entityIdx + " ]" , entity. getModelMatrix());
996+ entityIdx++ ;
997+ }
998+ }
999+
1000+ for (int i = 0 ; i < CascadeShadow . SHADOW_MAP_CASCADE_COUNT ; i++ ) {
1001+ glFramebufferTexture2D(GL_FRAMEBUFFER , GL_DEPTH_ATTACHMENT , GL_TEXTURE_2D , shadowBuffer. getDepthMapTexture(). getIds()[i], 0 );
1002+ glClear(GL_DEPTH_BUFFER_BIT );
1003+ }
1004+
1005+ // Static meshes
1006+ glBindBuffer(GL_DRAW_INDIRECT_BUFFER , staticRenderBufferHandle);
1007+ glBindVertexArray(renderBuffers. getStaticVaoId());
1008+ for (int i = 0 ; i < CascadeShadow . SHADOW_MAP_CASCADE_COUNT ; i++ ) {
1009+ glFramebufferTexture2D(GL_FRAMEBUFFER , GL_DEPTH_ATTACHMENT , GL_TEXTURE_2D , shadowBuffer. getDepthMapTexture(). getIds()[i], 0 );
1010+
1011+ CascadeShadow shadowCascade = cascadeShadows. get(i);
1012+ uniformsMap. setUniform(" projViewMatrix" , shadowCascade. getProjViewMatrix());
1013+
1014+ glMultiDrawElementsIndirect(GL_TRIANGLES , GL_UNSIGNED_INT , 0 , staticDrawCount, 0 );
1015+ }
1016+ glBindVertexArray(0 );
1017+
1018+ shaderProgram. unbind();
1019+ glBindFramebuffer(GL_FRAMEBUFFER , 0 );
1020+ }
1021+ ...
1022+ }
1023+ ```
1024+ Finally, we need a similar method to set up the indirect draw buffer (which will be encpasulated into a ` setupData ` just to maintain the same style):
1025+
1026+ ``` java
1027+ public class ShadowRender {
1028+ ...
1029+ public void setupData (Scene scene ) {
1030+ setupStaticCommandBuffer(scene);
1031+ }
1032+
1033+ private void setupStaticCommandBuffer (Scene scene ) {
1034+ List<Model > modelList = scene. getModelMap(). values(). stream(). filter(m - > ! m. isAnimated()). toList();
1035+ Map<String , Integer > entitiesIdxMap = new HashMap<> ();
1036+ int entityIdx = 0 ;
1037+ int numMeshes = 0 ;
1038+ for (Model model : scene. getModelMap(). values()) {
1039+ List<Entity > entities = model. getEntitiesList();
1040+ numMeshes += model. getMeshDrawDataList(). size();
1041+ for (Entity entity : entities) {
1042+ entitiesIdxMap. put(entity. getId(), entityIdx);
1043+ entityIdx++ ;
1044+ }
1045+ }
1046+
1047+ int firstIndex = 0 ;
1048+ int baseInstance = 0 ;
1049+ int drawElement = 0 ;
1050+ shaderProgram. bind();
1051+ ByteBuffer commandBuffer = MemoryUtil . memAlloc(numMeshes * COMMAND_SIZE );
1052+ for (Model model : modelList) {
1053+ List<Entity > entities = model. getEntitiesList();
1054+ int numEntities = entities. size();
1055+ for (RenderBuffers . MeshDrawData meshDrawData : model. getMeshDrawDataList()) {
1056+ // count
1057+ commandBuffer. putInt(meshDrawData. vertices());
1058+ // instanceCount
1059+ commandBuffer. putInt(numEntities);
1060+ commandBuffer. putInt(firstIndex);
1061+ // baseVertex
1062+ commandBuffer. putInt(meshDrawData. offset());
1063+ commandBuffer. putInt(baseInstance);
1064+
1065+ firstIndex += meshDrawData. vertices();
1066+ baseInstance += entities. size();
1067+
1068+ for (Entity entity : entities) {
1069+ String name = " drawElements[" + drawElement + " ]" ;
1070+ uniformsMap. setUniform(name + " .modelMatrixIdx" , entitiesIdxMap. get(entity. getId()));
1071+ drawElement++ ;
1072+ }
1073+ }
1074+ }
1075+ commandBuffer. flip();
1076+ shaderProgram. unbind();
1077+
1078+ staticDrawCount = commandBuffer. remaining() / COMMAND_SIZE ;
1079+
1080+ staticRenderBufferHandle = glGenBuffers();
1081+ glBindBuffer(GL_DRAW_INDIRECT_BUFFER , staticRenderBufferHandle);
1082+ glBufferData(GL_DRAW_INDIRECT_BUFFER , commandBuffer, GL_DYNAMIC_DRAW );
1083+
1084+ MemoryUtil . memFree(commandBuffer);
1085+ }
1086+ }
1087+ ```
1088+
1089+ In the ` Render ` class, we just need to instantiate the ` RenderBuffers ` class and provide a new method ` setupData ` which can be called when every model and entity has been created to create the indirect drawing buffers and associated data.
1090+
1091+ ``` java
1092+ public class Render {
1093+ ...
1094+ private RenderBuffers renderBuffers;
1095+ ...
1096+ public Render (Window window ) {
1097+ ...
1098+ renderBuffers = new RenderBuffers ();
1099+ }
1100+
1101+ public void cleanup () {
1102+ ...
1103+ renderBuffers. cleanup();
1104+ }
1105+ ...
1106+ public void render (Window window , Scene scene ) {
1107+ shadowRender. render(scene, renderBuffers);
1108+ sceneRender. render(scene, renderBuffers, gBuffer);
1109+ ...
1110+ }
1111+ ...
1112+ public void setupData (Scene scene ) {
1113+ renderBuffers. loadStaticModels(scene);
1114+ renderBuffers. loadAnimatedModels(scene);
1115+ sceneRender. setupData(scene);
1116+ shadowRender. setupData(scene);
1117+ List<Model > modelList = new ArrayList<> (scene. getModelMap(). values());
1118+ modelList. forEach(m - > m. getMeshDataList(). clear());
1119+ }
1120+ }
1121+ ```
1122+
1123+ We need toi update the ` TextureCache ` class to provide a method to return all the textures:
1124+
1125+ ``` java
1126+ public class TextureCache {
1127+ ...
1128+ public Collection<Texture > getAll () {
1129+ return textureMap. values();
1130+ }
1131+ ...
1132+ }
1133+ ```
1134+
1135+ Since we have modified the class hierarchy that deals with models and materials, we need to update the ` SkyBox ` class (loading individual models require now additional steps):
1136+
1137+ ``` java
1138+ public class SkyBox {
1139+
1140+ private Material material;
1141+ private Mesh mesh;
1142+ ...
1143+ public SkyBox (String skyBoxModelPath , TextureCache textureCache , MaterialCache materialCache ) {
1144+ skyBoxModel = ModelLoader . loadModel(" skybox-model" , skyBoxModelPath, textureCache, materialCache, false );
1145+ MeshData meshData = skyBoxModel. getMeshDataList(). get(0 );
1146+ material = materialCache. getMaterial(meshData. getMaterialIdx());
1147+ mesh = new Mesh (meshData);
1148+ skyBoxModel. getMeshDataList(). clear();
1149+ skyBoxEntity = new Entity (" skyBoxEntity-entity" , skyBoxModel. getId());
1150+ }
1151+
1152+ public void cleanuo () {
1153+ mesh. cleanup();
1154+ }
1155+
1156+ public Material getMaterial () {
1157+ return material;
1158+ }
1159+
1160+ public Mesh getMesh () {
1161+ return mesh;
1162+ }
1163+ ...
1164+ }
1165+ ```
1166+
1167+ These changes also affect the ` SkyBoxRender ` class. For sky bos render we will not use indirect drawing (it is not worth it since we will be rendering just one mesh):
1168+
1169+ ``` java
1170+ public class SkyBoxRender {
1171+ ...
1172+ public void render (Scene scene ) {
1173+ SkyBox skyBox = scene. getSkyBox();
1174+ if (skyBox == null ) {
1175+ return ;
1176+ }
1177+ shaderProgram. bind();
1178+
1179+ uniformsMap. setUniform(" projectionMatrix" , scene. getProjection(). getProjMatrix());
1180+ viewMatrix. set(scene. getCamera(). getViewMatrix());
1181+ viewMatrix. m30(0 );
1182+ viewMatrix. m31(0 );
1183+ viewMatrix. m32(0 );
1184+ uniformsMap. setUniform(" viewMatrix" , viewMatrix);
1185+ uniformsMap. setUniform(" txtSampler" , 0 );
1186+
1187+ Entity skyBoxEntity = skyBox. getSkyBoxEntity();
1188+ TextureCache textureCache = scene. getTextureCache();
1189+ Material material = skyBox. getMaterial();
1190+ Mesh mesh = skyBox. getMesh();
1191+ Texture texture = textureCache. getTexture(material. getTexturePath());
1192+ glActiveTexture(GL_TEXTURE0 );
1193+ texture. bind();
1194+
1195+ uniformsMap. setUniform(" diffuse" , material. getDiffuseColor());
1196+ uniformsMap. setUniform(" hasTexture" , texture. getTexturePath(). equals(TextureCache . DEFAULT_TEXTURE ) ? 0 : 1 );
1197+
1198+ glBindVertexArray(mesh. getVaoId());
1199+
1200+ uniformsMap. setUniform(" modelMatrix" , skyBoxEntity. getModelMatrix());
1201+ glDrawElements(GL_TRIANGLES , mesh. getNumVertices(), GL_UNSIGNED_INT , 0 );
1202+
1203+ glBindVertexArray(0 );
1204+
1205+ shaderProgram. unbind();
1206+ }
1207+ ...
1208+ }
1209+ ```
1210+
1211+ In the ` Scene ` class, we just do not need to invoke the ` Scene ` ` cleanup ` method (since the data associated to the buffers is in the ` RenderBuffers ` class):
1212+
1213+ ``` java
1214+ public class Engine {
1215+ ...
1216+ private void cleanup () {
1217+ appLogic. cleanup();
1218+ render. cleanup();
1219+ window. cleanup();
1220+ }
1221+ ...
1222+ }
1223+ ```
1224+
1225+ Finally, in the ` Main ` class, we will load two entities associated to a cube model. We will rotate them independently to check that the code works ok. The most important part is to cal the ` Render ` class ` setupData ` method when everything is loaded.
1226+
1227+ ``` java
1228+ public class Main implements IAppLogic {
1229+ ...
1230+ private Entity cubeEntity1;
1231+ private Entity cubeEntity2;
1232+ ...
1233+ private float rotation;
1234+
1235+ public static void main (String [] args ) {
1236+ ...
1237+ Engine gameEng = new Engine (" chapter-20" , opts, main);
1238+ ...
1239+ }
1240+
1241+ public void init (Window window , Scene scene , Render render ) {
1242+ ...
1243+ Model terrainModel = ModelLoader . loadModel(terrainModelId, " resources/models/terrain/terrain.obj" ,
1244+ scene. getTextureCache(), scene. getMaterialCache(), false );
1245+ ...
1246+ Model cubeModel = ModelLoader . loadModel(" cube-model" , " resources/models/cube/cube.obj" ,
1247+ scene. getTextureCache(), scene. getMaterialCache(), false );
1248+ scene. addModel(cubeModel);
1249+ cubeEntity1 = new Entity (" cube-entity-1" , cubeModel. getId());
1250+ cubeEntity1. setPosition(0 , 2 , - 1 );
1251+ cubeEntity1. updateModelMatrix();
1252+ scene. addEntity(cubeEntity1);
1253+
1254+ cubeEntity2 = new Entity (" cube-entity-2" , cubeModel. getId());
1255+ cubeEntity2. setPosition(- 2 , 2 , - 1 );
1256+ cubeEntity2. updateModelMatrix();
1257+ scene. addEntity(cubeEntity2);
1258+
1259+ render. setupData(scene);
1260+ ...
1261+ SkyBox skyBox = new SkyBox (" resources/models/skybox/skybox.obj" , scene. getTextureCache(),
1262+ scene. getMaterialCache());
1263+ ...
1264+ }
1265+ ...
1266+ public void update (Window window , Scene scene , long diffTimeMillis ) {
1267+ rotation += 1.5 ;
1268+ if (rotation > 360 ) {
1269+ rotation = 0 ;
1270+ }
1271+ cubeEntity1. setRotation(1 , 1 , 1 , (float ) Math . toRadians(rotation));
1272+ cubeEntity1. updateModelMatrix();
1273+
1274+ cubeEntity2. setRotation(1 , 1 , 1 , (float ) Math . toRadians(360 - rotation));
1275+ cubeEntity2. updateModelMatrix();
1276+ }
1277+ }
1278+
1279+ ```
1280+
1281+ With all of that changes implemented you should be able to see something similar to this.
1282+
1283+ ![ Screen shot] ( screenshot.png )
1284+
9221285
9231286[ Next chapter] ( ../chapter-21/chapter-21.md )
0 commit comments