Advanced-GLSL.html (36293B)
1 <div id="content"> 2 <h1 id="content-title">Advanced GLSL</h1> 3 <h1 id="content-url" style='display:none;'>Advanced-OpenGL/Advanced-GLSL</h1> 4 <p> 5 This chapter won't really show you super advanced cool new features that give an enormous boost to your scene's visual quality. This chapter goes more or less into some interesting aspects of GLSL and some nice tricks that may help you in your future endeavors. Basically some <em>good to knows</em> and <em>features that may make your life easier</em> when creating OpenGL applications in combination with GLSL. 6 </p> 7 8 <p> 9 We'll discuss some interesting <def>built-in variables</def>, new ways to organize shader input and output, and a very useful tool called <def>uniform buffer objects</def>. 10 </p> 11 12 <h1>GLSL's built-in variables</h1> 13 <p> 14 Shaders are extremely pipelined, if we need data from any other source outside of the current shader we'll have to pass data around. We learned to do this via vertex attributes, uniforms, and samplers. There are however a few extra variables defined by GLSL prefixed with <code>gl_</code> that give us an extra means to gather and/or write data. We've already seen two of them in the chapters so far: <var>gl_Position</var> that is the output vector of the vertex shader, and the fragment shader's <var>gl_FragCoord</var>. 15 </p> 16 17 <p> 18 We'll discuss a few interesting built-in input and output variables that are built-in in GLSL and explain how they may benefit us. Note that we won't discuss all built-in variables that exist in GLSL so if you want to see all built-in variables you can check OpenGL's <a href="https://www.khronos.org/opengl/wiki/Built-in_Variable_(GLSL)" target="_blank">wiki</a>. 19 </p> 20 21 <h2>Vertex shader variables</h2> 22 <p> 23 We've already seen <var>gl_Position</var> which is the clip-space output position vector of the vertex shader. Setting <var>gl_Position</var> in the vertex shader is a strict requirement if you want to render anything on the screen. Nothing we haven't seen before. 24 </p> 25 26 <h3>gl_PointSize</h3> 27 <p> 28 One of the render primitives we're able to choose from is <var>GL_POINTS</var> in which case each single vertex is a primitive and rendered as a point. It is possible to set the size of the points being rendered via OpenGL's <fun>glPointSize</fun> function, but we can also influence this value in the vertex shader. 29 </p> 30 31 <p> 32 One output variable defined by GLSL is called <var>gl_PointSize</var> that is a <fun>float</fun> variable where you can set the point's width and height in pixels. By setting the point's size in the vertex shader we get per-vertex control over this point's dimensions. 33 </p> 34 35 <p> 36 Influencing the point sizes in the vertex shader is disabled by default, but if you want to enable this you'll have to enable OpenGL's <var>GL_PROGRAM_POINT_SIZE</var>: 37 </p> 38 39 <pre><code> 40 <function id='60'>glEnable</function>(GL_PROGRAM_POINT_SIZE); 41 </code></pre> 42 43 <p> 44 A simple example of influencing point sizes is by setting the point size equal to the clip-space position's z value which is equal to the vertex's distance to the viewer. The point size should then increase the further we are from the vertices as the viewer. 45 </p> 46 47 <pre><code> 48 void main() 49 { 50 gl_Position = projection * view * model * vec4(aPos, 1.0); 51 gl_PointSize = gl_Position.z; 52 } 53 </code></pre> 54 55 <p> 56 The result is that the points we've drawn are rendered larger the more we move away from them: 57 </p> 58 59 <img src="/img/advanced/advanced_glsl_pointsize.png" class="clean" alt="Points in OpenGL drawn with their gl_PointSize influenced in the vertex shader"/> 60 61 <p> 62 You can imagine that varying the point size per vertex is interesting for techniques like particle generation. 63 </p> 64 65 <h3>gl_VertexID</h3> 66 <p> 67 The <var>gl_Position</var> and <var>gl_PointSize</var> are <em>output variables</em> since their value is read as output from the vertex shader; we can influence the result by writing to them. The vertex shader also gives us an interesting <em>input variable</em>, that we can only read from, called <var>gl_VertexID</var>. 68 </p> 69 70 <p> 71 The integer variable <var>gl_VertexID</var> holds the current ID of the vertex we're drawing. When doing <em>indexed rendering</em> (with <fun><function id='2'>glDrawElements</function></fun>) this variable holds the current index of the vertex we're drawing. When drawing without indices (via <fun><function id='1'>glDrawArrays</function></fun>) this variable holds the number of the currently processed vertex since the start of the render call. 72 </p> 73 74 <h2>Fragment shader variables</h2> 75 <p> 76 Within the fragment shader we also have access to some interesting variables. GLSL gives us two interesting input variables called <var>gl_FragCoord</var> and <var>gl_FrontFacing</var>. 77 </p> 78 79 <h3>gl_FragCoord</h3> 80 <p> 81 We've seen the <var>gl_FragCoord</var> a couple of times before during the discussion of depth testing, because the <code>z</code> component of the <var>gl_FragCoord</var> vector is equal to the depth value of that particular fragment. However, we can also use the x and y component of that vector for some interesting effects. 82 </p> 83 84 <p> 85 The <var>gl_FragCoord</var>'s <code>x</code> and <code>y</code> component are the window- or screen-space coordinates of the fragment, originating from the bottom-left of the window. We specified a render window of 800x600 with <fun><function id='22'>glViewport</function></fun> so the screen-space coordinates of the fragment will have <code>x</code> values between 0 and 800, and <code>y</code> values between 0 and 600. 86 </p> 87 88 <p> 89 Using the fragment shader we could calculate a different color value based on the screen coordinate of the fragment. A common usage for the <var>gl_FragCoord</var> variable is for comparing visual output of different fragment calculations, as usually seen in tech demos. We could for example split the screen in two by rendering one output to the left side of the window and another output to the right side of the window. An example fragment shader that outputs a different color based on the fragment's screen coordinates is given below: 90 </p> 91 92 <pre><code> 93 void main() 94 { 95 if(gl_FragCoord.x < 400) 96 FragColor = vec4(1.0, 0.0, 0.0, 1.0); 97 else 98 FragColor = vec4(0.0, 1.0, 0.0, 1.0); 99 } 100 </code></pre> 101 102 <p> 103 Because the width of the window is equal to 800, whenever a pixel's x-coordinate is less than 400 it must be at the left side of the window and we'll give that fragment a different color. 104 </p> 105 106 <img src="/img/advanced/advanced_glsl_fragcoord.png" class="clean" alt="Cube in OpenGL drawn with 2 colors using gl_FragCoord"/> 107 108 <p> 109 We can now calculate two completely different fragment shader results and display each of them on a different side of the window. This is great for testing out different lighting techniques for example. 110 </p> 111 112 <h3>gl_FrontFacing</h3> 113 <p> 114 Another interesting input variable in the fragment shader is the <var>gl_FrontFacing</var> variable. In the <a href="https://learnopengl.com/Advanced-OpenGL/Face-culling" target="_blank">face culling</a> chapter we mentioned that OpenGL is able to figure out if a face is a front or back face due to the winding order of the vertices. The <var>gl_FrontFacing</var> variable tells us if the current fragment is part of a front-facing or a back-facing face. We could, for example, decide to output different colors for all back faces. 115 </p> 116 117 <p> 118 The <var>gl_FrontFacing</var> variable is a <fun>bool</fun> that is <code>true</code> if the fragment is part of a front face and <code>false</code> otherwise. We could create a cube this way with a different texture on the inside than on the outside: 119 </p> 120 121 <pre><code> 122 #version 330 core 123 out vec4 FragColor; 124 125 in vec2 TexCoords; 126 127 uniform sampler2D frontTexture; 128 uniform sampler2D backTexture; 129 130 void main() 131 { 132 if(gl_FrontFacing) 133 FragColor = texture(frontTexture, TexCoords); 134 else 135 FragColor = texture(backTexture, TexCoords); 136 } 137 </code></pre> 138 139 <p> 140 If we take a peek inside the container we can now see a different texture being used. 141 </p> 142 143 <img src="/img/advanced/advanced_glsl_frontfacing.png" class="clean" alt="OpenGL container using two different textures via gl_FrontFacing"/> 144 145 <p> 146 Note that if you enabled face culling you won't be able to see any faces inside the container and using <var>gl_FrontFacing</var> would then be pointless. 147 </p> 148 149 <h3>gl_FragDepth</h3> 150 <p> 151 The input variable <var>gl_FragCoord</var> is an input variable that allows us to read screen-space coordinates and get the depth value of the current fragment, but it is a <def>read-only</def> variable. We can't influence the screen-space coordinates of the fragment, but it is possible to set the depth value of the fragment. GLSL gives us an output variable called <var>gl_FragDepth</var> that we can use to manually set the depth value of the fragment within the shader. 152 </p> 153 154 <p> 155 To set the depth value in the shader we write any value between <code>0.0</code> and <code>1.0</code> to the output variable: 156 </p> 157 158 <pre><code> 159 gl_FragDepth = 0.0; // this fragment now has a depth value of 0.0 160 </code></pre> 161 162 <p> 163 If the shader does not write anything to <var>gl_FragDepth</var>, the variable will automatically take its value from <code>gl_FragCoord.z</code>. 164 </p> 165 166 <p> 167 Setting the depth value manually has a major disadvantage however. That is because OpenGL disables <def>early depth testing</def> (as discussed in the <a href="https://learnopengl.com/Advanced-OpenGL/Depth-testing" target="_blank">depth testing</a> chapter) as soon as we write to <var>gl_FragDepth</var> in the fragment shader. It is disabled, because OpenGL cannot know what depth value the fragment will have <em>before</em> we run the fragment shader, since the fragment shader may actually change this value. 168 </p> 169 170 <p> 171 By writing to <var>gl_FragDepth</var> you should take this performance penalty into consideration. From OpenGL 4.2 however, we can still sort of mediate between both sides by redeclaring the <var>gl_FragDepth</var> variable at the top of the fragment shader with a <def>depth condition</def>: 172 </p> 173 174 <pre><code> 175 layout (depth_<condition>) out float gl_FragDepth; 176 </code></pre> 177 178 <p> 179 This <code>condition</code> can take the following values: 180 </p> 181 182 <table> 183 <tr> 184 <th>Condition</th> 185 <th>Description</th> 186 </tr> 187 <tr> 188 <td><code>any</code></td> 189 <td>The default value. Early depth testing is disabled.</td> 190 </tr> 191 <tr> 192 <td><code>greater</code></td> 193 <td>You can only make the depth value larger compared to <code>gl_FragCoord.z</code>.</td> 194 </tr> 195 <tr> 196 <td><code>less</code></td> 197 <td>You can only make the depth value smaller compared to <code>gl_FragCoord.z</code>.</td> 198 </tr> 199 <tr> 200 <td><code>unchanged</code></td> 201 <td>If you write to <code>gl_FragDepth</code>, you will write exactly <code>gl_FragCoord.z</code>.</td> 202 </tr> 203 </table> 204 205 <p> 206 By specifying <code>greater</code> or <code>less</code> as the depth condition, OpenGL can make the assumption that you'll only write depth values larger or smaller than the fragment's depth value. This way OpenGL is still able to do early depth testing when the depth buffer value is part of the other direction of <code>gl_FragCoord.z</code>. 207 </p> 208 209 <p> 210 An example of where we increase the depth value in the fragment shader, but still want to preserve some of the early depth testing is shown in the fragment shader below: 211 </p> 212 213 <pre><code> 214 #version 420 core // note the GLSL version! 215 out vec4 FragColor; 216 layout (depth_greater) out float gl_FragDepth; 217 218 void main() 219 { 220 FragColor = vec4(1.0); 221 gl_FragDepth = gl_FragCoord.z + 0.1; 222 } 223 </code></pre> 224 225 <p> 226 Do note that this feature is only available from OpenGL version 4.2 or higher. 227 </p> 228 229 <h1>Interface blocks</h1> 230 <p> 231 So far, every time we sent data from the vertex to the fragment shader we declared several matching input/output variables. Declaring these one at a time is the easiest way to send data from one shader to another, but as applications become larger you probably want to send more than a few variables over. 232 </p> 233 234 <p> 235 To help us organize these variables GLSL offers us something called <def>interface blocks</def> that allows us to group variables together. The declaration of such an interface block looks a lot like a <fun>struct</fun> declaration, except that it is now declared using an <fun>in</fun> or <fun>out</fun> keyword based on the block being an input or an output block. 236 </p> 237 238 <pre><code> 239 #version 330 core 240 layout (location = 0) in vec3 aPos; 241 layout (location = 1) in vec2 aTexCoords; 242 243 uniform mat4 model; 244 uniform mat4 view; 245 uniform mat4 projection; 246 247 out VS_OUT 248 { 249 vec2 TexCoords; 250 } vs_out; 251 252 void main() 253 { 254 gl_Position = projection * view * model * vec4(aPos, 1.0); 255 vs_out.TexCoords = aTexCoords; 256 } 257 </code></pre> 258 259 <p> 260 This time we declared an interface block called <var>vs_out</var> that groups together all the output variables we want to send to the next shader. This is kind of a trivial example, but you can imagine that this helps organize your shaders' inputs/outputs. It is also useful when we want to group shader input/output into arrays as we'll see in the <a href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader" target="_blank">next</a> chapter about geometry shaders. 261 </p> 262 263 <p> 264 Then we also need to declare an input interface block in the next shader which is the fragment shader. The <def>block name</def> (<fun>VS_OUT</fun>) should be the same in the fragment shader, but the <def>instance name</def> (<var>vs_out</var> as used in the vertex shader) can be anything we like - avoiding confusing names like <var>vs_out</var> for a fragment struct containing input values. 265 </p> 266 267 <pre><code> 268 #version 330 core 269 out vec4 FragColor; 270 271 in VS_OUT 272 { 273 vec2 TexCoords; 274 } fs_in; 275 276 uniform sampler2D texture; 277 278 void main() 279 { 280 FragColor = texture(texture, fs_in.TexCoords); 281 } 282 </code></pre> 283 284 <p> 285 As long as both interface block names are equal, their corresponding input and output is matched together. This is another useful feature that helps organize your code and proves useful when crossing between certain shader stages like the geometry shader. 286 </p> 287 288 <h1>Uniform buffer objects</h1> 289 <p> 290 We've been using OpenGL for quite a while now and learned some pretty cool tricks, but also a few annoyances. For example, when using more than one shader we continuously have to set uniform variables where most of them are exactly the same for each shader. 291 </p> 292 293 <p> 294 OpenGL gives us a tool called <def>uniform buffer objects</def> that allow us to declare a set of <em>global</em> uniform variables that remain the same over any number of shader programs. When using uniform buffer objects we set the relevant uniforms only <strong>once</strong> in fixed GPU memory. We do still have to manually set the uniforms that are unique per shader. Creating and configuring a uniform buffer object requires a bit of work though. 295 </p> 296 297 <p> 298 Because a uniform buffer object is a buffer like any other buffer we can create one via <fun><function id='12'>glGenBuffers</function></fun>, bind it to the <var>GL_UNIFORM_BUFFER</var> buffer target and store all the relevant uniform data into the buffer. There are certain rules as to how the data for uniform buffer objects should be stored and we'll get to that later. First, we'll take a simple vertex shader and store our <var>projection</var> and <var>view</var> matrix in a so called <def>uniform block</def>: 299 </p> 300 301 <pre><code> 302 #version 330 core 303 layout (location = 0) in vec3 aPos; 304 305 layout (std140) uniform Matrices 306 { 307 mat4 projection; 308 mat4 view; 309 }; 310 311 uniform mat4 model; 312 313 void main() 314 { 315 gl_Position = projection * view * model * vec4(aPos, 1.0); 316 } 317 </code></pre> 318 319 <p> 320 In most of our samples we set a projection and view uniform matrix every frame for each shader we're using. This is a perfect example of where uniform buffer objects become useful since now we only have to store these matrices once. 321 </p> 322 323 <p> 324 Here we declared a uniform block called <var>Matrices</var> that stores two 4x4 matrices. Variables in a uniform block can be directly accessed without the block name as a prefix. Then we store these matrix values in a buffer somewhere in the OpenGL code and each shader that declares this uniform block has access to the matrices. 325 </p> 326 327 <p> 328 You're probably wondering right now what the <code>layout</code> <code>(std140)</code> statement means. What this says is that the currently defined uniform block uses a specific memory layout for its content; this statement sets the <def>uniform block layout</def>. 329 </p> 330 331 <h2>Uniform block layout</h2> 332 <p> 333 The content of a uniform block is stored in a buffer object, which is effectively nothing more than a reserved piece of global GPU memory. Because this piece of memory holds no information on what kind of data it holds, we need to tell OpenGL what parts of the memory correspond to which uniform variables in the shader. 334 </p> 335 336 <p> 337 Imagine the following uniform block in a shader: 338 </p> 339 340 <pre><code> 341 layout (std140) uniform ExampleBlock 342 { 343 float value; 344 vec3 vector; 345 mat4 matrix; 346 float values[3]; 347 bool boolean; 348 int integer; 349 }; 350 </code></pre> 351 352 <p> 353 What we want to know is the size (in bytes) and the offset (from the start of the block) of each of these variables so we can place them in the buffer in their respective order. The size of each of the elements is clearly stated in OpenGL and directly corresponds to C++ data types; vectors and matrices being (large) arrays of floats. What OpenGL doesn't clearly state is the <def>spacing</def> between the variables. This allows the hardware to position or pad variables as it sees fit. The hardware is able to place a <fun>vec3</fun> adjacent to a <fun>float</fun> for example. Not all hardware can handle this and pads the <fun>vec3</fun> to an array of 4 floats before appending the <fun>float</fun>. A great feature, but inconvenient for us. 354 </p> 355 356 <p> 357 By default, GLSL uses a uniform memory layout called a <def>shared</def> layout - shared because once the offsets are defined by the hardware, they are consistently <em>shared</em> between multiple programs. With a shared layout GLSL is allowed to reposition the uniform variables for optimization as long as the variables' order remains intact. Because we don't know at what offset each uniform variable will be we don't know how to precisely fill our uniform buffer. We can query this information with functions like <fun>glGetUniformIndices</fun>, but that's not the approach we're going to take in this chapter. 358 </p> 359 360 <p> 361 While a shared layout gives us some space-saving optimizations, we'd need to query the offset for each uniform variable which translates to a lot of work. The general practice however is to not use the shared layout, but to use the <def>std140</def> layout. The std140 layout <strong>explicitly</strong> states the memory layout for each variable type by standardizing their respective offsets governed by a set of rules. Since this is standardized we can manually figure out the offsets for each variable. 362 </p> 363 364 <p> 365 Each variable has a <def>base alignment</def> equal to the space a variable takes (including padding) within a uniform block using the std140 layout rules. For each variable, we calculate its <def>aligned offset</def>: the byte offset of a variable from the start of the block. The aligned byte offset of a variable <strong>must</strong> be equal to a multiple of its base alignment. This is a bit of a mouthful, but we'll get to see some examples soon enough to clear things up. 366 </p> 367 368 <p> 369 The exact layout rules can be found at OpenGL's uniform buffer specification <a href="http://www.opengl.org/registry/specs/ARB/uniform_buffer_object.txt" target="_blank">here</a>, but we'll list the most common rules below. Each variable type in GLSL such as <fun>int</fun>, <fun>float</fun> and <fun>bool</fun> are defined to be four-byte quantities with each entity of 4 bytes represented as <code>N</code>. 370 </p> 371 372 <table> 373 <tr> 374 <th>Type</th> 375 <th>Layout rule</th> 376 </tr> 377 378 <tr> 379 <td>Scalar e.g. <fun>int</fun> or <fun>bool</fun></td> 380 <td>Each scalar has a base alignment of N.</td> 381 </tr> 382 <tr> 383 <td>Vector</td> 384 <td>Either 2N or 4N. This means that a <fun>vec3</fun> has a base alignment of 4N.</td> 385 </tr> 386 <tr> 387 <td>Array of scalars or vectors</td> 388 <td>Each element has a base alignment equal to that of a <fun>vec4</fun>.</td> 389 </tr> 390 <tr> 391 <td>Matrices</td> 392 <td>Stored as a large array of column vectors, where each of those vectors has a base alignment of <fun>vec4</fun>.</td> 393 </tr> 394 <tr> 395 <td>Struct</td> 396 <td>Equal to the computed size of its elements according to the previous rules, but padded to a multiple of the size of a <fun>vec4</fun>.</td> 397 </tr> 398 </table> 399 400 <p> 401 Like most of OpenGL's specifications it's easier to understand with an example. We're taking the uniform block called <var>ExampleBlock</var> we introduced earlier and calculate the aligned offset for each of its members using the std140 layout: 402 </p> 403 404 <pre><code> 405 layout (std140) uniform ExampleBlock 406 { 407 // base alignment // aligned offset 408 float value; // 4 // 0 409 vec3 vector; // 16 // 16 (offset must be multiple of 16 so 4->16) 410 mat4 matrix; // 16 // 32 (column 0) 411 // 16 // 48 (column 1) 412 // 16 // 64 (column 2) 413 // 16 // 80 (column 3) 414 float values[3]; // 16 // 96 (values[0]) 415 // 16 // 112 (values[1]) 416 // 16 // 128 (values[2]) 417 bool boolean; // 4 // 144 418 int integer; // 4 // 148 419 }; 420 </code></pre> 421 422 <p> 423 As an exercise, try to calculate the offset values yourself and compare them to this table. With these calculated offset values, based on the rules of the std140 layout, we can fill the buffer with data at the appropriate offsets using functions like <fun><function id='90'>glBufferSubData</function></fun>. While not the most efficient, the std140 layout does guarantee us that the memory layout remains the same over each program that declared this uniform block. 424 </p> 425 426 <p> 427 By adding the statement <code>layout</code> <code>(std140)</code> in the definition of the uniform block we tell OpenGL that this uniform block uses the std140 layout. There are two other layouts to choose from that require us to query each offset before filling the buffers. We've already seen the <code>shared</code> layout, with the other remaining layout being <code>packed</code>. When using the <code>packed</code> layout, there is no guarantee that the layout remains the same between programs (not shared) because it allows the compiler to optimize uniform variables away from the uniform block which may differ per shader. 428 </p> 429 430 <h2>Using uniform buffers</h2> 431 <p> 432 We've defined uniform blocks and specified their memory layout, but we haven't discussed how to actually use them yet. 433 </p> 434 435 <p> 436 First, we need to create a uniform buffer object which is done via the familiar <fun><function id='12'>glGenBuffers</function></fun>. Once we have a buffer object we bind it to the <var>GL_UNIFORM_BUFFER</var> target and allocate enough memory by calling <fun><function id='31'>glBufferData</function></fun>. 437 </p> 438 439 <pre><code> 440 unsigned int uboExampleBlock; 441 <function id='12'>glGenBuffers</function>(1, &uboExampleBlock); 442 <function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, uboExampleBlock); 443 <function id='31'>glBufferData</function>(GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); // allocate 152 bytes of memory 444 <function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, 0); 445 </code></pre> 446 447 <p> 448 Now whenever we want to update or insert data into the buffer, we bind to <var>uboExampleBlock</var> and use <fun><function id='90'>glBufferSubData</function></fun> to update its memory. We only have to update this uniform buffer once, and all shaders that use this buffer now use its updated data. But, how does OpenGL know what uniform buffers correspond to which uniform blocks? 449 </p> 450 451 <p> 452 In the OpenGL context there is a number of <def>binding points</def> defined where we can link a uniform buffer to. Once we created a uniform buffer we link it to one of those binding points and we also link the uniform block in the shader to the same binding point, effectively linking them together. The following diagram illustrates this: 453 </p> 454 455 <img src="/img/advanced/advanced_glsl_binding_points.png" class="clean" alt="Diagram of uniform binding points in OpenGL"/> 456 457 <p> 458 As you can see we can bind multiple uniform buffers to different binding points. Because shader A and shader B both have a uniform block linked to the same binding point <code>0</code>, their uniform blocks share the same uniform data found in <var>uboMatrices</var>; a requirement being that both shaders defined the same <var>Matrices</var> uniform block. 459 </p> 460 461 <p> 462 To set a shader uniform block to a specific binding point we call <fun><function id='95'><function id='44'>glUniform</function>BlockBinding</function></fun> that takes a program object, a uniform block index, and the binding point to link to. The <def>uniform block index</def> is a location index of the defined uniform block in the shader. This can be retrieved via a call to <fun><function id='94'>glGetUniformBlockIndex</function></fun> that accepts a program object and the name of the uniform block. We can set the <var>Lights</var> uniform block from the diagram to binding point <code>2</code> as follows: 463 </p> 464 465 <pre><code> 466 unsigned int lights_index = <function id='94'>glGetUniformBlockIndex</function>(shaderA.ID, "Lights"); 467 <function id='95'><function id='44'>glUniform</function>BlockBinding</function>(shaderA.ID, lights_index, 2); 468 </code></pre> 469 470 <p> 471 Note that we have to repeat this process for <strong>each</strong> shader. 472 </p> 473 474 <note> 475 From OpenGL version 4.2 and onwards it is also possible to store the binding point of a uniform block explicitly in the shader by adding another layout specifier, saving us the calls to <fun><function id='94'>glGetUniformBlockIndex</function></fun> and <fun><function id='95'><function id='44'>glUniform</function>BlockBinding</function></fun>. The following code sets the binding point of the <var>Lights</var> uniform block explicitly: 476 <pre class="cpp"><code> 477 layout(std140, binding = 2) uniform Lights { ... }; 478 </code></pre> 479 </note> 480 481 <p> 482 Then we also need to bind the uniform buffer object to the same binding point and this can be accomplished with either <fun><function id='96'><function id='32'>glBindBuffer</function>Base</function></fun> or <fun><function id='97'><function id='32'>glBindBuffer</function>Range</function></fun>. 483 </p> 484 485 <pre><code> 486 <function id='96'><function id='32'>glBindBuffer</function>Base</function>(GL_UNIFORM_BUFFER, 2, uboExampleBlock); 487 // or 488 <function id='97'><function id='32'>glBindBuffer</function>Range</function>(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152); 489 </code></pre> 490 491 <p> 492 The function <fun><function id='96'><function id='32'>glBindbuffer</function>Base</function></fun> expects a target, a binding point index and a uniform buffer object. This function links <var>uboExampleBlock</var> to binding point <code>2</code>; from this point on, both sides of the binding point are linked. You can also use <fun><function id='97'><function id='32'>glBindBuffer</function>Range</function></fun> that expects an extra offset and size parameter - this way you can bind only a specific range of the uniform buffer to a binding point. Using <fun><function id='97'><function id='32'>glBindBuffer</function>Range</function></fun> you could have multiple different uniform blocks linked to a single uniform buffer object. 493 </p> 494 495 <p> 496 Now that everything is set up, we can start adding data to the uniform buffer. We could add all the data as a single byte array, or update parts of the buffer whenever we feel like it using <fun><function id='90'>glBufferSubData</function></fun>. To update the uniform variable <var>boolean</var> we could update the uniform buffer object as follows: 497 </p> 498 499 <pre><code> 500 <function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, uboExampleBlock); 501 int b = true; // bools in GLSL are represented as 4 bytes, so we store it in an integer 502 <function id='90'>glBufferSubData</function>(GL_UNIFORM_BUFFER, 144, 4, &b); 503 <function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, 0); 504 </code></pre> 505 506 <p> 507 And the same procedure applies for all the other uniform variables inside the uniform block, but with different range arguments. 508 </p> 509 510 <h2>A simple example</h2> 511 <p> 512 So let's demonstrate a real example of uniform buffer objects. If we look back at all the previous code samples we've continually been using 3 matrices: the projection, view and model matrix. Of all those matrices, only the model matrix changes frequently. If we have multiple shaders that use this same set of matrices, we'd probably be better off using uniform buffer objects. 513 </p> 514 515 <p> 516 We're going to store the projection and view matrix in a uniform block called <var>Matrices</var>. We're not going to store the model matrix in there since the model matrix tends to change frequently between shaders, so we wouldn't really benefit from uniform buffer objects. 517 </p> 518 519 <pre><code> 520 #version 330 core 521 layout (location = 0) in vec3 aPos; 522 523 layout (std140) uniform Matrices 524 { 525 mat4 projection; 526 mat4 view; 527 }; 528 uniform mat4 model; 529 530 void main() 531 { 532 gl_Position = projection * view * model * vec4(aPos, 1.0); 533 } 534 </code></pre> 535 536 <p> 537 Not much going on here, except that we now use a uniform block with a std140 layout. What we're going to do in our sample application is display 4 cubes where each cube is displayed with a different shader program. Each of the 4 shader programs uses the same vertex shader, but has a unique fragment shader that only outputs a single color that differs per shader. 538 </p> 539 540 <p> 541 First, we set the uniform block of the vertex shaders equal to binding point <code>0</code>. Note that we have to do this for each shader: 542 </p> 543 544 <pre><code> 545 unsigned int uniformBlockIndexRed = <function id='94'>glGetUniformBlockIndex</function>(shaderRed.ID, "Matrices"); 546 unsigned int uniformBlockIndexGreen = <function id='94'>glGetUniformBlockIndex</function>(shaderGreen.ID, "Matrices"); 547 unsigned int uniformBlockIndexBlue = <function id='94'>glGetUniformBlockIndex</function>(shaderBlue.ID, "Matrices"); 548 unsigned int uniformBlockIndexYellow = <function id='94'>glGetUniformBlockIndex</function>(shaderYellow.ID, "Matrices"); 549 550 <function id='95'><function id='44'>glUniform</function>BlockBinding</function>(shaderRed.ID, uniformBlockIndexRed, 0); 551 <function id='95'><function id='44'>glUniform</function>BlockBinding</function>(shaderGreen.ID, uniformBlockIndexGreen, 0); 552 <function id='95'><function id='44'>glUniform</function>BlockBinding</function>(shaderBlue.ID, uniformBlockIndexBlue, 0); 553 <function id='95'><function id='44'>glUniform</function>BlockBinding</function>(shaderYellow.ID, uniformBlockIndexYellow, 0); 554 </code></pre> 555 556 <p> 557 Next we create the actual uniform buffer object and bind that buffer to binding point <code>0</code>: 558 </p> 559 560 <pre><code> 561 unsigned int uboMatrices 562 <function id='12'>glGenBuffers</function>(1, &uboMatrices); 563 564 <function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, uboMatrices); 565 <function id='31'>glBufferData</function>(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW); 566 <function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, 0); 567 568 <function id='97'><function id='32'>glBindBuffer</function>Range</function>(GL_UNIFORM_BUFFER, 0, uboMatrices, 0, 2 * sizeof(glm::mat4)); 569 </code></pre> 570 571 <p> 572 First we allocate enough memory for our buffer which is equal to 2 times the size of <fun>glm::mat4</fun>. The size of GLM's matrix types correspond directly to <fun>mat4</fun> in GLSL. Then we link a specific range of the buffer, in this case the entire buffer, to binding point <code>0</code>. 573 </p> 574 575 <p> 576 Now all that's left to do is fill the buffer. If we keep the <em>field of view</em> value constant of the projection matrix (so no more camera zoom) we only have to update it once in our application - this means we only have to insert this into the buffer only once as well. Because we already allocated enough memory in the buffer object we can use <fun><function id='90'>glBufferSubData</function></fun> to store the projection matrix before we enter the render loop: 577 </p> 578 579 <pre><code> 580 glm::mat4 projection = <function id='58'>glm::perspective</function>(<function id='63'>glm::radians</function>(45.0f), (float)width/(float)height, 0.1f, 100.0f); 581 <function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, uboMatrices); 582 <function id='90'>glBufferSubData</function>(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(projection)); 583 <function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, 0); 584 </code></pre> 585 586 <p> 587 Here we store the first half of the uniform buffer with the projection matrix. Then before we render the objects each frame we update the second half of the buffer with the view matrix: 588 </p> 589 590 <pre><code> 591 glm::mat4 view = camera.GetViewMatrix(); 592 <function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, uboMatrices); 593 <function id='90'>glBufferSubData</function>(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view)); 594 <function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, 0); 595 </code></pre> 596 597 <p> 598 And that's it for uniform buffer objects. Each vertex shader that contains a <var>Matrices</var> uniform block will now contain the data stored in <var>uboMatrices</var>. So if we now were to draw 4 cubes using 4 different shaders, their projection and view matrix should be the same: 599 </p> 600 601 <pre><code> 602 <function id='27'>glBindVertexArray</function>(cubeVAO); 603 shaderRed.use(); 604 glm::mat4 model = glm::mat4(1.0f); 605 model = <function id='55'>glm::translate</function>(model, glm::vec3(-0.75f, 0.75f, 0.0f)); // move top-left 606 shaderRed.setMat4("model", model); 607 <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 36); 608 // ... draw Green Cube 609 // ... draw Blue Cube 610 // ... draw Yellow Cube 611 </code></pre> 612 613 <p> 614 The only uniform we still need to set is the <var>model</var> uniform. Using uniform buffer objects in a scenario like this saves us from quite a few uniform calls per shader. The result looks something like this: 615 </p> 616 617 <img src="/img/advanced/advanced_glsl_uniform_buffer_objects.png" class="clean" alt="Image of 4 cubes with their uniforms set via OpenGL's uniform buffer objects"/> 618 619 <p> 620 Each of the cubes is moved to one side of the window by translating the model matrix and, thanks to the different fragment shaders, their colors differ per object. This is a relatively simple scenario of where we could use uniform buffer objects, but any large rendering application can have over hundreds of shader programs active which is where uniform buffer objects really start to shine. 621 </p> 622 623 <p> 624 You can find the full source code of the uniform example application <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/8.advanced_glsl_ubo/advanced_glsl_ubo.cpp" target="_blank">here</a>. 625 </p> 626 627 <p> 628 Uniform buffer objects have several advantages over single uniforms. First, setting a lot of uniforms at once is faster than setting multiple uniforms one at a time. Second, if you want to change the same uniform over several shaders, it is much easier to change a uniform once in a uniform buffer. One last advantage that is not immediately apparent is that you can use a lot more uniforms in shaders using uniform buffer objects. OpenGL has a limit to how much uniform data it can handle which can be queried with <var>GL_MAX_VERTEX_UNIFORM_COMPONENTS</var>. When using uniform buffer objects, this limit is much higher. So whenever you reach a maximum number of uniforms (when doing skeletal animation for example) there's always uniform buffer objects. 629 </p> 630 631 </div> 632