Skip to content

Commit 6263462

Browse files
committed
Complete chapter 21
1 parent de3e1e0 commit 6263462

File tree

1 file changed

+341
-5
lines changed

1 file changed

+341
-5
lines changed

chapter-21/chapter-21.md

+341-5
Original file line numberDiff line numberDiff line change
@@ -329,14 +329,350 @@ As in the previous methods, we will store the weights and bone indices informati
329329

330330
## Compute shaders
331331

332-
TBD: AnimationRender
332+
It is turn now to implement animation transformations through compute shaders. As it has been said before, a shader is like any other shader but it does not compose any restrictions on its inputs and its outputs. We will use them to transform data, they will have access to the global buffers that hold information about binding poses and animation transformation matrices and it will dump the result into another buffer. The shader code for animations (`anim.comp`) is defined like this:
333+
334+
```glsl
335+
#version 460
336+
337+
layout (std430, binding=0) readonly buffer srcBuf {
338+
float data[];
339+
} srcVector;
340+
341+
layout (std430, binding=1) readonly buffer weightsBuf {
342+
float data[];
343+
} weightsVector;
344+
345+
layout (std430, binding=2) readonly buffer bonesBuf {
346+
mat4 data[];
347+
} bonesMatrices;
348+
349+
layout (std430, binding=3) buffer dstBuf {
350+
float data[];
351+
} dstVector;
352+
353+
struct DrawParameters
354+
{
355+
int srcOffset;
356+
int srcSize;
357+
int weightsOffset;
358+
int bonesMatricesOffset;
359+
int dstOffset;
360+
};
361+
uniform DrawParameters drawParameters;
362+
363+
layout (local_size_x=1, local_size_y=1, local_size_z=1) in;
364+
365+
void main()
366+
{
367+
int baseIdx = int(gl_GlobalInvocationID.x) * 14;
368+
uint baseIdxWeightsBuf = drawParameters.weightsOffset + int(gl_GlobalInvocationID.x) * 8;
369+
uint baseIdxSrcBuf = drawParameters.srcOffset + baseIdx;
370+
uint baseIdxDstBuf = drawParameters.dstOffset + baseIdx;
371+
if (baseIdx >= drawParameters.srcSize) {
372+
return;
373+
}
374+
375+
vec4 weights = vec4(weightsVector.data[baseIdxWeightsBuf], weightsVector.data[baseIdxWeightsBuf + 1], weightsVector.data[baseIdxWeightsBuf + 2], weightsVector.data[baseIdxWeightsBuf + 3]);
376+
ivec4 bonesIndices = ivec4(weightsVector.data[baseIdxWeightsBuf + 4], weightsVector.data[baseIdxWeightsBuf + 5], weightsVector.data[baseIdxWeightsBuf + 6], weightsVector.data[baseIdxWeightsBuf + 7]);
377+
378+
vec4 position = vec4(srcVector.data[baseIdxSrcBuf], srcVector.data[baseIdxSrcBuf + 1], srcVector.data[baseIdxSrcBuf + 2], 1);
379+
position =
380+
weights.x * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.x] * position +
381+
weights.y * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.y] * position +
382+
weights.z * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.z] * position +
383+
weights.w * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.w] * position;
384+
dstVector.data[baseIdxDstBuf] = position.x / position.w;
385+
dstVector.data[baseIdxDstBuf + 1] = position.y / position.w;
386+
dstVector.data[baseIdxDstBuf + 2] = position.z / position.w;
387+
388+
baseIdxSrcBuf += 3;
389+
baseIdxDstBuf += 3;
390+
vec4 normal = vec4(srcVector.data[baseIdxSrcBuf], srcVector.data[baseIdxSrcBuf + 1], srcVector.data[baseIdxSrcBuf + 2], 0);
391+
normal =
392+
weights.x * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.x] * normal +
393+
weights.y * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.y] * normal +
394+
weights.z * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.z] * normal +
395+
weights.w * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.w] * normal;
396+
dstVector.data[baseIdxDstBuf] = normal.x;
397+
dstVector.data[baseIdxDstBuf + 1] = normal.y;
398+
dstVector.data[baseIdxDstBuf + 2] = normal.z;
399+
400+
baseIdxSrcBuf += 3;
401+
baseIdxDstBuf += 3;
402+
vec4 tangent = vec4(srcVector.data[baseIdxSrcBuf], srcVector.data[baseIdxSrcBuf + 1], srcVector.data[baseIdxSrcBuf + 2], 0);
403+
tangent =
404+
weights.x * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.x] * tangent +
405+
weights.y * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.y] * tangent +
406+
weights.z * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.z] * tangent +
407+
weights.w * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.w] * tangent;
408+
dstVector.data[baseIdxDstBuf] = tangent.x;
409+
dstVector.data[baseIdxDstBuf + 1] = tangent.y;
410+
dstVector.data[baseIdxDstBuf + 2] = tangent.z;
411+
412+
baseIdxSrcBuf += 3;
413+
baseIdxDstBuf += 3;
414+
vec4 bitangent = vec4(srcVector.data[baseIdxSrcBuf], srcVector.data[baseIdxSrcBuf + 1], srcVector.data[baseIdxSrcBuf + 2], 0);
415+
bitangent =
416+
weights.x * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.x] * bitangent +
417+
weights.y * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.y] * bitangent +
418+
weights.z * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.z] * bitangent +
419+
weights.w * bonesMatrices.data[drawParameters.bonesMatricesOffset + bonesIndices.w] * bitangent;
420+
dstVector.data[baseIdxDstBuf] = bitangent.x;
421+
dstVector.data[baseIdxDstBuf + 1] = bitangent.y;
422+
dstVector.data[baseIdxDstBuf + 2] = bitangent.z;
423+
424+
baseIdxSrcBuf += 3;
425+
baseIdxDstBuf += 3;
426+
vec2 textCoords = vec2(srcVector.data[baseIdxSrcBuf], srcVector.data[baseIdxSrcBuf + 1]);
427+
dstVector.data[baseIdxDstBuf] = textCoords.x;
428+
dstVector.data[baseIdxDstBuf + 1] = textCoords.y;
429+
}
430+
```
431+
432+
As you can see the code is very similar to the one used in previous chapters for animation (unrolling the loops). You wil notice that we need to apply an offset for each mesh, since the data is now stored in a common buffer. In order to support push constants in the compute shader. The input / output data is defined as a set of buffers:
433+
* `srcVector`: this buffer will contain vertices information (positions, normals, etc.).
434+
* `weightsVector`: this buffer will contain the weights for the current animation state for a specific mesh and entity.
435+
* `bonesMatrices`: the same but with bones matrices information.
436+
* `dstVector`: this buffer will hold the result of applying animation transformations.
437+
438+
The interesting thing is how we compute that offset. The `gl_GlobalInvocationID` variable will contain the index of work item currently being execute din the compute shader. In our case, we will create as many work items as "chunks" we will have in the global buffer. A chunk models a vertex data, that its its position, normals, texture coordinates etc. Therefore, por vertices data each time the work item is increased, we need to move forward in the buffer 14 positions (14 floats: 3 for positions,. 3 for normals, 3 for bitangents, 3 for tangent and 2 for texture coordinates). The same applies for weights buffers which holds data for weights (4 floats) and bone indices (4 floats) associated to each vertex. We use also the vertex offset to move long the binding poses buffer and the destination buffer along with the `drawParameters` data which point to th ebase offset for each mesh and entity.
439+
440+
We will use this shader in a new class named `AnimationRender` which is defined like this:
441+
442+
```java
443+
package org.lwjglb.engine.graph;
444+
445+
import org.lwjglb.engine.scene.*;
446+
447+
import java.util.*;
448+
449+
import static org.lwjgl.opengl.GL43.*;
450+
451+
public class AnimationRender {
452+
453+
private ShaderProgram shaderProgram;
454+
private UniformsMap uniformsMap;
455+
456+
public AnimationRender() {
457+
List<ShaderProgram.ShaderModuleData> shaderModuleDataList = new ArrayList<>();
458+
shaderModuleDataList.add(new ShaderProgram.ShaderModuleData("resources/shaders/anim.comp", GL_COMPUTE_SHADER));
459+
shaderProgram = new ShaderProgram(shaderModuleDataList);
460+
createUniforms();
461+
}
462+
463+
public void cleanup() {
464+
shaderProgram.cleanup();
465+
}
466+
467+
private void createUniforms() {
468+
uniformsMap = new UniformsMap(shaderProgram.getProgramId());
469+
uniformsMap.createUniform("drawParameters.srcOffset");
470+
uniformsMap.createUniform("drawParameters.srcSize");
471+
uniformsMap.createUniform("drawParameters.weightsOffset");
472+
uniformsMap.createUniform("drawParameters.bonesMatricesOffset");
473+
uniformsMap.createUniform("drawParameters.dstOffset");
474+
}
475+
476+
public void render(Scene scene, RenderBuffers globalBuffer) {
477+
shaderProgram.bind();
478+
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, globalBuffer.getBindingPosesBuffer());
479+
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, globalBuffer.getBonesIndicesWeightsBuffer());
480+
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, globalBuffer.getBonesMatricesBuffer());
481+
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, globalBuffer.getDestAnimationBuffer());
482+
483+
int dstOffset = 0;
484+
for (Model model : scene.getModelMap().values()) {
485+
if (model.isAnimated()) {
486+
for (RenderBuffers.MeshDrawData meshDrawData : model.getMeshDrawDataList()) {
487+
RenderBuffers.AnimMeshDrawData animMeshDrawData = meshDrawData.animMeshDrawData();
488+
Entity entity = animMeshDrawData.entity();
489+
Model.AnimatedFrame frame = entity.getAnimationData().getCurrentFrame();
490+
int groupSize = (int) Math.ceil((float) meshDrawData.sizeInBytes() / (14 * 4));
491+
uniformsMap.setUniform("drawParameters.srcOffset", animMeshDrawData.bindingPoseOffset());
492+
uniformsMap.setUniform("drawParameters.srcSize", meshDrawData.sizeInBytes() / 4);
493+
uniformsMap.setUniform("drawParameters.weightsOffset", animMeshDrawData.weightsOffset());
494+
uniformsMap.setUniform("drawParameters.bonesMatricesOffset", frame.getOffset());
495+
uniformsMap.setUniform("drawParameters.dstOffset", dstOffset);
496+
glDispatchCompute(groupSize, 1, 1);
497+
dstOffset += meshDrawData.sizeInBytes() / 4;
498+
}
499+
}
500+
}
501+
502+
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
503+
shaderProgram.unbind();
504+
}
505+
}
506+
```
507+
508+
As you can see, the definition is quite simple, when creating the shader we need to set up the `GL_COMPUTE_SHADER` to indicate that this is the compute shader. The uniforms that wew ill use will contain the offset in binding pose buffer, weights and matrices buffer and destination buffer. In the `render` method we just iterate over the models and get the mesh draw data for each entity to dispatch a call to the compute shader by invoking the `glDispatchCompute`. The key, again tos the `groupSize` variable. As you can see we need to invoke the shader as many times as vertices chunks there are in the mesh.
333509

