Skip to content

Commit 15d1c20

Browse files
committed
Chapter 20 additional content (Draft)
1 parent b2b3f19 commit 15d1c20

File tree

1 file changed

+248
-2
lines changed

1 file changed

+248
-2
lines changed

chapter-20/chapter-20.md

Lines changed: 248 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -538,12 +538,258 @@ public class RenderBuffers {
538538

539539
We just define the vertex attributes for the VAO as in previous examples. The only difference here is that we are using a single VBO for them.
540540

541+
Prior to examining the changes in the `SceneRender` class, let's start with the vertex shader (`scene.vert`), which starts like this:
542+
543+
```glsl
544+
#version 460
545+
546+
const int MAX_DRAW_ELEMENTS = 100;
547+
const int MAX_ENTITIES = 50;
548+
549+
layout (location=0) in vec3 position;
550+
layout (location=1) in vec3 normal;
551+
layout (location=2) in vec3 tangent;
552+
layout (location=3) in vec3 bitangent;
553+
layout (location=4) in vec2 texCoord;
554+
555+
out vec3 outNormal;
556+
out vec3 outTangent;
557+
out vec3 outBitangent;
558+
out vec2 outTextCoord;
559+
out vec4 outViewPosition;
560+
out vec4 outWorldPosition;
561+
out uint outMaterialIdx;
562+
563+
struct DrawElement
564+
{
565+
int modelMatrixIdx;
566+
int materialIdx;
567+
};
568+
569+
uniform mat4 projectionMatrix;
570+
uniform mat4 viewMatrix;
571+
uniform mat4 modelMatrix;
572+
uniform DrawElement drawElements[MAX_DRAW_ELEMENTS];
573+
uniform mat4 modelMatrices[MAX_ENTITIES];
574+
...
575+
```
576+
577+
The first thing that you will notice is that we have increased the version to `460`. We also have removed the constants associated with animations (`MAX_WEIGHTS` and `MAX_BONES`), the attributes for bones indices and the uniform for bone matrices. You will see in next chapter that we will no need this information here for animations. We have created two new constants to define the size oif the `drawElements` and `modelMatrices` uniforms. The `drawElements` uniform will hold `DrawElement` instances. It will have one item per mesh and associated entity. If you remember, we will record a single instruction to draw all the items associated to a mesh, setting the number of instances to be drawn. We will need however, specific per entity data, such as the model matrix. This will be hold in the `drawElements` array, which will also point to the material index to be used. The `modelMatrices` array will just hold the model matrices for each of the entities. Material information will be used in the fragment shader you we pass it using the `outMaterialIdx` output variable.
578+
579+
The `main` function, since we do not have to deal with animations, has been simplified a lot:
580+
581+
```glsl
582+
...
583+
void main()
584+
{
585+
vec4 initPos = vec4(position, 1.0);
586+
vec4 initNormal = vec4(normal, 0.0);
587+
vec4 initTangent = vec4(tangent, 0.0);
588+
vec4 initBitangent = vec4(bitangent, 0.0);
589+
590+
uint idx = gl_BaseInstance + gl_InstanceID;
591+
DrawElement drawElement = drawElements[idx];
592+
outMaterialIdx = drawElement.materialIdx;
593+
mat4 modelMatrix = modelMatrices[drawElement.modelMatrixIdx];
594+
mat4 modelViewMatrix = viewMatrix * modelMatrix;
595+
outWorldPosition = modelMatrix * initPos;
596+
outViewPosition = viewMatrix * outWorldPosition;
597+
gl_Position = projectionMatrix * outViewPosition;
598+
outNormal = normalize(modelViewMatrix * initNormal).xyz;
599+
outTangent = normalize(modelViewMatrix * initTangent).xyz;
600+
outBitangent = normalize(modelViewMatrix * initBitangent).xyz;
601+
outTextCoord = texCoord;
602+
}
603+
```
604+
605+
The key here is to get the proper index to access the `drawElements` size. We use the `gl_BaseInstance` and `gl_InstanceID` built-in in variables. When recording the instructions for indirect drawing we will use the `baseInstance` attribute. The value for that attribute will be the one associated to `gl_BaseInstance` built-in in variable. The `gl_InstanceID` will start at `0` whenever we change form a mesh to another, and will be increased for of of the instances of the entities associated to the models. Therefore, by combining this two variables we will be able to access the per-entity specific information in the `drawElements` array. Once we have the proper index, we just transform positions and normal information as in previous versions of the shader.
606+
607+
The scene fragment shader (`scene.frag`) is defined like this:
608+
609+
```glsl
610+
#version 330
611+
612+
const int MAX_MATERIALS = 20;
613+
const int MAX_TEXTURES = 16;
614+
615+
in vec3 outNormal;
616+
in vec3 outTangent;
617+
in vec3 outBitangent;
618+
in vec2 outTextCoord;
619+
in vec4 outViewPosition;
620+
in vec4 outWorldPosition;
621+
flat in uint outMaterialIdx;
622+
623+
layout (location = 0) out vec4 buffAlbedo;
624+
layout (location = 1) out vec4 buffNormal;
625+
layout (location = 2) out vec4 buffSpecular;
626+
627+
struct Material
628+
{
629+
vec4 diffuse;
630+
vec4 specular;
631+
float reflectance;
632+
int normalMapIdx;
633+
int textureIdx;
634+
};
635+
636+
uniform sampler2D txtSampler[MAX_TEXTURES];
637+
uniform Material materials[MAX_MATERIALS];
638+
639+
vec3 calcNormal(int idx, vec3 normal, vec3 tangent, vec3 bitangent, vec2 textCoords) {
640+
mat3 TBN = mat3(tangent, bitangent, normal);
641+
vec3 newNormal = texture(txtSampler[idx], textCoords).rgb;
642+
newNormal = normalize(newNormal * 2.0 - 1.0);
643+
newNormal = normalize(TBN * newNormal);
644+
return newNormal;
645+
}
646+
647+
void main() {
648+
Material material = materials[outMaterialIdx];
649+
vec4 text_color = texture(txtSampler[material.textureIdx], outTextCoord);
650+
vec4 diffuse = text_color + material.diffuse;
651+
if (diffuse.a < 0.5) {
652+
discard;
653+
}
654+
vec4 specular = text_color + material.specular;
655+
656+
vec3 normal = outNormal;
657+
if (material.normalMapIdx > 0) {
658+
normal = calcNormal(material.normalMapIdx, outNormal, outTangent, outBitangent, outTextCoord);
659+
}
660+
661+
buffAlbedo = vec4(diffuse.xyz, material.reflectance);
662+
buffNormal = vec4(0.5 * normal + 0.5, 1.0);
663+
buffSpecular = specular;
664+
}
665+
```
666+
667+
The main changes are related to the way we access material information and textures. We will now have an array of materials information, which will be accessed by the index we calculated in the vertex shader which is now in the `outMaterialIdx` input variable (which has the `flat` modifier which states that this value should not be interpolated from vertex to fragment stage). We will be using an array of textures to access either regular textures or normal maps. The index to those textures are stored now in the `Material` struct.
668+
669+
670+
Now it is the turn to examine the changes in the `SceneRender` class. We will start by defining a set of constants that will be used in the code and by modifying the `createUniforms` according to the changes in the shaders shown before:
671+
672+
```java
673+
public class RenderBuffers {
674+
...
675+
public static final int MAX_DRAW_ELEMENTS = 100;
676+
public static final int MAX_ENTITIES = 50;
677+
private static final int COMMAND_SIZE = 5 * 4;
678+
private static final int MAX_MATERIALS = 20;
679+
private static final int MAX_TEXTURES = 16;
680+
...
681+
private void createUniforms() {
682+
uniformsMap = new UniformsMap(shaderProgram.getProgramId());
683+
uniformsMap.createUniform("projectionMatrix");
684+
uniformsMap.createUniform("viewMatrix");
685+
686+
for (int i = 0; i < MAX_TEXTURES; i++) {
687+
uniformsMap.createUniform("txtSampler[" + i + "]");
688+
}
689+
690+
for (int i = 0; i < MAX_MATERIALS; i++) {
691+
String name = "materials[" + i + "]";
692+
uniformsMap.createUniform(name + ".diffuse");
693+
uniformsMap.createUniform(name + ".specular");
694+
uniformsMap.createUniform(name + ".reflectance");
695+
uniformsMap.createUniform(name + ".normalMapIdx");
696+
uniformsMap.createUniform(name + ".textureIdx");
697+
}
698+
699+
for (int i = 0; i < MAX_DRAW_ELEMENTS; i++) {
700+
String name = "drawElements[" + i + "]";
701+
uniformsMap.createUniform(name + ".modelMatrixIdx");
702+
uniformsMap.createUniform(name + ".materialIdx");
703+
}
704+
705+
for (int i = 0; i < MAX_ENTITIES; i++) {
706+
uniformsMap.createUniform("modelMatrices[" + i + "]");
707+
}
708+
}
709+
...
710+
}
711+
```
712+
713+
The main changes are in the `render` method, which is defined like this:
714+
715+
```java
716+
public class RenderBuffers {
717+
...
718+
public void render(Scene scene, RenderBuffers globalBuffer, GBuffer gBuffer) {
719+
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gBuffer.getGBufferId());
720+
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
721+
glViewport(0, 0, gBuffer.getWidth(), gBuffer.getHeight());
722+
glDisable(GL_BLEND);
723+
724+
shaderProgram.bind();
725+
726+
uniformsMap.setUniform("projectionMatrix", scene.getProjection().getProjMatrix());
727+
uniformsMap.setUniform("viewMatrix", scene.getCamera().getViewMatrix());
728+
729+
TextureCache textureCache = scene.getTextureCache();
730+
List<Texture> textures = textureCache.getAll().stream().toList();
731+
int numTextures = textures.size();
732+
if (numTextures > MAX_TEXTURES) {
733+
Logger.warn("Only " + MAX_TEXTURES + " textures can be used");
734+
}
735+
for (int i = 0; i < Math.min(MAX_TEXTURES, numTextures); i++) {
736+
uniformsMap.setUniform("txtSampler[" + i + "]", i);
737+
Texture texture = textures.get(i);
738+
glActiveTexture(GL_TEXTURE0 + i);
739+
texture.bind();
740+
}
741+
742+
Map<String, Integer> entitiesIdxMap = new HashMap<>();
743+
int entityIdx = 0;
744+
for (Model model : scene.getModelMap().values()) {
745+
List<Entity> entities = model.getEntitiesList();
746+
for (Entity entity : entities) {
747+
entitiesIdxMap.put(entity.getId(), entityIdx);
748+
uniformsMap.setUniform("modelMatrices[" + entityIdx + "]", entity.getModelMatrix());
749+
entityIdx++;
750+
}
751+
}
752+
753+
renderStaticMeshes(scene, globalBuffer, entitiesIdxMap);
754+
755+
glEnable(GL_BLEND);
756+
shaderProgram.unbind();
757+
}
758+
...
759+
}
760+
```
761+
762+
You can see that we now have to bind the array of texture samplers and activate all the texture units. In addition to that, we iterate over the entities and set up the uniform values for the model matrices. After that, we call the `renderStaticMeshes` method which will be the one that populates the indirect drawing buffer. In the next chapter we will see that we need to separate how we do this for static vs animated meshes. The `renderStaticMeshes` method is defined like this:
763+
764+
```java
765+
public class RenderBuffers {
766+
...
767+
private void renderStaticMeshes(Scene scene, RenderBuffers globalBuffer, Map<String, Integer> entitiesIdxMap) {
768+
List<Model> modelList = scene.getModelMap().values().stream().filter(m -> !m.isAnimated()).toList();
769+
770+
ByteBuffer commandBuffer = buildStaticCommandBuffer(modelList, entitiesIdxMap);
771+
int drawCount = commandBuffer.remaining() / COMMAND_SIZE;
772+
int bufferHandle = glGenBuffers();
773+
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, bufferHandle);
774+
glBufferData(GL_DRAW_INDIRECT_BUFFER, commandBuffer, GL_DYNAMIC_DRAW);
775+
776+
glBindVertexArray(globalBuffer.getStaticVaoId());
777+
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, 0, drawCount, 0);
778+
glBindVertexArray(0);
779+
780+
MemoryUtil.memFree(commandBuffer);
781+
glDeleteBuffers(bufferHandle);
782+
}
783+
...
784+
}
785+
```
786+
787+
In this case, we will populate the buffer that will hold the draw indirect instructions (by calling the `buildStaticCommandBuffer`). Each set of draw instructions si composed by five attributes, ech of the with a length of 4 bytes (total length of each set of parameters is what defines the `COMMAND_SIZE` constant). In this case, we are creating a new buffer in each draw call. This is not the most efficient way of doing it at all, but it keeps the things simple enough. In your game engine you will need to reuse a buffer. Also, there is no need to populate the indirect drawing buffer. We will keep this approach as an example, it gives you some flexibility so you can add new entities, but should think in caching for your engine.
541788

542789
SceneRender
543-
scene.vert
544-
scene.frag
545790
ShadowRender
546791
shadow.vert
792+
Render
547793
SkyBoxRender
548794
SkyBox
549795
TextureCache

0 commit comments

Comments
 (0)