LearnOpenGL

Translation in progress of learnopengl.com.
git clone https://git.mtkn.jp/LearnOpenGL
Log | Files | Refs

Framebuffers.html (43036B)


      1 <!DOCTYPE html>
      2 <html lang="ja"> 
      3 <head>
      4     <meta charset="utf-8"/>
      5     <title>LearnOpenGL</title>
      6     <link rel="shortcut icon" type="image/ico" href="/favicon.ico"  />
      7 	<link rel="stylesheet" href="../static/style.css" />
      8 	<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"> </script>
      9 	<script src="/static/functions.js"></script>
     10 </head>
     11 <body>
     12 	<nav>
     13 <ol>
     14 	<li id="Introduction">
     15 		<a href="https://learnopengl.com/Introduction">はじめに</a>
     16 	</li>
     17 	<li id="Getting-started">
     18 		<span class="closed">入門</span>
     19 		<ol>
     20 			<li id="Getting-started/OpenGL">
     21 				<a href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a>
     22 			</li>
     23 			<li id="Getting-started/Creating-a-window">
     24 				<a href="https://learnopengl.com/Getting-started/Creating-a-window">ウィンドウの作成</a>
     25 			</li>
     26 			<li id="Getting-started/Hello-Window">
     27 				<a href="https://learnopengl.com/Getting-started/Hello-Window">最初のウィンドウ</a>
     28 			</li>
     29 			<li id="Getting-started/Hello-Triangle">
     30 				<a href="https://learnopengl.com/Getting-started/Hello-Triangle">最初の三角形</a>
     31 			</li>
     32 			<li id="Getting-started/Shaders">
     33 				<a href="https://learnopengl.com/Getting-started/Shaders">シェーダー</a>
     34 			</li>
     35 			<li id="Getting-started/Textures">
     36 				<a href="https://learnopengl.com/Getting-started/Textures">テクスチャ</a>
     37 			</li>
     38 			<li id="Getting-started/Transformations">
     39 				<a href="https://learnopengl.com/Getting-started/Transformations">座標変換</a>
     40 			</li>
     41 			<li id="Getting-started/Coordinate-Systems">
     42 				<a href="https://learnopengl.com/Getting-started/Coordinate-Systems">座標系</a>
     43 			</li>
     44 			<li id="Getting-started/Camera">
     45 				<a href="https://learnopengl.com/Getting-started/Camera">カメラ</a>
     46 			</li>
     47 			<li id="Getting-started/Review">
     48 				<a href="https://learnopengl.com/Getting-started/Review">まとめ</a>
     49 			</li>
     50 		</ol>
     51 	</li>
     52 	<li id="Lighting">
     53 		<span class="closed">Lighting </span>
     54 		<ol>
     55 			<li id="Lighting/Colors">
     56 				<a href="https://learnopengl.com/Lighting/Colors">Colors </a>
     57 			</li>
     58 			<li id="Lighting/Basic-Lighting">
     59 				<a href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a>
     60 			</li>
     61 			<li id="Lighting/Materials">
     62 				<a href="https://learnopengl.com/Lighting/Materials">Materials </a>
     63 			</li>
     64 			<li id="Lighting/Lighting-maps">
     65 				<a href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a>
     66 			</li>
     67 			<li id="Lighting/Light-casters">
     68 				<a href="https://learnopengl.com/Lighting/Light-casters">Light casters </a>
     69 			</li>
     70 			<li id="Lighting/Multiple-lights">
     71 				<a href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a>
     72 			</li>
     73 			<li id="Lighting/Review">
     74 				<a href="https://learnopengl.com/Lighting/Review">Review </a>
     75 			</li>
     76 		</ol>
     77 	</li>
     78 	<li id="Model-Loading">
     79 		<span class="closed">Model Loading </span>
     80 		<ol>
     81 			<li id="Model-Loading/Assimp">
     82 				<a href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a>
     83 			</li>
     84 			<li id="Model-Loading/Mesh">
     85 				<a href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a>
     86 			</li>
     87 			<li id="Model-Loading/Model">
     88 				<a href="https://learnopengl.com/Model-Loading/Model">Model </a>
     89 			</li>
     90 		</ol>
     91 	</li>
     92 	<li id="Advanced-OpenGL">
     93 		<span class="closed">Advanced OpenGL </span>
     94 		<ol>
     95 			<li id="Advanced-OpenGL/Depth-testing">
     96 				<a href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a>
     97 			</li>
     98 			<li id="Advanced-OpenGL/Stencil-testing">
     99 				<a href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a>
    100 			</li>
    101 			<li id="Advanced-OpenGL/Blending">
    102 				<a href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a>
    103 			</li>
    104 			<li id="Advanced-OpenGL/Face-culling">
    105 				<a href="https://learnopengl.cm/Advanced-OpenGL/Face-culling">Face culling </a>
    106 			</li>
    107 			<li id="Advanced-OpenGL/Framebuffers">
    108 				<a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a>
    109 			</li>
    110 			<li id="Advanced-OpenGL/Cubemaps">
    111 				<a href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a>
    112 			</li>
    113 			<li id="Advanced-OpenGL/Advanced-Data">
    114 				<a href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a>
    115 			</li>
    116 			<li id="Advanced-OpenGL/Advanced-GLSL">
    117 				<a href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a>
    118 			</li>
    119 			<li id="Advanced-OpenGL/Geometry-Shader">
    120 				<a href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a>
    121 			</li>
    122 			<li id="Advanced-OpenGL/Instancing">
    123 				<a href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a>
    124 			</li>
    125 			<li id="Advanced-OpenGL/Anti-Aliasing">
    126 				<a href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a>
    127 			</li>
    128 		</ol>
    129 	</li>
    130 	<li id="Advanced-Lighting">
    131 		<span class="closed">Advanced Lighting </span>
    132 		<ol>
    133 			<li id="Advanced-Lighting/Advanced-Lighting">
    134 				<a href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a>
    135 			</li>
    136 			<li id="Advanced-Lighting/Gamma-Correction">
    137 				<a href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a>
    138 			</li>
    139 			<li id="Advanced-Lighting/Shadows">
    140 				<span class="closed">Shadows </span>
    141 				<ol>
    142 					<li id="Advanced-Lighting/Shadows/Shadow-Mapping">
    143 						<a href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a>
    144 					</li>
    145 					<li id="Advanced-Lighting/Shadows/Point-Shadows">
    146 						<a href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a>
    147 					</li>
    148 				</ol>
    149 			</li>
    150 			<li id="Advanced-Lighting/Normal-Mapping">
    151 				<a href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a>
    152 			</li>
    153 			<li id="Advanced-Lighting/Parallax-Mapping">
    154 				<a href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a>
    155 			</li>
    156 			<li id="Advanced-Lighting/HDR">
    157 				<a href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a>
    158 			</li>
    159 			<li id="Advanced-Lighting/Bloom">
    160 				<a href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a>
    161 			</li>
    162 			<li id="Advanced-Lighting/Deferred-Shading">
    163 				<a href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a>
    164 			</li>
    165 			<li id="Advanced-Lighting/SSAO">
    166 				<a href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a>
    167 			</li>
    168 		</ol>
    169 	</li>
    170 	<li id="PBR">
    171 		<span class="closed">PBR </span>
    172 		<ol>
    173 			<li id="PBR/Theory">
    174 				<a href="https://learnopengl.com/PBR/Theory">Theory </a>
    175 			</li>
    176 			<li id="PBR/Lighting">
    177 				<a href="https://learnopengl.com/PBR/Lighting">Lighting </a>
    178 			</li>
    179 			<li id="PBR/IBL">
    180 				<span class="closed">IBL </span>
    181 				<ol>
    182 					<li id="PBR/IBL/Diffuse-irradiance">
    183 						<a href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a>
    184 					</li>
    185 					<li id="PBR/IBL/Specular-IBL">
    186 						<a href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a>
    187 					</li>
    188 				</ol>
    189 			</li>
    190 		</ol>
    191 	</li>
    192 	<li id="In-Practice">
    193 		<span class="closed">In Practice </span>
    194 		<ol>
    195 			<li id="In-Practice/Debugging">
    196 				<a href="https://learnopengl.com/In-Practice/Debugging">Debugging </a>
    197 			</li>
    198 			<li id="In-Practice/Text-Rendering">
    199 				<a href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a>
    200 			</li>
    201 			<li id="In-Practice/2D-Game">
    202 				<span class="closed">2D Game </span>
    203 				<ol>
    204 					<li id="In-Practice/2D-Game/Breakout">
    205 						<a href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a>
    206 					</li>
    207 					<li id="In-Practice/2D-Game/Setting-up">
    208 						<a href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a>
    209 					</li>
    210 					<li id="In-Practice/2D-Game/Rendering-Sprites">
    211 						<a href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a>
    212 					</li>
    213 					<li id="In-Practice/2D-Game/Levels">
    214 						<a href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a>
    215 					</li>
    216 					<li id="In-Practice/2D-Game/Collisions">
    217 						<span class="closed">Collisions </span>
    218 						<ol>
    219 							<li id="In-Practice/2D-Game/Collisions/Ball">
    220 								<a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a>
    221 							</li>
    222 							<li id="In-Practice/2D-Game/Collisions/Collision-detection">
    223 								<a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a>
    224 							</li>
    225 							<li id="In-Practice/2D-Game/Collisions/Collision-resolution">
    226 								<a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a>
    227 							</li>
    228 						</ol>
    229 					</li>
    230 					<li id="In-Practice/2D-Game/Particles">
    231 						<a href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a>
    232 					</li>
    233 					<li id="In-Practice/2D-Game/Postprocessing">
    234 						<a href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a>
    235 					</li>
    236 					<li id="In-Practice/2D-Game/Powerups">
    237 						<a href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a>
    238 					</li>
    239 					<li id="In-Practice/2D-Game/Audio">
    240 						<a href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a>
    241 					</li>
    242 					<li id="In-Practice/2D-Game/Render-text">
    243 						<a href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a>
    244 					</li>
    245 					<li id="In-Practice/2D-Game/Final-thoughts">
    246 						<a href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a>
    247 					</li>
    248 				</ol>
    249 			</li>
    250 		</ol>
    251 	</li>
    252 	<li id="Guest-Articles">
    253 		<span class="closed">Guest Articles </span>
    254 		<ol>
    255 			<li id="Guest-Articles/How-to-publish">
    256 				<a href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a>
    257 			</li>
    258 			<li id="Guest-Articles/2020">
    259 				<span class="closed">2020 </span>
    260 				<ol>
    261 					<li id="Guest-Articles/2020/OIT">
    262 						<span class="closed">OIT </span>
    263 						<ol>
    264 							<li id="Guest-Articles/2020/OIT/Introduction">
    265 								<a href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a>
    266 							</li>
    267 							<li id="Guest-Articles/2020/OIT/Weighted-Blended">
    268 								<a href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a>
    269 							</li>
    270 						</ol>
    271 					</li>
    272 					<li id="Guest-Articles/2020/Skeletal-Animation">
    273 						<a href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a>
    274 					</li>
    275 				</ol>
    276 			</li>
    277 			<li id="Guest-Articles/2021">
    278 				<span class="closed">2021 </span>
    279 				<ol>
    280 					<li id="Guest-Articles/2021/CSM">
    281 						<a href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a>
    282 					</li>
    283 					<li id="Guest-Articles/2021/Scene">
    284 						<span class="closed">Scene </span>
    285 						<ol>
    286 							<li id="Guest-Articles/2021/Scene/Scene-Graph">
    287 								<a href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a>
    288 							</li>
    289 							<li id="Guest-Articles/2021/Scene/Frustum-Culling">
    290 								<a href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a>
    291 							</li>
    292 						</ol>
    293 					</li>
    294 					<li id="Guest-Articles/2021/Tessellation">
    295 						<span class="closed">Tessellation </span>
    296 						<ol>
    297 							<li id="Guest-Articles/2021/Tessellation/Height-map">
    298 								<a href="https://learnopengl.com/Guest-Articles/2021/Tessellation/Height-map">Height map </a>
    299 							</li>
    300 						</ol>
    301 					</li>
    302 				</ol>
    303 			</li>
    304 		</ol>
    305 	</li>
    306 	<li id="Code-repository">
    307 		<a href="https://learnopengl.com/Code-repository">Code repository </a>
    308 	</li>
    309 	<li id="Translations">
    310 		<a href="https://learnopengl.com/Translations">Translations </a>
    311 	</li>
    312 	<li id="About">
    313 		<a href="https://learnopengl.com/About">About </a>
    314 	</li>
    315 </ol>
    316 	</nav>
    317 	<main>
    318     <div id="content">
    319     <h1 id="content-title">Framebuffers</h1>
    320 <h1 id="content-url" style='display:none;'>Advanced-OpenGL/Framebuffers</h1>
    321 <p>
    322   So far we've used several types of screen buffers: a color buffer for writing color values, a depth buffer to write and test depth information, and finally a stencil buffer that allows us to discard certain fragments based on some condition. The combination of these buffers is stored somewhere in GPU memory and is called a <def>framebuffer</def>. OpenGL gives us the flexibility to define our own framebuffers and thus define our own color (and optionally a depth and stencil) buffer.  
    323 </p>
    324 
    325 <p>
    326   The rendering operations we've done so far were all done on top of the render buffers attached to the <def>default framebuffer</def>. The default framebuffer is created and configured when you create your window (GLFW does this for us). By creating our own framebuffer we can get an additional target to render to.
    327 </p>
    328 
    329 <p>
    330   The application of framebuffers may not immediately make sense, but rendering your scene to a different framebuffer allows us to use that result to create mirrors in a scene, or do cool post-processing effects for example. First we'll discuss how they actually work and then we'll use them by implementing those cool post-processing effects.
    331 </p>
    332 
    333 <h2>Creating a framebuffer</h2>
    334 <p>
    335   Just like any other object in OpenGL we can create a framebuffer object (abbreviated to FBO) by using a function called <fun><function id='76'>glGenFramebuffers</function></fun>:
    336 </p>
    337 
    338 <pre class="cpp"><code>
    339 unsigned int fbo;
    340 <function id='76'>glGenFramebuffers</function>(1, &fbo);
    341 </code></pre>
    342 
    343 <p>
    344   This pattern of object creation and usage is something we've seen dozens of times now so their usage functions are similar to all the other object's we've seen: first we create a framebuffer object, bind it as the active framebuffer, do some operations, and unbind the framebuffer. To bind the framebuffer we use <fun><function id='77'>glBindFramebuffer</function></fun>:
    345 </p>
    346 
    347 <pre><code>
    348 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, fbo);  
    349 </code></pre>
    350 
    351 <p>
    352   By binding to the <var>GL_FRAMEBUFFER</var> target all the next <em>read</em> and <em>write</em> framebuffer operations will affect the currently bound framebuffer. It is also possible to bind a framebuffer to a read or write target specifically by binding to <var>GL_READ_FRAMEBUFFER</var> or <var>GL_DRAW_FRAMEBUFFER</var> respectively. The framebuffer bound to <var>GL_READ_FRAMEBUFFER</var> is then used for all read operations like <fun><function id='78'>glReadPixels</function></fun> and the framebuffer bound to <var>GL_DRAW_FRAMEBUFFER</var> is used as the destination for rendering, clearing and other write operations. Most of the times you won't need to make this distinction though and you generally bind to both with <var>GL_FRAMEBUFFER</var>. 
    353 </p>
    354 
    355 <p>
    356   Unfortunately, we can't use our framebuffer yet because it is not <def>complete</def>. For a framebuffer to be complete the following requirements have to be satisfied: 
    357 </p>
    358 
    359 <ul>
    360   <li>We have to attach at least one buffer (color, depth or stencil buffer).</li>
    361   <li>There should be at least one color attachment.</li>
    362   <li>All attachments should be complete as well (reserved memory).</li>
    363   <li>Each buffer should have the same number of samples.</li>
    364 </ul>
    365 
    366 <p>
    367   Don't worry if you don't know what samples are, we'll get to those in a <a href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing" target="_blank">later</a> chapter.
    368 </p>
    369 
    370 <p>
    371   From the requirements it should be clear that we need to create some kind of attachment for the framebuffer and attach this attachment to the framebuffer. After we've completed all requirements we can check if we actually successfully completed the framebuffer by calling <fun><function id='79'>glCheckFramebufferStatus</function></fun> with <var>GL_FRAMEBUFFER</var>. It then checks the currently bound framebuffer and returns any of <a href="https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/%67lCheckFramebufferStatus.xhtml" target="_blank">these</a> values found in the specification. If it returns <var>GL_FRAMEBUFFER_COMPLETE</var> we're good to go:
    372 </p>
    373 
    374 <pre><code>
    375 if(<function id='79'>glCheckFramebufferStatus</function>(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
    376   // execute victory dance
    377 </code></pre>
    378 
    379 <p>
    380   All subsequent rendering operations will now render to the attachments of the currently bound framebuffer. Since our framebuffer is not the default framebuffer, the rendering commands will have no impact on the visual output of your window. For this reason it is called <def>off-screen rendering</def> when rendering to a different framebuffer. If you want all rendering operations to have a visual impact again on the main window we need to make the default framebuffer active by binding to <code>0</code>:
    381 </p>
    382 
    383 <pre class="cpp"><code>
    384 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0);   
    385 </code></pre>
    386 
    387 <p>
    388   When we're done with all framebuffer operations, do not forget to delete the framebuffer object:
    389 </p>
    390 
    391 <pre class="cpp"><code>
    392 <function id='80'>glDeleteFramebuffers</function>(1, &fbo);  
    393 </code></pre>
    394 
    395 <p>
    396   Now before the completeness check is executed we need to attach one or more attachments to the framebuffer. An <def>attachment</def> is a memory location that can act as a buffer for the framebuffer, think of it as an image. When creating an attachment we have two options to take: textures or <def>renderbuffer</def> objects. 
    397 </p>
    398 
    399 <h3>Texture attachments</h3>
    400 <p>
    401   When attaching a texture to a framebuffer, all rendering commands will write to the texture as if it was a normal color/depth or stencil buffer. The advantage of using textures is that the render output is stored inside the texture image that we can then easily use in our shaders.
    402 </p>
    403 
    404 <p>
    405   Creating a texture for a framebuffer is roughly the same as creating a normal texture:
    406 </p>
    407 
    408 <pre class="cpp"><code>
    409 unsigned int texture;
    410 <function id='50'>glGenTextures</function>(1, &texture);
    411 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, texture);
    412   
    413 <function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
    414 
    415 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    416 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
    417 </code></pre>
    418 
    419 <p>
    420   The main differences here is that we set the dimensions equal to the screen size (although this is not required) and we pass <code>NULL</code> as the texture's <code>data</code> parameter. For this texture, we're only allocating memory and not actually filling it. Filling the texture will happen as soon as we render to the framebuffer. Also note that we do not care about any of the wrapping methods or mipmapping since we won't be needing those in most cases.
    421 </p>
    422 
    423 <note>
    424  If you want to render your whole screen to a texture of a smaller or larger size you need to call <fun><function id='22'>glViewport</function></fun> again (before rendering to your framebuffer) with the new dimensions of your texture, otherwise render commands will only fill part of the texture.
    425 </note>
    426 
    427 <p>
    428   Now that we've created a texture, the last thing we need to do is actually attach it to the framebuffer:
    429 </p>
    430 
    431 <pre class="cpp"><code>
    432 <function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);  
    433 </code></pre>
    434 
    435 <p>
    436   The <fun><function id='81'>glFrameBufferTexture2D</function></fun> function has the following parameters:
    437 </p>
    438 
    439 <ul>
    440   <li><code>target</code>: the framebuffer type we're targeting (draw, read or both).</li>
    441   <li><code>attachment</code>: the type of attachment we're going to attach. Right now we're attaching a color attachment. Note that the <code>0</code> at the end suggests we can attach more than 1 color attachment. We'll get to that in a later chapter.</li>
    442   <li><code>textarget</code>: the type of the texture you want to attach.</li>
    443   <li><code>texture</code>: the actual texture to attach.</li>
    444   <li><code>level</code>: the mipmap level. We keep this at <code>0</code>.</li>
    445 </ul>
    446 
    447 <p>
    448   Next to the color attachments we can also attach a depth and a stencil texture to the framebuffer object. To attach a depth attachment we specify the attachment type as <var>GL_DEPTH_ATTACHMENT</var>. Note that the texture's <def>format</def> and <def>internalformat</def> type should then become <var>GL_DEPTH_COMPONENT</var> to reflect the depth buffer's storage format. To attach a stencil buffer you use <var>GL_STENCIL_ATTACHMENT</var> as the second argument and specify the texture's formats as <var>GL_STENCIL_INDEX</var>.
    449 </p>
    450 
    451 <p>
    452   It is also possible to attach both a depth buffer and a stencil buffer as a single texture. Each 32 bit value of the texture then contains 24 bits of depth information and 8 bits of stencil information. To attach a depth and stencil buffer as one texture we use the <var>GL_DEPTH_STENCIL_ATTACHMENT</var> type and configure the texture's formats to contain combined depth and stencil values. An example of attaching a depth and stencil buffer as one texture to the framebuffer is given below:
    453 </p>
    454 
    455 
    456 <pre class="cpp"><code>
    457 <function id='52'>glTexImage2D</function>(
    458   GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0, 
    459   GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL
    460 );
    461 
    462 <function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);  
    463 </code></pre>
    464 
    465 <h3>Renderbuffer object attachments</h3>
    466 <p>
    467   <def>Renderbuffer objects</def> were introduced to OpenGL after textures as a possible type of framebuffer attachment, Just like a texture image, a renderbuffer object is an actual buffer e.g. an array of bytes, integers, pixels or whatever. However, a renderbuffer object can not be directly read from. This gives it the added advantage that OpenGL can do a few memory optimizations that can give it a performance edge over textures for off-screen rendering to a framebuffer. 
    468 </p>
    469 
    470 <p>
    471   Renderbuffer objects store all the render data directly into their buffer without any conversions to texture-specific formats, making them faster as a writeable storage medium. You cannot read from them directly, but it is possible to read from them via the slow <fun><function id='78'>glReadPixels</function></fun>. This returns a specified area of pixels from the currently bound framebuffer, but not directly from the attachment itself.  
    472 </p>
    473 
    474 <p>
    475   Because their data is in a native format they are quite fast when writing data or copying data to other buffers. Operations like switching buffers are therefore quite fast when using renderbuffer objects. The <fun><function id='24'>glfwSwapBuffers</function></fun> function we've been using at the end of each frame may as well be implemented with renderbuffer objects: we simply write to a renderbuffer image, and swap to the other one at the end. Renderbuffer objects are perfect for these kind of operations. 
    476 </p>
    477 
    478 <p>
    479   Creating a renderbuffer object looks similar to the framebuffer's code:
    480 </p>
    481 
    482 <pre class="cpp"><code>
    483 unsigned int rbo;
    484 <function id='82'>glGenRenderbuffers</function>(1, &rbo);
    485 </code></pre>
    486 
    487 <p>
    488   And similarly we want to bind the renderbuffer object so all subsequent renderbuffer operations affect the current <var>rbo</var>:
    489 </p>
    490 
    491 <pre><code>
    492 <function id='83'>glBindRenderbuffer</function>(GL_RENDERBUFFER, rbo);  
    493 </code></pre>
    494 
    495 <p>
    496   Since renderbuffer objects are write-only they are often used as depth and stencil attachments, since most of the time we don't really need to read values from them, but we do care about depth and stencil testing. We <strong>need</strong> the depth and stencil values for testing, but don't need to <em>sample</em> these values so a renderbuffer object suits this perfectly. When we're not sampling from these buffers, a renderbuffer object is generally preferred. 
    497 </p>
    498 
    499 <p>
    500   Creating a depth and stencil renderbuffer object is done by calling the <fun><function id='88'>glRenderbufferStorage</function></fun> function:
    501 </p>
    502 
    503 <pre class="cpp"><code>
    504 <function id='88'>glRenderbufferStorage</function>(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
    505 </code></pre>
    506 
    507 <p>
    508   Creating a renderbuffer object is similar to texture objects, the difference being that this object is specifically designed to be used as a framebuffer attachment, instead of a general purpose data buffer like a texture. Here we've chosen <var>GL_DEPTH24_STENCIL8</var> as the internal format, which holds both the depth and stencil buffer with 24 and 8 bits respectively.
    509 </p>
    510 
    511 <p>
    512   The last thing left to do is to actually attach the renderbuffer object:
    513 </p>
    514 
    515 <pre><code>
    516 <function id='89'>glFramebufferRenderbuffer</function>(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);  
    517 </code></pre>
    518 
    519 <p>
    520   Renderbuffer objects can be more efficient for use in your off-screen render projects, but it is important to realize when to use renderbuffer objects and when to use textures. The general rule is that if you never need to sample data from a specific buffer, it is wise to use a renderbuffer object for that specific buffer. If you need to sample data from a specific buffer like colors or depth values, you should use a texture attachment instead. 
    521 </p>
    522 
    523 <h2>Rendering to a texture</h2>
    524 <p>
    525   Now that we know how framebuffers (sort of) work it's time to put them to good use. We're going to render the scene into a color texture attached to a framebuffer object we created and then draw this texture over a simple quad that spans the whole screen. The visual output is then exactly the same as without a framebuffer, but this time it's all printed on top of a single quad. Now why is this useful? In the next section we'll see why.
    526 </p>
    527 
    528 <p>
    529   First thing to do is to create an actual framebuffer object and bind it, this is all relatively straightforward:
    530 </p>
    531 
    532 <pre class="cpp"><code>
    533 unsigned int framebuffer;
    534 <function id='76'>glGenFramebuffers</function>(1, &framebuffer);
    535 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, framebuffer);    
    536 </code></pre>
    537 
    538 <p>
    539   Next we create a texture image that we attach as a color attachment to the framebuffer. We set the texture's dimensions equal to the width and height of the window and keep its data uninitialized:
    540 </p>
    541 
    542 <pre><code>
    543 // generate texture
    544 unsigned int texColorBuffer;
    545 <function id='50'>glGenTextures</function>(1, &texColorBuffer);
    546 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, texColorBuffer);
    547 <function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
    548 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    549 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    550 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, 0);
    551 
    552 // attach it to currently bound framebuffer object
    553 <function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0);  
    554 </code></pre>
    555 
    556 <p>
    557   We also want to make sure OpenGL is able to do depth testing (and optionally stencil testing) so we have to make sure to add a depth (and stencil) attachment to the framebuffer. Since we'll only be sampling the color buffer and not the other buffers we can create a renderbuffer object for this purpose. 
    558 </p>
    559 
    560 <p>
    561   Creating a renderbuffer object isn't too hard. The only thing we have to remember is that we're creating it as a depth <strong>and</strong> stencil attachment renderbuffer object. We set its <em>internal format</em> to <var>GL_DEPTH24_STENCIL8</var> which is enough precision for our purposes:
    562 </p>
    563 
    564 <pre class="cpp"><code>
    565 unsigned int rbo;
    566 <function id='82'>glGenRenderbuffers</function>(1, &rbo);
    567 <function id='83'>glBindRenderbuffer</function>(GL_RENDERBUFFER, rbo); 
    568 <function id='88'>glRenderbufferStorage</function>(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);  
    569 <function id='83'>glBindRenderbuffer</function>(GL_RENDERBUFFER, 0);
    570 </code></pre>
    571 
    572 <p>
    573   Once we've allocated enough memory for the renderbuffer object we can unbind the renderbuffer.
    574 </p>
    575 
    576 <p>
    577   Then, as a final step before we complete the framebuffer, we attach the renderbuffer object to the depth <strong>and</strong> stencil attachment of the framebuffer:
    578 </p>
    579 
    580 <pre><code>
    581 <function id='89'>glFramebufferRenderbuffer</function>(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
    582 </code></pre>
    583 
    584 <p>
    585   Then we want to check if the framebuffer is complete and if it's not, we print an error message.
    586 </p>
    587 
    588 <pre class="cpp"><code>
    589 if(<function id='79'>glCheckFramebufferStatus</function>(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    590 	std::cout &lt;&lt; "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" &lt;&lt; std::endl;
    591 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0);  
    592 </code></pre>
    593 
    594 <p>
    595   Be sure to unbind the framebuffer to make sure we're not accidentally rendering to the wrong framebuffer.
    596 </p>
    597 
    598 <p>
    599   Now that the framebuffer is complete, all we need to do to render to the framebuffer's buffers instead of the default framebuffers is to simply bind the framebuffer object. All subsequent render commands will then influence the currently bound framebuffer. All the depth and stencil operations will also read from the currently bound framebuffer's depth and stencil attachments if they're available. If you were to omit a depth buffer for example, all depth testing operations will no longer work.
    600 </p>
    601 
    602 <p>
    603   So, to draw the scene to a single texture we'll have to take the following steps:
    604 </p>
    605 
    606 <ol>
    607   <li>Render the scene as usual with the new framebuffer bound as the active framebuffer.</li>
    608   <li>Bind to the default framebuffer.</li>
    609   <li>Draw a quad that spans the entire screen with the new framebuffer's color buffer as its texture.</li>
    610 </ol>
    611 
    612 <p>
    613   We'll render the same scene we've used in the <a href="https://learnopengl.com/Advanced-OpenGL/Depth-testing" target="_blank">depth testing</a> chapter, but this time with the old-school <a href="https://learnopengl.com/img/textures/container.jpg" target="_blank">container</a> texture.
    614 </p>
    615 
    616 <p>
    617   To render the quad we're going to create a fresh set of simple shaders. We're not going to include fancy matrix transformations since we'll be supplying the <a href="/code_viewer.php?code=advanced/framebuffers_quad_vertices" target="_blank">vertex coordinates as normalized device coordinates</a> so we can directly forward them as output of the vertex shader. The vertex shader looks like this:
    618 </p>
    619 
    620 <pre><code>
    621 #version 330 core
    622 layout (location = 0) in vec2 aPos;
    623 layout (location = 1) in vec2 aTexCoords;
    624 
    625 out vec2 TexCoords;
    626 
    627 void main()
    628 {
    629     gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); 
    630     TexCoords = aTexCoords;
    631 }  
    632 </code></pre>
    633 
    634 <p>
    635   Nothing too fancy. The fragment shader is even more basic since the only thing we have to do is sample from a texture:
    636 </p>
    637 
    638 <pre><code>
    639 #version 330 core
    640 out vec4 FragColor;
    641   
    642 in vec2 TexCoords;
    643 
    644 uniform sampler2D screenTexture;
    645 
    646 void main()
    647 { 
    648     FragColor = texture(screenTexture, TexCoords);
    649 }
    650 </code></pre>
    651 
    652 <p>
    653   It is then up to you to create and configure a VAO for the screen quad. A single render iteration of the framebuffer procedure has the following structure:
    654 </p>
    655 
    656 <pre><code>
    657 // first pass
    658 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, framebuffer);
    659 <function id='13'><function id='10'>glClear</function>Color</function>(0.1f, 0.1f, 0.1f, 1.0f);
    660 <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // we're not using the stencil buffer now
    661 <function id='60'>glEnable</function>(GL_DEPTH_TEST);
    662 DrawScene();	
    663   
    664 // second pass
    665 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); // back to default
    666 <function id='13'><function id='10'>glClear</function>Color</function>(1.0f, 1.0f, 1.0f, 1.0f); 
    667 <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT);
    668   
    669 screenShader.use();  
    670 <function id='27'>glBindVertexArray</function>(quadVAO);
    671 glDisable(GL_DEPTH_TEST);
    672 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, textureColorbuffer);
    673 <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6);  
    674 </code></pre>
    675 
    676 <p>
    677   There are a few things to note. First, since each framebuffer we're using has its own set of buffers, we want to clear each of those buffers with the appropriate bits set by calling <fun><function id='10'>glClear</function></fun>. Second, when drawing the quad, we're disabling depth testing since we want to make sure the quad always renders in front of everything else; we'll have to enable depth testing again when we draw the normal scene though.
    678 </p>
    679 
    680 <p>
    681   There are quite some steps that could go wrong here, so if you have no output, try to debug where possible and re-read the relevant sections of the chapter. If everything did work out successfully you'll get a visual result that looks like this:
    682 </p>
    683 
    684 <img src="/img/advanced/framebuffers_screen_texture.png" alt="An image of a 3D scene in OpenGL rendered to a texture via framebuffers"/>
    685 
    686 <p>
    687   The left shows the visual output, exactly the same as we've seen in the <a href="https://learnopengl.com/Advanced-OpenGL/Depth-testing" target="_blank">depth testing</a> chapter, but this time rendered on a simple quad. If we render the scene in wireframe it's obvious we've only drawn a single quad in the default framebuffer.
    688 </p>
    689 
    690 <p>
    691   You can find the source code of the application <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/5.1.framebuffers/framebuffers.cpp" target="_blank">here</a>.
    692 </p>
    693 
    694 <p>
    695   So what was the use of this again? Well, because we can now freely access each of the pixels of the completely rendered scene as a single texture image, we can create some interesting effects in the fragment shader. 
    696 </p>
    697 
    698 <h1>Post-processing</h1>
    699 <p>
    700   Now that the entire scene is rendered to a single texture we can create cool <def>post-processing</def> effects by manipulating the scene texture. In this section we'll show you some of the more popular post-processing effects and how you may create your own with some added creativity.
    701 </p>
    702 
    703 <p>
    704   Let's start with one of the simplest post-processing effects.
    705 </p>
    706 
    707 <h3>Inversion</h3>
    708 <p>
    709   We have access to each of the colors of the render output so it's not so hard to return the inverse of these colors in the fragment shader. We can take the color of the screen texture and inverse it by subtracting it from <code>1.0</code>:
    710 </p>
    711 
    712 <pre><code>
    713 void main()
    714 {
    715     FragColor = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);
    716 }  
    717 </code></pre>
    718 
    719 <p>
    720   While inversion is a relatively simple post-processing effect it already creates funky results:
    721 </p>
    722 
    723 <img src="/img/advanced/framebuffers_inverse.png" class="clean" alt="Post-processing image of a 3D scene in OpenGL with inversed colors"/>
    724 
    725 <p>
    726   The entire scene now has all its colors inversed with a single line of code in the fragment shader. Pretty cool huh?
    727 </p>
    728 
    729 <h3>Grayscale</h3>
    730 <p>
    731   Another interesting effect is to remove all colors from the scene except the white, gray and black colors; effectively grayscaling the entire image. An easy way to do this is by taking all the color components and averaging their results:
    732 </p>
    733 
    734 <pre><code>
    735 void main()
    736 {
    737     FragColor = texture(screenTexture, TexCoords);
    738     float average = (FragColor.r + FragColor.g + FragColor.b) / 3.0;
    739     FragColor = vec4(average, average, average, 1.0);
    740 }   
    741 </code></pre>
    742 
    743 <p>
    744   This already creates pretty good results, but the human eye tends to be more sensitive to green colors and the least to blue. So to get the most physically accurate results we'll need to use weighted channels:
    745 </p>
    746 
    747 <pre><code>
    748 void main()
    749 {
    750     FragColor = texture(screenTexture, TexCoords);
    751     float average = 0.2126 * FragColor.r + 0.7152 * FragColor.g + 0.0722 * FragColor.b;
    752     FragColor = vec4(average, average, average, 1.0);
    753 }   
    754 </code></pre>
    755 
    756 <img src="/img/advanced/framebuffers_grayscale.png" class="clean" alt="Post-processing image of a 3D scene in OpenGL with grayscale colors"/>
    757 
    758 <p>
    759   You probably won't notice the difference right away, but with more complicated scenes, such a weighted grayscaling effect tends to be more realistic.
    760 </p>
    761 
    762 <h2>Kernel effects</h2>
    763 <p>
    764   Another advantage about doing post-processing on a single texture image is that we can sample color values from other parts of the texture not specific to that fragment. We could for example take a small area around the current texture coordinate and sample multiple texture values around the current texture value. We can then create interesting effects by combining them in creative ways.
    765 </p>
    766 
    767 <p>
    768   A <def>kernel</def> (or convolution matrix) is a small matrix-like array of values centered on the current pixel that multiplies surrounding pixel values by its kernel values and adds them all together to form a single value. We're adding a small offset to the texture coordinates in surrounding directions of the current pixel and combine the results based on the kernel. An example of a kernel is given below:
    769 </p>
    770 
    771 \[\begin{bmatrix}2 & 2 & 2 \\ 2 & -15 & 2 \\ 2 & 2 & 2 \end{bmatrix}\]
    772 
    773 <p>
    774   This kernel takes 8 surrounding pixel values and multiplies them by <code>2</code> and the current pixel by <code>-15</code>. This example kernel multiplies the surrounding pixels by several weights determined in the kernel and balances the result by multiplying the current pixel by a large negative weight.
    775 </p>
    776 
    777 <note>
    778   Most kernels you'll find over the internet all sum up to <code>1</code> if you add all the weights together. If they don't add up to <code>1</code> it means that the resulting texture color ends up brighter or darker than the original texture value.
    779 </note> 
    780 
    781 <p>
    782   Kernels are an extremely useful tool for post-processing since they're quite easy to use and experiment with, and a lot of examples can be found online. We do have to slightly adapt the fragment shader a bit to actually support kernels. We make the assumption that each kernel we'll be using is a 3x3 kernel (which most kernels are):
    783 </p>
    784 
    785 <pre><code>
    786 const float offset = 1.0 / 300.0;  
    787 
    788 void main()
    789 {
    790     vec2 offsets[9] = vec2[](
    791         vec2(-offset,  offset), // top-left
    792         vec2( 0.0f,    offset), // top-center
    793         vec2( offset,  offset), // top-right
    794         vec2(-offset,  0.0f),   // center-left
    795         vec2( 0.0f,    0.0f),   // center-center
    796         vec2( offset,  0.0f),   // center-right
    797         vec2(-offset, -offset), // bottom-left
    798         vec2( 0.0f,   -offset), // bottom-center
    799         vec2( offset, -offset)  // bottom-right    
    800     );
    801 
    802     float kernel[9] = float[](
    803         -1, -1, -1,
    804         -1,  9, -1,
    805         -1, -1, -1
    806     );
    807     
    808     vec3 sampleTex[9];
    809     for(int i = 0; i &lt; 9; i++)
    810     {
    811         sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));
    812     }
    813     vec3 col = vec3(0.0);
    814     for(int i = 0; i &lt; 9; i++)
    815         col += sampleTex[i] * kernel[i];
    816     
    817     FragColor = vec4(col, 1.0);
    818 }  
    819 </code></pre>
    820 
    821 <p>
    822   In the fragment shader we first create an array of 9 <code>vec2</code> offsets for each surrounding texture coordinate. The offset is a constant value that you could customize to your liking. Then we define the kernel, which in this case is a <def>sharpen</def> kernel that sharpens each color value by sampling all surrounding pixels in an interesting way. Lastly, we add each offset to the current texture coordinate when sampling and multiply these texture values with the weighted kernel values that we add together.
    823 </p>
    824 
    825 <p>
    826   This particular sharpen kernel looks like this:
    827 </p>
    828 
    829 <img src="/img/advanced/framebuffers_sharpen.png" class="clean" alt="Post-processing image of a 3D scene in OpenGL with blurred colors"/>
    830 
    831 <p>
    832   This could be the base of some interesting effects where your player may be on a narcotic adventure.
    833 </p>
    834 
    835 
    836 <h3>Blur</h3>
    837 <p>
    838   A kernel that creates a <def>blur</def> effect is defined as follows:
    839 </p>
    840 
    841 \[\begin{bmatrix} 1 & 2 & 1 \\ 2 & 4 & 2 \\ 1 & 2 & 1 \end{bmatrix} / 16\]
    842 
    843 <p>
    844   Because all values add up to 16, directly returning the combined sampled colors would result in an extremely bright color so we have to divide each value of the kernel by <code>16</code>. The resulting kernel array then becomes:
    845 </p>
    846 
    847 <pre><code>
    848 float kernel[9] = float[](
    849     1.0 / 16, 2.0 / 16, 1.0 / 16,
    850     2.0 / 16, 4.0 / 16, 2.0 / 16,
    851     1.0 / 16, 2.0 / 16, 1.0 / 16  
    852 );
    853 </code></pre>
    854 
    855 <p>
    856   By only changing the kernel array in the fragment shader we can completely change the post-processing effect. It now looks something like this:
    857 </p>
    858 
    859 <img src="/img/advanced/framebuffers_blur.png" class="clean" alt="Post-processing image of a 3D scene in OpenGL with sharpened colors"/>
    860 
    861 
    862 <p>
    863   Such a blur effect creates interesting possibilities. We could vary the blur amount over time to create the effect of someone being drunk, or increase the blur whenever the main character is not wearing glasses. Blurring can also be a useful tool for smoothing color values which we'll see use of in later chapters.
    864 </p>
    865 
    866 <p>
    867   You can see that once we have such a little kernel implementation in place it is quite easy to create cool post-processing effects. Let's show you a last popular effect to finish this discussion.
    868 </p>
    869 
    870 <h3>Edge detection</h3>
    871 <p>
    872   Below you can find an <def>edge-detection</def> kernel that is similar to the sharpen kernel:
    873 </p>
    874 
    875 \[\begin{bmatrix} 1 & 1 & 1 \\ 1 & -8 & 1 \\ 1 & 1 & 1 \end{bmatrix}\]
    876 
    877 <p>
    878   This kernel highlights all edges and darkens the rest, which is pretty useful when we only care about edges in an image.
    879 </p>
    880 
    881 <img src="/img/advanced/framebuffers_edge_detection.png" class="clean" alt="Post-processing image of a 3D scene in OpenGL with edge detection filter"/>
    882 
    883 <p>
    884   It probably does not come as a surprise that kernels like this are used as image-manipulating tools/filters in tools like Photoshop. Because of a graphic card's ability to process fragments with extreme parallel capabilities, we can manipulate images on a per-pixel basis in real-time with relative ease. Image-editing tools therefore tend to use graphics cards for image-processing. 
    885 </p>
    886 
    887 
    888 <h2>Exercises</h2>
    889 <ul>
    890   <li>Can you use framebuffers to create a rear-view mirror? For this you'll have to draw your scene twice: one with the camera rotated 180 degrees and the other as normal. Try to create a small quad at the top of your screen to apply the mirror texture on, something like <a href="/img/advanced/framebuffers_mirror.png" target="_blank">this</a>; <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/5.2.framebuffers_exercise1/framebuffers_exercise1.cpp" target="_blank">solution</a>.</li>
    891   <li>Play around with the kernel values and create your own interesting post-processing effects. Try searching the internet as well for other interesting kernels.</li>
    892 </ul>
    893        
    894 
    895     </div>
    896     
    897 	</main>
    898 </body>
    899 </html>