Skip to content

Commit 75b28c0

Browse files
committed
Chapter 20
1 parent fa4d441 commit 75b28c0

File tree

2 files changed

+371
-8
lines changed

2 files changed

+371
-8
lines changed

chapter-20/chapter-20.md

Lines changed: 371 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

chapter-20/screenshot.png

51.7 KB
Loading

0 commit comments

Comments
 (0)