|
2 | 2 | OpenGL Objects |
3 | 3 | ============== |
4 | 4 |
|
5 | | -Write stuff.. |
| 5 | +We proved some simple and powerful wrappers over OpenGL features. |
| 6 | + |
| 7 | +- **Texture**: Textures from images or manually constructed/generated |
| 8 | +- **Shader**: Shader programs currently supporting vertex/fragment/geometry shaders |
| 9 | +- **Frame Buffer Object**: Offscreen rendering targets |
| 10 | +- **Vertex Array Object**: Represents the geometry we are drawing using a shader |
| 11 | + |
| 12 | +Texture |
| 13 | +^^^^^^^ |
| 14 | + |
| 15 | +Textures are normally loaded by requesting the resource by path/name in the initializer |
| 16 | +of an effect using the ``self.get_texture`` method inherited from the ``Effect`` base class. |
| 17 | + |
| 18 | +Textures can of course also be crated manually. |
| 19 | + |
| 20 | +.. autoclass:: demosys.opengl.texture.Texture |
| 21 | + :members: |
| 22 | + :undoc-members: |
| 23 | + :show-inheritance: |
| 24 | + |
| 25 | +Shader |
| 26 | +^^^^^^ |
| 27 | + |
| 28 | +In oder to draw something to the screen, we need a shader. There is no other way. |
| 29 | + |
| 30 | +Shader should ideally always be loaded from ``.glsl`` files residing in a shader directory |
| 31 | +in your effect or project global resource directory. Shaders have to be written in a single |
| 32 | +file were the different shader types are separated using preprocessors. |
| 33 | + |
| 34 | +Like textures they are loaded in the effect using the ``get_shader`` method in the initializer. |
| 35 | + |
| 36 | +Once we have the reference to the shader object we will need a VAO object |
| 37 | +in order to bind it. We could of course just call ``bind()``, but **the VAOs |
| 38 | +will do this for you**. More details in the VAO section below. |
| 39 | + |
| 40 | +.. code-block:: glsl |
| 41 | +
|
| 42 | + #version 410 |
| 43 | +
|
| 44 | + #if defined VERTEX_SHADER |
| 45 | + // Vertex shader here |
| 46 | + #elif defined FRAGMENT_SHADER |
| 47 | + // Fragment shader here |
| 48 | + #elif defined GEOMETRY_SHADER |
| 49 | + // Geometry shader here |
| 50 | + #endif |
| 51 | +
|
| 52 | +Once the shader is bound we can set uniforms through the various ``uniform_`` methods. |
| 53 | + |
| 54 | +Assuming we have a reference to a shader in ``s``: |
| 55 | + |
| 56 | +.. code-block:: python |
| 57 | +
|
| 58 | + # Set the uniform (float) with name 'value' to 1.0 |
| 59 | + s.uniform_1f("value", 1.0) |
| 60 | + # Set the uniform (mat4) with name `m_view' to a 4x4 matrix |
| 61 | + s.uniform_mat4("m_view", view_matrix) |
| 62 | + # Set the sampler2d uniform to use a Texture object we have loaded |
| 63 | + s.sampler_2d(0, "texture0", texture) |
| 64 | +
|
| 65 | +The Shader class contains an internal cache of all the uniform variables |
| 66 | +the shader has, so it will generally do very efficient type checks at run time |
| 67 | +and give useful error feedback if something is wrong. |
| 68 | + |
| 69 | +Other than setting uniforms and using the right file format for shaders, there |
| 70 | +are not much more to them. |
| 71 | + |
| 72 | +.. autoclass:: demosys.opengl.shader.Shader |
| 73 | + :members: |
| 74 | + :undoc-members: |
| 75 | + :show-inheritance: |
| 76 | + |
| 77 | +Vertex Array Object |
| 78 | +^^^^^^^^^^^^^^^^^^^ |
| 79 | + |
| 80 | +Vertex Array Objects represents the geometry we are drawing with shaders. |
| 81 | +They keep track of the buffer binding states of one or multiple Vertex |
| 82 | +Buffer Objects. |
| 83 | + |
| 84 | +VAOs and shaders interact in a very important way. The first time the VAO |
| 85 | +and shader interacts, they will figure out if they are compatible when it |
| 86 | +comes to the attributes in the shader and the buffers in the VAO. |
| 87 | + |
| 88 | +When we create VAOs we tell explicitly what attribute name each buffer belongs to. |
| 89 | + |
| 90 | +Example: I have three buffers representing positions, normals and uvs. |
| 91 | + |
| 92 | +- Map positions to ``in_position`` attribute with 3 components |
| 93 | +- Map normals to ``in_normal`` attribute with 3 components |
| 94 | +- Map uvs to the ``in_uv`` attribute with 2 components |
| 95 | + |
| 96 | +The vertex shader will have to define the exact same attribute names: |
| 97 | + |
| 98 | +.. code-block:: glsl |
| 99 | +
|
| 100 | + in vec3 in_position; |
| 101 | + in vec3 in_normal; |
| 102 | + in vec2 in_uv |
| 103 | +
|
| 104 | +This is not entirely true. The shader will at least have to define |
| 105 | +the ``in_position``. The other two attributes are optional. This |
| 106 | +is were the VAO and the Shader object negotiates the attribute binding. |
| 107 | +The VAO object will on-the-fly generate a version of itself that |
| 108 | +supports the shaders attributes. |
| 109 | + |
| 110 | +The VAO/Shader binding can also be used as a context manager as seen below, |
| 111 | +but this is optional. The context manager will return the reference to |
| 112 | +the shader so you can use a shorter name. |
| 113 | + |
| 114 | +.. code-block:: python |
| 115 | +
|
| 116 | + # Bind the shader and negotiate attribute binding |
| 117 | + with vao.bind(shader) as s: |
| 118 | + s.unform_1f("value", 1.0) |
| 119 | + # ... |
| 120 | + # Finally draw the geometry |
| 121 | + vao.draw() |
| 122 | +
|
| 123 | +When creating a VBO we need to use the `OpenGL.arrays.vbo.VBO instance` in |
| 124 | +PyOpenGL. We pass a numpy array to the constructor. It's imporant to use |
| 125 | +the correct ``dtype`` so it matches the type passed in ``add_array_buffer``. |
| 126 | + |
| 127 | +Each VBO is first added to the VAO using ``add_array_buffer``. This is simply |
| 128 | +to register the buffer and tell the VAO what format it has. |
| 129 | + |
| 130 | +The ``map_buffer`` part will define the actual attribute mapping. |
| 131 | +Without this the VAO is not complete. |
| 132 | + |
| 133 | +Calling ``build()`` will finalize and sanity check the VAO. |
| 134 | + |
| 135 | +The VAO will always do **very** strict error checking and give |
| 136 | +useful feedback when something is wrong. VAOs must also be |
| 137 | +assigned a name so the framework can reference it in error messages. |
| 138 | + |
| 139 | +.. code-block:: python |
| 140 | +
|
| 141 | + def quad_2d(width, height, xpos, ypos): |
| 142 | + """ |
| 143 | + Creates a 2D quad VAO using 2 triangles. |
| 144 | +
|
| 145 | + :param width: Width of the quad |
| 146 | + :param height: Height of the quad |
| 147 | + :param xpos: Center position x |
| 148 | + :param ypos: Center position y |
| 149 | + """ |
| 150 | + pos = VBO(numpy.array([ |
| 151 | + xpos - width / 2.0, ypos + height / 2.0, 0.0, |
| 152 | + xpos - width / 2.0, ypos - height / 2.0, 0.0, |
| 153 | + xpos + width / 2.0, ypos - height / 2.0, 0.0, |
| 154 | + xpos - width / 2.0, ypos + height / 2.0, 0.0, |
| 155 | + xpos + width / 2.0, ypos - height / 2.0, 0.0, |
| 156 | + xpos + width / 2.0, ypos + height / 2.0, 0.0, |
| 157 | + ], dtype=numpy.float32)) |
| 158 | + normals = VBO(numpy.array([ |
| 159 | + 0.0, 0.0, 1.0, |
| 160 | + 0.0, 0.0, 1.0, |
| 161 | + 0.0, 0.0, 1.0, |
| 162 | + 0.0, 0.0, 1.0, |
| 163 | + 0.0, 0.0, 1.0, |
| 164 | + 0.0, 0.0, 1.0, |
| 165 | + ], dtype=numpy.float32)) |
| 166 | + uvs = VBO(numpy.array([ |
| 167 | + 0.0, 1.0, |
| 168 | + 0.0, 0.0, |
| 169 | + 1.0, 0.0, |
| 170 | + 0.0, 1.0, |
| 171 | + 1.0, 0.0, |
| 172 | + 1.0, 1.0, |
| 173 | + ], dtype=numpy.float32)) |
| 174 | + vao = VAO("geometry:quad", mode=GL.GL_TRIANGLES) |
| 175 | + vao.add_array_buffer(GL.GL_FLOAT, pos) |
| 176 | + vao.add_array_buffer(GL.GL_FLOAT, normals) |
| 177 | + vao.add_array_buffer(GL.GL_FLOAT, uvs) |
| 178 | + vao.map_buffer(pos, "in_position", 3) |
| 179 | + vao.map_buffer(normals, "in_normal", 3) |
| 180 | + vao.map_buffer(uvs, "in_uv", 2) |
| 181 | + vao.build() |
| 182 | + return vao |
| 183 | +
|
| 184 | +We can also pass index/element buffers to VAOs. We can also use |
| 185 | +interleaved VBOs by passing the same VBO to ``map_buffer`` multiple |
| 186 | +times. |
| 187 | + |
| 188 | +More examples can be found in the :doc:`geometry` module. |
| 189 | + |
| 190 | +.. autoclass:: demosys.opengl.vao.VAO |
| 191 | + :members: |
| 192 | + :undoc-members: |
| 193 | + :show-inheritance: |
| 194 | + |
| 195 | +Frame Buffer Object |
| 196 | +^^^^^^^^^^^^^^^^^^^ |
| 197 | + |
| 198 | +Frame Buffer Objects are offscreen render targets. |
| 199 | +Internally they are simply textures that can be used further in rendering. |
| 200 | +FBOs can even have multiple layers so a shader can write to multiple buffers at once. |
| 201 | +They can also have depth buffers and stencil buffers. Currently we by default |
| 202 | +use a depth 24 / stencil 8 buffer as the depth format. |
| 203 | + |
| 204 | +Creating an FBO: |
| 205 | + |
| 206 | +.. code-block:: python |
| 207 | +
|
| 208 | + # Shorcut for creating a single layer FBO with depth buffer |
| 209 | + fbo = FBO.create(1024, 1024, depth=True) |
| 210 | +
|
| 211 | + # Multilayer FBO (We really need to make a shortcut for this!) |
| 212 | + fbo = FBO() |
| 213 | + fbo.add_color_attachment(texture1) |
| 214 | + fbo.add_color_attachment(texture2) |
| 215 | + fbo.add_color_attachment(texture3) |
| 216 | + fbo.set_depth_attachment(depth_texture) |
| 217 | +
|
| 218 | + # Binding and releasing FBOs |
| 219 | + fbo.bind() |
| 220 | + fbo.release() |
| 221 | +
|
| 222 | + # Using a context manager |
| 223 | + with fbo: |
| 224 | + # Draw stuff in the FBO |
| 225 | +
|
| 226 | +When binding the FBOs with multiple color attachments it will automatically |
| 227 | +call ``glDrawBuffers`` enabling multiple outputs in the framgment shader. |
| 228 | + |
| 229 | +Shader example with multiple layers: |
| 230 | + |
| 231 | +.. code-block:: glsl |
| 232 | +
|
| 233 | + #version 410 |
| 234 | +
|
| 235 | + layout(location = 0) out vec4 outColor0; |
| 236 | + layout(location = 1) out vec4 outColor1; |
| 237 | + layout(location = 2) out vec4 outColor2; |
| 238 | +
|
| 239 | + void main( void ) { |
| 240 | + outColor0 = vec4(1.0, 0.0, 0.0, 1.0) |
| 241 | + outColor1 = vec4(0.0, 1.0, 0.0, 1.0) |
| 242 | + outColor1 = vec4(0.0, 0.0, 1.0, 1.0) |
| 243 | + } |
| 244 | +
|
| 245 | +Will draw red, green and blue in the separate layers in the FBO. |
| 246 | + |
| 247 | +.. Warning:: It's important to use explicit attribute locations as not all drivers |
| 248 | + will guarantee preservation of the order and things end up in the wrong buffers! |
| 249 | + |
| 250 | +Another **very important** feature of the FBO implementation is |
| 251 | +the concept of FBO stacks. |
| 252 | + |
| 253 | +- The default render target is the window frame buffer. |
| 254 | +- When the stack is empty we are rendering to the screen. |
| 255 | +- When binding an FBO it will be pushed to the stack and the correct viewport |
| 256 | + for the FBO will be set |
| 257 | +- When releasing the FBO it will be popped from the stack and |
| 258 | + the viewport for the default render target will be applied |
| 259 | +- This also means we can build deeper stacks with the same behavior |
| 260 | +- The maximum stack depth is currently 8 and the framework will |
| 261 | + aggressively react when FBOs are popped and pushed in the wrong order |
| 262 | + |
| 263 | +A more complex example: |
| 264 | + |
| 265 | +.. code-block:: python |
| 266 | +
|
| 267 | + # Push fbo1 to stack, bind and set viewport |
| 268 | + fbo1.bind() |
| 269 | + # Push fbo2 to stack, bind and set viewport |
| 270 | + fbo2.bind() |
| 271 | + # Push fbo3 to stack, bind and set viewport |
| 272 | + fbo3.bind() |
| 273 | + # Pop fbo3 from stack, bind fbo2 and set the viewport |
| 274 | + fbo3.release() |
| 275 | + # Pop fbo2 from stack, bind fbo1 and set the viewport |
| 276 | + fbo2.release() |
| 277 | + # Pop fbo1 from stack, unbind the fbo and set the screen viewport |
| 278 | + fbo1.release() |
| 279 | +
|
| 280 | +Using context managers: |
| 281 | + |
| 282 | +.. code-block:: python |
| 283 | +
|
| 284 | + with fbo1: |
| 285 | + with fbo2: |
| 286 | + with fbo2: |
| 287 | + pass |
| 288 | +
|
| 289 | +This is especially useful in realation to the ``draw`` method in effects. |
| 290 | +The last parameter is the target FBO. The effect will never know if the |
| 291 | +FBO passed in is the fake window FBO or an actual FBO. It might also |
| 292 | +do offscreen rendering to its own fbos and things start get get really ugly. |
| 293 | + |
| 294 | +The FBO stack makes this fairly painless. |
| 295 | + |
| 296 | +By using the ``bind_target`` decorator on the ``draw`` method of your effect |
| 297 | +you will never need to think about this issue. Not having to worry about |
| 298 | +resporting the viewport size is also a huge burden off our shoulders. |
| 299 | + |
| 300 | +.. code-block:: python |
| 301 | +
|
| 302 | + @effect.bind_target |
| 303 | + def draw(self, time, frametime, target): |
| 304 | + # ... |
| 305 | +
|
| 306 | +There are of course ways to bypass the stack, but should be done with extreme caution. |
| 307 | + |
| 308 | +.. autoclass:: demosys.opengl.fbo.FBO |
| 309 | + :members: |
| 310 | + :undoc-members: |
| 311 | + :show-inheritance: |
0 commit comments