334510
## Other changes
335511

336-
TODO:
337-
- SceneRender
338-
- ShadowRender
339-
- Main
512+
We need to update the `SceneRender` class to render the entities associated to animated models. The changes are shown below:
513+
514+
```java
515+
public class SceneRender {
516+
...
517+
private int animDrawCount;
518+
private int animRenderBufferHandle;
519+
...
520+
public void cleanup() {
521+
...
522+
glDeleteBuffers(animRenderBufferHandle);
523+
}
524+
...
525+
public void render(Scene scene, RenderBuffers renderBuffers, GBuffer gBuffer) {
526+
...
527+
// Animated meshes
528+
drawElement = 0;
529+
modelList = scene.getModelMap().values().stream().filter(m -> m.isAnimated()).toList();
530+
for (Model model : modelList) {
531+
for (RenderBuffers.MeshDrawData meshDrawData : model.getMeshDrawDataList()) {
532+
RenderBuffers.AnimMeshDrawData animMeshDrawData = meshDrawData.animMeshDrawData();
533+
Entity entity = animMeshDrawData.entity();
534+
String name = "drawElements[" + drawElement + "]";
535+
uniformsMap.setUniform(name + ".modelMatrixIdx", entitiesIdxMap.get(entity.getId()));
536+
uniformsMap.setUniform(name + ".materialIdx", meshDrawData.materialIdx());
537+
drawElement++;
538+
}
539+
}
540+
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, animRenderBufferHandle);
541+
glBindVertexArray(renderBuffers.getAnimVaoId());
542+
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, 0, animDrawCount, 0);
543+
544+
glBindVertexArray(0);
545+
glEnable(GL_BLEND);
546+
shaderProgram.unbind();
547+
}
548+
549+
private void setupAnimCommandBuffer(Scene scene) {
550+
List<Model> modelList = scene.getModelMap().values().stream().filter(m -> m.isAnimated()).toList();
551+
int numMeshes = 0;
552+
for (Model model : modelList) {
553+
numMeshes += model.getMeshDrawDataList().size();
554+
}
555+
556+
int firstIndex = 0;
557+
int baseInstance = 0;
558+
ByteBuffer commandBuffer = MemoryUtil.memAlloc(numMeshes * COMMAND_SIZE);
559+
for (Model model : modelList) {
560+
for (RenderBuffers.MeshDrawData meshDrawData : model.getMeshDrawDataList()) {
561+
// count
562+
commandBuffer.putInt(meshDrawData.vertices());
563+
// instanceCount
564+
commandBuffer.putInt(1);
565+
commandBuffer.putInt(firstIndex);
566+
// baseVertex
567+
commandBuffer.putInt(meshDrawData.offset());
568+
commandBuffer.putInt(baseInstance);
569+
570+
firstIndex += meshDrawData.vertices();
571+
baseInstance++;
572+
}
573+
}
574+
commandBuffer.flip();
575+
576+
animDrawCount = commandBuffer.remaining() / COMMAND_SIZE;
577+
578+
animRenderBufferHandle = glGenBuffers();
579+
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, animRenderBufferHandle);
580+
glBufferData(GL_DRAW_INDIRECT_BUFFER, commandBuffer, GL_DYNAMIC_DRAW);
581+
582+
MemoryUtil.memFree(commandBuffer);
583+
}
584+
585+
public void setupData(Scene scene) {
586+
...
587+
setupAnimCommandBuffer(scene);
588+
...
589+
}
590+
...
591+
}
592+
```
593+
594+
The code to render animated models is quite similar as the one used for static entities. The differences is that we are not grouping entities that share the same model, we need to record draw instructions for each of the entities and associated meshes.
595+
596+
We need also to update the `ShadowRender` class to render animated models:
597+
598+
```java
599+
public class ShadowRender {
600+
...
601+
private int animDrawCount;
602+
private int animRenderBufferHandle;
603+
...
604+
public void cleanup() {
605+
...
606+
glDeleteBuffers(animRenderBufferHandle);
607+
}
608+
...
609+
public void render(Scene scene, RenderBuffers renderBuffers) {
610+
...
611+
// Anim meshes
612+
drawElement = 0;
613+
modelList = scene.getModelMap().values().stream().filter(m -> m.isAnimated()).toList();
614+
for (Model model : modelList) {
615+
for (RenderBuffers.MeshDrawData meshDrawData : model.getMeshDrawDataList()) {
616+
RenderBuffers.AnimMeshDrawData animMeshDrawData = meshDrawData.animMeshDrawData();
617+
Entity entity = animMeshDrawData.entity();
618+
String name = "drawElements[" + drawElement + "]";
619+
uniformsMap.setUniform(name + ".modelMatrixIdx", entitiesIdxMap.get(entity.getId()));
620+
drawElement++;
621+
}
622+
}
623+
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, animRenderBufferHandle);
624+
glBindVertexArray(renderBuffers.getAnimVaoId());
625+
for (int i = 0; i < CascadeShadow.SHADOW_MAP_CASCADE_COUNT; i++) {
626+
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowBuffer.getDepthMapTexture().getIds()[i], 0);
627+
628+
CascadeShadow shadowCascade = cascadeShadows.get(i);
629+
uniformsMap.setUniform("projViewMatrix", shadowCascade.getProjViewMatrix());
630+
631+
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, 0, animDrawCount, 0);
632+
}
633+
634+
glBindVertexArray(0);
635+
}
636+
637+
private void setupAnimCommandBuffer(Scene scene) {
638+
List<Model> modelList = scene.getModelMap().values().stream().filter(m -> m.isAnimated()).toList();
639+
int numMeshes = 0;
640+
for (Model model : modelList) {
641+
numMeshes += model.getMeshDrawDataList().size();
642+
}
643+
644+
int firstIndex = 0;
645+
int baseInstance = 0;
646+
ByteBuffer commandBuffer = MemoryUtil.memAlloc(numMeshes * COMMAND_SIZE);
647+
for (Model model : modelList) {
648+
for (RenderBuffers.MeshDrawData meshDrawData : model.getMeshDrawDataList()) {
649+
RenderBuffers.AnimMeshDrawData animMeshDrawData = meshDrawData.animMeshDrawData();
650+
Entity entity = animMeshDrawData.entity();
651+
// count
652+
commandBuffer.putInt(meshDrawData.vertices());
653+
// instanceCount
654+
commandBuffer.putInt(1);
655+
commandBuffer.putInt(firstIndex);
656+
// baseVertex
657+
commandBuffer.putInt(meshDrawData.offset());
658+
commandBuffer.putInt(baseInstance);
659+
660+
firstIndex += meshDrawData.vertices();
661+
baseInstance++;
662+
}
663+
}
664+
commandBuffer.flip();
665+
666+
animDrawCount = commandBuffer.remaining() / COMMAND_SIZE;
667+
668+
animRenderBufferHandle = glGenBuffers();
669+
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, animRenderBufferHandle);
670+
glBufferData(GL_DRAW_INDIRECT_BUFFER, commandBuffer, GL_DYNAMIC_DRAW);
671+
672+
MemoryUtil.memFree(commandBuffer);
673+
}
674+
}
675+
```
340676

341677
In the `Render` class we just need to instantiate the `AnimationRender` class, and use it in the `render` loop and the `cleanup` method. In the `render` loop we will invoke the `AnimationRender` class `render` method at the very beginning, so animation transformations are applied prior to render the scene.
342678

0 commit comments

Comments
 (0)