In this post, I show how to update shaders based on the current layer.
This post is part of the OpenGL 2D Facade series
I choose the translation as the property to update for each layer or group of layers. The ground layer has a translation we can control with the arrows. Simultaneously, the translation of the front layers (frame box and text) remains unchanged. The rendering is fast (compared to a pure Python implementation) since the GPU performs most computations:
First of all, we need allow properties per layer in the facade. I propose a solution with groups of layers where each group has specific properties like the translation:
It reduces the number of methods in the GUIFacade
interface, a better design since too much functionality in a single class is not optimal.
The GUIFacade
interface has new methods to create and move layer groups between levels. These levels are the order in which we render the groups: level 0 is the first one (background), level 1 the next one, etc.
The LayerGroup
interface contains methods similar to those previously in GUIFacade
, except for the level argument that controls the rendering order. There is a setTranslation()
method that sets the translation for the layer group. You can imagine any other property like the brightness or the scale and then create the corresponding methods.
The Layer
interface is as before. We could also add layer-specific properties: you can add methods for each case.
The OpenGLLayerGroup
class stores a reference to the facade, the list of layers, and the translation vector (translationX
, translationY
).
There are several solutions to update shaders during the rendering.
The first one is to create several shader programs and select them during the rendering using glUseProgram()
. This approach is interesting if the behavior of each case is a complex combination of properties. In other words, if you need to write a lot of code in your shader to select or build the proper layer rendering, you should create several shader programs. However, this approach is not efficient because shader switching is slow.
A second approach consists of using a single shader and update uniform variables during the rendering. These updates are fast compared to shader switching. We can use the values directly, in which case there is no overhead. We can also use them in branches (if
statement) to select a behavior. In this case, there is a small overhead that depends on the number of if
to evaluate.
Most methods are straightforward to implement since the design already contains the main part of the logic.
The setTranslation()
method in the OpenGLLayerGroup
class computes and stores the translation in OpenGL Coordinates:
def setTranslation(self, x: float, y: float):
self.__translationX = -x * self.__gui.screenPixelWidth
self.__translationY = y * self.__gui.screenPixelHeight
We use a usual orientation, where the horizontal axis is left-right, and the vertical one is top-bottom. The OpenGL vertical axis is bottom-top, which is why translationX
is opposed to translationY
.
The screenPixelWidth
and screenPixelHeight
contain the pixel size in OpenGL screen coordinates. Remind that these coordinates are float values from -1 to 1 (see this series's first posts for details).
The render()
method of the OpenGLGUIFacade
class renders all the layer groups:
def render(self):
glClear(GL_COLOR_BUFFER_BIT)
glUseProgram(self.__shaderProgramId)
translationShaderVar = glGetUniformLocation(self.__shaderProgramId, "translation")
for layerGroup in self.__layerGroups:
if layerGroup is None:
continue
glUniform4f(translationShaderVar,
layerGroup.translationX,
layerGroup.translationY,
0.0, 0.0)
layerGroup.draw()
glUseProgram(0)
pygame.display.flip()
self.__clock.tick(60)
Line 2 clears all the screen (with a black color).
Line 4 selects the shaders. We use the following vertex shader:
#version 330
layout (location=0) in vec4 vertex;
layout (location=1) in vec2 inputUV;
out vec2 outputUV;
uniform vec4 translation;
void main() {
gl_Position = vertex + translation;
outputUV = inputUV;
}
Line 6 gets an id to manipulate the uniform variable value in the shader.
Lines 7-14 iterate through the layer groups.
Lines 10-13 sets the translation in the shader.
Line 14 calls the draw()
method of the LayerGroup
class, which calls the draw()
method of all layers in the group.
In the next post, I'll show how to load and save game state.