commit f5b0afda7973758cd1c6c003bd84ea724118dd46 parent 70c323565b57d8914bb197f8997226bfd773371d Author: Kenji Matsuda <ftvda283@gmail.com> Date: Tue, 26 Oct 2021 14:47:38 +0900 change directory Diffstat:
213 files changed, 0 insertions(+), 44862 deletions(-)
diff --git a/orig/About.html b/man/About.html diff --git a/orig/Advanced-Lighting/Advanced-Lighting.html b/man/Advanced-Lighting/Advanced-Lighting.html diff --git a/orig/Advanced-Lighting/Bloom.html b/man/Advanced-Lighting/Bloom.html diff --git a/orig/Advanced-Lighting/Deferred-Shading.html b/man/Advanced-Lighting/Deferred-Shading.html diff --git a/orig/Advanced-Lighting/Gamma-Correction.html b/man/Advanced-Lighting/Gamma-Correction.html diff --git a/orig/Advanced-Lighting/HDR.html b/man/Advanced-Lighting/HDR.html diff --git a/orig/Advanced-Lighting/Normal-Mapping.html b/man/Advanced-Lighting/Normal-Mapping.html diff --git a/orig/Advanced-Lighting/Parallax-Mapping.html b/man/Advanced-Lighting/Parallax-Mapping.html diff --git a/orig/Advanced-Lighting/SSAO.html b/man/Advanced-Lighting/SSAO.html diff --git a/orig/Advanced-Lighting/Shadows/Point-Shadows.html b/man/Advanced-Lighting/Shadows/Point-Shadows.html diff --git a/orig/Advanced-Lighting/Shadows/Shadow-Mapping.html b/man/Advanced-Lighting/Shadows/Shadow-Mapping.html diff --git a/orig/Advanced-OpenGL/Advanced-Data.html b/man/Advanced-OpenGL/Advanced-Data.html diff --git a/orig/Advanced-OpenGL/Advanced-GLSL.html b/man/Advanced-OpenGL/Advanced-GLSL.html diff --git a/orig/Advanced-OpenGL/Anti-Aliasing.html b/man/Advanced-OpenGL/Anti-Aliasing.html diff --git a/orig/Advanced-OpenGL/Blending.html b/man/Advanced-OpenGL/Blending.html diff --git a/orig/Advanced-OpenGL/Cubemaps.html b/man/Advanced-OpenGL/Cubemaps.html diff --git a/orig/Advanced-OpenGL/Depth-testing.html b/man/Advanced-OpenGL/Depth-testing.html diff --git a/orig/Advanced-OpenGL/Face-culling.html b/man/Advanced-OpenGL/Face-culling.html diff --git a/orig/Advanced-OpenGL/Framebuffers.html b/man/Advanced-OpenGL/Framebuffers.html diff --git a/orig/Advanced-OpenGL/Geometry-Shader.html b/man/Advanced-OpenGL/Geometry-Shader.html diff --git a/orig/Advanced-OpenGL/Instancing.html b/man/Advanced-OpenGL/Instancing.html diff --git a/orig/Advanced-OpenGL/Stencil-testing.html b/man/Advanced-OpenGL/Stencil-testing.html diff --git a/orig/Code-repository.html b/man/Code-repository.html diff --git a/translation/Getting-started/Camera.html b/man/Getting-started/Camera.html diff --git a/translation/Getting-started/Coordinate-Systems.html b/man/Getting-started/Coordinate-Systems.html diff --git a/translation/Getting-started/Creating-a-window.html b/man/Getting-started/Creating-a-window.html diff --git a/translation/Getting-started/Hello-Triangle.html b/man/Getting-started/Hello-Triangle.html diff --git a/translation/Getting-started/Hello-Window.html b/man/Getting-started/Hello-Window.html diff --git a/translation/Getting-started/OpenGL.html b/man/Getting-started/OpenGL.html diff --git a/translation/Getting-started/Review.html b/man/Getting-started/Review.html diff --git a/translation/Getting-started/Shaders.html b/man/Getting-started/Shaders.html diff --git a/translation/Getting-started/Textures.html b/man/Getting-started/Textures.html diff --git a/translation/Getting-started/Transformations.html b/man/Getting-started/Transformations.html diff --git a/orig/Guest-Articles/2020/OIT/Introduction.html b/man/Guest-Articles/2020/OIT/Introduction.html diff --git a/orig/Guest-Articles/2020/OIT/Weighted-Blended.html b/man/Guest-Articles/2020/OIT/Weighted-Blended.html diff --git a/orig/Guest-Articles/2020/Skeletal-Animation.html b/man/Guest-Articles/2020/Skeletal-Animation.html diff --git a/orig/Guest-Articles/2021/CSM.html b/man/Guest-Articles/2021/CSM.html diff --git a/orig/Guest-Articles/2021/Scene/Frustum-Culling.html b/man/Guest-Articles/2021/Scene/Frustum-Culling.html diff --git a/orig/Guest-Articles/2021/Scene/Scene-Graph.html b/man/Guest-Articles/2021/Scene/Scene-Graph.html diff --git a/orig/Guest-Articles/How-to-publish.html b/man/Guest-Articles/How-to-publish.html diff --git a/orig/In-Practice/2D-Game/Audio.html b/man/In-Practice/2D-Game/Audio.html diff --git a/orig/In-Practice/2D-Game/Breakout.html b/man/In-Practice/2D-Game/Breakout.html diff --git a/orig/In-Practice/2D-Game/Collisions/Ball.html b/man/In-Practice/2D-Game/Collisions/Ball.html diff --git a/orig/In-Practice/2D-Game/Collisions/Collision-detection.html b/man/In-Practice/2D-Game/Collisions/Collision-detection.html diff --git a/orig/In-Practice/2D-Game/Collisions/Collision-resolution.html b/man/In-Practice/2D-Game/Collisions/Collision-resolution.html diff --git a/orig/In-Practice/2D-Game/Final-thoughts.html b/man/In-Practice/2D-Game/Final-thoughts.html diff --git a/orig/In-Practice/2D-Game/Levels.html b/man/In-Practice/2D-Game/Levels.html diff --git a/orig/In-Practice/2D-Game/Particles.html b/man/In-Practice/2D-Game/Particles.html diff --git a/orig/In-Practice/2D-Game/Postprocessing.html b/man/In-Practice/2D-Game/Postprocessing.html diff --git a/orig/In-Practice/2D-Game/Powerups.html b/man/In-Practice/2D-Game/Powerups.html diff --git a/orig/In-Practice/2D-Game/Render-text.html b/man/In-Practice/2D-Game/Render-text.html diff --git a/orig/In-Practice/2D-Game/Rendering-Sprites.html b/man/In-Practice/2D-Game/Rendering-Sprites.html diff --git a/orig/In-Practice/2D-Game/Setting-up.html b/man/In-Practice/2D-Game/Setting-up.html diff --git a/orig/In-Practice/Debugging.html b/man/In-Practice/Debugging.html diff --git a/orig/In-Practice/Text-Rendering.html b/man/In-Practice/Text-Rendering.html diff --git a/translation/Introduction.html b/man/Introduction.html diff --git a/orig/Lighting/Basic-Lighting.html b/man/Lighting/Basic-Lighting.html diff --git a/orig/Lighting/Colors.html b/man/Lighting/Colors.html diff --git a/orig/Lighting/Light-casters.html b/man/Lighting/Light-casters.html diff --git a/orig/Lighting/Lighting-maps.html b/man/Lighting/Lighting-maps.html diff --git a/orig/Lighting/Materials.html b/man/Lighting/Materials.html diff --git a/orig/Lighting/Multiple-lights.html b/man/Lighting/Multiple-lights.html diff --git a/orig/Lighting/Review.html b/man/Lighting/Review.html diff --git a/orig/Model-Loading/Assimp.html b/man/Model-Loading/Assimp.html diff --git a/orig/Model-Loading/Mesh.html b/man/Model-Loading/Mesh.html diff --git a/orig/Model-Loading/Model.html b/man/Model-Loading/Model.html diff --git a/orig/PBR/IBL/Diffuse-irradiance.html b/man/PBR/IBL/Diffuse-irradiance.html diff --git a/orig/PBR/IBL/Specular-IBL.html b/man/PBR/IBL/Specular-IBL.html diff --git a/orig/PBR/Lighting.html b/man/PBR/Lighting.html diff --git a/orig/PBR/Theory.html b/man/PBR/Theory.html diff --git a/orig/Translations.html b/man/Translations.html diff --git a/translation/img/getting-started/camera_axes.png b/man/img/getting-started/camera_axes.png Binary files differ. diff --git a/translation/img/getting-started/camera_pitch.png b/man/img/getting-started/camera_pitch.png Binary files differ. diff --git a/translation/img/getting-started/camera_pitch_yaw_roll.png b/man/img/getting-started/camera_pitch_yaw_roll.png Binary files differ. diff --git a/translation/img/getting-started/camera_triangle.png b/man/img/getting-started/camera_triangle.png Binary files differ. diff --git a/translation/img/getting-started/camera_yaw.png b/man/img/getting-started/camera_yaw.png Binary files differ. diff --git a/translation/img/getting-started/cmake.png b/man/img/getting-started/cmake.png Binary files differ. diff --git a/translation/img/getting-started/coordinate_systems.png b/man/img/getting-started/coordinate_systems.png Binary files differ. diff --git a/translation/img/getting-started/coordinate_systems_multiple_objects.png b/man/img/getting-started/coordinate_systems_multiple_objects.png Binary files differ. diff --git a/translation/img/getting-started/coordinate_systems_result.png b/man/img/getting-started/coordinate_systems_result.png Binary files differ. diff --git a/translation/img/getting-started/coordinate_systems_right_handed.png b/man/img/getting-started/coordinate_systems_right_handed.png Binary files differ. diff --git a/translation/img/getting-started/filter_linear.png b/man/img/getting-started/filter_linear.png Binary files differ. diff --git a/translation/img/getting-started/filter_nearest.png b/man/img/getting-started/filter_nearest.png Binary files differ. diff --git a/translation/img/getting-started/glm.png b/man/img/getting-started/glm.png Binary files differ. diff --git a/translation/img/getting-started/hellotriangle.png b/man/img/getting-started/hellotriangle.png Binary files differ. diff --git a/translation/img/getting-started/hellotriangle2.png b/man/img/getting-started/hellotriangle2.png Binary files differ. diff --git a/translation/img/getting-started/hellowindow.png b/man/img/getting-started/hellowindow.png Binary files differ. diff --git a/translation/img/getting-started/hellowindow2.png b/man/img/getting-started/hellowindow2.png Binary files differ. diff --git a/translation/img/getting-started/include_directories.png b/man/img/getting-started/include_directories.png Binary files differ. diff --git a/translation/img/getting-started/linker_input.png b/man/img/getting-started/linker_input.png Binary files differ. diff --git a/translation/img/getting-started/matrix_multiplication.png b/man/img/getting-started/matrix_multiplication.png Binary files differ. diff --git a/translation/img/getting-started/mipmaps.png b/man/img/getting-started/mipmaps.png Binary files differ. diff --git a/translation/img/getting-started/ndc.png b/man/img/getting-started/ndc.png Binary files differ. diff --git a/translation/img/getting-started/opengl.jpg b/man/img/getting-started/opengl.jpg Binary files differ. diff --git a/translation/img/getting-started/orthographic_frustum.png b/man/img/getting-started/orthographic_frustum.png Binary files differ. diff --git a/translation/img/getting-started/perspective.png b/man/img/getting-started/perspective.png Binary files differ. diff --git a/translation/img/getting-started/perspective_frustum.png b/man/img/getting-started/perspective_frustum.png Binary files differ. diff --git a/translation/img/getting-started/perspective_orthographic.png b/man/img/getting-started/perspective_orthographic.png Binary files differ. diff --git a/translation/img/getting-started/pipeline.png b/man/img/getting-started/pipeline.png Binary files differ. diff --git a/translation/img/getting-started/shader2.png b/man/img/getting-started/shader2.png Binary files differ. diff --git a/translation/img/getting-started/shaders.png b/man/img/getting-started/shaders.png Binary files differ. diff --git a/translation/img/getting-started/shaders3.png b/man/img/getting-started/shaders3.png Binary files differ. diff --git a/translation/img/getting-started/start_video.png b/man/img/getting-started/start_video.png Binary files differ. diff --git a/translation/img/getting-started/tex_coords.png b/man/img/getting-started/tex_coords.png Binary files differ. diff --git a/translation/img/getting-started/texture_filtering.png b/man/img/getting-started/texture_filtering.png Binary files differ. diff --git a/translation/img/getting-started/texture_wrapping.png b/man/img/getting-started/texture_wrapping.png Binary files differ. diff --git a/translation/img/getting-started/textures.png b/man/img/getting-started/textures.png Binary files differ. diff --git a/translation/img/getting-started/textures2.png b/man/img/getting-started/textures2.png Binary files differ. diff --git a/translation/img/getting-started/textures_combined.png b/man/img/getting-started/textures_combined.png Binary files differ. diff --git a/translation/img/getting-started/textures_combined2.png b/man/img/getting-started/textures_combined2.png Binary files differ. diff --git a/translation/img/getting-started/textures_funky.png b/man/img/getting-started/textures_funky.png Binary files differ. diff --git a/translation/img/getting-started/transformations.png b/man/img/getting-started/transformations.png Binary files differ. diff --git a/translation/img/getting-started/vc_directories.png b/man/img/getting-started/vc_directories.png Binary files differ. diff --git a/translation/img/getting-started/vectors.png b/man/img/getting-started/vectors.png Binary files differ. diff --git a/translation/img/getting-started/vectors_addition.png b/man/img/getting-started/vectors_addition.png Binary files differ. diff --git a/translation/img/getting-started/vectors_angle.png b/man/img/getting-started/vectors_angle.png Binary files differ. diff --git a/translation/img/getting-started/vectors_crossproduct.png b/man/img/getting-started/vectors_crossproduct.png Binary files differ. diff --git a/translation/img/getting-started/vectors_scale.png b/man/img/getting-started/vectors_scale.png Binary files differ. diff --git a/translation/img/getting-started/vectors_subtraction.png b/man/img/getting-started/vectors_subtraction.png Binary files differ. diff --git a/translation/img/getting-started/vectors_triangle.png b/man/img/getting-started/vectors_triangle.png Binary files differ. diff --git a/translation/img/getting-started/vertex_array_objects.png b/man/img/getting-started/vertex_array_objects.png Binary files differ. diff --git a/translation/img/getting-started/vertex_array_objects_ebo.png b/man/img/getting-started/vertex_array_objects_ebo.png Binary files differ. diff --git a/translation/img/getting-started/vertex_attribute_pointer.png b/man/img/getting-started/vertex_attribute_pointer.png Binary files differ. diff --git a/translation/img/getting-started/vertex_attribute_pointer_interleaved.png b/man/img/getting-started/vertex_attribute_pointer_interleaved.png Binary files differ. diff --git a/translation/img/getting-started/vertex_attribute_pointer_interleaved_textures.png b/man/img/getting-started/vertex_attribute_pointer_interleaved_textures.png Binary files differ. diff --git a/translation/img/getting-started/x64.png b/man/img/getting-started/x64.png Binary files differ. diff --git a/translation/img/start_video.png b/man/img/start_video.png Binary files differ. diff --git a/translation/img/textures/awesomeface.png b/man/img/textures/awesomeface.png Binary files differ. diff --git a/translation/static/functions.js b/man/static/functions.js diff --git a/translation/static/mathjax.js b/man/static/mathjax.js diff --git a/translation/static/style.css b/man/static/style.css diff --git a/translation/video/getting-started/camera_circle.mp4 b/man/video/getting-started/camera_circle.mp4 Binary files differ. diff --git a/translation/video/getting-started/camera_mouse.mp4 b/man/video/getting-started/camera_mouse.mp4 Binary files differ. diff --git a/translation/video/getting-started/camera_smooth.mp4 b/man/video/getting-started/camera_smooth.mp4 Binary files differ. diff --git a/translation/video/getting-started/coordinate_system_depth.mp4 b/man/video/getting-started/coordinate_system_depth.mp4 Binary files differ. diff --git a/translation/video/getting-started/coordinate_system_no_depth.mp4 b/man/video/getting-started/coordinate_system_no_depth.mp4 Binary files differ. diff --git a/translation/video/getting-started/shaders.mp4 b/man/video/getting-started/shaders.mp4 Binary files differ. diff --git a/translation/video/getting-started/transformations.mp4 b/man/video/getting-started/transformations.mp4 Binary files differ. diff --git a/orig/Getting-started/Camera.html b/orig/Getting-started/Camera.html @@ -1,843 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Camera</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Camera</h1> -<h1 id="content-url" style='display:none;'>Getting-started/Camera</h1> -<p> - In the previous chapter we discussed the view matrix and how we can use the view matrix to move around the scene (we moved backwards a little). OpenGL by itself is not familiar with the concept of a <em>camera</em>, but we can try to simulate one by moving all objects in the scene in the reverse direction, giving the illusion that <strong>we</strong> are moving. -</p> - -<p> - In this chapter we'll discuss how we can set up a camera in OpenGL. We will discuss a fly style camera that allows you to freely move around in a 3D scene. We'll also discuss keyboard and mouse input and finish with a custom camera class. -</p> - -<h2>Camera/View space</h2> -<p> - When we're talking about camera/view space we're talking about all the vertex coordinates as seen from the camera's perspective as the origin of the scene: the view matrix transforms all the world coordinates into view coordinates that are relative to the camera's position and direction. To define a camera we need its position in world space, the direction it's looking at, a vector pointing to the right and a vector pointing upwards from the camera. A careful reader may notice that we're actually going to create a coordinate system with 3 perpendicular unit axes with the camera's position as the origin. -</p> - -<img src="/img/getting-started/camera_axes.png" class="clean"/> - -<h3>1. Camera position</h3> -<p> - Getting the camera position is easy. The camera position is a vector in world space that points to the camera's position. We set the camera at the same position we've set the camera in the previous chapter: -</p> - -<pre><code> -glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); -</code></pre> - -<note> - Don't forget that the positive z-axis is going through your screen towards you so if we want the camera to move backwards, we move along the positive z-axis. -</note> - -<h3>2. Camera direction</h3> -<p> - The next vector required is the camera's direction e.g. at what direction it is pointing at. For now we let the camera point to the origin of our scene: <code>(0,0,0)</code>. Remember that if we subtract two vectors from each other we get a vector that's the difference of these two vectors? Subtracting the camera position vector from the scene's origin vector thus results in the direction vector we want. For the view matrix's coordinate system we want its z-axis to be positive and because by convention (in OpenGL) the camera points towards the negative z-axis we want to negate the direction vector. If we switch the subtraction order around we now get a vector pointing towards the camera's positive z-axis: -</p> - -<pre><code> -glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f); -glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget); -</code></pre> - -<warning> - The name <em>direction</em> vector is not the best chosen name, since it is actually pointing in the reverse direction of what it is targeting. -</warning> - -<h3>3. Right axis</h3> -<p> - The next vector that we need is a <em>right</em> vector that represents the positive x-axis of the camera space. To get the <em>right</em> vector we use a little trick by first specifying an <em>up</em> vector that points upwards (in world space). Then we do a cross product on the up vector and the direction vector from step 2. Since the result of a cross product is a vector perpendicular to both vectors, we will get a vector that points in the positive x-axis's direction (if we would switch the cross product order we'd get a vector that points in the negative x-axis): -</p> - -<pre><code> -glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f); -glm::vec3 cameraRight = glm::normalize(<function id='61'>glm::cross</function>(up, cameraDirection)); -</code></pre> - -<h3>4. Up axis</h3> -<p> - Now that we have both the x-axis vector and the z-axis vector, retrieving the vector that points to the camera's positive y-axis is relatively easy: we take the cross product of the right and direction vector: -</p> - -<pre><code> -glm::vec3 cameraUp = <function id='61'>glm::cross</function>(cameraDirection, cameraRight); -</code></pre> - -<p> - With the help of the cross product and a few tricks we were able to create all the vectors that form the view/camera space. For the more mathematically inclined readers, this process is known as the <a href="http://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process" target="_blank">Gram-Schmidt</a> process in linear algebra. Using these camera vectors we can now create a <def>LookAt</def> matrix that proves very useful for creating a camera. -</p> - -<h2>Look At</h2> -<p> - A great thing about matrices is that if you define a coordinate space using 3 perpendicular (or non-linear) axes you can create a matrix with those 3 axes plus a translation vector and you can transform any vector to that coordinate space by multiplying it with this matrix. This is exactly what the <em>LookAt</em> matrix does and now that we have 3 perpendicular axes and a position vector to define the camera space we can create our own LookAt matrix: - - \[LookAt = \begin{bmatrix} \color{red}{R_x} & \color{red}{R_y} & \color{red}{R_z} & 0 \\ \color{green}{U_x} & \color{green}{U_y} & \color{green}{U_z} & 0 \\ \color{blue}{D_x} & \color{blue}{D_y} & \color{blue}{D_z} & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} * \begin{bmatrix} 1 & 0 & 0 & -\color{purple}{P_x} \\ 0 & 1 & 0 & -\color{purple}{P_y} \\ 0 & 0 & 1 & -\color{purple}{P_z} \\ 0 & 0 & 0 & 1 \end{bmatrix} \] - - Where \(\color{red}R\) is the right vector, \(\color{green}U\) is the up vector, \(\color{blue}D\) is the direction vector and \(\color{purple}P\) is the camera's position vector. Note that the rotation (left matrix) and translation (right matrix) parts are inverted (transposed and negated respectively) since we want to rotate and translate the world in the opposite direction of where we want the camera to move. Using this LookAt matrix as our view matrix effectively transforms all the world coordinates to the view space we just defined. The LookAt matrix then does exactly what it says: it creates a view matrix that <em>looks</em> at a given target. -</p> - -<p> - Luckily for us, GLM already does all this work for us. We only have to specify a camera position, a target position and a vector that represents the up vector in world space (the up vector we used for calculating the right vector). GLM then creates the LookAt matrix that we can use as our view matrix: -</p> - -<pre><code> -glm::mat4 view; -view = <function id='62'>glm::lookAt</function>(glm::vec3(0.0f, 0.0f, 3.0f), - glm::vec3(0.0f, 0.0f, 0.0f), - glm::vec3(0.0f, 1.0f, 0.0f)); -</code></pre> - -<p> - The <fun><function id='62'>glm::LookAt</function></fun> function requires a position, target and up vector respectively. This example creates a view matrix that is the same as the one we created in the previous chapter. -</p> - -<p> - Before delving into user input, let's get a little funky first by rotating the camera around our scene. We keep the target of the scene at <code>(0,0,0)</code>. We use a little bit of trigonometry to create an <code>x</code> and <code>z</code> coordinate each frame that represents a point on a circle and we'll use these for our camera position. By re-calculating the <code>x</code> and <code>y</code> coordinate over time we're traversing all the points in a circle and thus the camera rotates around the scene. We enlarge this circle by a pre-defined <var>radius</var> and create a new view matrix each frame using GLFW's <fun><function id='47'>glfwGetTime</function></fun> function: -</p> - -<pre><code> -const float radius = 10.0f; -float camX = sin(<function id='47'>glfwGetTime</function>()) * radius; -float camZ = cos(<function id='47'>glfwGetTime</function>()) * radius; -glm::mat4 view; -view = <function id='62'>glm::lookAt</function>(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0)); -</code></pre> - -<p> - If you run this code you should get something like this: -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/getting-started/camera_circle.mp4" type="video/mp4"/> - <img src="/img/getting-started/camera_circle.png" class="clean"/> - </video> -</div> - -<p> - With this little snippet of code the camera now circles around the scene over time. Feel free to experiment with the radius and position/direction parameters to get the feel of how this <em>LookAt</em> matrix works. Also, check the <a href="/code_viewer_gh.php?code=src/1.getting_started/7.1.camera_circle/camera_circle.cpp" target="_blank">source code</a> if you're stuck. -</p> - -<h1>Walk around</h1> -<p> - Swinging the camera around a scene is fun, but it's more fun to do all the movement ourselves! First we need to set up a camera system, so it is useful to define some camera variables at the top of our program: -</p> - -<pre><code> -glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); -glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f); -glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f); -</code></pre> - -<p> - The <code>LookAt</code> function now becomes: -</p> - -<pre><code> -view = <function id='62'>glm::lookAt</function>(cameraPos, cameraPos + cameraFront, cameraUp); -</code></pre> - -<p> - First we set the camera position to the previously defined <var>cameraPos</var>. The direction is the current position + the direction vector we just defined. This ensures that however we move, the camera keeps looking at the target direction. Let's play a bit with these variables by updating the <var>cameraPos</var> vector when we press some keys. -</p> - -<p> - We already defined a <fun>processInput</fun> function to manage GLFW's keyboard input so let's add a few extra key commands: -</p> - -<pre><code> -void processInput(GLFWwindow *window) -{ - ... - const float cameraSpeed = 0.05f; // adjust accordingly - if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) - cameraPos += cameraSpeed * cameraFront; - if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) - cameraPos -= cameraSpeed * cameraFront; - if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) - cameraPos -= glm::normalize(<function id='61'>glm::cross</function>(cameraFront, cameraUp)) * cameraSpeed; - if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) - cameraPos += glm::normalize(<function id='61'>glm::cross</function>(cameraFront, cameraUp)) * cameraSpeed; -} -</code></pre> - -<p> - Whenever we press one of the <code>WASD</code> keys, the camera's position is updated accordingly. If we want to move forward or backwards we add or subtract the direction vector from the position vector scaled by some speed value. If we want to move sideways we do a cross product to create a <em>right</em> vector and we move along the right vector accordingly. This creates the familiar <def>strafe</def> effect when using the camera. -</p> - -<note> - Note that we normalize the resulting <em>right</em> vector. If we wouldn't normalize this vector, the resulting cross product may return differently sized vectors based on the <var>cameraFront</var> variable. If we would not normalize the vector we would move slow or fast based on the camera's orientation instead of at a consistent movement speed. -</note> - -<p> - By now, you should already be able to move the camera somewhat, albeit at a speed that's system-specific so you may need to adjust <var>cameraSpeed</var>. - -<h2>Movement speed</h2> -<p> - Currently we used a constant value for movement speed when walking around. In theory this seems fine, but in practice people's machines have different processing powers and the result of that is that some people are able to render much more frames than others each second. Whenever a user renders more frames than another user he also calls <fun>processInput</fun> more often. The result is that some people move really fast and some really slow depending on their setup. When shipping your application you want to make sure it runs the same on all kinds of hardware. -</p> - -<p> - Graphics applications and games usually keep track of a <def>deltatime</def> variable that stores the time it took to render the last frame. We then multiply all velocities with this <var>deltaTime</var> value. The result is that when we have a large <var>deltaTime</var> in a frame, meaning that the last frame took longer than average, the velocity for that frame will also be a bit higher to balance it all out. When using this approach it does not matter if you have a very fast or slow pc, the velocity of the camera will be balanced out accordingly so each user will have the same experience. -</p> - -<p> - To calculate the <var>deltaTime</var> value we keep track of 2 global variables: -</p> - -<pre><code> -float deltaTime = 0.0f; // Time between current frame and last frame -float lastFrame = 0.0f; // Time of last frame -</code></pre> - -<p> - Within each frame we then calculate the new <var>deltaTime</var> value for later use: -</p> - -<pre><code> -float currentFrame = <function id='47'>glfwGetTime</function>(); -deltaTime = currentFrame - lastFrame; -lastFrame = currentFrame; -</code></pre> - -<p> - Now that we have <var>deltaTime</var> we can take it into account when calculating the velocities: -</p> - -<pre><code> -void processInput(GLFWwindow *window) -{ - float cameraSpeed = 2.5f * deltaTime; - [...] -} -</code></pre> - -<p> - Since we're using <var>deltaTime</var> the camera will now move at a constant speed of <code>2.5</code> units per second. Together with the previous section we should now have a much smoother and more consistent camera system for moving around the scene: -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/getting-started/camera_smooth.mp4" type="video/mp4" /> - <img src="/img/getting-started/camera_smooth.png" class="clean"/> - </video> -</div> - -<p> - And now we have a camera that walks and looks equally fast on any system. Again, check the <a href="/code_viewer_gh.php?code=src/1.getting_started/7.2.camera_keyboard_dt/camera_keyboard_dt.cpp" target="_blank">source code</a> if you're stuck. We'll see the <var>deltaTime</var> value frequently return with anything movement related. -</p> - -<h1>Look around</h1> -<p> - Only using the keyboard keys to move around isn't that interesting. Especially since we can't turn around making the movement rather restricted. That's where the mouse comes in! -</p> - -<p> - To look around the scene we have to change the <var>cameraFront</var> vector based on the input of the mouse. However, changing the direction vector based on mouse rotations is a little complicated and requires some trigonometry. If you do not understand the trigonometry, don't worry, you can just skip to the code sections and paste them in your code; you can always come back later if you want to know more. -</p> - -<h2>Euler angles</h2> -<p> - Euler angles are 3 values that can represent any rotation in 3D, defined by Leonhard Euler somewhere in the 1700s. There are 3 Euler angles: <em>pitch</em>, <em>yaw</em> and <em>roll</em>. The following image gives them a visual meaning: -</p> - -<img src="/img/getting-started/camera_pitch_yaw_roll.png" alt="Euler angles yaw pitch and roll" class="clean"/> - -<p> - The <def>pitch</def> is the angle that depicts how much we're looking up or down as seen in the first image. The second image shows the <def>yaw</def> value which represents the magnitude we're looking to the left or to the right. The <def>roll</def> represents how much we <em>roll</em> as mostly used in space-flight cameras. Each of the Euler angles are represented by a single value and with the combination of all 3 of them we can calculate any rotation vector in 3D. -</p> - -<p> - For our camera system we only care about the yaw and pitch values so we won't discuss the roll value here. Given a pitch and a yaw value we can convert them into a 3D vector that represents a new direction vector. The process of converting yaw and pitch values to a direction vector requires a bit of trigonometry. and we start with a basic case: -</p> - -<p> - Let's start with a bit of a refresher and check the general right triangle case (with one side at a 90 degree angle): - -<img src="/img/getting-started/camera_triangle.png" class="clean"/> - -<p> - If we define the hypotenuse to be of length <code>1</code> we know from trigonometry (soh cah toa) that the adjacant side's length is \(\cos \ \color{red}x/\color{purple}h = \cos \ \color{red}x/\color{purple}1 = \cos\ \color{red}x\) and that the opposing side's length is \(\sin \ \color{green}y/\color{purple}h = \sin \ \color{green}y/\color{purple}1 = \sin\ \color{green}y\). This gives us some general formulas for retrieving the length in both the <code>x</code> and <code>y</code> sides on right triangles, depending on the given angle. Let's use this to calculate the components of the direction vector. -</p> - -<p> - Let's imagine this same triangle, but now looking at it from a top perspective with the adjacent and opposite sides being parallel to the scene's x and z axis (as if looking down the y-axis). -</p> - -<img src="/img/getting-started/camera_yaw.png" class="clean"/> - -<p> - If we visualize the yaw angle to be the counter-clockwise angle starting from the <code>x</code> side we can see that the length of the <code>x</code> side relates to <code>cos(yaw)</code>. And similarly how the length of the <code>z</code> side relates to <code>sin(yaw)</code>. -</p> - -<p> - If we take this knowledge and a given <code>yaw</code> value we can use it to create a camera direction vector: -</p> - -<pre><code> -glm::vec3 direction; -direction.x = cos(<function id='63'>glm::radians</function>(yaw)); // Note that we convert the angle to radians first -direction.z = sin(<function id='63'>glm::radians</function>(yaw)); -</code></pre> - -<p> - This solves how we can get a 3D direction vector from a yaw value, but pitch needs to be included as well. Let's now look at the <code>y</code> axis side as if we're sitting on the <code>xz</code> plane: -</p> - - -<img src="/img/getting-started/camera_pitch.png" class="clean"/> - -<p> - Similarly, from this triangle we can see that the direction's y component equals <code>sin(pitch)</code> so let's fill that in: -</p> - - -<pre><code> -direction.y = sin(<function id='63'>glm::radians</function>(pitch)); -</code></pre> - -<p> - However, from the pitch triangle we can also see the <code>xz</code> sides are influenced by <code>cos(pitch)</code> so we need to make sure this is also part of the direction vector. With this included we get the final direction vector as translated from yaw and pitch Euler angles: -</p> - -<pre><code> -direction.x = cos(<function id='63'>glm::radians</function>(yaw)) * cos(<function id='63'>glm::radians</function>(pitch)); -direction.y = sin(<function id='63'>glm::radians</function>(pitch)); -direction.z = sin(<function id='63'>glm::radians</function>(yaw)) * cos(<function id='63'>glm::radians</function>(pitch)); -</code></pre> - -<p> - This gives us a formula to convert yaw and pitch values to a 3-dimensional direction vector that we can use for looking around. -</p> - -<p> - We've set up the scene world so everything's positioned in the direction of the negative z-axis. However, if we look at the <code>x</code> and <code>z</code> yaw triangle we see that a \(\theta\) of <code>0</code> results in the camera's <code>direction</code> vector to point towards the positive x-axis. To make sure the camera points towards the negative z-axis by default we can give the <code>yaw</code> a default value of a 90 degree clockwise rotation. Positive degrees rotate counter-clockwise so we set the default <code>yaw</code> value to: -</p> - -<pre><code> -yaw = -90.0f; -</code></pre> - -<p> - - You've probably wondered by now: how do we set and modify these yaw and pitch values? -</p> - - -<h2>Mouse input</h2> -<p> - The yaw and pitch values are obtained from mouse (or controller/joystick) movement where horizontal mouse-movement affects the yaw and vertical mouse-movement affects the pitch. The idea is to store the last frame's mouse positions and calculate in the current frame how much the mouse values changed. The higher the horizontal or vertical difference, the more we update the pitch or yaw value and thus the more the camera should move. -</p> - -<p> - First we will tell GLFW that it should hide the cursor and <def>capture</def> it. Capturing a cursor means that, once the application has focus, the mouse cursor stays within the center of the window (unless the application loses focus or quits). We can do this with one simple configuration call: -</p> - -<pre><code> -glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); -</code></pre> - -<p> - After this call, wherever we move the mouse it won't be visible and it should not leave the window. This is perfect for an FPS camera system. -</p> - -<p> -To calculate the pitch and yaw values we need to tell GLFW to listen to mouse-movement events. We do this by creating a callback function with the following prototype: -</p> - -<pre><code> -void mouse_callback(GLFWwindow* window, double xpos, double ypos); -</code></pre> - -<p> - Here <var>xpos</var> and <var>ypos</var> represent the current mouse positions. As soon as we register the callback function with GLFW each time the mouse moves, the <fun>mouse_callback</fun> function is called: -</p> - -<pre><code> -glfwSetCursorPosCallback(window, mouse_callback); -</code></pre> - -<p> - When handling mouse input for a fly style camera there are several steps we have to take before we're able to fully calculate the camera's direction vector: - - <ol> - <li>Calculate the mouse's offset since the last frame.</li> - <li>Add the offset values to the camera's yaw and pitch values.</li> - <li>Add some constraints to the minimum/maximum pitch values.</li> - <li>Calculate the direction vector.</li> - </ol> -</p> - -<p> - The first step is to calculate the offset of the mouse since last frame. We first have to store the last mouse positions in the application, which we initialize to be in the center of the screen (screen size is <code>800</code> by <code>600</code>) initially: -</p> - -<pre class="cpp"><code> -float lastX = 400, lastY = 300; -</code></pre> - -<p> - Then in the mouse's callback function we calculate the offset movement between the last and current frame: -</p> - -<pre><code> -float xoffset = xpos - lastX; -float yoffset = lastY - ypos; // reversed since y-coordinates range from bottom to top -lastX = xpos; -lastY = ypos; - -const float sensitivity = 0.1f; -xoffset *= sensitivity; -yoffset *= sensitivity; -</code></pre> - -<p> - Note that we multiply the offset values by a <var>sensitivity</var> value. If we omit this multiplication the mouse movement would be way too strong; fiddle around with the sensitivity value to your liking. -</p> - -<p> - Next we add the offset values to the globally declared <var>pitch</var> and <var>yaw</var> values: -</p> - -<pre><code> -yaw += xoffset; -pitch += yoffset; -</code></pre> - -<p> - In the third step we'd like to add some constraints to the camera so users won't be able to make weird camera movements (also causes a LookAt flip once direction vector is parallel to the world up direction). The pitch needs to be constrained in such a way that users won't be able to look higher than <code>89</code> degrees (at <code>90</code> degrees we get the LookAt flip) and also not below <code>-89</code> degrees. This ensures the user will be able to look up to the sky or below to his feet but not further. The constraints work by replacing the Euler value with its constraint value whenever it breaches the constraint: -</p> - -<pre><code> -if(pitch > 89.0f) - pitch = 89.0f; -if(pitch < -89.0f) - pitch = -89.0f; -</code></pre> - -<p> - Note that we set no constraint on the yaw value since we don't want to constrain the user in horizontal rotation. However, it's just as easy to add a constraint to the yaw as well if you feel like it. -</p> - -<p> - The fourth and last step is to calculate the actual direction vector using the formula from the previous section: -</p> - -<pre><code> -glm::vec3 direction; -direction.x = cos(<function id='63'>glm::radians</function>(yaw)) * cos(<function id='63'>glm::radians</function>(pitch)); -direction.y = sin(<function id='63'>glm::radians</function>(pitch)); -direction.z = sin(<function id='63'>glm::radians</function>(yaw)) * cos(<function id='63'>glm::radians</function>(pitch)); -cameraFront = glm::normalize(direction); -</code></pre> - - <p> - This computed direction vector then contains all the rotations calculated from the mouse's movement. Since the <var>cameraFront</var> vector is already included in glm's <fun>lookAt</fun> function we're set to go. -</p> - -<p> - If you'd now run the code you'll notice the camera makes a large sudden jump whenever the window first receives focus of your mouse cursor. The cause for this sudden jump is that as soon as your cursor enters the window the mouse callback function is called with an <var>xpos</var> and <var>ypos</var> position equal to the location your mouse entered the screen from. This is often a position that is significantly far away from the center of the screen, resulting in large offsets and thus a large movement jump. We can circumvent this issue by defining a global <code>bool</code> variable to check if this is the first time we receive mouse input. If it is the first time, we update the initial mouse positions to the new <var>xpos</var> and <code>ypos</code> values. The resulting mouse movements will then use the newly entered mouse's position coordinates to calculate the offsets: -</p> - -<pre><code> -if (firstMouse) // initially set to true -{ - lastX = xpos; - lastY = ypos; - firstMouse = false; -} -</code></pre> - -<p> - The final code then becomes: -</p> - -<pre><code> -void mouse_callback(GLFWwindow* window, double xpos, double ypos) -{ - if (firstMouse) - { - lastX = xpos; - lastY = ypos; - firstMouse = false; - } - - float xoffset = xpos - lastX; - float yoffset = lastY - ypos; - lastX = xpos; - lastY = ypos; - - float sensitivity = 0.1f; - xoffset *= sensitivity; - yoffset *= sensitivity; - - yaw += xoffset; - pitch += yoffset; - - if(pitch > 89.0f) - pitch = 89.0f; - if(pitch < -89.0f) - pitch = -89.0f; - - glm::vec3 direction; - direction.x = cos(<function id='63'>glm::radians</function>(yaw)) * cos(<function id='63'>glm::radians</function>(pitch)); - direction.y = sin(<function id='63'>glm::radians</function>(pitch)); - direction.z = sin(<function id='63'>glm::radians</function>(yaw)) * cos(<function id='63'>glm::radians</function>(pitch)); - cameraFront = glm::normalize(direction); -} -</code></pre> - -<p> - There we go! Give it a spin and you'll see that we can now freely move through our 3D scene! -</p> - - -<h2>Zoom</h2> -<p> - As a little extra to the camera system we'll also implement a zooming interface. In the previous chapter we said the <em>Field of view</em> or <em>fov</em> largely defines how much we can see of the scene. When the field of view becomes smaller, the scene's projected space gets smaller. This smaller space is projected over the same NDC, giving the illusion of zooming in. To zoom in, we're going to use the mouse's scroll wheel. Similar to mouse movement and keyboard input we have a callback function for mouse scrolling: -</p> - -<pre><code> -void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) -{ - fov -= (float)yoffset; - if (fov < 1.0f) - fov = 1.0f; - if (fov > 45.0f) - fov = 45.0f; -} -</code></pre> - -<p> - When scrolling, the <var>yoffset</var> value tells us the amount we scrolled vertically. When the <fun>scroll_callback</fun> function is called we change the content of the globally declared <var>fov</var> variable. Since <code>45.0</code> is the default fov value we want to constrain the zoom level between <code>1.0</code> and <code> 45.0</code>. -</p> - -<p> - We now have to upload the perspective projection matrix to the GPU each frame, but this time with the <var>fov</var> variable as its field of view: -</p> - -<pre><code> -projection = <function id='58'>glm::perspective</function>(<function id='63'>glm::radians</function>(fov), 800.0f / 600.0f, 0.1f, 100.0f); -</code></pre> - -<p> - And lastly don't forget to register the scroll callback function: -</p> - -<pre><code> -<function id='64'>glfwSetScrollCallback</function>(window, scroll_callback); -</code></pre> - -<p> - And there you have it. We implemented a simple camera system that allows for free movement in a 3D environment. -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/getting-started/camera_mouse.mp4" type="video/mp4" /> - <img src="/img/getting-started/camera_mouse.png" class="clean"/> - </video> -</div> - -<p> - Feel free to experiment a little and if you're stuck compare your code with the <a href="/code_viewer_gh.php?code=src/1.getting_started/7.3.camera_mouse_zoom/camera_mouse_zoom.cpp" target="_blank">source code</a>. -</p> - -<h1>Camera class</h1> -<p> - In the upcoming chapters we'll always use a camera to easily look around the scenes and see the results from all angles. However, since the camera code can take up a significant amount of space on each chapter we'll abstract its details a little and create our own camera object that does most of the work for us with some neat little extras. Unlike the Shader chapter we won't walk you through creating the camera class, but provide you with the (fully commented) source code if you want to know the inner workings. -</p> - -<p> - Like the <code>Shader</code> object, we define the camera class entirely in a single header file. You can find the camera class <a href="/code_viewer_gh.php?code=includes/learnopengl/camera.h" target="_blank">here</a>; you should be able to understand the code after this chapter. It is advised to at least check the class out once as an example on how you could create your own camera system. -</p> - -<warning> - The camera system we introduced is a fly like camera that suits most purposes and works well with Euler angles, but be careful when creating different camera systems like an FPS camera, or a flight simulation camera. Each camera system has its own tricks and quirks so be sure to read up on them. For example, this fly camera doesn't allow for pitch values higher than or equal to <code>90</code> degrees and a static up vector of <code>(0,1,0)</code> doesn't work when we take roll values into account. -</warning> - -<p> - The updated version of the source code using the new camera object can be found <a href="/code_viewer_gh.php?code=src/1.getting_started/7.4.camera_class/camera_class.cpp" target="_blank">here</a>. -</p> - -<h2>Exercises</h2> -<p> - <ul> - <li>See if you can transform the camera class in such a way that it becomes a <strong>true</strong> fps camera where you cannot fly; you can only look around while staying on the <code>xz</code> plane: <a href="/code_viewer_gh.php?code=src/1.getting_started/7.5.camera_exercise1/camera_exercise1.cpp" target="_blank">solution</a>.</li> - <li>Try to create your own LookAt function where you manually create a view matrix as discussed at the start of this chapter. Replace glm's LookAt function with your own implementation and see if it still acts the same: <a href="/code_viewer_gh.php?code=src/1.getting_started/7.6.camera_exercise2/camera_exercise2.cpp" target="_blank">solution</a>.</li> - </ul> -</p> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/orig/Getting-started/Coordinate-Systems.html b/orig/Getting-started/Coordinate-Systems.html @@ -1,726 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Coordinate Systems</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Coordinate Systems</h1> -<h1 id="content-url" style='display:none;'>Getting-started/Coordinate-Systems</h1> -<p> - In the last chapter we learned how we can use matrices to our advantage by transforming all vertices with transformation matrices. OpenGL expects all the vertices, that we want to become visible, to be in normalized device coordinates after each vertex shader run. That is, the <code>x</code>, <code>y</code> and <code>z</code> coordinates of each vertex should be between <code>-1.0</code> and <code>1.0</code>; coordinates outside this range will not be visible. What we usually do, is specify the coordinates in a range (or space) we determine ourselves and in the vertex shader transform these coordinates to normalized device coordinates (NDC). These NDC are then given to the rasterizer to transform them to 2D coordinates/pixels on your screen. -</p> - -<p> - Transforming coordinates to NDC is usually accomplished in a step-by-step fashion where we transform an object's vertices to several coordinate systems before finally transforming them to NDC. The advantage of transforming them to several <em>intermediate</em> coordinate systems is that some operations/calculations are easier in certain coordinate systems as will soon become apparent. There are a total of 5 different coordinate systems that are of importance to us: -</p> - - <ul> - <li>Local space (or Object space)</li> - <li>World space</li> - <li>View space (or Eye space)</li> - <li>Clip space</li> - <li>Screen space</li> - </ul> - -<p> - Those are all a different state at which our vertices will be transformed in before finally ending up as fragments. -</p> - -<p> - You're probably quite confused by now by what a space or coordinate system actually is so we'll explain them in a more high-level fashion first by showing the total picture and what each specific space represents. -</p> - -<h2>The global picture</h2> -<p> - To transform the coordinates from one space to the next coordinate space we'll use several transformation matrices of which the most important are the <def>model</def>, <def>view</def> and <def>projection</def> matrix. Our vertex coordinates first start in <def>local space</def> as <def>local coordinates</def> and are then further processed to <def>world coordinates</def>, <def>view coordinates</def>, <def>clip coordinates</def> and eventually end up as <def>screen coordinates</def>. The following image displays the process and shows what each transformation does: -</p> - -<img src="/img/getting-started/coordinate_systems.png" class="clean"/> - - <ol> - <li>Local coordinates are the coordinates of your object relative to its local origin; they're the coordinates your object begins in. </li> - <li>The next step is to transform the local coordinates to world-space coordinates which are coordinates in respect of a larger world. These coordinates are relative to some global origin of the world, together with many other objects also placed relative to this world's origin.</li> - <li>Next we transform the world coordinates to view-space coordinates in such a way that each coordinate is as seen from the camera or viewer's point of view. </li> - <li>After the coordinates are in view space we want to project them to clip coordinates. Clip coordinates are processed to the <code>-1.0</code> and <code>1.0</code> range and determine which vertices will end up on the screen. Projection to clip-space coordinates can add perspective if using perspective projection. </li> - <li> - And lastly we transform the clip coordinates to screen coordinates in a process we call <def>viewport transform</def> that transforms the coordinates from <code>-1.0</code> and <code>1.0</code> to the coordinate range defined by <fun><function id='22'>glViewport</function></fun>. The resulting coordinates are then sent to the rasterizer to turn them into fragments. - </li> - </ol> - -<p> - You probably got a slight idea what each individual space is used for. The reason we're transforming our vertices into all these different spaces is that some operations make more sense or are easier to use in certain coordinate systems. For example, when modifying your object it makes most sense to do this in local space, while calculating certain operations on the object with respect to the position of other objects makes most sense in world coordinates and so on. If we want, we could define one transformation matrix that goes from local space to clip space all in one go, but that leaves us with less flexibility. -</p> - -<p> - We'll discuss each coordinate system in more detail below. -</p> - -<h2>Local space</h2> -<p> - Local space is the coordinate space that is local to your object, i.e. where your object begins in. Imagine that you've created your cube in a modeling software package (like Blender). The origin of your cube is probably at <code>(0,0,0)</code> even though your cube may end up at a different location in your final application. Probably all the models you've created all have <code>(0,0,0)</code> as their initial position. All the vertices of your model are therefore in <em>local</em> space: they are all local to your object. -</p> - -<p> - The vertices of the container we've been using were specified as coordinates between <code>-0.5</code> and <code>0.5</code> with <code>0.0</code> as its origin. These are local coordinates. -</p> - -<h2>World space</h2> -<p> - If we would import all our objects directly in the application they would probably all be somewhere positioned inside each other at the world's origin of <code>(0,0,0)</code> which is not what we want. We want to define a position for each object to position them inside a larger world. The coordinates in world space are exactly what they sound like: the coordinates of all your vertices relative to a (game) world. This is the coordinate space where you want your objects transformed to in such a way that they're all scattered around the place (preferably in a realistic fashion). The coordinates of your object are transformed from local to world space; this is accomplished with the <def>model</def> matrix. -</p> - -<p> - The model matrix is a transformation matrix that translates, scales and/or rotates your object to place it in the world at a location/orientation they belong to. Think of it as transforming a house by scaling it down (it was a bit too large in local space), translating it to a suburbia town and rotating it a bit to the left on the y-axis so that it neatly fits with the neighboring houses. You could think of the matrix in the previous chapter to position the container all over the scene as a sort of model matrix as well; we transformed the local coordinates of the container to some different place in the scene/world. -</p> - -<h2>View space</h2> -<p> - The view space is what people usually refer to as the <def>camera</def> of OpenGL (it is sometimes also known as <def>camera space</def> or <def>eye space</def>). The view space is the result of transforming your world-space coordinates to coordinates that are in front of the user's view. The view space is thus the space as seen from the camera's point of view. This is usually accomplished with a combination of translations and rotations to translate/rotate the scene so that certain items are transformed to the front of the camera. These combined transformations are generally stored inside a <def>view matrix</def> that transforms world coordinates to view space. In the next chapter we'll extensively discuss how to create such a view matrix to simulate a camera. -</p> - -<h2>Clip space</h2> -<p> - At the end of each vertex shader run, OpenGL expects the coordinates to be within a specific range and any coordinate that falls outside this range is <def>clipped</def>. Coordinates that are clipped are discarded, so the remaining coordinates will end up as fragments visible on your screen. This is also where <def>clip space</def> gets its name from. -</p> - -<p> - Because specifying all the visible coordinates to be within the range <code>-1.0</code> and <code>1.0</code> isn't really intuitive, we specify our own coordinate set to work in and convert those back to NDC as OpenGL expects them. -</p> - -<p> - To transform vertex coordinates from view to clip-space we define a so called <def>projection matrix</def> that specifies a range of coordinates e.g. <code>-1000</code> and <code>1000</code> in each dimension. The projection matrix then transforms coordinates within this specified range to normalized device coordinates (<code>-1.0</code>, <code>1.0</code>). All coordinates outside this range will not be mapped between <code>-1.0</code> and <code>1.0</code> and therefore be clipped. With this range we specified in the projection matrix, a coordinate of (<code>1250</code>, <code>500</code>, <code>750</code>) would not be visible, since the <code>x</code> coordinate is out of range and thus gets converted to a coordinate higher than <code>1.0</code> in NDC and is therefore clipped. -</p> - -<note> - Note that if only a part of a primitive e.g. a triangle is outside the <def>clipping volume</def> OpenGL will reconstruct the triangle as one or more triangles to fit inside the clipping range. -</note> - -<p> - This <em>viewing box</em> a projection matrix creates is called a <def>frustum</def> and each coordinate that ends up inside this frustum will end up on the user's screen. The total process to convert coordinates within a specified range to NDC that can easily be mapped to 2D view-space coordinates is called <def>projection</def> since the projection matrix <def>projects</def> 3D coordinates to the easy-to-map-to-2D normalized device coordinates. -</p> - -<p> - Once all the vertices are transformed to clip space a final operation called <def>perspective division</def> is performed where we divide the <code>x</code>, <code>y</code> and <code>z</code> components of the position vectors by the vector's homogeneous <code>w</code> component; perspective division is what transforms the 4D clip space coordinates to 3D normalized device coordinates. This step is performed automatically at the end of the vertex shader step. -</p> - -<p> - It is after this stage where the resulting coordinates are mapped to screen coordinates (using the settings of <fun><function id='22'>glViewport</function></fun>) and turned into fragments. -</p> - -<p> - The projection matrix to transform view coordinates to clip coordinates usually takes two different forms, where each form defines its own unique frustum. We can either create an <def>orthographic</def> projection matrix or a <def>perspective</def> projection matrix. -</p> - -<h3>Orthographic projection</h3> -<p> - An orthographic projection matrix defines a cube-like frustum box that defines the clipping space where each vertex outside this box is clipped. When creating an orthographic projection matrix we specify the width, height and length of the visible frustum. All the coordinates inside this frustum will end up within the NDC range after transformed by its matrix and thus won't be clipped. The frustum looks a bit like a container: -</p> - -<img src="/img/getting-started/orthographic_frustum.png" class="clean"/> - -<p> - The frustum defines the visible coordinates and is specified by a width, a height and a <def>near</def> and <def>far</def> plane. Any coordinate in front of the near plane is clipped and the same applies to coordinates behind the far plane. The orthographic frustum <strong>directly</strong> maps all coordinates inside the frustum to normalized device coordinates without any special side effects since it won't touch the <code>w</code> component of the transformed vector; if the <code>w</code> component remains equal to <code>1.0</code> perspective division won't change the coordinates. -</p> - -<p> - To create an orthographic projection matrix we make use of GLM's built-in function <code><function id='59'>glm::ortho</function></code>: -</p> - -<pre><code> -<function id='59'>glm::ortho</function>(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f); -</code></pre> - -<p> - The first two parameters specify the left and right coordinate of the frustum and the third and fourth parameter specify the bottom and top part of the frustum. With those 4 points we've defined the size of the near and far planes and the 5th and 6th parameter then define the distances between the near and far plane. This specific projection matrix transforms all coordinates between these <code>x</code>, <code>y</code> and <code>z</code> range values to normalized device coordinates. -</p> - -<p> - An orthographic projection matrix directly maps coordinates to the 2D plane that is your screen, but in reality a direct projection produces unrealistic results since the projection doesn't take <def>perspective</def> into account. That is something the <def>perspective projection</def> matrix fixes for us. -</p> - -<h3>Perspective projection</h3> -<p> - If you ever were to enjoy the graphics the <em>real life</em> has to offer you'll notice that objects that are farther away appear much smaller. This weird effect is something we call <def>perspective</def>. Perspective is especially noticeable when looking down the end of an infinite motorway or railway as seen in the following image: -</p> - -<img src="/img/getting-started/perspective.png" class="clean"/> - -<p> - As you can see, due to perspective the lines seem to coincide at a far enough distance. This is exactly the effect perspective projection tries to mimic and it does so using a <def>perspective projection matrix</def>. The projection matrix maps a given frustum range to clip space, but also manipulates the <code>w</code> value of each vertex coordinate in such a way that the further away a vertex coordinate is from the viewer, the higher this <code>w</code> component becomes. Once the coordinates are transformed to clip space they are in the range <code>-w</code> to <code>w</code> (anything outside this range is clipped). OpenGL requires that the visible coordinates fall between the range <code>-1.0</code> and <code>1.0</code> as the final vertex shader output, thus once the coordinates are in clip space, perspective division is applied to the clip space coordinates: - - \[ out = \begin{pmatrix} x /w \\ y / w \\ z / w \end{pmatrix} \] - - Each component of the vertex coordinate is divided by its <code>w</code> component giving smaller vertex coordinates the further away a vertex is from the viewer. This is another reason why the <code>w</code> component is important, since it helps us with perspective projection. The resulting coordinates are then in normalized device space. If you're interested to figure out how the orthographic and perspective projection matrices are actually calculated (and aren't too scared of the mathematics) I can recommend <a href="http://www.songho.ca/opengl/gl_projectionmatrix.html" target="_blank">this excellent article</a> by Songho. -</p> - -<p> - A perspective projection matrix can be created in GLM as follows: -</p> - -<pre><code> -glm::mat4 proj = <function id='58'>glm::perspective</function>(<function id='63'>glm::radians</function>(45.0f), (float)width/(float)height, 0.1f, 100.0f); -</code></pre> - -<p> - What <code><function id='58'>glm::perspective</function></code> does is again create a large <em>frustum</em> that defines the visible space, anything outside the frustum will not end up in the clip space volume and will thus become clipped. A perspective frustum can be visualized as a non-uniformly shaped box from where each coordinate inside this box will be mapped to a point in clip space. An image of a perspective frustum is seen below: -</p> - -<img src="/img/getting-started/perspective_frustum.png" class="clean"/> - - -<p> -Its first parameter defines the <def>fov</def> value, that stands for <def>field of view</def> and sets how large the viewspace is. For a realistic view it is usually set to 45 degrees, but for more doom-style results you could set it to a higher value. The second parameter sets the aspect ratio which is calculated by dividing the viewport's width by its height. The third and fourth parameter set the <em>near</em> and <em>far</em> plane of the frustum. We usually set the near distance to <code>0.1</code> and the far distance to <code>100.0</code>. All the vertices between the near and far plane and inside the frustum will be rendered. -</p> - -<note> - Whenever the <em>near</em> value of your perspective matrix is set too high (like <code>10.0</code>), OpenGL will clip all coordinates close to the camera (between <code>0.0</code> and <code>10.0</code>), which can give a visual result you maybe have seen before in videogames where you could see through certain objects when moving uncomfortably close to them. -</note> - -<p> - When using orthographic projection, each of the vertex coordinates are directly mapped to clip space without any fancy perspective division (it still does perspective division, but the <code>w</code> component is not manipulated (it stays <code>1</code>) and thus has no effect). Because the orthographic projection doesn't use perspective projection, objects farther away do not seem smaller, which produces a weird visual output. For this reason the orthographic projection is mainly used for 2D renderings and for some architectural or engineering applications where we'd rather not have vertices distorted by perspective. Applications like <em>Blender</em> that are used for 3D modeling sometimes use orthographic projection for modeling, because it more accurately depicts each object's dimensions. Below you'll see a comparison of both projection methods in Blender: -</p> - -<img src="/img/getting-started/perspective_orthographic.png" class="clean"/> - -<p> - You can see that with perspective projection, the vertices farther away appear much smaller, while in orthographic projection each vertex has the same distance to the user. -</p> - -<h2>Putting it all together</h2> -<p> - We create a transformation matrix for each of the aforementioned steps: model, view and projection matrix. A vertex coordinate is then transformed to clip coordinates as follows: - - \[ V_{clip} = M_{projection} \cdot M_{view} \cdot M_{model} \cdot V_{local} \] - -Note that the order of matrix multiplication is reversed (remember that we need to read matrix multiplication from right to left). The resulting vertex should then be assigned to <var>gl_Position</var> in the vertex shader and OpenGL will then automatically perform perspective division and clipping. -</p> - -<note> - <strong>And then?</strong><br/> - The output of the vertex shader requires the coordinates to be in clip-space which is what we just did with the transformation matrices. OpenGL then performs <em>perspective division</em> on the <em>clip-space coordinates</em> to transform them to <em>normalized-device coordinates</em>. OpenGL then uses the parameters from <fun><function id='22'>glViewPort</function></fun> to map the normalized-device coordinates to <em>screen coordinates</em> where each coordinate corresponds to a point on your screen (in our case a 800x600 screen). This process is called the <em>viewport transform</em>. -</note> - -<p> - This is a difficult topic to understand so if you're still not exactly sure about what each space is used for you don't have to worry. Below you'll see how we can actually put these coordinate spaces to good use and enough examples will follow in the upcoming chapters. -</p> - -<h1>Going 3D</h1> -<p> - Now that we know how to transform 3D coordinates to 2D coordinates we can start rendering real 3D objects instead of the lame 2D plane we've been showing so far. -</p> - -<p> - To start drawing in 3D we'll first create a model matrix. The model matrix consists of translations, scaling and/or rotations we'd like to apply to <em>transform</em> all object's vertices to the global world space. Let's transform our plane a bit by rotating it on the x-axis so it looks like it's laying on the floor. The model matrix then looks like this: -</p> - -<pre><code> -glm::mat4 model = glm::mat4(1.0f); -model = <function id='57'>glm::rotate</function>(model, <function id='63'>glm::radians</function>(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f)); -</code></pre> - -<p> -By multiplying the vertex coordinates with this model matrix we're transforming the vertex coordinates to world coordinates. Our plane that is slightly on the floor thus represents the plane in the global world. - </p> - -<p> - Next we need to create a view matrix. We want to move slightly backwards in the scene so the object becomes visible (when in world space we're located at the origin <code>(0,0,0)</code>). To move around the scene, think about the following: - <ul> - <li>To move a camera backwards, is the same as moving the entire scene forward.</li> - </ul> - That is exactly what a view matrix does, we move the entire scene around inversed to where we want the camera to move.<br/> - Because we want to move backwards and since OpenGL is a right-handed system we have to move in the positive z-axis. We do this by translating the scene towards the negative z-axis. This gives the impression that we are moving backwards. -</p> - - -<note> - <strong>Right-handed system</strong> - <p> - By convention, OpenGL is a right-handed system. What this basically says is that the positive x-axis is to your right, the positive y-axis is up and the positive z-axis is backwards. Think of your screen being the center of the 3 axes and the positive z-axis going through your screen towards you. The axes are drawn as follows: -</p> - <img src="/img/getting-started/coordinate_systems_right_handed.png" class="clean"/> - <p> - To understand why it's called right-handed do the following: - <ul> - <li>Stretch your right-arm along the positive y-axis with your hand up top.</li> - <li>Let your thumb point to the right.</li> - <li>Let your pointing finger point up.</li> - <li>Now bend your middle finger downwards 90 degrees.</li> - </ul> - If you did things right, your thumb should point towards the positive x-axis, the pointing finger towards the positive y-axis and your middle finger towards the positive z-axis. If you were to do this with your left-arm you would see the z-axis is reversed. This is known as a left-handed system and is commonly used by DirectX. Note that in normalized device coordinates OpenGL actually uses a left-handed system (the projection matrix switches the handedness). - </p> -</note> - -<p> - We'll discuss how to move around the scene in more detail in the next chapter. For now the view matrix looks like this: -</p> - -<pre><code> -glm::mat4 view = glm::mat4(1.0f); -// note that we're translating the scene in the reverse direction of where we want to move -view = <function id='55'>glm::translate</function>(view, glm::vec3(0.0f, 0.0f, -3.0f)); -</code></pre> - -<p> - The last thing we need to define is the projection matrix. We want to use perspective projection for our scene so we'll declare the projection matrix like this: -</p> - -<pre><code> -glm::mat4 projection; -projection = <function id='58'>glm::perspective</function>(<function id='63'>glm::radians</function>(45.0f), 800.0f / 600.0f, 0.1f, 100.0f); -</code></pre> - -<p> - Now that we created the transformation matrices we should pass them to our shaders. First let's declare the transformation matrices as uniforms in the vertex shader and multiply them with the vertex coordinates: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; -... -uniform mat4 model; -uniform mat4 view; -uniform mat4 projection; - -void main() -{ - // note that we read the multiplication from right to left - gl_Position = projection * view * model * vec4(aPos, 1.0); - ... -} -</code></pre> - -<p> - We should also send the matrices to the shader (this is usually done each frame since transformation matrices tend to change a lot): -</p> - -<pre><code> -int modelLoc = <function id='45'>glGetUniformLocation</function>(ourShader.ID, "model"); -<function id='44'>glUniform</function>Matrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model)); -... // same for View Matrix and Projection Matrix -</code></pre> - -<p> - Now that our vertex coordinates are transformed via the model, view and projection matrix the final object should be: - - <ul> - <li>Tilted backwards to the floor. </li> - <li>A bit farther away from us.</li> - <li>Be displayed with perspective (it should get smaller, the further its vertices are).</li> - </ul> - - Let's check if the result actually does fulfill these requirements: -</p> - -<img src="/img/getting-started/coordinate_systems_result.png" class="clean"/> - -<p> - It does indeed look like the plane is a 3D plane that's resting at some imaginary floor. If you're not getting the same result, compare your code with the complete <a href="/code_viewer_gh.php?code=src/1.getting_started/6.1.coordinate_systems/coordinate_systems.cpp" target="_blank">source code</a>. -</p> - -<h2>More 3D</h2> -<p> - So far we've been working with a 2D plane, even in 3D space, so let's take the adventurous route and extend our 2D plane to a 3D cube. To render a cube we need a total of 36 vertices (6 faces * 2 triangles * 3 vertices each). 36 vertices are a lot to sum up so you can retrieve them from <a href="/code_viewer.php?code=getting-started/cube_vertices" target="_blank">here</a>. -</p> - -<p> - For fun, we'll let the cube rotate over time: -</p> - -<pre><code> -model = <function id='57'>glm::rotate</function>(model, (float)<function id='47'>glfwGetTime</function>() * <function id='63'>glm::radians</function>(50.0f), glm::vec3(0.5f, 1.0f, 0.0f)); -</code></pre> - -<p> - And then we'll draw the cube using <fun><function id='1'>glDrawArrays</function></fun> (as we didn't specify indices), but this time with a count of 36 vertices. -</p> - -<pre class="cpp"><code> -<function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 36); -</code></pre> - -<p> - You should get something similar to the following: -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/getting-started/coordinate_system_no_depth.mp4" type="video/mp4" /> - <img src="/img/getting-started/coordinate_systems_no_depth.png" class="clean"/> - </video> -</div> - - -<p> - It does resemble a cube slightly but something's off. Some sides of the cubes are being drawn over other sides of the cube. This happens because when OpenGL draws your cube triangle-by-triangle, fragment by fragment, it will overwrite any pixel color that may have already been drawn there before. Since OpenGL gives no guarantee on the order of triangles rendered (within the same draw call), some triangles are drawn on top of each other even though one should clearly be in front of the other. -</p> - -<p> - Luckily, OpenGL stores depth information in a buffer called the <def>z-buffer</def> that allows OpenGL to decide when to draw over a pixel and when not to. Using the z-buffer we can configure OpenGL to do depth-testing. -</p> - -<h3>Z-buffer</h3> -<p> - OpenGL stores all its depth information in a z-buffer, also known as a <def>depth buffer</def>. GLFW automatically creates such a buffer for you (just like it has a color-buffer that stores the colors of the output image). The depth is stored within each fragment (as the fragment's <code>z</code> value) and whenever the fragment wants to output its color, OpenGL compares its depth values with the z-buffer. If the current fragment is behind the other fragment it is discarded, otherwise overwritten. This process is called <def>depth testing</def> and is done automatically by OpenGL. -</p> - -<p> - However, if we want to make sure OpenGL actually performs the depth testing we first need to tell OpenGL we want to enable depth testing; it is disabled by default. We can enable depth testing using <fun><function id='60'>glEnable</function></fun>. The <fun><function id='60'>glEnable</function></fun> and <fun>glDisable</fun> functions allow us to enable/disable certain functionality in OpenGL. That functionality is then enabled/disabled until another call is made to disable/enable it. Right now we want to enable depth testing by enabling <var>GL_DEPTH_TEST</var>: -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_DEPTH_TEST); -</code></pre> - -<p> - Since we're using a depth buffer we also want to clear the depth buffer before each render iteration (otherwise the depth information of the previous frame stays in the buffer). Just like clearing the color buffer, we can clear the depth buffer by specifying the <var>DEPTH_BUFFER_BIT</var> bit in the <fun><function id='10'>glClear</function></fun> function: -</p> - -<pre><code> -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -</code></pre> - -<p> - Let's re-run our program and see if OpenGL now performs depth testing: -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/getting-started/coordinate_system_depth.mp4" type="video/mp4" /> - <img src="/img/getting-started/coordinate_systems_with_depth.png" class="clean"/> - </video> -</div> - - -<p> - There we go! A fully textured cube with proper depth testing that rotates over time. Check the source code <a href="/code_viewer_gh.php?code=src/1.getting_started/6.2.coordinate_systems_depth/coordinate_systems_depth.cpp" target="_blank">here</a>. -</p> - - - -<h3>More cubes!</h3> -<p> - Say we wanted to display 10 of our cubes on screen. Each cube will look the same but will only differ in where it's located in the world with each a different rotation. The graphical layout of the cube is already defined so we don't have to change our buffers or attribute arrays when rendering more objects. The only thing we have to change for each object is its model matrix where we transform the cubes into the world. -</p> - -<p> - First, let's define a translation vector for each cube that specifies its position in world space. We'll define 10 cube positions in a <code>glm::vec3</code> array: -</p> - -<pre><code> -glm::vec3 cubePositions[] = { - glm::vec3( 0.0f, 0.0f, 0.0f), - glm::vec3( 2.0f, 5.0f, -15.0f), - glm::vec3(-1.5f, -2.2f, -2.5f), - glm::vec3(-3.8f, -2.0f, -12.3f), - glm::vec3( 2.4f, -0.4f, -3.5f), - glm::vec3(-1.7f, 3.0f, -7.5f), - glm::vec3( 1.3f, -2.0f, -2.5f), - glm::vec3( 1.5f, 2.0f, -2.5f), - glm::vec3( 1.5f, 0.2f, -1.5f), - glm::vec3(-1.3f, 1.0f, -1.5f) -}; -</code></pre> - -<p> - Now, within the render loop we want to call <fun><function id='1'>glDrawArrays</function></fun> 10 times, but this time send a different model matrix to the vertex shader each time before we send out the draw call. We will create a small loop within the render loop that renders our object 10 times with a different model matrix each time. Note that we also add a small unique rotation to each container. -</p> - -<pre><code> -<function id='27'>glBindVertexArray</function>(VAO); -for(unsigned int i = 0; i < 10; i++) -{ - glm::mat4 model = glm::mat4(1.0f); - model = <function id='55'>glm::translate</function>(model, cubePositions[i]); - float angle = 20.0f * i; - model = <function id='57'>glm::rotate</function>(model, <function id='63'>glm::radians</function>(angle), glm::vec3(1.0f, 0.3f, 0.5f)); - ourShader.setMat4("model", model); - - <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 36); -} -</code></pre> - -<p> - This snippet of code will update the model matrix each time a new cube is drawn and do this 10 times in total. Right now we should be looking into a world filled with 10 oddly rotated cubes: -</p> - -<img src="/img/getting-started/coordinate_systems_multiple_objects.png" class="clean"/> - -<p> - Perfect! It looks like our container found some like-minded friends. If you're stuck see if you can compare your code with the <a href="/code_viewer_gh.php?code=src/1.getting_started/6.3.coordinate_systems_multiple/coordinate_systems_multiple.cpp" target="_blank">source code</a>. -</p> - -<h2>Exercises</h2> -<ul> - <li>Try experimenting with the <code>FoV</code> and <code>aspect-ratio</code> parameters of GLM's <code>projection</code> function. See if you can figure out how those affect the perspective frustum.</li> - <li>Play with the view matrix by translating in several directions and see how the scene changes. Think of the view matrix as a camera object.</li> - <li>Try to make every 3rd container (including the 1st) rotate over time, while leaving the other containers static using just the model matrix: <a href="/code_viewer_gh.php?code=src/1.getting_started/6.4.coordinate_systems_exercise3/coordinate_systems_exercise3.cpp" target="_blank">solution</a>.</li> -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/orig/Getting-started/Creating-a-window.html b/orig/Getting-started/Creating-a-window.html @@ -1,469 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Creating a window</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Creating a window</h1> -<h1 id="content-url" style='display:none;'>Getting-started/Creating-a-window</h1> -<p> - The first thing we need to do before we start creating stunning graphics is to create an OpenGL context and an application window to draw in. However, those operations are specific per operating system and OpenGL purposefully tries to abstract itself from these operations. This means we have to create a window, define a context, and handle user input all by ourselves. -</p> - -<p> - Luckily, there are quite a few libraries out there that provide the functionality we seek, some specifically aimed at OpenGL. Those libraries save us all the operation-system specific work and give us a window and an OpenGL context to render in. Some of the more popular libraries are GLUT, SDL, SFML and GLFW. On LearnOpenGL we will be using <strong>GLFW</strong>. Feel free to use any of the other libraries, the setup for most is similar to GLFW's setup. -</p> - -<h2> GLFW </h2> -<p> - GLFW is a library, written in C, specifically targeted at OpenGL. GLFW gives us the bare necessities required for rendering goodies to the screen. It allows us to create an OpenGL context, define window parameters, and handle user input, which is plenty enough for our purposes. -</p> - -<p> - The focus of this and the next chapter is to get GLFW up and running, making sure it properly creates an OpenGL context and that it displays a simple window for us to mess around in. This chapter takes a step-by-step approach in retrieving, building and linking the GLFW library. We'll use Microsoft Visual Studio 2019 IDE as of this writing (note that the process is the same on the more recent visual studio versions). If you're not using Visual Studio (or an older version) don't worry, the process will be similar on most other IDEs. -</p> - -<h2>Building GLFW</h2> -<p> - GLFW can be obtained from their webpage's <a href="http://www.glfw.org/download.html" target="_blank">download</a> page. GLFW already has pre-compiled binaries and header files for Visual Studio 2012 up to 2019, but for completeness' sake we will compile GLFW ourselves from the source code. This is to give you a feel for the process of compiling open-source libraries yourself as not every library will have pre-compiled binaries available. So let's download the <em>Source package</em>. -</p> - -<warning> - We'll be building all libraries as 64-bit binaries so make sure to get the 64-bit binaries if you're using their pre-compiled binaries. -</warning> - -<p> - Once you've downloaded the source package, extract it and open its content. We are only interested in a few items: - -</p> - <ul> - <li>The resulting library from compilation.</li> - <li>The <strong>include</strong> folder.</li> - </ul> - -<p> - Compiling the library from the source code guarantees that the resulting library is perfectly tailored for your CPU/OS, a luxury pre-compiled binaries don't always provide (sometimes, pre-compiled binaries are not available for your system). The problem with providing source code to the open world however is that not everyone uses the same IDE or build system for developing their application, which means the project/solution files provided may not be compatible with other people's setup. - So people then have to setup their own project/solution with the given .c/.cpp and .h/.hpp files, which is cumbersome. Exactly for those reasons there is a tool called CMake. -</p> - -<h3>CMake</h3> -<p> - CMake is a tool that can generate project/solution files of the user's choice (e.g. Visual Studio, Code::Blocks, Eclipse) from a collection of source code files using pre-defined CMake scripts. This allows us to generate a Visual Studio 2019 project file from GLFW's source package which we can use to compile the library. First we need to download and install CMake which can be downloaded on their <a href="http://www.cmake.org/cmake/resources/software.html" target="_blank">download</a> page. -</p> - -<p> - Once CMake is installed you can choose to run CMake from the command line or through their GUI. Since we're not trying to overcomplicate things we're going to use the GUI. CMake requires a source code folder and a destination folder for the binaries. For the source code folder we're going to choose the root folder of the downloaded GLFW source package and for the build folder we're creating a new directory <em>build</em> and then select that directory. -</p> - -<img src="/img/getting-started/cmake.png" width="800px" alt="Image of CMake's logo"/> - -<p> - Once the source and destination folders have been set, click the <code>Configure</code> button so CMake can read the required settings and the source code. We then have to choose the generator for the project and since we're using Visual Studio 2019 we will choose the <code>Visual Studio 16</code> option (Visual Studio 2019 is also known as Visual Studio 16). CMake will then display the possible build options to configure the resulting library. We can leave them to their default values and click <code>Configure</code> again to store the settings. Once the settings have been set, we click <code>Generate</code> and the resulting project files will be generated in your <code>build</code> folder. -</p> - -<h3>Compilation</h3> -<p> - In the <code>build</code> folder a file named <code>GLFW.sln</code> can now be found and we open it with Visual Studio 2019. Since CMake generated a project file that already contains the proper configuration settings we only have to build the solution. CMake should've automatically configured the solution so it compiles to a 64-bit library; now hit build solution. This will give us a compiled library file that can be found in <code>build/src/Debug</code> named <code>glfw3.lib</code>.</p> - -<p> - Once we generated the library we need to make sure the IDE knows where to find the library and the include files for our OpenGL program. There are two common approaches in doing this: - <ol> - <li> We find the <code>/lib</code> and <code>/include</code> folders of the IDE/compiler and add the content of GLFW's <code>include</code> folder to the IDE's <code>/include</code> folder and similarly add <code>glfw3.lib</code> to the IDE's <code>/lib</code> folder. This works, but it's is not the recommended approach. It's hard to keep track of your library and include files and a new installation of your IDE/compiler results in you having to do this process all over again. </li> - <li> - Another approach (and recommended) is to create a new set of directories at a location of your choice that contains all the header files/libraries from third party libraries to which you can refer to from your IDE/compiler. You could, for instance, create a single folder that contains a <code>Libs</code> and <code>Include</code> folder where we store all our library and header files respectively for OpenGL projects. Now all the third party libraries are organized within a single location (that can be shared across multiple computers). The requirement is, however, that each time we create a new project we have to tell the IDE where to find those directories. - </li> - </ol> - Once the required files are stored at a location of your choice, we can start creating our first OpenGL GLFW project. -</p> - -<h2>Our first project</h2> -<p> - First, let's open up Visual Studio and create a new project. Choose C++ if multiple options are given and take the <code>Empty Project</code> (don't forget to give your project a suitable name). Since we're going to be doing everything in 64-bit and the project defaults to 32-bit, we'll need to change the dropdown at the top next to Debug from x86 to x64: -</p> - -<img src="/img/getting-started/x64.png" alt="Image of how to switch from x86 to x64"/> - -<p> - Once that's done, we now have a workspace to create our very first OpenGL application! -</p> - -<h2>Linking</h2> -<p> - In order for the project to use GLFW we need to <def>link</def> the library with our project. This can be done by specifying we want to use <code>glfw3.lib</code> in the linker settings, but our project does not yet know where to find <code>glfw3.lib</code> since we store our third party libraries in a different directory. We thus need to add this directory to the project first. -</p> - -<p> - We can tell the IDE to take this directory into account when it needs to look for library and include files. Right-click the project name in the solution explorer and then go to <code>VC++ Directories</code> as seen in the image below: -</p> - -<img src="/img/getting-started/vc_directories.png" width="600px" alt="Image of Visual Studio's VC++ Directories configuration"/> - -<p> - From there on out you can add your own directories to let the project know where to search. This can be done by manually inserting it into the text or clicking the appropriate location string and selecting the <code><Edit..></code> option. Do this for both the <code>Library Directories</code> and <code>Include Directories</code>: -</p> - -<img src="/img/getting-started/include_directories.png" width="600px" alt="Image of Visual Studio's Include Directories configuration"/> - -<p> - Here you can add as many extra directories as you'd like and from that point on the IDE will also search those directorie when searching for library and header files. As soon as your <code>Include</code> folder from GLFW is included, you will be able to find all the header files for GLFW by including <code><GLFW/..></code>. The same applies for the library directories. -</p> - -<p> - Since VS can now find all the required files we can finally link GLFW to the project by going to the <code>Linker</code> tab and <code>Input</code>: -</p> - -<img src="/img/getting-started/linker_input.png" width="600px" alt="Image of Visual Studio's link configuration"/> - -<p> - To then link to a library you'd have to specify the name of the library to the linker. Since the library name is <code>glfw3.lib</code>, we add that to the <code>Additional Dependencies</code> field (either manually or using the <code><Edit..></code> option) and from that point on GLFW will be linked when we compile. In addition to GLFW we should also add a link entry to the OpenGL library, but this may differ per operating system: -</p> - -<h3>OpenGL library on Windows</h3> -<p> - If you're on Windows the OpenGL library <code>opengl32.lib</code> comes with the Microsoft SDK, which is installed by default when you install Visual Studio. Since this chapter uses the VS compiler and is on windows we add <code>opengl32.lib</code> to the linker settings. Note that the 64-bit equivalent of the OpenGL library is called <code>opengl32.lib</code>, just like the 32-bit equivalent, which is a bit of an unfortunate name. -</p> - -<h3>OpenGL library on Linux</h3> -<p> - On Linux systems you need to link to the <code>libGL.so</code> library by adding <code>-lGL</code> to your linker settings. If you can't find the library you probably need to install any of the Mesa, NVidia or AMD dev packages. -</p> - -<p> - Then, once you've added both the GLFW and OpenGL library to the linker settings you can include the header files for GLFW as follows: -</p> - -<pre><code> -#include <GLFW\glfw3.h> -</code></pre> - -<note> - For Linux users compiling with GCC, the following command line options may help you compile the project: <code>-lglfw3 -lGL -lX11 -lpthread -lXrandr -lXi -ldl</code>. Not correctly linking the corresponding libraries will generate many <em>undefined reference</em> errors. -</note> - -<p> - This concludes the setup and configuration of GLFW. -</p> - -<h2>GLAD</h2> -<p> - We're still not quite there yet, since there is one other thing we still need to do. Because OpenGL is only really a standard/specification it is up to the driver manufacturer to implement the specification to a driver that the specific graphics card supports. Since there are many different versions of OpenGL drivers, the location of most of its functions is not known at compile-time and needs to be queried at run-time. It is then the task of the developer to retrieve the location of the functions he/she needs and store them in function pointers for later use. Retrieving those locations is <a href="https://www.khronos.org/opengl/wiki/Load_OpenGL_Functions" target="_blank">OS-specific</a>. In Windows it looks something like this: -</p> - -<pre><code> -// define the function's prototype -typedef void (*GL_GENBUFFERS) (GLsizei, GLuint*); -// find the function and assign it to a function pointer -GL_GENBUFFERS <function id='12'>glGenBuffers</function> = (GL_GENBUFFERS)wglGetProcAddress("<function id='12'>glGenBuffers</function>"); -// function can now be called as normal -unsigned int buffer; -<function id='12'>glGenBuffers</function>(1, &buffer); -</code></pre> - - <p> - As you can see the code looks complex and it's a cumbersome process to do this for each function you may need that is not yet declared. Thankfully, there are libraries for this purpose as well where <strong>GLAD</strong> is a popular and up-to-date library. - </p> - -<h3>Setting up GLAD</h3> - <p> - GLAD is an <a href="https://github.com/Dav1dde/glad" target="_blank">open source</a> library that manages all that cumbersome work we talked about. GLAD has a slightly different configuration setup than most common open source libraries. GLAD uses a <a href="http://glad.dav1d.de/" target="_blank">web service</a> where we can tell GLAD for which version of OpenGL we'd like to define and load all relevant OpenGL functions according to that version. - </p> - -<p> - Go to the GLAD <a href="http://glad.dav1d.de/" target="_blank">web service</a>, make sure the language is set to C++, and in the API section select an OpenGL version of at least 3.3 (which is what we'll be using; higher versions are fine as well). Also make sure the profile is set to <em>Core</em> and that the <em>Generate a loader</em> option is ticked. Ignore the extensions (for now) and click <em>Generate</em> to produce the resulting library files. -</p> - -<p> - GLAD by now should have provided you a zip file containing two include folders, and a single <code>glad.c</code> file. Copy both include folders (<code>glad</code> and <code>KHR</code>) into your include(s) directoy (or add an extra item pointing to these folders), and add the <code>glad.c</code> file to your project. -</p> - -<p> - After the previous steps, you should be able to add the following include directive above your file: -</p> - -<pre><code> -#include <glad/glad.h> -</code></pre> - -<p> - Hitting the compile button shouldn't give you any errors, at which point we're set to go for the <a href="https://learnopengl.com/Getting-started/Hello-Window" target="_blank">next</a> chapter where we'll discuss how we can actually use GLFW and GLAD to configure an OpenGL context and spawn a window. Be sure to check that all your include and library directories are correct and that the library names in the linker settings match the corresponding libraries. -</p> - -<h2>Additional resources</h2> -<ul> - <li><a href="http://www.glfw.org/docs/latest/window_guide.html" target="_blank">GLFW: Window Guide</a>: official GLFW guide on setting up and configuring a GLFW window.</li> - <li><a href="http://www.opengl-tutorial.org/miscellaneous/building-your-own-c-application/" target="_blank">Building applications</a>: provides great info about the compilation/linking process of your application and a large list of possible errors (plus solutions) that may come up.</li> - <li><a href="http://wiki.codeblocks.org/index.php?title=Using_GLFW_with_Code::Blocks" target="_blank">GLFW with Code::Blocks</a>: building GLFW in Code::Blocks IDE.</li> - <li><a href="http://www.cmake.org/runningcmake/" target="_blank">Running CMake</a>: short overview of how to run CMake on both Windows and Linux.</li> - <li><a href="https://learnopengl.com/demo/autotools_tutorial.txt" target="_blank">Writing a build system under Linux</a>: an autotools tutorial by Wouter Verholst on how to write a build system in Linux.</li> - <li><a href="https://github.com/Polytonic/Glitter" target="_blank">Polytonic/Glitter</a>: a simple boilerplate project that comes pre-configured with all relevant libraries; great for if you want a sample project without the hassle of having to compile all the libraries yourself.</li> -</ul> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/orig/Getting-started/Hello-Triangle.html b/orig/Getting-started/Hello-Triangle.html @@ -1,960 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Hello Triangle</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Hello Triangle</h1> -<h1 id="content-url" style='display:none;'>Getting-started/Hello-Triangle</h1> -<p> - In OpenGL everything is in 3D space, but the screen or window is a 2D array of pixels so a large part of OpenGL's work is about transforming all 3D coordinates to 2D pixels that fit on your screen. The process of transforming 3D coordinates to 2D pixels is managed by the <def>graphics pipeline</def> of OpenGL. The graphics pipeline can be divided into two large parts: the first transforms your 3D coordinates into 2D coordinates and the second part transforms the 2D coordinates into actual colored pixels. In this chapter we'll briefly discuss the graphics pipeline and how we can use it to our advantage to create fancy pixels. -</p> - -<!--<note> - There is a difference between a 2D coordinate and a pixel. A 2D coordinate is a very precise representation of where a point is in 2D space, while a 2D pixel is an approximation of that point limited by the resolution of your screen/window. -</note>--> - -<p> - The graphics pipeline takes as input a set of 3D coordinates and transforms these to colored 2D pixels on your screen. The graphics pipeline can be divided into several steps where each step requires the output of the previous step as its input. All of these steps are highly specialized (they have one specific function) and can easily be executed in parallel. Because of their parallel nature, graphics cards of today have thousands of small processing cores to quickly process your data within the graphics pipeline. The processing cores run small programs on the GPU for each step of the pipeline. These small programs are called <def>shaders</def>. -</p> - -<p> - Some of these shaders are configurable by the developer which allows us to write our own shaders to replace the existing default shaders. This gives us much more fine-grained control over specific parts of the pipeline and because they run on the GPU, they can also save us valuable CPU time. Shaders are written in the <def>OpenGL Shading Language</def> (<def>GLSL</def>) and we'll delve more into that in the next chapter. -</p> - -<p> - Below you'll find an abstract representation of all the stages of the graphics pipeline. Note that the blue sections represent sections where we can inject our own shaders. -</p> - -<img src="/img/getting-started/pipeline.png" class="clean" alt="The OpenGL graphics pipeline with shader stages" /> - - -<p> - As you can see, the graphics pipeline contains a large number of sections that each handle one specific part of converting your vertex data to a fully rendered pixel. We will briefly explain each part of the pipeline in a simplified way to give you a good overview of how the pipeline operates. -</p> - -<p> - As input to the graphics pipeline we pass in a list of three 3D coordinates that should form a triangle in an array here called <code>Vertex Data</code>; this vertex data is a collection of vertices. A <def>vertex</def> is a collection of data per 3D coordinate. This vertex's data is represented using <def>vertex attributes</def> that can contain any data we'd like, but for simplicity's sake let's assume that each vertex consists of just a 3D position and some color value. -</p> - -<note> - In order for OpenGL to know what to make of your collection of coordinates and color values OpenGL requires you to hint what kind of render types you want to form with the data. Do we want the data rendered as a collection of points, a collection of triangles or perhaps just one long line? Those hints are called <def>primitives</def> and are given to OpenGL while calling any of the drawing commands. Some of these hints are <var>GL_POINTS</var>, <var>GL_TRIANGLES</var> and <var>GL_LINE_STRIP</var>. -</note> - -<p> - The first part of the pipeline is the <def>vertex shader</def> that takes as input a single vertex. The main purpose of the vertex shader is to transform 3D coordinates into different 3D coordinates (more on that later) and the vertex shader allows us to do some basic processing on the vertex attributes. -</p> - -<p> - The <def>primitive assembly</def> stage takes as input all the vertices (or vertex if <var>GL_POINTS</var> is chosen) from the vertex shader that form a primitive and assembles all the point(s) in the primitive shape given; in this case a triangle. -</p> - -<p> - The output of the primitive assembly stage is passed to the <def>geometry shader</def>. The geometry shader takes as input a collection of vertices that form a primitive and has the ability to generate other shapes by emitting new vertices to form new (or other) primitive(s). In this example case, it generates a second triangle out of the given shape. -</p> - -<p> - The output of the geometry shader is then passed on to the <def>rasterization stage</def> where it maps the resulting primitive(s) to the corresponding pixels on the final screen, resulting in fragments for the fragment shader to use. Before the fragment shaders run, <def>clipping</def> is performed. Clipping discards all fragments that are outside your view, increasing performance. -</p> - -<note> - A fragment in OpenGL is all the data required for OpenGL to render a single pixel. -</note> - -<p> - The main purpose of the <def>fragment shader</def> is to calculate the final color of a pixel and this is usually the stage where all the advanced OpenGL effects occur. Usually the fragment shader contains data about the 3D scene that it can use to calculate the final pixel color (like lights, shadows, color of the light and so on). -</p> - - -<p> - After all the corresponding color values have been determined, the final object will then pass through one more stage that we call the <def>alpha test</def> and <def>blending</def> stage. This stage checks the corresponding depth (and stencil) value (we'll get to those later) of the fragment and uses those to check if the resulting fragment is in front or behind other objects and should be discarded accordingly. The stage also checks for <def>alpha</def> values (alpha values define the opacity of an object) and <def>blends</def> the objects accordingly. So even if a pixel output color is calculated in the fragment shader, the final pixel color could still be something entirely different when rendering multiple triangles. -</p> - -<p> - As you can see, the graphics pipeline is quite a complex whole and contains many configurable parts. However, for almost all the cases we only have to work with the vertex and fragment shader. The geometry shader is optional and usually left to its default shader. There is also the tessellation stage and transform feedback loop that we haven't depicted here, but that's something for later. -</p> - -<p> - In modern OpenGL we are <strong>required</strong> to define at least a vertex and fragment shader of our own (there are no default vertex/fragment shaders on the GPU). For this reason it is often quite difficult to start learning modern OpenGL since a great deal of knowledge is required before being able to render your first triangle. Once you do get to finally render your triangle at the end of this chapter you will end up knowing a lot more about graphics programming. -</p> - -<h2>Vertex input</h2> -<p> - To start drawing something we have to first give OpenGL some input vertex data. OpenGL is a 3D graphics library so all coordinates that we specify in OpenGL are in 3D (<code>x</code>, <code>y</code> and <code>z</code> coordinate). OpenGL doesn't simply transform <strong>all</strong> your 3D coordinates to 2D pixels on your screen; OpenGL only processes 3D coordinates when they're in a specific range between <code>-1.0</code> and <code>1.0</code> on all 3 axes (<code>x</code>, <code>y</code> and <code>z</code>). All coordinates within this so called <def>normalized device coordinates</def> range will end up visible on your screen (and all coordinates outside this region won't). -</p> - -<p> - Because we want to render a single triangle we want to specify a total of three vertices with each vertex having a 3D position. We define them in normalized device coordinates (the visible region of OpenGL) in a <code>float</code> array: -</p> - -<pre><code> -float vertices[] = { - -0.5f, -0.5f, 0.0f, - 0.5f, -0.5f, 0.0f, - 0.0f, 0.5f, 0.0f -}; -</code></pre> - -<p> - Because OpenGL works in 3D space we render a 2D triangle with each vertex having a <code>z</code> coordinate of <code>0.0</code>. This way the <em>depth</em> of the triangle remains the same making it look like it's 2D. -</p> - -<note> - <strong>Normalized Device Coordinates (NDC)</strong><br/> - <p> - Once your vertex coordinates have been processed in the vertex shader, they should be in <def>normalized device coordinates</def> which is a small space where the <code>x</code>, <code>y</code> and <code>z</code> values vary from <code>-1.0</code> to <code>1.0</code>. Any coordinates that fall outside this range will be discarded/clipped and won't be visible on your screen. Below you can see the triangle we specified within normalized device coordinates (ignoring the <code>z</code> axis): - </p> - <img src="/img/getting-started/ndc.png" class="clean" alt="2D Normalized Device Coordinates as shown in a graph"/> - <p> - Unlike usual screen coordinates the positive y-axis points in the up-direction and the <code>(0,0)</code> coordinates are at the center of the graph, instead of top-left. Eventually you want all the (transformed) coordinates to end up in this coordinate space, otherwise they won't be visible. -</p> -<p> - Your NDC coordinates will then be transformed to <def>screen-space coordinates</def> via the <def>viewport transform</def> using the data you provided with <fun><function id='22'>glViewport</function></fun>. The resulting screen-space coordinates are then transformed to fragments as inputs to your fragment shader. - </p> -</note> - -<p> - With the vertex data defined we'd like to send it as input to the first process of the graphics pipeline: the vertex shader. This is done by creating memory on the GPU where we store the vertex data, configure how OpenGL should interpret the memory and specify how to send the data to the graphics card. The vertex shader then processes as much vertices as we tell it to from its memory. -</p> - -<p> - We manage this memory via so called <def>vertex buffer objects</def> (<def>VBO</def>) that can store a large number of vertices in the GPU's memory. The advantage of using those buffer objects is that we can send large batches of data all at once to the graphics card, and keep it there if there's enough memory left, without having to send data one vertex at a time. Sending data to the graphics card from the CPU is relatively slow, so wherever we can we try to send as much data as possible at once. Once the data is in the graphics card's memory the vertex shader has almost instant access to the vertices making it extremely fast -</p> - -<p> - A vertex buffer object is our first occurrence of an OpenGL object as we've discussed in the <a href="https://learnopengl.com/Getting-Started/OpenGL" target="_blank">OpenGL</a> chapter. Just like any object in OpenGL, this buffer has a unique ID corresponding to that buffer, so we can generate one with a buffer ID using the <fun><function id='12'>glGenBuffers</function></fun> function: -</p> - -<pre class="cpp"><code> -unsigned int VBO; -<function id='12'>glGenBuffers</function>(1, &VBO); -</code></pre> - -<p> - OpenGL has many types of buffer objects and the buffer type of a vertex buffer object is <var>GL_ARRAY_BUFFER</var>. OpenGL allows us to bind to several buffers at once as long as they have a different buffer type. We can bind the newly created buffer to the <var>GL_ARRAY_BUFFER</var> target with the <fun><function id='32'>glBindBuffer</function></fun> function: -</p> - -<pre><code> -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, VBO); -</code></pre> - -<p> - From that point on any buffer calls we make (on the <var>GL_ARRAY_BUFFER</var> target) will be used to configure the currently bound buffer, which is <var>VBO</var>. Then we can make a call to the - <fun><function id='31'>glBufferData</function></fun> function that copies the previously defined vertex data into the buffer's memory: -</p> - -<pre><code> -<function id='31'>glBufferData</function>(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); -</code></pre> - -<p> - <fun><function id='31'>glBufferData</function></fun> is a function specifically targeted to copy user-defined data into the currently bound buffer. Its first argument is the type of the buffer we want to copy data into: the vertex buffer object currently bound to the <var>GL_ARRAY_BUFFER</var> target. The second argument specifies the size of the data (in bytes) we want to pass to the buffer; a simple <code>sizeof</code> of the vertex data suffices. The third parameter is the actual data we want to send. -</p> - -<p> - The fourth parameter specifies how we want the graphics card to manage the given data. This can take 3 forms: -</p> - - <ul> - <li><var>GL_STREAM_DRAW</var>: the data is set only once and used by the GPU at most a few times.</li> - <li><var>GL_STATIC_DRAW</var>: the data is set only once and used many times.</li> - <li><var>GL_DYNAMIC_DRAW</var>: the data is changed a lot and used many times.</li> - - </ul> - -<p> - The position data of the triangle does not change, is used a lot, and stays the same for every render call so its usage type should best be <var>GL_STATIC_DRAW</var>. If, for instance, one would have a buffer with data that is likely to change frequently, a usage type of <var>GL_DYNAMIC_DRAW</var> ensures the graphics card will place the data in memory that allows for faster writes. -</p> - -<p> - As of now we stored the vertex data within memory on the graphics card as managed by a vertex buffer object named <var>VBO</var>. Next we want to create a vertex and fragment shader that actually processes this data, so let's start building those. -</p> - -<h2>Vertex shader</h2> -<p> - The vertex shader is one of the shaders that are programmable by people like us. Modern OpenGL requires that we at least set up a vertex and fragment shader if we want to do some rendering so we will briefly introduce shaders and configure two very simple shaders for drawing our first triangle. In the next chapter we'll discuss shaders in more detail. -</p> - -<p> - The first thing we need to do is write the vertex shader in the shader language GLSL (OpenGL Shading Language) and then compile this shader so we can use it in our application. Below you'll find the source code of a very basic vertex shader in GLSL: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; - -void main() -{ - gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); -} -</code></pre> - -<p> - As you can see, GLSL looks similar to C. Each shader begins with a declaration of its version. Since OpenGL 3.3 and higher the version numbers of GLSL match the version of OpenGL (GLSL version 420 corresponds to OpenGL version 4.2 for example). We also explicitly mention we're using core profile functionality. -</p> - -<p> - Next we declare all the input vertex attributes in the vertex shader with the <code>in</code> keyword. Right now we only care about position data so we only need a single vertex attribute. GLSL has a vector datatype that contains 1 to 4 floats based on its postfix digit. Since each vertex has a 3D coordinate we create a <code>vec3</code> input variable with the name <var>aPos</var>. We also specifically set the location of the input variable via <code>layout (location = 0)</code> and you'll later see that why we're going to need that location. -</p> - -<note> - <strong>Vector</strong><br/> - In graphics programming we use the mathematical concept of a vector quite often, since it neatly represents positions/directions in any space and has useful mathematical properties. A vector in GLSL has a maximum size of 4 and each of its values can be retrieved via <code>vec.x</code>, <code>vec.y</code>, <code>vec.z</code> and <code>vec.w</code> respectively where each of them represents a coordinate in space. Note that the <code>vec.w</code> component is not used as a position in space (we're dealing with 3D, not 4D) but is used for something called <def>perspective division</def>. We'll discuss vectors in much greater depth in a later chapter. -</note> - -<p> - To set the output of the vertex shader we have to assign the position data to the predefined <var>gl_Position</var> variable which is a <code>vec4</code> behind the scenes. At the end of the <fun>main</fun> function, whatever we set <var>gl_Position</var> to will be used as the output of the vertex shader. Since our input is a vector of size 3 we have to cast this to a vector of size 4. We can do this by inserting the <code>vec3</code> values inside the constructor of <code>vec4</code> and set its <code>w</code> component to <code>1.0f</code> (we will explain why in a later chapter). -</p> - -<p> - The current vertex shader is probably the most simple vertex shader we can imagine because we did no processing whatsoever on the input data and simply forwarded it to the shader's output. In real applications the input data is usually not already in normalized device coordinates so we first have to transform the input data to coordinates that fall within OpenGL's visible region. -</p> - -<h2>Compiling a shader</h2> -<p> - We take the source code for the vertex shader and store it in a const C string at the top of the code file for now: -</p> - -<pre><code> -const char *vertexShaderSource = "#version 330 core\n" - "layout (location = 0) in vec3 aPos;\n" - "void main()\n" - "{\n" - " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" - "}\0"; -</code></pre> - -<p> - In order for OpenGL to use the shader it has to dynamically compile it at run-time from its source code. The first thing we need to do is create a shader object, again referenced by an ID. So we store the vertex shader as an <code>unsigned int</code> and create the shader with <fun><function id='37'>glCreateShader</function></fun>: -</p> - -<pre><code> -unsigned int vertexShader; -vertexShader = <function id='37'>glCreateShader</function>(GL_VERTEX_SHADER); -</code></pre> - -<p> - We provide the type of shader we want to create as an argument to <fun><function id='37'>glCreateShader</function></fun>. Since we're creating a vertex shader we pass in <var>GL_VERTEX_SHADER</var>. -</p> - -<p> - Next we attach the shader source code to the shader object and compile the shader: -</p> - -<pre class="cpp"><code> -<function id='42'>glShaderSource</function>(vertexShader, 1, &vertexShaderSource, NULL); -<function id='38'>glCompileShader</function>(vertexShader); -</code></pre> - -<p> - The <fun><function id='42'>glShaderSource</function></fun> function takes the shader object to compile to as its first argument. The second argument specifies how many strings we're passing as source code, which is only one. The third parameter is the actual source code of the vertex shader and we can leave the 4th parameter to <code>NULL</code>. -</p> - -<note> - <p> - You probably want to check if compilation was successful after the call to <fun><function id='38'>glCompileShader</function></fun> and if not, what errors were found so you can fix those. Checking for compile-time errors is accomplished as follows: - </p> - -<pre class="cpp"><code> -int success; -char infoLog[512]; -<function id='39'>glGetShaderiv</function>(vertexShader, GL_COMPILE_STATUS, &success); -</code></pre> - -<p> - First we define an integer to indicate success and a storage container for the error messages (if any). Then we check if compilation was successful with <fun><function id='39'>glGetShaderiv</function></fun>. If compilation failed, we should retrieve the error message with <fun><function id='40'>glGetShaderInfoLog</function></fun> and print the error message. - </p> - -<pre><code> -if(!success) -{ - <function id='40'>glGetShaderInfoLog</function>(vertexShader, 512, NULL, infoLog); - std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; -} -</code></pre> -</note> - -<p> - If no errors were detected while compiling the vertex shader it is now compiled. -</p> - -<h2>Fragment shader</h2> -<p> - The fragment shader is the second and final shader we're going to create for rendering a triangle. The fragment shader is all about calculating the color output of your pixels. To keep things simple the fragment shader will always output an orange-ish color. -</p> - -<note> - Colors in computer graphics are represented as an array of 4 values: the red, green, blue and alpha (opacity) component, commonly abbreviated to RGBA. When defining a color in OpenGL or GLSL we set the strength of each component to a value between <code>0.0</code> and <code>1.0</code>. If, for example, we would set red to <code>1.0</code> and green to <code>1.0</code> we would get a mixture of both colors and get the color yellow. Given those 3 color components we can generate over 16 million different colors! -</note> - -<pre><code> -#version 330 core -out vec4 FragColor; - -void main() -{ - FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); -} -</code></pre> - -<p> - The fragment shader only requires one output variable and that is a vector of size 4 that defines the final color output that we should calculate ourselves. We can declare output values with the <code>out</code> keyword, that we here promptly named <var>FragColor</var>. Next we simply assign a <code>vec4</code> to the color output as an orange color with an alpha value of <code>1.0</code> (<code>1.0</code> being completely opaque). -</p> - -<p> - The process for compiling a fragment shader is similar to the vertex shader, although this time we use the <var>GL_FRAGMENT_SHADER</var> constant as the shader type: -</p> - -<pre class="cpp"><code> -unsigned int fragmentShader; -fragmentShader = <function id='37'>glCreateShader</function>(GL_FRAGMENT_SHADER); -<function id='42'>glShaderSource</function>(fragmentShader, 1, &fragmentShaderSource, NULL); -<function id='38'>glCompileShader</function>(fragmentShader); -</code></pre> - -<p> - Both the shaders are now compiled and the only thing left to do is link both shader objects into a <def>shader program</def> that we can use for rendering. Make sure to check for compile errors here as well! -</p> - -<h3>Shader program</h3> -<p> - A shader program object is the final linked version of multiple shaders combined. To use the recently compiled shaders we have to <def>link</def> them to a shader program object and then activate this shader program when rendering objects. The activated shader program's shaders will be used when we issue render calls. -</p> - -<p> - When linking the shaders into a program it links the outputs of each shader to the inputs of the next shader. This is also where you'll get linking errors if your outputs and inputs do not match. </p> - -<p> - Creating a program object is easy: -</p> - -<pre><code> -unsigned int shaderProgram; -shaderProgram = <function id='36'>glCreateProgram</function>(); -</code></pre> - -<p> - The <fun><function id='36'>glCreateProgram</function></fun> function creates a program and returns the ID reference to the newly created program object. Now we need to attach the previously compiled shaders to the program object and then link them with <fun><function id='35'>glLinkProgram</function></fun>: -</p> - -<pre><code> -<function id='34'>glAttachShader</function>(shaderProgram, vertexShader); -<function id='34'>glAttachShader</function>(shaderProgram, fragmentShader); -<function id='35'>glLinkProgram</function>(shaderProgram); -</code></pre> - -<p> - The code should be pretty self-explanatory, we attach the shaders to the program and link them via <fun><function id='35'>glLinkProgram</function></fun>. -</p> - -<note> - Just like shader compilation we can also check if linking a shader program failed and retrieve the corresponding log. However, instead of using <fun><function id='39'>glGetShaderiv</function></fun> and <fun><function id='40'>glGetShaderInfoLog</function></fun> we now use: - -<pre class="cpp"><code> -<function id='41'>glGetProgramiv</function>(shaderProgram, GL_LINK_STATUS, &success); -if(!success) { - glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); - ... -} -</code></pre> -</note> - -<p> - The result is a program object that we can activate by calling <fun><function id='28'>glUseProgram</function></fun> with the newly created program object as its argument: -</p> - -<pre><code> -<function id='28'>glUseProgram</function>(shaderProgram); -</code></pre> - -<p> - Every shader and rendering call after <fun><function id='28'>glUseProgram</function></fun> will now use this program object (and thus the shaders). -</p> - -<p> - Oh yeah, and don't forget to delete the shader objects once we've linked them into the program object; we no longer need them anymore: -</p> - -<pre><code> -<function id='46'>glDeleteShader</function>(vertexShader); -<function id='46'>glDeleteShader</function>(fragmentShader); -</code></pre> - -<p> - Right now we sent the input vertex data to the GPU and instructed the GPU how it should process the vertex data within a vertex and fragment shader. We're almost there, but not quite yet. OpenGL does not yet know how it should interpret the vertex data in memory and how it should connect the vertex data to the vertex shader's attributes. We'll be nice and tell OpenGL how to do that. -</p> - -<h2>Linking Vertex Attributes</h2> -<p> - The vertex shader allows us to specify any input we want in the form of vertex attributes and while this allows for great flexibility, it does mean we have to manually specify what part of our input data goes to which vertex attribute in the vertex shader. This means we have to specify how OpenGL should interpret the vertex data before rendering. -</p> - -<p> - Our vertex buffer data is formatted as follows: -</p> - -<img src="/img/getting-started/vertex_attribute_pointer.png" class="clean" alt="Vertex attribte pointer setup of OpenGL VBO"/> - - <ul> - <li>The position data is stored as 32-bit (4 byte) floating point values.</li> - <li>Each position is composed of 3 of those values.</li> - <li>There is no space (or other values) between each set of 3 values. The values are <def>tightly packed</def> in the array.</li> - <li>The first value in the data is at the beginning of the buffer.</li> - </ul> - -<p> - With this knowledge we can tell OpenGL how it should interpret the vertex data (per vertex attribute) using <fun><function id='30'>glVertexAttribPointer</function></fun>: -</p> - -<pre class="cpp"><code> -<function id='30'>glVertexAttribPointer</function>(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); -<function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(0); -</code></pre> - -<p> - The function <fun><function id='30'>glVertexAttribPointer</function></fun> has quite a few parameters so let's carefully walk through them: -</p> - - <ul> - <li>The first parameter specifies which vertex attribute we want to configure. Remember that we specified the location of the <var>position</var> vertex attribute in the vertex shader with <code>layout (location = 0)</code>. This sets the location of the vertex attribute to <code>0</code> and since we want to pass data to this vertex attribute, we pass in <code>0</code>.</li> - - <li>The next argument specifies the size of the vertex attribute. The vertex attribute is a <code>vec3</code> so it is composed of <code>3</code> values.</li> - - <li>The third argument specifies the type of the data which is <var>GL_FLOAT</var> (a <code>vec*</code> in GLSL consists of floating point values).</li> - - <li>The next argument specifies if we want the data to be normalized. If we're inputting integer data types (int, byte) and we've set this to <var>GL_TRUE</var>, the integer data is normalized to <code>0</code> (or <code>-1</code> for signed data) and <code>1</code> when converted to float. This is not relevant for us so we'll leave this at <var>GL_FALSE</var>.</li> - - <li>The fifth argument is known as the <def>stride</def> and tells us the space between consecutive vertex attributes. Since the next set of position data is located exactly 3 times the size of a <code>float</code> away we specify that value as the stride. Note that since we know that the array is tightly packed (there is no space between the next vertex attribute value) we could've also specified the stride as <code>0</code> to let OpenGL determine the stride (this only works when values are tightly packed). Whenever we have more vertex attributes we have to carefully define the spacing between each vertex attribute but we'll get to see more examples of that later on.</li> - - <li>The last parameter is of type <code>void*</code> and thus requires that weird cast. This is the <def>offset</def> of where the position data begins in the buffer. Since the position data is at the start of the data array this value is just <code>0</code>. We will explore this parameter in more detail later on</li> - </ul> - - <note> -Each vertex attribute takes its data from memory managed by a VBO and which VBO it takes its data from (you can have multiple VBOs) is determined by the VBO currently bound to <var>GL_ARRAY_BUFFER</var> when calling <fun><function id='30'>glVertexAttribPointer</function></fun>. Since the previously defined <var>VBO</var> is still bound before calling <fun><function id='30'>glVertexAttribPointer</function></fun> vertex attribute <code>0</code> is now associated with its vertex data. -</note> - - -<p> - Now that we specified how OpenGL should interpret the vertex data we should also enable the vertex attribute with <fun><function id='29'><function id='60'>glEnable</function>VertexAttribArray</function></fun> giving the vertex attribute location as its argument; vertex attributes are disabled by default. From that point on we have everything set up: we initialized the vertex data in a buffer using a vertex buffer object, set up a vertex and fragment shader and told OpenGL how to link the vertex data to the vertex shader's vertex attributes. Drawing an object in OpenGL would now look something like this: -</p> - -<pre><code> -// 0. copy our vertices array in a buffer for OpenGL to use -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, VBO); -<function id='31'>glBufferData</function>(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); -// 1. then set the vertex attributes pointers -<function id='30'>glVertexAttribPointer</function>(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); -<function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(0); -// 2. use our shader program when we want to render an object -<function id='28'>glUseProgram</function>(shaderProgram); -// 3. now draw the object -someOpenGLFunctionThatDrawsOurTriangle(); -</code></pre> - -<p> - We have to repeat this process every time we want to draw an object. It may not look like that much, but imagine if we have over 5 vertex attributes and perhaps 100s of different objects (which is not uncommon). Binding the appropriate buffer objects and configuring all vertex attributes for each of those objects quickly becomes a cumbersome process. What if there was some way we could store all these state configurations into an object and simply bind this object to restore its state? -</p> - -<h3>Vertex Array Object</h3> -<p> - A <def>vertex array object</def> (also known as <def>VAO</def>) can be bound just like a vertex buffer object and any subsequent vertex attribute calls from that point on will be stored inside the VAO. This has the advantage that when configuring vertex attribute pointers you only have to make those calls once and whenever we want to draw the object, we can just bind the corresponding VAO. This makes switching between different vertex data and attribute configurations as easy as binding a different VAO. All the state we just set is stored inside the VAO. -</p> - -<warning> - Core OpenGL <strong>requires</strong> that we use a VAO so it knows what to do with our vertex inputs. If we fail to bind a VAO, OpenGL will most likely refuse to draw anything. -</warning> - -<p> - A vertex array object stores the following: -</p> - -<ul> - <li>Calls to <fun><function id='29'><function id='60'>glEnable</function>VertexAttribArray</function></fun> or <fun>glDisableVertexAttribArray</fun>.</li> - <li>Vertex attribute configurations via <fun><function id='30'>glVertexAttribPointer</function></fun>.</li> - <li>Vertex buffer objects associated with vertex attributes by calls to <fun><function id='30'>glVertexAttribPointer</function></fun>.</li> -</ul> - - <img src="/img/getting-started/vertex_array_objects.png" class="clean" alt="Image of how a VAO (Vertex Array Object) operates and what it stores in OpenGL"/> - -<p> - The process to generate a VAO looks similar to that of a VBO: -</p> - -<pre class="cpp"><code> -unsigned int VAO; -<function id='33'>glGenVertexArrays</function>(1, &VAO); -</code></pre> - -<p> - To use a VAO all you have to do is bind the VAO using <fun><function id='27'>glBindVertexArray</function></fun>. From that point on we should bind/configure the corresponding VBO(s) and attribute pointer(s) and then unbind the VAO for later use. As soon as we want to draw an object, we simply bind the VAO with the preferred settings before drawing the object and that is it. In code this would look a bit like this: -</p> - -<pre><code> -// ..:: Initialization code (done once (unless your object frequently changes)) :: .. -// 1. bind Vertex Array Object -<function id='27'>glBindVertexArray</function>(VAO); -// 2. copy our vertices array in a buffer for OpenGL to use -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, VBO); -<function id='31'>glBufferData</function>(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); -// 3. then set our vertex attributes pointers -<function id='30'>glVertexAttribPointer</function>(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); -<function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(0); - - -[...] - -// ..:: Drawing code (in render loop) :: .. -// 4. draw the object -<function id='28'>glUseProgram</function>(shaderProgram); -<function id='27'>glBindVertexArray</function>(VAO); -someOpenGLFunctionThatDrawsOurTriangle(); -</code></pre> - -<p> - And that is it! Everything we did the last few million pages led up to this moment, a VAO that stores our vertex attribute configuration and which VBO to use. Usually when you have multiple objects you want to draw, you first generate/configure all the VAOs (and thus the required VBO and attribute pointers) and store those for later use. The moment we want to draw one of our objects, we take the corresponding VAO, bind it, then draw the object and unbind the VAO again. -</p> - -<h3>The triangle we've all been waiting for</h3> -<p> - To draw our objects of choice, OpenGL provides us with the <fun><function id='1'>glDrawArrays</function></fun> function that draws primitives using the currently active shader, the previously defined vertex attribute configuration and with the VBO's vertex data (indirectly bound via the VAO). -</p> - -<pre class="cpp"><code> -<function id='28'>glUseProgram</function>(shaderProgram); -<function id='27'>glBindVertexArray</function>(VAO); -<function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 3); -</code></pre> - -<p> - The <fun><function id='1'>glDrawArrays</function></fun> function takes as its first argument the OpenGL primitive type we would like to draw. Since I said at the start we wanted to draw a triangle, and I don't like lying to you, we pass in <var>GL_TRIANGLES</var>. The second argument specifies the starting index of the vertex array we'd like to draw; we just leave this at <code>0</code>. The last argument specifies how many vertices we want to draw, which is <code>3</code> (we only render 1 triangle from our data, which is exactly 3 vertices long). -</p> - -<p> - Now try to compile the code and work your way backwards if any errors popped up. As soon as your application compiles, you should see the following result: -</p> - -<img src="/img/getting-started/hellotriangle.png" width="600px" class="clean" alt="An image of a basic triangle rendered in modern OpenGL" /> - -<p> - The source code for the complete program can be found <a href="/code_viewer_gh.php?code=src/1.getting_started/2.1.hello_triangle/hello_triangle.cpp" target="_blank">here</a> . -</p> - -<p> - If your output does not look the same you probably did something wrong along the way so check the complete source code and see if you missed anything. -</p> - -<h2> Element Buffer Objects </h2> -<p> - There is one last thing we'd like to discuss when rendering vertices and that is <def>element buffer objects</def> abbreviated to EBO. To explain how element buffer objects work it's best to give an example: suppose we want to draw a rectangle instead of a triangle. We can draw a rectangle using two triangles (OpenGL mainly works with triangles). This will generate the following set of vertices: -</p> - -<pre><code> -float vertices[] = { - // first triangle - 0.5f, 0.5f, 0.0f, // top right - 0.5f, -0.5f, 0.0f, // bottom right - -0.5f, 0.5f, 0.0f, // top left - // second triangle - 0.5f, -0.5f, 0.0f, // bottom right - -0.5f, -0.5f, 0.0f, // bottom left - -0.5f, 0.5f, 0.0f // top left -}; -</code></pre> - -<p> - As you can see, there is some overlap on the vertices specified. We specify <code>bottom right</code> and <code>top left</code> twice! This is an overhead of 50% since the same rectangle could also be specified with only 4 vertices, instead of 6. This will only get worse as soon as we have more complex models that have over 1000s of triangles where there will be large chunks that overlap. What would be a better solution is to store only the unique vertices and then specify the order at which we want to draw these vertices in. In that case we would only have to store 4 vertices for the rectangle, and then just specify at which order we'd like to draw them. Wouldn't it be great if OpenGL provided us with a feature like that? -</p> - -<p> - Thankfully, element buffer objects work exactly like that. An EBO is a buffer, just like a vertex buffer object, that stores indices that OpenGL uses to decide what vertices to draw. This so called <def>indexed drawing</def> is exactly the solution to our problem. To get started we first have to specify the (unique) vertices and the indices to draw them as a rectangle: -</p> - -<pre><code> -float vertices[] = { - 0.5f, 0.5f, 0.0f, // top right - 0.5f, -0.5f, 0.0f, // bottom right - -0.5f, -0.5f, 0.0f, // bottom left - -0.5f, 0.5f, 0.0f // top left -}; -unsigned int indices[] = { // note that we start from 0! - 0, 1, 3, // first triangle - 1, 2, 3 // second triangle -}; -</code></pre> - -<p> - You can see that, when using indices, we only need 4 vertices instead of 6. Next we need to create the element buffer object: -</p> - -<pre class="cpp"><code> -unsigned int EBO; -<function id='12'>glGenBuffers</function>(1, &EBO); -</code></pre> - -<p> - Similar to the VBO we bind the EBO and copy the indices into the buffer with <fun><function id='31'>glBufferData</function></fun>. Also, just like the VBO we want to place those calls between a bind and an unbind call, although this time we specify <var>GL_ELEMENT_ARRAY_BUFFER</var> as the buffer type. -</p> - -<pre><code> -<function id='32'>glBindBuffer</function>(GL_ELEMENT_ARRAY_BUFFER, EBO); -<function id='31'>glBufferData</function>(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); -</code></pre> - -<p> - Note that we're now giving <var>GL_ELEMENT_ARRAY_BUFFER</var> as the buffer target. The last thing left to do is replace the <fun><function id='1'>glDrawArrays</function></fun> call with <fun><function id='2'>glDrawElements</function></fun> to indicate we want to render the triangles from an index buffer. When using <fun><function id='2'>glDrawElements</function></fun> we're going to draw using indices provided in the element buffer object currently bound: -</p> - -<pre class="cpp"><code> -<function id='32'>glBindBuffer</function>(GL_ELEMENT_ARRAY_BUFFER, EBO); -<function id='2'>glDrawElements</function>(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); -</code></pre> - -<p> - The first argument specifies the mode we want to draw in, similar to <fun><function id='1'>glDrawArrays</function></fun>. The second argument is the count or number of elements we'd like to draw. We specified 6 indices so we want to draw 6 vertices in total. The third argument is the type of the indices which is of type <var>GL_UNSIGNED_INT</var>. The last argument allows us to specify an offset in the EBO (or pass in an index array, but that is when you're not using element buffer objects), but we're just going to leave this at 0. -</p> - -<p> - The <fun><function id='2'>glDrawElements</function></fun> function takes its indices from the EBO currently bound to the <var>GL_ELEMENT_ARRAY_BUFFER</var> target. This means we have to bind the corresponding EBO each time we want to render an object with indices which again is a bit cumbersome. It just so happens that a vertex array object also keeps track of element buffer object bindings. The last element buffer object that gets bound while a VAO is bound, is stored as the VAO's element buffer object. Binding to a VAO then also automatically binds that EBO. -</p> - -<img src="/img/getting-started/vertex_array_objects_ebo.png" class="clean" alt="Image of VAO's structure / what it stores now also with EBO bindings."/> - -<warning> - A VAO stores the <fun><function id='32'>glBindBuffer</function></fun> calls when the target is <var>GL_ELEMENT_ARRAY_BUFFER</var>. This also means it stores its unbind calls so make sure you don't unbind the element array buffer before unbinding your VAO, otherwise it doesn't have an EBO configured. -</warning> - -<p> - The resulting initialization and drawing code now looks something like this: -</p> - -<pre><code> -// ..:: Initialization code :: .. -// 1. bind Vertex Array Object -<function id='27'>glBindVertexArray</function>(VAO); -// 2. copy our vertices array in a vertex buffer for OpenGL to use -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, VBO); -<function id='31'>glBufferData</function>(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); -// 3. copy our index array in a element buffer for OpenGL to use -<function id='32'>glBindBuffer</function>(GL_ELEMENT_ARRAY_BUFFER, EBO); -<function id='31'>glBufferData</function>(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); -// 4. then set the vertex attributes pointers -<function id='30'>glVertexAttribPointer</function>(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); -<function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(0); - -[...] - -// ..:: Drawing code (in render loop) :: .. -<function id='28'>glUseProgram</function>(shaderProgram); -<function id='27'>glBindVertexArray</function>(VAO); -<function id='2'>glDrawElements</function>(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0) -<function id='27'>glBindVertexArray</function>(0); -</code></pre> - -<p> - Running the program should give an image as depicted below. The left image should look familiar and the right image is the rectangle drawn in <def>wireframe mode</def>. The wireframe rectangle shows that the rectangle indeed consists of two triangles. -</p> - -<img src="/img/getting-started/hellotriangle2.png" width="800px" class="clean" alt="A rectangle drawn using indexed rendering in OpenGL"/> - -<note> - <strong>Wireframe mode</strong><br/> - To draw your triangles in wireframe mode, you can configure how OpenGL draws its primitives via <code><function id='43'>glPolygonMode</function>(GL_FRONT_AND_BACK, GL_LINE)</code>. The first argument says we want to apply it to the front and back of all triangles and the second line tells us to draw them as lines. Any subsequent drawing calls will render the triangles in wireframe mode until we set it back to its default using <code><function id='43'>glPolygonMode</function>(GL_FRONT_AND_BACK, GL_FILL)</code>. -</note> - -<p> - If you have any errors, work your way backwards and see if you missed anything. You can find the complete source code <a href="/code_viewer_gh.php?code=src/1.getting_started/2.2.hello_triangle_indexed/hello_triangle_indexed.cpp" target="_blank">here</a>. -</p> - -<p> - If you managed to draw a triangle or a rectangle just like we did then congratulations, you managed to make it past one of the hardest parts of modern OpenGL: drawing your first triangle. This is a difficult part since there is a large chunk of knowledge required before being able to draw your first triangle. Thankfully, we now made it past that barrier and the upcoming chapters will hopefully be much easier to understand. -</p> - -<h2>Additional resources</h2> -<ul> - <li><a href="http://antongerdelan.net/opengl/hellotriangle.html" target="_blank">antongerdelan.net/hellotriangle</a>: Anton Gerdelan's take on rendering the first triangle.</li> - <li><a href="https://open.gl/drawing" target="_blank">open.gl/drawing</a>: Alexander Overvoorde's take on rendering the first triangle.</li> - <li><a href="http://antongerdelan.net/opengl/vertexbuffers.html" target="_blank">antongerdelan.net/vertexbuffers</a>: some extra insights into vertex buffer objects.</li> - <li><a href="https://learnopengl.com/In-Practice/Debugging" target="_blank">learnopengl.com/In-Practice/Debugging</a>: there are a lot of steps involved in this chapter; if you're stuck it may be worthwhile to read a bit on debugging in OpenGL (up until the debug output section).</li> -</ul> - -<h1>Exercises</h1> -<p> - To really get a good grasp of the concepts discussed a few exercises were set up. It is advised to work through them before continuing to the next subject to make sure you get a good grasp of what's going on. -</p> - -<ol> - <li>Try to draw 2 triangles next to each other using <fun><function id='1'>glDrawArrays</function></fun> by adding more vertices to your data: <a href="/code_viewer_gh.php?code=src/1.getting_started/2.3.hello_triangle_exercise1/hello_triangle_exercise1.cpp" target="_blank">solution</a>.</li> - <li>Now create the same 2 triangles using two different VAOs and VBOs for their data: <a href="/code_viewer_gh.php?code=src/1.getting_started/2.4.hello_triangle_exercise2/hello_triangle_exercise2.cpp" target="_blank">solution</a>.</li> - <li>Create two shader programs where the second program uses a different fragment shader that outputs the color yellow; draw both triangles again where one outputs the color yellow: <a href="/code_viewer_gh.php?code=src/1.getting_started/2.5.hello_triangle_exercise3/hello_triangle_exercise3.cpp" target="_blank">solution</a>.</li> -</ol> - - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/orig/Getting-started/Hello-Window.html b/orig/Getting-started/Hello-Window.html @@ -1,542 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Hello Window</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Hello Window</h1> -<h1 id="content-url" style='display:none;'>Getting-started/Hello-Window</h1> -<p> - Let's see if we can get GLFW up and running. First, create a <code>.cpp</code> file and add the following includes to the top of your newly created file. -</p> - -<pre><code> -#include <glad/glad.h> -#include <GLFW/glfw3.h> -</code></pre> - -<warning> - Be sure to include GLAD before GLFW. The include file for GLAD includes the required OpenGL headers behind the scenes (like <code>GL/gl.h</code>) so be sure to include GLAD before other header files that require OpenGL (like GLFW). -</warning> - -<p> - Next, we create the <fun>main</fun> function where we will instantiate the GLFW window: -</p> - -<pre><code> -int main() -{ - <function id='17'>glfwInit</function>(); - <function id='18'>glfwWindowHint</function>(GLFW_CONTEXT_VERSION_MAJOR, 3); - <function id='18'>glfwWindowHint</function>(GLFW_CONTEXT_VERSION_MINOR, 3); - <function id='18'>glfwWindowHint</function>(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - //<function id='18'>glfwWindowHint</function>(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); - - return 0; -} -</code></pre> - -<p> - In the main function we first initialize GLFW with <fun><function id='17'>glfwInit</function></fun>, after which we can configure GLFW using <fun><function id='18'>glfwWindowHint</function></fun>. The first argument of <fun><function id='18'>glfwWindowHint</function></fun> tells us what option we want to configure, where we can select the option from a large enum of possible options prefixed with <code>GLFW_</code>. The second argument is an integer that sets the value of our option. A list of all the possible options and its corresponding values can be found at <a href="http://www.glfw.org/docs/latest/window.html#window_hints" target="_blank">GLFW's window handling</a> documentation. If you try to run the application now and it gives a lot of <em>undefined reference</em> errors it means you didn't successfully link the GLFW library. -</p> - -<p> - Since the focus of this book is on OpenGL version 3.3 we'd like to tell GLFW that 3.3 is the OpenGL version we want to use. This way GLFW can make the proper arrangements when creating the OpenGL context. This ensures that when a user does not have the proper OpenGL version GLFW fails to run. We set the major and minor version both to <code>3</code>. We also tell GLFW we want to explicitly use the core-profile. Telling GLFW we want to use the core-profile means we'll get access to a smaller subset of OpenGL features without backwards-compatible features we no longer need. Note that on Mac OS X you need to add <code><function id='18'>glfwWindowHint</function>(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);</code> to your initialization code for it to work. -</p> - -<note> - Make sure you have OpenGL versions 3.3 or higher installed on your system/hardware otherwise the application will crash or display undefined behavior. To find the OpenGL version on your machine either call <strong>glxinfo</strong> on Linux machines or use a utility like the <a href="http://download.cnet.com/OpenGL-Extensions-Viewer/3000-18487_4-34442.html" target="_blank">OpenGL Extension Viewer</a> for Windows. If your supported version is lower try to check if your video card supports OpenGL 3.3+ (otherwise it's really old) and/or update your drivers. -</note> - -<p> - - Next we're required to create a window object. This window object holds all the windowing data and is required by most of GLFW's other functions. -</p> - -<pre><code> -GLFWwindow* window = <function id='20'>glfwCreateWindow</function>(800, 600, "LearnOpenGL", NULL, NULL); -if (window == NULL) -{ - std::cout << "Failed to create GLFW window" << std::endl; - <function id='25'>glfwTerminate</function>(); - return -1; -} -<function id='19'>glfwMakeContextCurrent</function>(window); -</code></pre> - -<p> - The <fun><function id='20'>glfwCreateWindow</function></fun> function requires the window width and height as its first two arguments respectively. The third argument allows us to create a name for the window; for now we call it <code>"LearnOpenGL"</code> but you're allowed to name it however you like. We can ignore the last 2 parameters. The function returns a <fun>GLFWwindow</fun> object that we'll later need for other GLFW operations. After that we tell GLFW to make the context of our window the main context on the current thread. -</p> - -<h2>GLAD</h2> -<p> - In the previous chapter we mentioned that GLAD manages function pointers for OpenGL so we want to initialize GLAD before we call any OpenGL function: -</p> - -<pre><code> -if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) -{ - std::cout << "Failed to initialize GLAD" << std::endl; - return -1; -} -</code></pre> - -<p> - We pass GLAD the function to load the address of the OpenGL function pointers which is OS-specific. GLFW gives us <fun>glfwGetProcAddress</fun> that defines the correct function based on which OS we're compiling for. -</p> - -<h2>Viewport</h2> -<p> - Before we can start rendering we have to do one last thing. We have to tell OpenGL the size of the rendering window so OpenGL knows how we want to display the data and coordinates with respect to the window. We can set those <em>dimensions</em> via the <fun><function id='22'>glViewport</function></fun> function: -</p> - -<pre class="cpp"><code> -<function id='22'>glViewport</function>(0, 0, 800, 600); -</code></pre> - -<p> - The first two parameters of <fun><function id='22'>glViewport</function></fun> set the location of the lower left corner of the window. The third and fourth parameter set the width and height of the rendering window in pixels, which we set equal to GLFW's window size. -</p> - -<p> - We could actually set the viewport dimensions at values smaller than GLFW's dimensions; then all the OpenGL rendering would be displayed in a smaller window and we could for example display other elements outside the OpenGL viewport. -</p> - -<note> - Behind the scenes OpenGL uses the data specified via <fun><function id='22'>glViewport</function></fun> to transform the 2D coordinates it processed to coordinates on your screen. For example, a processed point of location <code>(-0.5,0.5)</code> would (as its final transformation) be mapped to <code>(200,450)</code> in screen coordinates. Note that processed coordinates in OpenGL are between -1 and 1 so we effectively map from the range (-1 to 1) to (0, 800) and (0, 600). -</note> - -<p> - However, the moment a user resizes the window the viewport should be adjusted as well. We can register a callback function on the window that gets called each time the window is resized. This resize callback function has the following prototype: -</p> - -<pre><code> -void framebuffer_size_callback(GLFWwindow* window, int width, int height); -</code></pre> - -<p> - The framebuffer size function takes a <fun>GLFWwindow</fun> as its first argument and two integers indicating the new window dimensions. Whenever the window changes in size, GLFW calls this function and fills in the proper arguments for you to process. -</p> - -<pre><code> -void framebuffer_size_callback(GLFWwindow* window, int width, int height) -{ - <function id='22'>glViewport</function>(0, 0, width, height); -} -</code></pre> - -<p> - We do have to tell GLFW we want to call this function on every window resize by registering it: -</p> - -<pre><code> -glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); -</code></pre> - -<p> - When the window is first displayed <fun>framebuffer_size_callback</fun> gets called as well with the resulting window dimensions. For retina displays <var>width</var> and <var>height</var> will end up significantly higher than the original input values. -</p> - -<p> - There are many callbacks functions we can set to register our own functions. For example, we can make a callback function to process joystick input changes, process error messages etc. We register the callback functions after we've created the window and before the render loop is initiated. -</p> - -<h1>Ready your engines</h1> -<p> - We don't want the application to draw a single image and then immediately quit and close the window. We want the application to keep drawing images and handling user input until the program has been explicitly told to stop. For this reason we have to create a while loop, that we now call the <def>render loop</def>, that keeps on running until we tell GLFW to stop. The following code shows a very simple render loop: -</p> - -<pre><code> -while(!<function id='14'>glfwWindowShouldClose</function>(window)) -{ - <function id='24'>glfwSwapBuffers</function>(window); - <function id='23'>glfwPollEvents</function>(); -} -</code></pre> - -<p> - The <fun><function id='14'>glfwWindowShouldClose</function></fun> function checks at the start of each loop iteration if GLFW has been instructed to close. If so, the function returns <code>true</code> and the render loop stops running, after which we can close the application.<br/> - The <fun><function id='23'>glfwPollEvents</function></fun> function checks if any events are triggered (like keyboard input or mouse movement events), updates the window state, and calls the corresponding functions (which we can register via callback methods). - The <fun><function id='24'>glfwSwapBuffers</function></fun> will swap the color buffer (a large 2D buffer that contains color values for each pixel in GLFW's window) that is used to render to during this render iteration and show it as output to the screen. -</p> - -<note> - <strong>Double buffer</strong><br/> - When an application draws in a single buffer the resulting image may display flickering issues. This is because the resulting output image is not drawn in an instant, but drawn pixel by pixel and usually from left to right and top to bottom. Because this image is not displayed at an instant to the user while still being rendered to, the result may contain artifacts. To circumvent these issues, windowing applications apply a double buffer for rendering. The <strong>front</strong> buffer contains the final output image that is shown at the screen, while all the rendering commands draw to the <strong>back</strong> buffer. As soon as all the rendering commands are finished we <strong>swap</strong> the back buffer to the front buffer so the image can be displayed without still being rendered to, removing all the aforementioned artifacts. -</note> - -<h2>One last thing</h2> - <p> - As soon as we exit the render loop we would like to properly clean/delete all of GLFW's resources that were allocated. We can do this via the <fun><function id='25'>glfwTerminate</function></fun> function that we call at the end of the <fun>main</fun> function. - </p> - -<pre><code> -<function id='25'>glfwTerminate</function>(); -return 0; -</code></pre> - - <p> - This will clean up all the resources and properly exit the application. Now try to compile your application and if everything went well you should see the following output: - </p> - - <img src="/img/getting-started/hellowindow.png" width="600px" class="clean" alt="Image of GLFW window output as most basic example"/> - -<p> - If it's a very dull and boring black image, you did things right! If you didn't get the right image or you're confused as to how everything fits together, check the full source code <a href="/code_viewer_gh.php?code=src/1.getting_started/1.1.hello_window/hello_window.cpp" target="_blank">here</a> (and if it started flashing different colors, keep reading). - </p> - - <p> - If you have issues compiling the application, first make sure all your linker options are set correctly and that you properly included the right directories in your IDE (as explained in the previous chapter). Also make sure your code is correct; you can verify it by comparing it with the full source code. - </p> - -<h2>Input</h2> - <p> - We also want to have some form of input control in GLFW and we can achieve this with several of GLFW's input functions. We'll be using GLFW's <fun>glfwGetKey</fun> function that takes the window as input together with a key. The function returns whether this key is currently being pressed. We're creating a <fun>processInput</fun> function to keep all input code organized: -</p> - -<pre><code> -void processInput(GLFWwindow *window) -{ - if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) - glfwSetWindowShouldClose(window, true); -} -</code></pre> - -<p> - Here we check whether the user has pressed the escape key (if it's not pressed, <fun>glfwGetKey</fun> returns <var>GLFW_RELEASE</var>). If the user did press the escape key, we close GLFW by setting its <var>WindowShouldClose</var> property to <code>true</code> using <fun>glfwSetwindowShouldClose</fun>. The next condition check of the main <code>while</code> loop will then fail and the application closes. -</p> - -<p> - We then call <fun>processInput</fun> every iteration of the render loop: -</p> - -<pre><code> -while (!<function id='14'>glfwWindowShouldClose</function>(window)) -{ - processInput(window); - - <function id='24'>glfwSwapBuffers</function>(window); - <function id='23'>glfwPollEvents</function>(); -} -</code></pre> - -<p> - This gives us an easy way to check for specific key presses and react accordingly every <def>frame</def>. An iteration of the render loop is more commonly called a <def>frame</def>. -</p> - -<h2>Rendering</h2> -<p> - We want to place all the rendering commands in the render loop, since we want to execute all the rendering commands each iteration or frame of the loop. This would look a bit like this: - </p> - -<pre><code> -// render loop -while(!<function id='14'>glfwWindowShouldClose</function>(window)) -{ - // input - processInput(window); - - // rendering commands here - ... - - // check and call events and swap the buffers - <function id='23'>glfwPollEvents</function>(); - <function id='24'>glfwSwapBuffers</function>(window); -} -</code></pre> - - <p> - Just to test if things actually work we want to clear the screen with a color of our choice. At the start of frame we want to clear the screen. Otherwise we would still see the results from the previous frame (this could be the effect you're looking for, but usually you don't). We can clear the screen's color buffer using <fun><function id='10'>glClear</function></fun> where we pass in buffer bits to specify which buffer we would like to clear. The possible bits we can set are <var>GL_COLOR_BUFFER_BIT</var>, <var>GL_DEPTH_BUFFER_BIT</var> and <var>GL_STENCIL_BUFFER_BIT</var>. Right now we only care about the color values so we only clear the color buffer. - </p> - -<pre><code> -<function id='13'><function id='10'>glClear</function>Color</function>(0.2f, 0.3f, 0.3f, 1.0f); -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT); -</code></pre> - - <p> - Note that we also specify the color to clear the screen with using <fun><function id='13'><function id='10'>glClear</function>Color</function></fun>. Whenever we call <fun><function id='10'>glClear</function></fun> and clear the color buffer, the entire color buffer will be filled with the color as configured by <fun><function id='13'><function id='10'>glClear</function>Color</function></fun>. This will result in a dark green-blueish color. -</p> - -<note> - As you may recall from the <em>OpenGL</em> chapter, the <fun><function id='13'><function id='10'>glClear</function>Color</function></fun> function is a <em>state-setting</em> function and <fun><function id='10'>glClear</function></fun> is a <em>state-using</em> function in that it uses the current state to retrieve the clearing color from. -</note> - - <img src="/img/getting-started/hellowindow2.png" width="600px" class="clean" alt="Image of GLFW's window creation with <function id='13'><function id='10'>glClear</function>Color</function> defined"/> - - <p> - The full source code of the application can be found <a href="/code_viewer_gh.php?code=src/1.getting_started/1.2.hello_window_clear/hello_window_clear.cpp" target="_blank">here</a>. - </p> - -<p> - So right now we got everything ready to fill the render loop with lots of rendering calls, but that's for the <a href="https://learnopengl.com/Getting-started/Hello-Triangle" target="_blank">next</a> chapter. I think we've been rambling long enough here. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/orig/Getting-started/OpenGL.html b/orig/Getting-started/OpenGL.html @@ -1,427 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - OpenGL</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">OpenGL</h1> -<h1 id="content-url" style='display:none;'>Getting-started/OpenGL</h1> -<p> - Before starting our journey we should first define what OpenGL actually is. OpenGL is mainly considered an API (an <def>Application Programming Interface</def>) that provides us with a large set of functions that we can use to manipulate graphics and images. However, OpenGL by itself is not an API, but merely a specification, developed and maintained by the <a href="http://www.khronos.org/" target="_blank">Khronos Group</a>. -</p> - -<img src="/img/getting-started/opengl.jpg" class="right" alt="Image of OpenGL's logo"/> - -<p> - The OpenGL specification specifies exactly what the result/output of each function should be and how it should perform. It is then up to the developers <em>implementing</em> this specification to come up with a solution of how this function should operate. Since the OpenGL specification does not give us implementation details, the actual developed versions of OpenGL are allowed to have different implementations, as long as their results comply with the specification (and are thus the same to the user). -</p> - -<p> - The people developing the actual OpenGL libraries are usually the graphics card manufacturers. Each graphics card that you buy supports specific versions of OpenGL which are the versions of OpenGL developed specifically for that card (series). When using an Apple system the OpenGL library is maintained by Apple themselves and under Linux there exists a combination of graphic suppliers' versions and hobbyists' adaptations of these libraries. This also means that whenever OpenGL is showing weird behavior that it shouldn't, this is most likely the fault of the graphics cards manufacturers (or whoever developed/maintained the library). -</p> - -<note> - Since most implementations are built by graphics card manufacturers, whenever there is a bug in the implementation this is usually solved by updating your video card drivers; those drivers include the newest versions of OpenGL that your card supports. This is one of the reasons why it's always advised to occasionally update your graphic drivers. -</note> - -<p> - Khronos publicly hosts all specification documents for all the OpenGL versions. The interested reader can find the OpenGL specification of version 3.3 (which is what we'll be using) <a href="https://www.opengl.org/registry/doc/glspec33.core.20100311.withchanges.pdf" target="_blank">here</a> which is a good read if you want to delve into the details of OpenGL (note how they mostly just describe results and not implementations). The specifications also provide a great reference for finding the <strong>exact</strong> workings of its functions. -</p> - -<h2>Core-profile vs Immediate mode</h2> -<p> - In the old days, using OpenGL meant developing in <def>immediate mode</def> (often referred to as the <def>fixed function pipeline</def>) which was an easy-to-use method for drawing graphics. Most of the functionality of OpenGL was hidden inside the library and developers did not have much control over how OpenGL does its calculations. Developers eventually got hungry for more flexibility and over time the specifications became more flexible as a result; developers gained more control over their graphics. The immediate mode is really easy to use and understand, but it is also extremely inefficient. For that reason the specification started to deprecate immediate mode functionality from version 3.2 onwards and started motivating developers to develop in OpenGL's <def>core-profile</def> mode, which is a division of OpenGL's specification that removed all old deprecated functionality. -</p> - -<p> - When using OpenGL's core-profile, OpenGL forces us to use modern practices. Whenever we try to use one of OpenGL's deprecated functions, OpenGL raises an error and stops drawing. The advantage of learning the modern approach is that it is very flexible and efficient. However, it's also more difficult to learn. The immediate mode abstracted quite a lot from the <strong>actual</strong> operations OpenGL performed and while it was easy to learn, it was hard to grasp how OpenGL actually operates. The modern approach requires the developer to truly understand OpenGL and graphics programming and while it is a bit difficult, it allows for much more flexibility, more efficiency and most importantly: a much better understanding of graphics programming. -</p> - -<p> - This is also the reason why this book is geared at core-profile OpenGL version 3.3. Although it is more difficult, it is greatly worth the effort. -</p> - -<p> - As of today, higher versions of OpenGL are available to choose from (at the time of writing 4.6) at which you may ask: why do I want to learn OpenGL 3.3 when OpenGL 4.6 is out? The answer to that question is relatively simple. All future versions of OpenGL starting from 3.3 add extra useful features to OpenGL without changing OpenGL's core mechanics; the newer versions just introduce slightly more efficient or more useful ways to accomplish the same tasks. The result is that all concepts and techniques remain the same over the modern OpenGL versions so it is perfectly valid to learn OpenGL 3.3. Whenever you're ready and/or more experienced you can easily use specific functionality from more recent OpenGL versions. -</p> - -<warning> - When using functionality from the most recent version of OpenGL, only the most modern graphics cards will be able to run your application. This is often why most developers generally target lower versions of OpenGL and optionally enable higher version functionality. -</warning> - -<p> - In some chapters you'll find more modern features which are noted down as such. -</p> - -<h2>Extensions</h2> -<p> - A great feature of OpenGL is its support of extensions. Whenever a graphics company comes up with a new technique or a new large optimization for rendering this is often found in an <def>extension</def> implemented in the drivers. If the hardware an application runs on supports such an extension the developer can use the functionality provided by the extension for more advanced or efficient graphics. This way, a graphics developer can still use these new rendering techniques without having to wait for OpenGL to include the functionality in its future versions, simply by checking if the extension is supported by the graphics card. Often, when an extension is popular or very useful it eventually becomes part of future OpenGL versions. -</p> - -<p> - The developer has to query whether any of these extensions are available before using them (or use an OpenGL extension library). This allows the developer to do things better or more efficient, based on whether an extension is available: -</p> - -<pre><code> -if(GL_ARB_extension_name) -{ - // Do cool new and modern stuff supported by hardware -} -else -{ - // Extension not supported: do it the old way -} -</code></pre> - -<p> - With OpenGL version 3.3 we rarely need an extension for most techniques, but wherever it is necessary proper instructions are provided. -</p> - -<h2>State machine</h2> -<p> - OpenGL is by itself a large state machine: a collection of variables that define how OpenGL should currently operate. The state of OpenGL is commonly referred to as the OpenGL <def>context</def>. When using OpenGL, we often change its state by setting some options, manipulating some buffers and then render using the current context. -</p> - -<p> - Whenever we tell OpenGL that we now want to draw lines instead of triangles for example, we change the state of OpenGL by changing some context variable that sets how OpenGL should draw. As soon as we change the context by telling OpenGL it should draw lines, the next drawing commands will now draw lines instead of triangles. -</p> - -<p> - When working in OpenGL we will come across several <def>state-changing</def> functions that change the context and several <def>state-using</def> functions that perform some operations based on the current state of OpenGL. As long as you keep in mind that OpenGL is basically one large state machine, most of its functionality will make more sense. -</p> - -<h2>Objects</h2> -<p> - The OpenGL libraries are written in C and allows for many derivations in other languages, but in its core it remains a C-library. Since many of C's language-constructs do not translate that well to other higher-level languages, OpenGL was developed with several abstractions in mind. One of those abstractions are <def>objects</def> in OpenGL. -</p> - -<p> - An <def>object</def> in OpenGL is a collection of options that represents a subset of OpenGL's state. For example, we could have an object that represents the settings of the drawing window; we could then set its size, how many colors it supports and so on. One could visualize an object as a C-like struct: -</p> - -<pre><code> -struct object_name { - float option1; - int option2; - char[] name; -}; -</code></pre> - -<p> - Whenever we want to use objects it generally looks something like this (with OpenGL's context visualized as a large struct): -</p> - -<pre><code> -// The State of OpenGL -struct OpenGL_Context { - ... - object_name* object_Window_Target; - ... -}; -</code></pre> - -<pre><code> -// create object -unsigned int objectId = 0; -glGenObject(1, &objectId); -// bind/assign object to context -glBindObject(GL_WINDOW_TARGET, objectId); -// set options of object currently bound to GL_WINDOW_TARGET -glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800); -glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600); -// set context target back to default -glBindObject(GL_WINDOW_TARGET, 0); -</code></pre> - -<p> - This little piece of code is a workflow you'll frequently see when working with OpenGL. We first create an object and store a reference to it as an id (the real object's data is stored behind the scenes). Then we bind the object (using its id) to the target location of the context (the location of the example window object target is defined as <var>GL_WINDOW_TARGET</var>). Next we set the window options and finally we un-bind the object by setting the current object id of the window target to <code>0</code>. The options we set are stored in the object referenced by <var>objectId</var> and restored as soon as we bind the object back to <var>GL_WINDOW_TARGET</var>. -</p> - -<warning> - The code samples provided so far are only approximations of how OpenGL operates; throughout the book you will come across enough actual examples. -</warning> - -<p> - The great thing about using these objects is that we can define more than one object in our application, set their options and whenever we start an operation that uses OpenGL's state, we bind the object with our preferred settings. There are objects for example that act as container objects for 3D model data (a house or a character) and whenever we want to draw one of them, we bind the object containing the model data that we want to draw (we first created and set options for these objects). Having several objects allows us to specify many models and whenever we want to draw a specific model, we simply bind the corresponding object before drawing without setting all their options again. -</p> - -<h2>Let's get started</h2> -<p> - You now learned a bit about OpenGL as a specification and a library, how OpenGL approximately operates under the hood and a few custom tricks that OpenGL uses. Don't worry if you didn't get all of it; throughout the book we'll walk through each step and you'll see enough examples to really get a grasp of OpenGL. -</p> - -<h2>Additional resources</h2> -<ul> - <li><a href="https://www.opengl.org/" target="_blank">opengl.org</a>: official website of OpenGL.</li> - <li><a href="https://www.opengl.org/registry/" target="_blank">OpenGL registry</a>: hosts the OpenGL specifications and extensions for all OpenGL versions.</li> -</ul> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/orig/Getting-started/Review.html b/orig/Getting-started/Review.html @@ -1,316 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Review</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Review</h1> -<h1 id="content-url" style='display:none;'>Getting-started/Review</h1> -<p> - Congratulations on reaching the end of the <em>Getting started</em> chapters. By now you should be able to create a window, create and compile shaders, send vertex data to your shaders via buffer objects or uniforms, draw objects, use textures, understand vectors and matrices and combine all that knowledge to create a full 3D scene with a camera to play around with.</p> - -<p> - Phew, there is a lot that we learned these last few chapters. Try to play around with what you learned, experiment a bit or come up with your own ideas and solutions to some of the problems. As soon as you feel you got the hang of all the materials we've discussed it's time to move on to the <a href="https://learnopengl.com/Lighting/Colors" target="_blank">next</a> Lighting chapters. -</p> - -<h2>Glossary</h2> -<p> - <ul> - <li><code>OpenGL</code>: a formal specification of a graphics API that defines the layout and output of each function. </li> - <li><code>GLAD</code>: an extension loading library that loads and sets all OpenGL's function pointers for us so we can use all (modern) OpenGL's functions. </li> - <li><code>Viewport</code>: the 2D window region where we render to. </li> - <li><code>Graphics Pipeline</code>: the entire process vertices have to walk through before ending up as one or more pixels on the screen. </li> - <li><code>Shader</code>: a small program that runs on the graphics card. Several stages of the graphics pipeline can use user-made shaders to replace existing functionality.</li> - <li><code>Vertex</code>: a collection of data that represent a single point. </li> - <li><code>Normalized Device Coordinates</code>: the coordinate system your vertices end up in after perspective division is performed on clip coordinates. All vertex positions in NDC between <code>-1.0</code> and <code>1.0</code> will not be discarded or clipped and end up visible. </li> - <li><code>Vertex Buffer Object</code>: a buffer object that allocates memory on the GPU and stores all the vertex data there for the graphics card to use. </li> - <li><code>Vertex Array Object</code>: stores buffer and vertex attribute state information.</li> - <li><code>Element Buffer Object</code>: a buffer object that stores indices on the GPU for indexed drawing. </li> - <li><code>Uniform</code>: a special type of GLSL variable that is global (each shader in a shader program can access this uniform variable) and only has to be set once. </li> - <li><code>Texture</code>: a special type of image used in shaders and usually wrapped around objects, giving the illusion an object is extremely detailed. </li> - <li><code>Texture Wrapping</code>: defines the mode that specifies how OpenGL should sample textures when texture coordinates are outside the range: (<code>0</code>, <code>1</code>). </li> - <li><code>Texture Filtering</code>: defines the mode that specifies how OpenGL should sample the texture when there are several texels (texture pixels) to choose from. This usually occurs when a texture is magnified. </li> - <li><code>Mipmaps</code>: stored smaller versions of a texture where the appropriate sized version is chosen based on the distance to the viewer. </li> - <li><code>stb_image</code>: image loading library. </li> - <li><code>Texture Units</code>: allows for multiple textures on a single shader program by binding multiple textures, each to a different texture unit. </li> - <li><code>Vector</code>: a mathematical entity that defines directions and/or positions in any dimension. </li> - <li><code>Matrix</code>: a rectangular array of mathematical expressions with useful transformation properties. </li> - <li><code>GLM</code>: a mathematics library tailored for OpenGL. </li> - <li><code>Local Space</code>: the space an object begins in. All coordinates relative to an object's origin. </li> - <li><code>World Space</code>: all coordinates relative to a global origin. </li> - <li><code>View Space</code>: all coordinates as viewed from a camera's perspective. </li> - <li><code>Clip Space</code>: all coordinates as viewed from the camera's perspective but with projection applied. This is the space the vertex coordinates should end up in, as output of the vertex shader. OpenGL does the rest (clipping/perspective division). </li> - <li><code>Screen Space</code>: all coordinates as viewed from the screen. Coordinates range from <code>0</code> to screen width/height. </li> - <li><code>LookAt</code>: a special type of view matrix that creates a coordinate system where all coordinates are rotated and translated in such a way that the user is looking at a given target from a given position. </li> - <li><code>Euler Angles</code>: defined as <code>yaw</code>, <code>pitch</code> and <code>roll</code> that allow us to form any 3D direction vector from these 3 values. </li> - </ul> -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/orig/Getting-started/Shaders.html b/orig/Getting-started/Shaders.html @@ -1,842 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Shaders</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Shaders</h1> -<h1 id="content-url" style='display:none;'>Getting-started/Shaders</h1> -<p> - As mentioned in the <a href="https://learnopengl.com/Getting-started/Hello-Triangle" target="_blank">Hello Triangle</a> chapter, shaders are little programs that rest on the GPU. These programs are run for each specific section of the graphics pipeline. In a basic sense, shaders are nothing more than programs transforming inputs to outputs. Shaders are also very isolated programs in that they're not allowed to communicate with each other; the only communication they have is via their inputs and outputs. -</p> - -<p> - In the previous chapter we briefly touched the surface of shaders and how to properly use them. We will now explain shaders, and specifically the OpenGL Shading Language, in a more general fashion. -</p> - -<h1>GLSL</h1> -<p> - Shaders are written in the C-like language GLSL. GLSL is tailored for use with graphics and contains useful features specifically targeted at vector and matrix manipulation. -</p> - -<p> - Shaders always begin with a version declaration, followed by a list of input and output variables, uniforms and its <fun>main</fun> function. Each shader's entry point is at its <fun>main</fun> function where we process any input variables and output the results in its output variables. Don't worry if you don't know what uniforms are, we'll get to those shortly. -</p> - -<p> - A shader typically has the following structure: -</p> - -<pre><code> -#version version_number -in type in_variable_name; -in type in_variable_name; - -out type out_variable_name; - -uniform type uniform_name; - -void main() -{ - // process input(s) and do some weird graphics stuff - ... - // output processed stuff to output variable - out_variable_name = weird_stuff_we_processed; -} -</code></pre> - -<p> - When we're talking specifically about the vertex shader each input variable is also known as a <def>vertex attribute</def>. There is a maximum number of vertex attributes we're allowed to declare limited by the hardware. OpenGL guarantees there are always at least 16 4-component vertex attributes available, but some hardware may allow for more which you can retrieve by querying <var>GL_MAX_VERTEX_ATTRIBS</var>: -</p> - -<pre><code> -int nrAttributes; -glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes); -std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl; -</code></pre> - -<p> - This often returns the minimum of <code>16</code> which should be more than enough for most purposes. -</p> - -<h2>Types</h2> -<p> - GLSL has, like any other programming language, data types for specifying what kind of variable we want to work with. GLSL has most of the default basic types we know from languages like C: <code>int</code>, <code>float</code>, <code>double</code>, <code>uint</code> and <code>bool</code>. GLSL also features two container types that we'll be using a lot, namely <code>vectors</code> and <code>matrices</code>. We'll discuss matrices in a later chapter. -</p> - -<h3>Vectors</h3> -<p> - A vector in GLSL is a 1,2,3 or 4 component container for any of the basic types just mentioned. They can take the following form (<code>n</code> represents the number of components): -</p> - - <ul> - <li><code>vecn</code>: the default vector of <code>n</code> floats.</li> - <li><code>bvecn</code>: a vector of <code>n</code> booleans.</li> - <li><code>ivecn</code>: a vector of <code>n</code> integers.</li> - <li><code>uvecn</code>: a vector of <code>n</code> unsigned integers.</li> - <li><code>dvecn</code>: a vector of <code>n</code> double components.</li> - </ul> - -<p> - Most of the time we will be using the basic <code>vecn</code> since floats are sufficient for most of our purposes. -</p> - - <p> - Components of a vector can be accessed via <code>vec.x</code> where <code>x</code> is the first component of the vector. You can use <code>.x</code>, <code>.y</code>, <code>.z</code> and <code>.w</code> to access their first, second, third and fourth component respectively. GLSL also allows you to use <code>rgba</code> for colors or <code>stpq</code> for texture coordinates, accessing the same components. - </p> - - <p> - The vector datatype allows for some interesting and flexible component selection called <def>swizzling</def>. Swizzling allows us to use syntax like this: - </p> - -<pre><code> -vec2 someVec; -vec4 differentVec = someVec.xyxx; -vec3 anotherVec = differentVec.zyw; -vec4 otherVec = someVec.xxxx + anotherVec.yxzy; -</code></pre> - -<p> - You can use any combination of up to 4 letters to create a new vector (of the same type) as long as the original vector has those components; it is not allowed to access the <code>.z</code> component of a <code>vec2</code> for example. We can also pass vectors as arguments to different vector constructor calls, reducing the number of arguments required: -</p> - -<pre><code> -vec2 vect = vec2(0.5, 0.7); -vec4 result = vec4(vect, 0.0, 0.0); -vec4 otherResult = vec4(result.xyz, 1.0); -</code></pre> - -<p> - Vectors are thus a flexible datatype that we can use for all kinds of input and output. Throughout the book you'll see plenty of examples of how we can creatively manage vectors. -</p> - -<h2>Ins and outs</h2> -<p> - Shaders are nice little programs on their own, but they are part of a whole and for that reason we want to have inputs and outputs on the individual shaders so that we can move stuff around. GLSL defined the <code>in</code> and <code>out</code> keywords specifically for that purpose. Each shader can specify inputs and outputs using those keywords and wherever an output variable matches with an input variable of the next shader stage they're passed along. The vertex and fragment shader differ a bit though. -</p> - -<p> - The vertex shader <strong>should</strong> receive some form of input otherwise it would be pretty ineffective. The vertex shader differs in its input, in that it receives its input straight from the vertex data. To define how the vertex data is organized we specify the input variables with location metadata so we can configure the vertex attributes on the CPU. We've seen this in the previous chapter as <code>layout (location = 0)</code>. The vertex shader thus requires an extra layout specification for its inputs so we can link it with the vertex data. - </p> - -<note> - It is also possible to omit the <code>layout (location = 0)</code> specifier and query for the attribute locations in your OpenGL code via <fun><function id='104'>glGetAttribLocation</function></fun>, but I'd prefer to set them in the vertex shader. It is easier to understand and saves you (and OpenGL) some work. - </note> - -<p> - The other exception is that the fragment shader requires a <code>vec4</code> color output variable, since the fragment shaders needs to generate a final output color. If you fail to specify an output color in your fragment shader, the color buffer output for those fragments will be undefined (which usually means OpenGL will render them either black or white). - </p> - -<p> - So if we want to send data from one shader to the other we'd have to declare an output in the sending shader and a similar input in the receiving shader. When the types and the names are equal on both sides OpenGL will link those variables together and then it is possible to send data between shaders (this is done when linking a program object). To show you how this works in practice we're going to alter the shaders from the previous chapter to let the vertex shader decide the color for the fragment shader. - </p> - -<strong>Vertex shader</strong> -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; // the position variable has attribute position 0 - -out vec4 vertexColor; // specify a color output to the fragment shader - -void main() -{ - gl_Position = vec4(aPos, 1.0); // see how we directly give a vec3 to vec4's constructor - vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // set the output variable to a dark-red color -} -</code></pre> - -<strong>Fragment shader</strong> -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec4 vertexColor; // the input variable from the vertex shader (same name and same type) - -void main() -{ - FragColor = vertexColor; -} -</code></pre> - -<p> - You can see we declared a <var>vertexColor</var> variable as a <code>vec4</code> output that we set in the vertex shader and we declare a similar <var>vertexColor</var> input in the fragment shader. Since they both have the same type and name, the <var>vertexColor</var> in the fragment shader is linked to the <var>vertexColor</var> in the vertex shader. Because we set the color to a dark-red color in the vertex shader, the resulting fragments should be dark-red as well. The following image shows the output: - </p> - - <img src="/img/getting-started/shaders.png" class="clean"/> - -<p> - There we go! We just managed to send a value from the vertex shader to the fragment shader. Let's spice it up a bit and see if we can send a color from our application to the fragment shader! - </p> - - <h2>Uniforms</h2> - <p> - <def>Uniforms</def> are another way to pass data from our application on the CPU to the shaders on the GPU. Uniforms are however slightly different compared to vertex attributes. First of all, uniforms are <def>global</def>. Global, meaning that a uniform variable is unique per shader program object, and can be accessed from any shader at any stage in the shader program. Second, whatever you set the uniform value to, uniforms will keep their values until they're either reset or updated. - </p> - - <p> - To declare a uniform in GLSL we simply add the <code>uniform</code> keyword to a shader with a type and a name. From that point on we can use the newly declared uniform in the shader. Let's see if this time we can set the color of the triangle via a uniform: - </p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -uniform vec4 ourColor; // we set this variable in the OpenGL code. - -void main() -{ - FragColor = ourColor; -} -</code></pre> - - <p> - We declared a uniform <code>vec4</code> <var>ourColor</var> in the fragment shader and set the fragment's output color to the content of this uniform value. Since uniforms are global variables, we can define them in any shader stage we'd like so no need to go through the vertex shader again to get something to the fragment shader. We're not using this uniform in the vertex shader so there's no need to define it there. - </p> - - <warning> - If you declare a uniform that isn't used anywhere in your GLSL code the compiler will silently remove the variable from the compiled version which is the cause for several frustrating errors; keep this in mind! - </warning> - - <p> - The uniform is currently empty; we haven't added any data to the uniform yet so let's try that. We first need to find the index/location of the uniform attribute in our shader. Once we have the index/location of the uniform, we can update its values. Instead of passing a single color to the fragment shader, let's spice things up by gradually changing color over time: - </p> - -<pre><code> -float timeValue = <function id='47'>glfwGetTime</function>(); -float greenValue = (sin(timeValue) / 2.0f) + 0.5f; -int vertexColorLocation = <function id='45'>glGetUniformLocation</function>(shaderProgram, "ourColor"); -<function id='28'>glUseProgram</function>(shaderProgram); -<function id='44'>glUniform</function>4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f); -</code></pre> - - <p> - First, we retrieve the running time in seconds via <fun><function id='47'>glfwGetTime</function>()</fun>. Then we vary the color in the range of <code>0.0</code> - <code>1.0</code> by using the <fun>sin</fun> function and store the result in <var>greenValue</var>. - </p> - - <p> - Then we query for the location of the <var>ourColor</var> uniform using <fun><function id='45'>glGetUniformLocation</function></fun>. We supply the shader program and the name of the uniform (that we want to retrieve the location from) to the query function. If <fun><function id='45'>glGetUniformLocation</function></fun> returns <code>-1</code>, it could not find the location. Lastly we can set the uniform value using the <fun><function id='44'>glUniform</function>4f</fun> function. Note that finding the uniform location does not require you to use the shader program first, but updating a uniform <strong>does</strong> require you to first use the program (by calling <fun><function id='28'>glUseProgram</function></fun>), because it sets the uniform on the currently active shader program. - </p> - -<note> -<p> - Because OpenGL is in its core a C library it does not have native support for function overloading, so wherever a function can be called with different types OpenGL defines new functions for each type required; <fun><function id='44'>glUniform</function></fun> is a perfect example of this. The function requires a specific postfix for the type of the uniform you want to set. A few of the possible postfixes are: - <ul> - <li><code>f</code>: the function expects a <code>float</code> as its value.</li> - <li><code>i</code>: the function expects an <code>int</code> as its value.</li> - <li><code>ui</code>: the function expects an <code>unsigned int</code> as its value.</li> - <li><code>3f</code>: the function expects 3 <code>float</code>s as its value.</li> - <li><code>fv</code>: the function expects a <code>float</code> vector/array as its value.</li> - </ul> - Whenever you want to configure an option of OpenGL simply pick the overloaded function that corresponds with your type. In our case we want to set 4 floats of the uniform individually so we pass our data via <fun><function id='44'>glUniform</function>4f</fun> (note that we also could've used the <code>fv</code> version). -</p> -</note> - - <p> - Now that we know how to set the values of uniform variables, we can use them for rendering. If we want the color to gradually change, we want to update this uniform every frame, otherwise the triangle would maintain a single solid color if we only set it once. So we calculate the <var>greenValue</var> and update the uniform each render iteration: - </p> - -<pre><code> -while(!<function id='14'>glfwWindowShouldClose</function>(window)) -{ - // input - processInput(window); - - // render - // clear the colorbuffer - <function id='13'><function id='10'>glClear</function>Color</function>(0.2f, 0.3f, 0.3f, 1.0f); - <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT); - - // be sure to activate the shader - <function id='28'>glUseProgram</function>(shaderProgram); - - // update the uniform color - float timeValue = <function id='47'>glfwGetTime</function>(); - float greenValue = sin(timeValue) / 2.0f + 0.5f; - int vertexColorLocation = <function id='45'>glGetUniformLocation</function>(shaderProgram, "ourColor"); - <function id='44'>glUniform</function>4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f); - - // now render the triangle - <function id='27'>glBindVertexArray</function>(VAO); - <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 3); - - // swap buffers and poll IO events - <function id='24'>glfwSwapBuffers</function>(window); - <function id='23'>glfwPollEvents</function>(); -} -</code></pre> - - <p> - The code is a relatively straightforward adaptation of the previous code. This time, we update a uniform value each frame before drawing the triangle. If you update the uniform correctly you should see the color of your triangle gradually change from green to black and back to green. - </p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/getting-started/shaders.mp4" type="video/mp4" /> - <img src="/img/getting-started/shaders2.png" class="clean"/> - </video> -</div> - - -<p> - Check out the source code <a href="/code_viewer_gh.php?code=src/1.getting_started/3.1.shaders_uniform/shaders_uniform.cpp" target="_blank">here</a> if you're stuck. - </p> - -<p> - As you can see, uniforms are a useful tool for setting attributes that may change every frame, or for interchanging data between your application and your shaders, but what if we want to set a color for each vertex? In that case we'd have to declare as many uniforms as we have vertices. A better solution would be to include more data in the vertex attributes which is what we're going to do now. - </p> - - <h2>More attributes!</h2> - <p> - We saw in the previous chapter how we can fill a VBO, configure vertex attribute pointers and store it all in a VAO. This time, we also want to add color data to the vertex data. We're going to add color data as 3 <code>float</code>s to the <var>vertices</var> array. We assign a red, green and blue color to each of the corners of our triangle respectively: - </p> - -<pre><code> -float vertices[] = { - // positions // colors - 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right - -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left - 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top -}; -</code></pre> - -<p> - Since we now have more data to send to the vertex shader, it is necessary to adjust the vertex shader to also receive our color value as a vertex attribute input. Note that we set the location of the <var>aColor</var> attribute to 1 with the layout specifier: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; // the position variable has attribute position 0 -layout (location = 1) in vec3 aColor; // the color variable has attribute position 1 - -out vec3 ourColor; // output a color to the fragment shader - -void main() -{ - gl_Position = vec4(aPos, 1.0); - ourColor = aColor; // set ourColor to the input color we got from the vertex data -} -</code></pre> - - <p> - Since we no longer use a uniform for the fragment's color, but now use the <var>ourColor</var> output variable we'll have to change the fragment shader as well: - </p> - -<pre><code> -#version 330 core -out vec4 FragColor; -in vec3 ourColor; - -void main() -{ - FragColor = vec4(ourColor, 1.0); -} -</code></pre> - -<p> - Because we added another vertex attribute and updated the VBO's memory we have to re-configure the vertex attribute pointers. The updated data in the VBO's memory now looks a bit like this: - </p> - - <img src="/img/getting-started/vertex_attribute_pointer_interleaved.png" class="clean" alt="Interleaved data of position and color within VBO to be configured wtih <function id='30'>glVertexAttribPointer</function>"/> - -<p> - Knowing the current layout we can update the vertex format with <fun><function id='30'>glVertexAttribPointer</function></fun>: -</p> - -<pre><code> -// position attribute -<function id='30'>glVertexAttribPointer</function>(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); -<function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(0); -// color attribute -<function id='30'>glVertexAttribPointer</function>(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float))); -<function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(1); -</code></pre> - -<p> - The first few arguments of <fun><function id='30'>glVertexAttribPointer</function></fun> are relatively straightforward. This time we are configuring the vertex attribute on attribute location <code>1</code>. The color values have a size of <code>3</code> <code>float</code>s and we do not normalize the values. - </p> - - <p> - Since we now have two vertex attributes we have to re-calculate the <em>stride</em> value. To get the next attribute value (e.g. the next <code>x</code> component of the position vector) in the data array we have to move <code>6</code> <code>float</code>s to the right, three for the position values and three for the color values. This gives us a stride value of 6 times the size of a <code>float</code> in bytes (= <code>24</code> bytes). <br/> - Also, this time we have to specify an offset. For each vertex, the position vertex attribute is first so we declare an offset of <code>0</code>. The color attribute starts after the position data so the offset is <code>3 * sizeof(float)</code> in bytes (= <code>12</code> bytes). -</p> - -<p> - Running the application should result in the following image: -</p> - - <img src="/img/getting-started/shaders3.png" class="clean"/> - - <p> - Check out the source code <a href="/code_viewer_gh.php?code=src/1.getting_started/3.2.shaders_interpolation/shaders_interpolation.cpp" target="_blank">here</a> if you're stuck. - </p> - - <p> - The image may not be exactly what you would expect, since we only supplied 3 colors, not the huge color palette we're seeing right now. This is all the result of something called <def>fragment interpolation</def> in the fragment shader. When rendering a triangle the rasterization stage usually results in a lot more fragments than vertices originally specified. The rasterizer then determines the positions of each of those fragments based on where they reside on the triangle shape.<br/> - Based on these positions, it <def>interpolates</def> all the fragment shader's input variables. Say for example we have a line where the upper point has a green color and the lower point a blue color. If the fragment shader is run at a fragment that resides around a position at <code>70%</code> of the line, its resulting color input attribute would then be a linear combination of green and blue; to be more precise: <code>30%</code> blue and <code>70%</code> green. - </p> - - <p> - This is exactly what happened at the triangle. We have 3 vertices and thus 3 colors, and judging from the triangle's pixels it probably contains around 50000 fragments, where the fragment shader interpolated the colors among those pixels. If you take a good look at the colors you'll see it all makes sense: red to blue first gets to purple and then to blue. Fragment interpolation is applied to all the fragment shader's input attributes. - </p> - -<h1>Our own shader class</h1> - <p> - Writing, compiling and managing shaders can be quite cumbersome. As a final touch on the shader subject we're going to make our life a bit easier by building a shader class that reads shaders from disk, compiles and links them, checks for errors and is easy to use. This also gives you a bit of an idea how we can encapsulate some of the knowledge we learned so far into useful abstract objects. - </p> - - <p> - We will create the shader class entirely in a header file, mainly for learning purposes and portability. Let's start by adding the required includes and by defining the class structure: - </p> - -<pre><code> -#ifndef SHADER_H -#define SHADER_H - -#include <glad/glad.h> // include glad to get all the required OpenGL headers - -#include <string> -#include <fstream> -#include <sstream> -#include <iostream> - - -class Shader -{ -public: - // the program ID - unsigned int ID; - - // constructor reads and builds the shader - Shader(const char* vertexPath, const char* fragmentPath); - // use/activate the shader - void use(); - // utility uniform functions - void setBool(const std::string &name, bool value) const; - void setInt(const std::string &name, int value) const; - void setFloat(const std::string &name, float value) const; -}; - -#endif -</code></pre> - - <note> - We used several <def>preprocessor directives</def> at the top of the header file. Using these little lines of code informs your compiler to only include and compile this header file if it hasn't been included yet, even if multiple files include the shader header. This prevents linking conflicts. - </note> - - <p> - The shader class holds the ID of the shader program. Its constructor requires the file paths of the source code of the vertex and fragment shader respectively that we can store on disk as simple text files. To add a little extra we also add several utility functions to ease our lives a little: <fun>use</fun> activates the shader program, and all <fun>set...</fun> functions query a uniform location and set its value. - </p> - -<h2>Reading from file</h2> -<p> - We're using C++ filestreams to read the content from the file into several <code>string</code> objects: -</p> - -<pre><code> -Shader(const char* vertexPath, const char* fragmentPath) -{ - // 1. retrieve the vertex/fragment source code from filePath - std::string vertexCode; - std::string fragmentCode; - std::ifstream vShaderFile; - std::ifstream fShaderFile; - // ensure ifstream objects can throw exceptions: - vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit); - fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit); - try - { - // open files - vShaderFile.open(vertexPath); - fShaderFile.open(fragmentPath); - std::stringstream vShaderStream, fShaderStream; - // read file's buffer contents into streams - vShaderStream << vShaderFile.rdbuf(); - fShaderStream << fShaderFile.rdbuf(); - // close file handlers - vShaderFile.close(); - fShaderFile.close(); - // convert stream into string - vertexCode = vShaderStream.str(); - fragmentCode = fShaderStream.str(); - } - catch(std::ifstream::failure e) - { - std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl; - } - const char* vShaderCode = vertexCode.c_str(); - const char* fShaderCode = fragmentCode.c_str(); - [...] -</code></pre> - - <p> - Next we need to compile and link the shaders. Note that we're also reviewing if compilation/linking failed and if so, print the compile-time errors. This is extremely useful when debugging (you are going to need those error logs eventually): - </p> - -<pre><code> -// 2. compile shaders -unsigned int vertex, fragment; -int success; -char infoLog[512]; - -// vertex Shader -vertex = <function id='37'>glCreateShader</function>(GL_VERTEX_SHADER); -<function id='42'>glShaderSource</function>(vertex, 1, &vShaderCode, NULL); -<function id='38'>glCompileShader</function>(vertex); -// print compile errors if any -<function id='39'>glGetShaderiv</function>(vertex, GL_COMPILE_STATUS, &success); -if(!success) -{ - <function id='40'>glGetShaderInfoLog</function>(vertex, 512, NULL, infoLog); - std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; -}; - -// similiar for Fragment Shader -[...] - -// shader Program -ID = <function id='36'>glCreateProgram</function>(); -<function id='34'>glAttachShader</function>(ID, vertex); -<function id='34'>glAttachShader</function>(ID, fragment); -<function id='35'>glLinkProgram</function>(ID); -// print linking errors if any -<function id='41'>glGetProgramiv</function>(ID, GL_LINK_STATUS, &success); -if(!success) -{ - glGetProgramInfoLog(ID, 512, NULL, infoLog); - std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl; -} - -// delete the shaders as they're linked into our program now and no longer necessary -<function id='46'>glDeleteShader</function>(vertex); -<function id='46'>glDeleteShader</function>(fragment); -</code></pre> - - <p> - The <fun>use</fun> function is straightforward: - </p> - -<pre><code> -void use() -{ - <function id='28'>glUseProgram</function>(ID); -} -</code></pre> - -<p> - Similarly for any of the uniform setter functions: -</p> - -<pre><code> -void setBool(const std::string &name, bool value) const -{ - <function id='44'>glUniform</function>1i(<function id='45'>glGetUniformLocation</function>(ID, name.c_str()), (int)value); -} -void setInt(const std::string &name, int value) const -{ - <function id='44'>glUniform</function>1i(<function id='45'>glGetUniformLocation</function>(ID, name.c_str()), value); -} -void setFloat(const std::string &name, float value) const -{ - <function id='44'>glUniform</function>1f(<function id='45'>glGetUniformLocation</function>(ID, name.c_str()), value); -} -</code></pre> - - <p> - And there we have it, a completed <a href="/code_viewer_gh.php?code=includes/learnopengl/shader_s.h" target="_blank">shader class</a>. Using the shader class is fairly easy; we create a shader object once and from that point on simply start using it: - </p> - -<pre><code> -Shader ourShader("path/to/shaders/shader.vs", "path/to/shaders/shader.fs"); -[...] -while(...) -{ - ourShader.use(); - ourShader.setFloat("someUniform", 1.0f); - DrawStuff(); -} -</code></pre> - -<p> - Here we stored the vertex and fragment shader source code in two files called <code>shader.vs</code> and <code>shader.fs</code>. You're free to name your shader files however you like; I personally find the extensions <code>.vs</code> and <code>.fs</code> quite intuitive. -</p> - - <p> - You can find the source code <a href="/code_viewer_gh.php?code=src/1.getting_started/3.3.shaders_class/shaders_class.cpp" target="_blank">here</a> using our newly created <a href="/code_viewer_gh.php?code=includes/learnopengl/shader_s.h" target="_blank">shader class</a>. Note that you can click the shader file paths to find the shaders' source code. - </p> - -<h1>Exercises</h1> - <ol> - <li>Adjust the vertex shader so that the triangle is upside down: <a href="/code_viewer_gh.php?code=src/1.getting_started/3.4.shaders_exercise1/shaders_exercise1.cpp" target="_blank">solution</a>.</li> - <li>Specify a horizontal offset via a uniform and move the triangle to the right side of the screen in the vertex shader using this offset value: <a href="/code_viewer_gh.php?code=src/1.getting_started/3.5.shaders_exercise2/shaders_exercise2.cpp" target="_blank">solution</a>.</li> - <li>Output the vertex position to the fragment shader using the <code>out</code> keyword and set the fragment's color equal to this vertex position (see how even the vertex position values are interpolated across the triangle). Once you managed to do this; try to answer the following question: why is the bottom-left side of our triangle black?: <a href="/code_viewer_gh.php?code=src/1.getting_started/3.6.shaders_exercise3/shaders_exercise3.cpp" target="_blank">solution</a>.</li> - </ol> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/orig/Getting-started/Textures.html b/orig/Getting-started/Textures.html @@ -1,802 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Textures</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Textures</h1> -<h1 id="content-url" style='display:none;'>Getting-started/Textures</h1> -<p> - We learned that to add more detail to our objects we can use colors for each vertex to create some interesting images. However, to get a fair bit of realism we'd have to have many vertices so we could specify a lot of colors. This takes up a considerable amount of extra overhead, since each model needs a lot more vertices and for each vertex a color attribute as well. -</p> -<p> - What artists and programmers generally prefer is to use a <def>texture</def>. A texture is a 2D image (even 1D and 3D textures exist) used to add detail to an object; think of a texture as a piece of paper with a nice brick image (for example) on it neatly folded over your 3D house so it looks like your house has a stone exterior. Because we can insert a lot of detail in a single image, we can give the illusion the object is extremely detailed without having to specify extra vertices. -</p> - -<note> - Next to images, textures can also be used to store a large collection of arbitrary data to send to the shaders, but we'll leave that for a different topic. -</note> - -<p> - Below you'll see a texture image of a <a href="/img/textures/wall.jpg" target="_blank">brick wall</a> mapped to the triangle from the previous chapter. -</p> - -<img src="/img/getting-started/textures.png" class="clean"/> - -<p> -In order to map a texture to the triangle we need to tell each vertex of the triangle which part of the texture it corresponds to. Each vertex should thus have a <def>texture coordinate</def> associated with them that specifies what part of the texture image to sample from. Fragment interpolation then does the rest for the other fragments. -</p> - -<p> - Texture coordinates range from <code>0</code> to <code>1</code> in the <code>x</code> and <code>y</code> axis (remember that we use 2D texture images). Retrieving the texture color using texture coordinates is called <def>sampling</def>. Texture coordinates start at <code>(0,0)</code> for the lower left corner of a texture image to <code>(1,1)</code> for the upper right corner of a texture image. The following image shows how we map texture coordinates to the triangle: -</p> - -<img src="/img/getting-started/tex_coords.png"/> - -<p> - We specify 3 texture coordinate points for the triangle. We want the bottom-left side of the triangle to correspond with the bottom-left side of the texture so we use the <code>(0,0)</code> texture coordinate for the triangle's bottom-left vertex. The same applies to the bottom-right side with a <code>(1,0)</code> texture coordinate. The top of the triangle should correspond with the top-center of the texture image so we take <code>(0.5,1.0)</code> as its texture coordinate. We only have to pass 3 texture coordinates to the vertex shader, which then passes those to the fragment shader that neatly interpolates all the texture coordinates for each fragment. -</p> - -<p> - The resulting texture coordinates would then look like this: -</p> - -<pre><code> -float texCoords[] = { - 0.0f, 0.0f, // lower-left corner - 1.0f, 0.0f, // lower-right corner - 0.5f, 1.0f // top-center corner -}; -</code></pre> - -<p> - Texture sampling has a loose interpretation and can be done in many different ways. It is thus our job to tell OpenGL how it should <em>sample</em> its textures. -</p> - -<h2>Texture Wrapping</h2> -<p> - Texture coordinates usually range from <code>(0,0)</code> to <code>(1,1)</code> but what happens if we specify coordinates outside this range? The default behavior of OpenGL is to repeat the texture images (we basically ignore the integer part of the floating point texture coordinate), but there are more options OpenGL offers: -</p> - - <ul> - <li><var>GL_REPEAT</var>: The default behavior for textures. Repeats the texture image.</li> - <li><var>GL_MIRRORED_REPEAT</var>: Same as <var>GL_REPEAT</var> but mirrors the image with each repeat.</li> - <li><var>GL_CLAMP_TO_EDGE</var>: Clamps the coordinates between <code>0</code> and <code>1</code>. The result is that higher coordinates become clamped to the edge, resulting in a stretched edge pattern.</li> - <li><var>GL_CLAMP_TO_BORDER</var>: Coordinates outside the range are now given a user-specified border color.</li> - </ul> - -<p> - Each of the options have a different visual output when using texture coordinates outside the default range. Let's see what these look like on a sample texture image (original image by Hólger Rezende): -</p> - -<img src="/img/getting-started/texture_wrapping.png" class="clean"/> - -<p> - Each of the aforementioned options can be set per coordinate axis (<code>s</code>, <code>t</code> (and <code>r</code> if you're using 3D textures) equivalent to <code>x</code>,<code>y</code>,<code>z</code>) with the <fun><function id='15'>glTexParameter</function>*</fun> function: -</p> - -<pre><code> -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); -</code></pre> - -<p> - The first argument specifies the texture target; we're working with 2D textures so the texture target is <var>GL_TEXTURE_2D</var>. The second argument requires us to tell what option we want to set and for which texture axis; we want to configure it for both the <code>S</code> and <code>T</code> axis. The last argument requires us to pass in the texture wrapping mode we'd like and in this case OpenGL will set its texture wrapping option on the currently active texture with <var>GL_MIRRORED_REPEAT</var>. -</p> - -<p> - If we choose the <var>GL_CLAMP_TO_BORDER</var> option we should also specify a border color. This is done using the <code>fv</code> equivalent of the <fun><function id='15'>glTexParameter</function></fun> function with <var>GL_TEXTURE_BORDER_COLOR</var> as its option where we pass in a float array of the border's color value: -</p> - -<pre><code> -float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f }; -<function id='15'>glTexParameter</function>fv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); -</code></pre> - -<h2>Texture Filtering</h2> -<p> - Texture coordinates do not depend on resolution but can be any floating point value, thus OpenGL has to figure out which texture pixel (also known as a <def>texel</def> ) to map the texture coordinate to. This becomes especially important if you have a very large object and a low resolution texture. You probably guessed by now that OpenGL has options for this <def>texture filtering</def> as well. There are several options available but for now we'll discuss the most important options: <var>GL_NEAREST</var> and <var>GL_LINEAR</var>. -</p> - -<p> - <var>GL_NEAREST</var> (also known as <def>nearest neighbor</def> or <def>point</def> filtering) is the default texture filtering method of OpenGL. When set to <var>GL_NEAREST</var>, OpenGL selects the texel that center is closest to the texture coordinate. Below you can see 4 pixels where the cross represents the exact texture coordinate. The upper-left texel has its center closest to the texture coordinate and is therefore chosen as the sampled color: -</p> - - <img src="/img/getting-started/filter_nearest.png" class="clean"/> - -<p> - <var>GL_LINEAR</var> (also known as <def>(bi)linear filtering</def>) takes an interpolated value from the texture coordinate's neighboring texels, approximating a color between the texels. The smaller the distance from the texture coordinate to a texel's center, the more that texel's color contributes to the sampled color. Below we can see that a mixed color of the neighboring pixels is returned: -</p> - -<img src="/img/getting-started/filter_linear.png" class="clean"/> - -<p> - But what is the visual effect of such a texture filtering method? Let's see how these methods work when using a texture with a low resolution on a large object (texture is therefore scaled upwards and individual texels are noticeable): -</p> - - <img src="/img/getting-started/texture_filtering.png" class="clean"/> - -<p> - <var>GL_NEAREST</var> results in blocked patterns where we can clearly see the pixels that form the texture while <var>GL_LINEAR</var> produces a smoother pattern where the individual pixels are less visible. <var>GL_LINEAR</var> produces a more realistic output, but some developers prefer a more 8-bit look and as a result pick the <var>GL_NEAREST</var> option. - </p> - -<p> - Texture filtering can be set for <def>magnifying</def> and <def>minifying</def> operations (when scaling up or downwards) so you could for example use nearest neighbor filtering when textures are scaled downwards and linear filtering for upscaled textures. We thus have to specify the filtering method for both options via <fun><function id='15'>glTexParameter</function>*</fun>. The code should look similar to setting the wrapping method: -</p> - -<pre><code> -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -</code></pre> - -<h3>Mipmaps</h3> -<p> - Imagine we had a large room with thousands of objects, each with an attached texture. There will be objects far away that have the same high resolution texture attached as the objects close to the viewer. Since the objects are far away and probably only produce a few fragments, OpenGL has difficulties retrieving the right color value for its fragment from the high resolution texture, since it has to pick a texture color for a fragment that spans a large part of the texture. This will produce visible artifacts on small objects, not to mention the waste of memory bandwidth using high resolution textures on small objects. -</p> - -<p> - To solve this issue OpenGL uses a concept called <def>mipmaps</def> that is basically a collection of texture images where each subsequent texture is twice as small compared to the previous one. The idea behind mipmaps should be easy to understand: after a certain distance threshold from the viewer, OpenGL will use a different mipmap texture that best suits the distance to the object. Because the object is far away, the smaller resolution will not be noticeable to the user. OpenGL is then able to sample the correct texels, and there's less cache memory involved when sampling that part of the mipmaps. Let's take a closer look at what a mipmapped texture looks like: -</p> - -<img src="/img/getting-started/mipmaps.png" class="clean"/> - -<p> - Creating a collection of mipmapped textures for each texture image is cumbersome to do manually, but luckily OpenGL is able to do all the work for us with a single call to <fun><function id='51'>glGenerateMipmap</function>s</fun> after we've created a texture. -</p> - -<p> - When switching between mipmaps levels during rendering OpenGL may show some artifacts like sharp edges visible between the two mipmap layers. Just like normal texture filtering, it is also possible to filter between mipmap levels using <var>NEAREST</var> and <var>LINEAR</var> filtering for switching between mipmap levels. To specify the filtering method between mipmap levels we can replace the original filtering methods with one of the following four options: -</p> - - <ul> - <li><var>GL_NEAREST_MIPMAP_NEAREST</var>: takes the nearest mipmap to match the pixel size and uses nearest neighbor interpolation for texture sampling.</li> - <li><var>GL_LINEAR_MIPMAP_NEAREST</var>: takes the nearest mipmap level and samples that level using linear interpolation. </li> - <li><var>GL_NEAREST_MIPMAP_LINEAR</var>: linearly interpolates between the two mipmaps that most closely match the size of a pixel and samples the interpolated level via nearest neighbor interpolation. </li> - <li><var>GL_LINEAR_MIPMAP_LINEAR</var>: linearly interpolates between the two closest mipmaps and samples the interpolated level via linear interpolation.</li> - </ul> - -<p> - Just like texture filtering we can set the filtering method to one of the 4 aforementioned methods using <fun><function id='15'>glTexParameter</function>i</fun>: -</p> - -<pre><code> -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -</code></pre> - -<p> - A common mistake is to set one of the mipmap filtering options as the magnification filter. This doesn't have any effect since mipmaps are primarily used for when textures get downscaled: texture magnification doesn't use mipmaps and giving it a mipmap filtering option will generate an OpenGL <var>GL_INVALID_ENUM</var> error code. -</p> - -<h1>Loading and creating textures</h1> -<p> - The first thing we need to do to actually use textures is to load them into our application. - Texture images can be stored in dozens of file formats, each with their own structure and ordering of data, so how do we get those images in our application? One solution would be to choose a file format we'd like to use, say <code>.PNG</code> and write our own image loader to convert the image format into a large array of bytes. While it's not very hard to write your own image loader, it's still cumbersome and what if you want to support more file formats? You'd then have to write an image loader for each format you want to support. -</p> - -<p> - Another solution, and probably a good one, is to use an image-loading library that supports several popular formats and does all the hard work for us. A library like <code>stb_image.h</code>. -</p> - -<h2>stb_image.h</h2> -<p> - <code>stb_image.h</code> is a very popular single header image loading library by <a href="https://github.com/nothings" target="_blank">Sean Barrett</a> that is able to load most popular file formats and is easy to integrate in your project(s). <code>stb_image.h</code> can be downloaded from <a href="https://github.com/nothings/stb/blob/master/stb_image.h" target="_blank">here</a>. Simply download the single header file, add it to your project as <code>stb_image.h</code>, and create an additional C++ file with the following code: -</p> - -<pre><code> -#define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" -</code></pre> - -<p> - By defining <var>STB_IMAGE_IMPLEMENTATION</var> the preprocessor modifies the header file such that it only contains the relevant definition source code, effectively turning the header file into a <code>.cpp</code> file, and that's about it. Now simply include <code>stb_image.h</code> somewhere in your program and compile. -</p> - -<p> - For the following texture sections we're going to use an image of a <a href="/img/textures/container.jpg" target="_blank">wooden container</a>. - To load an image using <code>stb_image.h</code> we use its <fun>stbi_load</fun> function: -</p> - -<pre><code> -int width, height, nrChannels; -unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0); -</code></pre> - -<p> - The function first takes as input the location of an image file. It then expects you to give three <code>ints</code> as its second, third and fourth argument that <code>stb_image.h</code> will fill with the resulting image's <em>width</em>, <em>height</em> and <em>number</em> of color channels. We need the image's width and height for generating textures later on. <!--The last argument allows us to force a number of channels. Let's say the image has 4 channels (RGBA) and we only want to load the 3 color channels (RGB) without alpha, we set its last argument to <code>3</code>. --> -</p> - -<h2>Generating a texture</h2> -<p> - Like any of the previous objects in OpenGL, textures are referenced with an ID; let's create one: -</p> - -<pre class="cpp"><code> -unsigned int texture; -<function id='50'>glGenTextures</function>(1, &texture); -</code></pre> - -<p> - The <fun><function id='50'>glGenTextures</function></fun> function first takes as input how many textures we want to generate and stores them in a <code>unsigned int</code> array given as its second argument (in our case just a single <code>unsigned int</code>). Just like other objects we need to bind it so any subsequent texture commands will configure the currently bound texture: -</p> - -<pre><code> -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, texture); -</code></pre> - -<p> - Now that the texture is bound, we can start generating a texture using the previously loaded image data. Textures are generated with <fun><function id='52'>glTexImage2D</function></fun>: -</p> - -<pre class="cpp"><code> -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); -<function id='51'>glGenerateMipmap</function>(GL_TEXTURE_2D); -</code></pre> - -<p> - This is a large function with quite a few parameters so we'll walk through them step-by-step: - <ul> - <li>The first argument specifies the texture target; setting this to <var>GL_TEXTURE_2D</var> means this operation will generate a texture on the currently bound texture object at the same target (so any textures bound to targets <var>GL_TEXTURE_1D</var> or <var>GL_TEXTURE_3D</var> will not be affected).</li> - <li>The second argument specifies the mipmap level for which we want to create a texture for if you want to set each mipmap level manually, but we'll leave it at the base level which is <code>0</code>.</li> - <li>The third argument tells OpenGL in what kind of format we want to store the texture. Our image has only <code>RGB</code> values so we'll store the texture with <code>RGB</code> values as well.</li> - <li>The 4th and 5th argument sets the width and height of the resulting texture. We stored those earlier when loading the image so we'll use the corresponding variables.</li> - <li>The next argument should always be <code>0</code> (some legacy stuff).</li> - <li>The 7th and 8th argument specify the format and datatype of the source image. We loaded the image with <code>RGB</code> values and stored them as <code>char</code>s (bytes) so we'll pass in the corresponding values.</li> - <li>The last argument is the actual image data.</li> - </ul> -</p> - -<p> - Once <fun><function id='52'>glTexImage2D</function></fun> is called, the currently bound texture object now has the texture image attached to it. However, currently it only has the base-level of the texture image loaded and if we want to use mipmaps we have to specify all the different images manually (by continually incrementing the second argument) or, we could call <fun><function id='51'>glGenerateMipmap</function></fun> after generating the texture. This will automatically generate all the required mipmaps for the currently bound texture. -</p> - -<p> - After we're done generating the texture and its corresponding mipmaps, it is good practice to free the image memory: -</p> - -<pre class="cpp"><code> -stbi_image_free(data); -</code></pre> - -<p> - The whole process of generating a texture thus looks something like this: -</p> - -<pre><code> -unsigned int texture; -<function id='50'>glGenTextures</function>(1, &texture); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, texture); -// set the texture wrapping/filtering options (on the currently bound texture object) -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -// load and generate the texture -int width, height, nrChannels; -unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0); -if (data) -{ - <function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); - <function id='51'>glGenerateMipmap</function>(GL_TEXTURE_2D); -} -else -{ - std::cout << "Failed to load texture" << std::endl; -} -stbi_image_free(data); -</code></pre> - -<h2>Applying textures</h2> -<p> - For the upcoming sections we will use the rectangle shape drawn with <fun><function id='2'>glDrawElements</function></fun> from the final part of the <a href="https://learnopengl.com/Getting-started/Hello-Triangle" target="_blank">Hello Triangle</a> chapter. - We need to inform OpenGL how to sample the texture so we'll have to update the vertex data with the texture coordinates: -</p> - -<pre><code> -float vertices[] = { - // positions // colors // texture coords - 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right - 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right - -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left - -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left -}; -</code></pre> - -<p> - Since we've added an extra vertex attribute we again have to notify OpenGL of the new vertex format: -</p> - -<img src="/img/getting-started/vertex_attribute_pointer_interleaved_textures.png" class="clean" alt="Image of VBO with interleaved position, color and texture data with strides and offsets shown for configuring vertex attribute pointers."/> - -<pre><code> -<function id='30'>glVertexAttribPointer</function>(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); -<function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(2); -</code></pre> - -<p> - Note that we have to adjust the stride parameter of the previous two vertex attributes to <code>8 * sizeof(float)</code> as well. -</p> - -<p> - Next we need to alter the vertex shader to accept the texture coordinates as a vertex attribute and then forward the coordinates to the fragment shader: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec3 aColor; -layout (location = 2) in vec2 aTexCoord; - -out vec3 ourColor; -out vec2 TexCoord; - -void main() -{ - gl_Position = vec4(aPos, 1.0); - ourColor = aColor; - TexCoord = aTexCoord; -} -</code></pre> - -<p> - The fragment shader should then accept the <code>TexCoord</code> output variable as an input variable. -</p> - -<p> - The fragment shader should also have access to the texture object, but how do we pass the texture object to the fragment shader? GLSL has a built-in data-type for texture objects called a <def>sampler</def> that takes as a postfix the texture type we want e.g. <code>sampler1D</code>, <code>sampler3D</code> or in our case <code>sampler2D</code>. We can then add a texture to the fragment shader by simply declaring a <code>uniform sampler2D</code> that we later assign our texture to. -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec3 ourColor; -in vec2 TexCoord; - -uniform sampler2D ourTexture; - -void main() -{ - FragColor = texture(ourTexture, TexCoord); -} -</code></pre> - -<p> - To sample the color of a texture we use GLSL's built-in <fun>texture</fun> function that takes as its first argument a texture sampler and as its second argument the corresponding texture coordinates. The <fun>texture</fun> function then samples the corresponding color value using the texture parameters we set earlier. The output of this fragment shader is then the (filtered) color of the texture at the (interpolated) texture coordinate. -</p> - -<p> - All that's left to do now is to bind the texture before calling <fun><function id='2'>glDrawElements</function></fun> and it will then automatically assign the texture to the fragment shader's sampler: -</p> - -<pre class="cpp"><code> -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, texture); -<function id='27'>glBindVertexArray</function>(VAO); -<function id='2'>glDrawElements</function>(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); -</code></pre> - -<p> - If you did everything right you should see the following image: -</p> - -<img src="/img/getting-started/textures2.png" class="clean"/> - -<p> - If your rectangle is completely white or black you probably made an error along the way. Check your shader logs and try to compare your code with the application's <a href="/code_viewer_gh.php?code=src/1.getting_started/4.1.textures/textures.cpp" target="_blank">source code</a>. -</p> - -<warning> - If your texture code doesn't work or shows up as completely black, continue reading and work your way to the last example that <strong>should</strong> work. On some drivers it is <strong>required</strong> to assign a texture unit to each sampler uniform, which is something we'll discuss further in this chapter. -</warning> - -<p> - To get a little funky we can also mix the resulting texture color with the vertex colors. We simply multiply the resulting texture color with the vertex color in the fragment shader to mix both colors: -</p> - -<pre><code> -FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0); -</code></pre> - -<p> - The result should be a mixture of the vertex's color and the texture's color: -</p> - -<img src="/img/getting-started/textures_funky.png" class="clean"/> - -<p> - I guess you could say our container likes to disco. -</p> - -<h2>Texture Units</h2> -<p> - You probably wondered why the <code>sampler2D</code> variable is a uniform if we didn't even assign it some value with <fun><function id='44'>glUniform</function></fun>. Using <fun><function id='44'>glUniform</function>1i</fun> we can actually assign a <em>location</em> value to the texture sampler so we can set multiple textures at once in a fragment shader. This location of a texture is more commonly known as a <def>texture unit</def>. The default texture unit for a texture is <code>0</code> which is the default active texture unit so we didn't need to assign a location in the previous section; note that not all graphics drivers assign a default texture unit so the previous section may not have rendered for you. -</p> - -<p> - The main purpose of texture units is to allow us to use more than 1 texture in our shaders. By assigning texture units to the samplers, we can bind to multiple textures at once as long as we activate the corresponding texture unit first. Just like <fun><function id='48'>glBindTexture</function></fun> we can activate texture units using <fun><function id='49'>glActiveTexture</function></fun> passing in the texture unit we'd like to use: -</p> - -<pre class="cpp"><code> -<function id='49'>glActiveTexture</function>(GL_TEXTURE0); // activate the texture unit first before binding texture -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, texture); -</code></pre> - -<p> - After activating a texture unit, a subsequent <fun><function id='48'>glBindTexture</function></fun> call will bind that texture to the currently active texture unit. Texture unit <var>GL_TEXTURE0</var> is always by default activated, so we didn't have to activate any texture units in the previous example when using <fun><function id='48'>glBindTexture</function></fun>. -</p> - -<note> - OpenGL should have a at least a minimum of 16 texture units for you to use which you can activate using <var>GL_TEXTURE0</var> to <var>GL_TEXTURE15</var>. They are defined in order so we could also get <var>GL_TEXTURE8</var> via <var>GL_TEXTURE0 + 8</var> for example, which is useful when we'd have to loop over several texture units. -</note> - -<p> - We still however need to edit the fragment shader to accept another sampler. This should be relatively straightforward now: -</p> - -<pre><code> -#version 330 core -... - -uniform sampler2D texture1; -uniform sampler2D texture2; - -void main() -{ - FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2); -} -</code></pre> - -<p> - The final output color is now the combination of two texture lookups. GLSL's built-in <fun>mix</fun> function takes two values as input and linearly interpolates between them based on its third argument. If the third value is <code>0.0</code> it returns the first input; if it's <code>1.0</code> it returns the second input value. A value of <code>0.2</code> will return <code>80%</code> of the first input color and <code>20%</code> of the second input color, resulting in a mixture of both our textures. -</p> - -<p> - We now want to load and create another texture; you should be familiar with the steps now. Make sure to create another texture object, load the image and generate the final texture using <fun><function id='52'>glTexImage2D</function></fun>. For the second texture we'll use an image of your <a href="/img/textures/awesomeface.png" target="_blank">facial expression while learning OpenGL</a>: -</p> - -<pre><code> -unsigned char *data = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0); -if (data) -{ - <function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - <function id='51'>glGenerateMipmap</function>(GL_TEXTURE_2D); -} -</code></pre> - -<p> - Note that we now load a <code>.png</code> image that includes an alpha (transparency) channel. This means we now need to specify that the image data contains an alpha channel as well by using <var>GL_RGBA</var>; otherwise OpenGL will incorrectly interpret the image data. -</p> - -<p> - To use the second texture (and the first texture) we'd have to change the rendering procedure a bit by binding both textures to the corresponding texture unit: -</p> - -<pre><code> -<function id='49'>glActiveTexture</function>(GL_TEXTURE0); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, texture1); -<function id='49'>glActiveTexture</function>(GL_TEXTURE1); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, texture2); - -<function id='27'>glBindVertexArray</function>(VAO); -<function id='2'>glDrawElements</function>(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); -</code></pre> - -<p> - We also have to tell OpenGL to which texture unit each shader sampler belongs to by setting each sampler using <fun><function id='44'>glUniform</function>1i</fun>. We only have to set this once, so we can do this before we enter the render loop: -</p> - -<pre><code> -ourShader.use(); // don't forget to activate the shader before setting uniforms! -<function id='44'>glUniform</function>1i(<function id='45'>glGetUniformLocation</function>(ourShader.ID, "texture1"), 0); // set it manually -ourShader.setInt("texture2", 1); // or with shader class - -while(...) -{ - [...] -} -</code></pre> - -<p> - By setting the samplers via <fun><function id='44'>glUniform</function>1i</fun> we make sure each uniform sampler corresponds to the proper texture unit. You should get the following result: -</p> - -<img src="/img/getting-started/textures_combined.png" class="clean"/> - -<p> - You probably noticed that the texture is flipped upside-down! This happens because OpenGL expects the <code>0.0</code> coordinate on the y-axis to be on the bottom side of the image, but images usually have <code>0.0</code> at the top of the y-axis. Luckily for us, <code>stb_image.h</code> can flip the y-axis during image loading by adding the following statement before loading any image: - </p> - -<pre><code> -stbi_set_flip_vertically_on_load(true); -</code></pre> - -<p> - After telling <code>stb_image.h</code> to flip the y-axis when loading images you should get the following result: -</p> - -<img src="/img/getting-started/textures_combined2.png" class="clean"/> - -<p> - If you see one happy container, you did things right. You can compare it with the <a href="/code_viewer_gh.php?code=src/1.getting_started/4.2.textures_combined/textures_combined.cpp" target="_blank">source code</a>. -</p> - -<h2>Exercises</h2> -<p> - To get more comfortable with textures it is advised to work through these exercises before continuing. - <ul> - <li>Make sure <strong>only</strong> the happy face looks in the other/reverse direction by changing the fragment shader: <a href="/code_viewer_gh.php?code=src/1.getting_started/4.3.textures_exercise1/textures_exercise1.cpp" target="_blank">solution</a>.</li> - <li>Experiment with the different texture wrapping methods by specifying texture coordinates in the range <code>0.0f</code> to <code>2.0f</code> instead of <code>0.0f</code> to <code>1.0f</code>. See if you can display 4 smiley faces on a single container image clamped at its edge: <a href="/code_viewer_gh.php?code=src/1.getting_started/4.4.textures_exercise2/textures_exercise2.cpp" target="_blank">solution</a>, <a href="/img/getting-started/textures_exercise2.png" target="_blank">result</a>. See if you can experiment with other wrapping methods as well.</li> - <li>Try to display only the center pixels of the texture image on the rectangle in such a way that the individual pixels are getting visible by changing the texture coordinates. Try to set the texture filtering method to <var>GL_NEAREST</var> to see the pixels more clearly: <a href="/code_viewer_gh.php?code=src/1.getting_started/4.5.textures_exercise3/textures_exercise3.cpp" target="_blank">solution</a>.</li> - <li>Use a uniform variable as the <fun>mix</fun> function's third parameter to vary the amount the two textures are visible. Use the up and down arrow keys to change how much the container or the smiley face is visible: <a href="/code_viewer_gh.php?code=src/1.getting_started/4.6.textures_exercise4/textures_exercise4.cpp" target="_blank">solution</a>.</li> - </ul> -</p> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/orig/Getting-started/Transformations.html b/orig/Getting-started/Transformations.html @@ -1,837 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Transformations</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Transformations</h1> -<h1 id="content-url" style='display:none;'>Getting-started/Transformations</h1> -<p> - We now know how to create objects, color them and/or give them a detailed appearance using textures, but they're still not that interesting since they're all static objects. We could try and make them move by changing their vertices and re-configuring their buffers each frame, but that's cumbersome and costs quite some processing power. There are much better ways to <def>transform</def> an object and that's by using (multiple) <def>matrix</def> objects. This doesn't mean we're going to talk about Kung Fu and a large digital artificial world. -</p> - -<p> - Matrices are very powerful mathematical constructs that seem scary at first, but once you'll grow accustomed to them they'll prove extremely useful. When discussing matrices, we'll have to make a small dive into some mathematics and for the more mathematically inclined readers I'll post additional resources for further reading. -</p> - -<p> - However, to fully understand transformations we first have to delve a bit deeper into vectors before discussing matrices. The focus of this chapter is to give you a basic mathematical background in topics we will require later on. If the subjects are difficult, try to understand them as much as you can and come back to this chapter later to review the concepts whenever you need them. -</p> - -<h1>Vectors</h1> -<p> - In its most basic definition, vectors are directions and nothing more. A vector has a <def>direction</def> and a <def>magnitude</def> (also known as its strength or length). You can think of vectors like directions on a treasure map: 'go left 10 steps, now go north 3 steps and go right 5 steps'; here 'left' is the direction and '10 steps' is the magnitude of the vector. The directions for the treasure map thus contains 3 vectors. Vectors can have any dimension, but we usually work with dimensions of 2 to 4. If a vector has 2 dimensions it represents a direction on a plane (think of 2D graphs) and when it has 3 dimensions it can represent any direction in a 3D world. -</p> - -<p> - Below you'll see 3 vectors where each vector is represented with <code>(x,y)</code> as arrows in a 2D graph. Because it is more intuitive to display vectors in 2D (rather than 3D) you can think of the 2D vectors as 3D vectors with a <code>z</code> coordinate of <code>0</code>. Since vectors represent directions, the origin of the vector does not change its value. In the graph below we can see that the vectors \(\color{red}{\bar{v}}\) and \(\color{blue}{\bar{w}}\) are equal even though their origin is different: -</p> - -<img src="/img/getting-started/vectors.png" class="clean" /> - -<p> - When describing vectors mathematicians generally prefer to describe vectors as character symbols with a little bar over their head like \(\bar{v}\). Also, when displaying vectors in formulas they are generally displayed as follows: - - \[\bar{v} = \begin{pmatrix} \color{red}x \\ \color{green}y \\ \color{blue}z \end{pmatrix} \] -</p> - -<p> - Because vectors are specified as directions it is sometimes hard to visualize them as positions. If we want to visualize vectors as positions we can imagine the origin of the direction vector to be <code>(0,0,0)</code> and then point towards a certain direction that specifies the point, making it a <def>position vector</def> (we could also specify a different origin and then say: 'this vector points to that point in space from this origin'). The position vector <code>(3,5)</code> would then point to <code>(3,5)</code> on the graph with an origin of <code>(0,0)</code>. Using vectors we can thus describe directions <strong>and</strong> positions in 2D and 3D space. -</p> - -<p> - Just like with normal numbers we can also define several operations on vectors (some of which you've already seen). -</p> - -<h2>Scalar vector operations</h2> -<p> - A <def>scalar</def> is a single digit. When adding/subtracting/multiplying or dividing a vector with a scalar we simply add/subtract/multiply or divide each element of the vector by the scalar. For addition it would look like this: - - \[ \begin{pmatrix} \color{red}1 \\ \color{green}2 \\ \color{blue}3 \end{pmatrix} + x \rightarrow \begin{pmatrix} \color{red}1 \\ \color{green}2 \\ \color{blue}3 \end{pmatrix} + \begin{pmatrix} x \\ x \\ x \end{pmatrix} = \begin{pmatrix} \color{red}1 + x \\ \color{green}2 + x \\ \color{blue}3 + x \end{pmatrix} \] - - Where \(+\) can be \(+\),\(-\),\(\cdot\) or \(\div\) where \(\cdot\) is the multiplication operator. -</p> - -<h2>Vector negation</h2> -<p> - Negating a vector results in a vector in the reversed direction. A vector pointing north-east would point south-west after negation. To negate a vector we add a minus-sign to each component (you can also represent it as a scalar-vector multiplication with a scalar value of <code>-1</code>): - - \[-\bar{v} = -\begin{pmatrix} \color{red}{v_x} \\ \color{blue}{v_y} \\ \color{green}{v_z} \end{pmatrix} = \begin{pmatrix} -\color{red}{v_x} \\ -\color{blue}{v_y} \\ -\color{green}{v_z} \end{pmatrix} \] -</p> - -<h2>Addition and subtraction</h2> -<p> - Addition of two vectors is defined as <def>component-wise</def> addition, that is each component of one vector is added to the same component of the other vector like so: - - \[\bar{v} = \begin{pmatrix} \color{red}1 \\ \color{green}2 \\ \color{blue}3 \end{pmatrix}, \bar{k} = \begin{pmatrix} \color{red}4 \\ \color{green}5 \\ \color{blue}6 \end{pmatrix} \rightarrow \bar{v} + \bar{k} = \begin{pmatrix} \color{red}1 + \color{red}4 \\ \color{green}2 + \color{green}5 \\ \color{blue}3 + \color{blue}6 \end{pmatrix} = \begin{pmatrix} \color{red}5 \\ \color{green}7 \\ \color{blue}9 \end{pmatrix} \] - - Visually, it looks like this on vectors <code>v=(4,2)</code> and <code>k=(1,2)</code>, where the second vector is added on top of the first vector's end to find the end point of the resulting vector (head-to-tail method): -</p> - - <img src="/img/getting-started/vectors_addition.png" class="clean"/> - -<p> - Just like normal addition and subtraction, vector subtraction is the same as addition with a negated second vector: - - \[\bar{v} = \begin{pmatrix} \color{red}{1} \\ \color{green}{2} \\ \color{blue}{3} \end{pmatrix}, \bar{k} = \begin{pmatrix} \color{red}{4} \\ \color{green}{5} \\ \color{blue}{6} \end{pmatrix} \rightarrow \bar{v} + -\bar{k} = \begin{pmatrix} \color{red}{1} + (-\color{red}{4}) \\ \color{green}{2} + (-\color{green}{5}) \\ \color{blue}{3} + (-\color{blue}{6}) \end{pmatrix} = \begin{pmatrix} -\color{red}{3} \\ -\color{green}{3} \\ -\color{blue}{3} \end{pmatrix} \] - -</p> - -<p> - Subtracting two vectors from each other results in a vector that's the difference of the positions both vectors are pointing at. This proves useful in certain cases where we need to retrieve a vector that's the difference between two points. -</p> - -<img src="/img/getting-started/vectors_subtraction.png" class="clean"/> - - -<h2>Length</h2> -<p> - To retrieve the length/magnitude of a vector we use the <def>Pythagoras theorem</def> that you may remember from your math classes. A vector forms a triangle when you visualize its individual <code>x</code> and <code>y</code> component as two sides of a triangle: -</p> - -<img src="/img/getting-started/vectors_triangle.png" class="clean"/> - -<p> - Since the length of the two sides <code>(x, y)</code> are known and we want to know the length of the tilted side \(\color{red}{\bar{v}}\) we can calculate it using the Pythagoras theorem as: - - \[||\color{red}{\bar{v}}|| = \sqrt{\color{green}x^2 + \color{blue}y^2} \] - - Where \(||\color{red}{\bar{v}}||\) is denoted as <em>the length of vector \(\color{red}{\bar{v}}\)</em>. This is easily extended to 3D by adding \(z^2\) to the equation. -</p> - -<p> - In this case the length of vector <code>(4, 2)</code> equals: - - \[||\color{red}{\bar{v}}|| = \sqrt{\color{green}4^2 + \color{blue}2^2} = \sqrt{\color{green}16 + \color{blue}4} = \sqrt{20} = 4.47 \] - - Which is <code>4.47</code>. -</p> - - - -<p> - There is also a special type of vector that we call a <def>unit vector</def>. A unit vector has one extra property and that is that its length is exactly 1. We can calculate a unit vector \(\hat{n}\) from any vector by dividing each of the vector's components by its length: - - \[\hat{n} = \frac{\bar{v}}{||\bar{v}||}\] - - We call this <def>normalizing</def> a vector. Unit vectors are displayed with a little roof over their head and are generally easier to work with, especially when we only care about their directions (the direction does not change if we change a vector's length). -</p> - -<h2>Vector-vector multiplication</h2> -<p> - Multiplying two vectors is a bit of a weird case. Normal multiplication isn't really defined on vectors since it has no visual meaning, but we have two specific cases that we could choose from when multiplying: one is the <def>dot product</def> denoted as \(\bar{v} \cdot \bar{k}\) and the other is the <def>cross product</def> denoted as \(\bar{v} \times \bar{k}\). -</p> - -<h3>Dot product</h3> -<p> - The dot product of two vectors is equal to the scalar product of their lengths times the cosine of the angle between them. If this sounds confusing take a look at its formula: - - \[\bar{v} \cdot \bar{k} = ||\bar{v}|| \cdot ||\bar{k}|| \cdot \cos \theta \] - - Where the angle between them is represented as theta (\(\theta\)). Why is this interesting? Well, imagine if \(\bar{v}\) and \(\bar{k}\) are unit vectors then their length would be equal to 1. This would effectively reduce the formula to: - - \[\hat{v} \cdot \hat{k} = 1 \cdot 1 \cdot \cos \theta = \cos \theta\] - - Now the dot product <strong>only</strong> defines the angle between both vectors. You may remember that the cosine or cos function becomes <code>0</code> when the angle is 90 degrees or <code>1</code> when the angle is 0. This allows us to easily test if the two vectors are <def>orthogonal</def> or <def>parallel</def> to each other using the dot product (orthogonal means the vectors are at a <def>right-angle</def> to each other). In case you want to know more about the <code>sin</code> or the <code>cos</code> functions I'd suggest the following <a href="https://www.khanacademy.org/math/trigonometry/basic-trigonometry/basic_trig_ratios/v/basic-trigonometry" target="_blank">Khan Academy videos</a> about basic trigonometry. -</p> - -<note> - You can also calculate the angle between two non-unit vectors, but then you'd have to divide the lengths of both vectors from the result to be left with \(cos \theta\). -</note> - -<p> - So how do we calculate the dot product? The dot product is a component-wise multiplication where we add the results together. It looks like this with two unit vectors (you can verify that both their lengths are exactly <code>1</code>): - - \[ \begin{pmatrix} \color{red}{0.6} \\ -\color{green}{0.8} \\ \color{blue}0 \end{pmatrix} \cdot \begin{pmatrix} \color{red}0 \\ \color{green}1 \\ \color{blue}0 \end{pmatrix} = (\color{red}{0.6} * \color{red}0) + (-\color{green}{0.8} * \color{green}1) + (\color{blue}0 * \color{blue}0) = -0.8 \] - - To calculate the degree between both these unit vectors we use the inverse of the cosine function \(cos^{-1}\) and this results in <code>143.1</code> degrees. We now effectively calculated the angle between these two vectors. The dot product proves very useful when doing lighting calculations later on. -</p> - -<h3>Cross product</h3> -<p> - The cross product is only defined in 3D space and takes two non-parallel vectors as input and produces a third vector that is orthogonal to both the input vectors. If both the input vectors are orthogonal to each other as well, a cross product would result in 3 orthogonal vectors; this will prove useful in the upcoming chapters. The following image shows what this looks like in 3D space: -</p> - -<img src="/img/getting-started/vectors_crossproduct.png" class="clean"/> - -<p> - Unlike the other operations, the cross product isn't really intuitive without delving into linear algebra so it's best to just memorize the formula and you'll be fine (or don't, you'll probably be fine as well). Below you'll see the cross product between two orthogonal vectors A and B: - - \[\begin{pmatrix} \color{red}{A_{x}} \\ \color{green}{A_{y}} \\ \color{blue}{A_{z}} \end{pmatrix} \times \begin{pmatrix} \color{red}{B_{x}} \\ \color{green}{B_{y}} \\ \color{blue}{B_{z}} \end{pmatrix} = \begin{pmatrix} \color{green}{A_{y}} \cdot \color{blue}{B_{z}} - \color{blue}{A_{z}} \cdot \color{green}{B_{y}} \\ \color{blue}{A_{z}} \cdot \color{red}{B_{x}} - \color{red}{A_{x}} \cdot \color{blue}{B_{z}} \\ \color{red}{A_{x}} \cdot \color{green}{B_{y}} - \color{green}{A_{y}} \cdot \color{red}{B_{x}} \end{pmatrix} \] - - As you can see, it doesn't really seem to make sense. However, if you just follow these steps you'll get another vector that is orthogonal to your input vectors. -</p> - -<h1>Matrices</h1> -<p> - Now that we've discussed almost all there is to vectors it is time to enter the matrix! - A matrix is a rectangular array of numbers, symbols and/or mathematical expressions. Each individual item in a matrix is called an <def>element</def> of the matrix. An example of a 2x3 matrix is shown below: - - \[\begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}\] - - Matrices are indexed by <code>(i,j)</code> where <code>i</code> is the row and <code>j</code> is the column, that is why the above matrix is called a 2x3 matrix (3 columns and 2 rows, also known as the <def>dimensions</def> of the matrix). This is the opposite of what you're used to when indexing 2D graphs as <code>(x,y)</code>. To retrieve the value 4 we would index it as <code>(2,1)</code> (second row, first column). -</p> - -<p> - Matrices are basically nothing more than that, just rectangular arrays of mathematical expressions. They do have a very nice set of mathematical properties and just like vectors we can define several operations on matrices, namely: addition, subtraction and multiplication. -</p> - -<h2>Addition and subtraction</h2> -<p> - Matrix addition and subtraction between two matrices is done on a per-element basis. So the same general rules apply that we're familiar with for normal numbers, but done on the elements of both matrices with the same index. This does mean that addition and subtraction is only defined for matrices of the same dimensions. A 3x2 matrix and a 2x3 matrix (or a 3x3 matrix and a 4x4 matrix) cannot be added or subtracted together. Let's see how matrix addition works on two 2x2 matrices: - - \[\begin{bmatrix} \color{red}1 & \color{red}2 \\ \color{green}3 & \color{green}4 \end{bmatrix} + \begin{bmatrix} \color{red}5 & \color{red}6 \\ \color{green}7 & \color{green}8 \end{bmatrix} = \begin{bmatrix} \color{red}1 + \color{red}5 & \color{red}2 + \color{red}6 \\ \color{green}3 + \color{green}7 & \color{green}4 + \color{green}8 \end{bmatrix} = \begin{bmatrix} \color{red}6 & \color{red}8 \\ \color{green}{10} & \color{green}{12} \end{bmatrix} \] - -The same rules apply for matrix subtraction: - - \[\begin{bmatrix} \color{red}4 & \color{red}2 \\ \color{green}1 & \color{green}6 \end{bmatrix} - \begin{bmatrix} \color{red}2 & \color{red}4 \\ \color{green}0 & \color{green}1 \end{bmatrix} = \begin{bmatrix} \color{red}4 - \color{red}2 & \color{red}2 - \color{red}4 \\ \color{green}1 - \color{green}0 & \color{green}6 - \color{green}1 \end{bmatrix} = \begin{bmatrix} \color{red}2 & -\color{red}2 \\ \color{green}1 & \color{green}5 \end{bmatrix} \] - -</p> - -<h2>Matrix-scalar products</h2> -<p> - A matrix-scalar product multiples each element of the matrix by a scalar. The following example illustrates the multiplication: - - \[\color{green}2 \cdot \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} = \begin{bmatrix} \color{green}2 \cdot 1 & \color{green}2 \cdot 2 \\ \color{green}2 \cdot 3 & \color{green}2 \cdot 4 \end{bmatrix} = \begin{bmatrix} 2 & 4 \\ 6 & 8 \end{bmatrix}\] - -Now it also makes sense as to why those single numbers are called scalars. A scalar basically <em>scales</em> all the elements of the matrix by its value. In the previous example, all elements were scaled by <code>2</code>. -</p> - -<p> - So far so good, all of our cases weren't really too complicated. That is, until we start on matrix-matrix multiplication. -</p> - -<h2>Matrix-matrix multiplication</h2> -<p> - Multiplying matrices is not necessarily complex, but rather difficult to get comfortable with. Matrix multiplication basically means to follow a set of pre-defined rules when multiplying. There are a few restrictions though: - - <ol> - <li>You can only multiply two matrices if the number of columns on the left-hand side matrix is equal to the number of rows on the right-hand side matrix.</li> - <li>Matrix multiplication is not <def>commutative</def> that is \(A \cdot B \neq B \cdot A\).</li> - </ol> -</p> - -<p> - Let's get started with an example of a matrix multiplication of 2 <code>2x2</code> matrices: - - \[ \begin{bmatrix} \color{red}1 & \color{red}2 \\ \color{green}3 & \color{green}4 \end{bmatrix} \cdot \begin{bmatrix} \color{blue}5 & \color{purple}6 \\ \color{blue}7 & \color{purple}8 \end{bmatrix} = \begin{bmatrix} \color{red}1 \cdot \color{blue}5 + \color{red}2 \cdot \color{blue}7 & \color{red}1 \cdot \color{purple}6 + \color{red}2 \cdot \color{purple}8 \\ \color{green}3 \cdot \color{blue}5 + \color{green}4 \cdot \color{blue}7 & \color{green}3 \cdot \color{purple}6 + \color{green}4 \cdot \color{purple}8 \end{bmatrix} = \begin{bmatrix} 19 & 22 \\ 43 & 50 \end{bmatrix} \] - - Right now you're probably trying to figure out what the hell just happened? Matrix multiplication is a combination of normal multiplication and addition using the left-matrix's rows with the right-matrix's columns. Let's try discussing this with the following image: -</p> - - <img src="/img/getting-started/matrix_multiplication.png" class="clean"/> - -<p> - We first take the upper row of the left matrix and then take a column from the right matrix. The row and column that we picked decides which output value of the resulting <code>2x2</code> matrix we're going to calculate. If we take the first row of the left matrix the resulting value will end up in the first row of the result matrix, then we pick a column and if it's the first column the result value will end up in the first column of the result matrix. This is exactly the case of the red pathway. To calculate the bottom-right result we take the bottom row of the first matrix and the rightmost column of the second matrix. -</p> - -<p> - To calculate the resulting value we multiply the first element of the row and column together using normal multiplication, we do the same for the second elements, third, fourth etc. The results of the individual multiplications are then summed up and we have our result. Now it also makes sense that one of the requirements is that the size of the left-matrix's columns and the right-matrix's rows are equal, otherwise we can't finish the operations! -</p> - -<p> - The result is then a matrix that has dimensions of (<code>n,m</code>) where <code>n</code> is equal to the number of rows of the left-hand side matrix and <code>m</code> is equal to the columns of the right-hand side matrix. -</p> - -<p> - Don't worry if you have difficulties imagining the multiplications inside your head. Just keep trying to do the calculations by hand and return to this page whenever you have difficulties. Over time, matrix multiplication becomes second nature to you. -</p> - -<p> - Let's finish the discussion of matrix-matrix multiplication with a larger example. Try to visualize the pattern using the colors. As a useful exercise, see if you can come up with your own answer of the multiplication and then compare them with the resulting matrix (once you try to do a matrix multiplication by hand you'll quickly get the grasp of them). - - \[ \begin{bmatrix} \color{red}4 & \color{red}2 & \color{red}0 \\ \color{green}0 & \color{green}8 & \color{green}1 \\ \color{blue}0 & \color{blue}1 & \color{blue}0 \end{bmatrix} \cdot \begin{bmatrix} \color{red}4 & \color{green}2 & \color{blue}1 \\ \color{red}2 & \color{green}0 & \color{blue}4 \\ \color{red}9 & \color{green}4 & \color{blue}2 \end{bmatrix} = \begin{bmatrix} \color{red}4 \cdot \color{red}4 + \color{red}2 \cdot \color{red}2 + \color{red}0 \cdot \color{red}9 & \color{red}4 \cdot \color{green}2 + \color{red}2 \cdot \color{green}0 + \color{red}0 \cdot \color{green}4 & \color{red}4 \cdot \color{blue}1 + \color{red}2 \cdot \color{blue}4 + \color{red}0 \cdot \color{blue}2 \\ \color{green}0 \cdot \color{red}4 + \color{green}8 \cdot \color{red}2 + \color{green}1 \cdot \color{red}9 & \color{green}0 \cdot \color{green}2 + \color{green}8 \cdot \color{green}0 + \color{green}1 \cdot \color{green}4 & \color{green}0 \cdot \color{blue}1 + \color{green}8 \cdot \color{blue}4 + \color{green}1 \cdot \color{blue}2 \\ \color{blue}0 \cdot \color{red}4 + \color{blue}1 \cdot \color{red}2 + \color{blue}0 \cdot \color{red}9 & \color{blue}0 \cdot \color{green}2 + \color{blue}1 \cdot \color{green}0 + \color{blue}0 \cdot \color{green}4 & \color{blue}0 \cdot \color{blue}1 + \color{blue}1 \cdot \color{blue}4 + \color{blue}0 \cdot \color{blue}2 \end{bmatrix} - \\ = \begin{bmatrix} 20 & 8 & 12 \\ 25 & 4 & 34 \\ 2 & 0 & 4 \end{bmatrix}\] -</p> - -<p> - As you can see, matrix-matrix multiplication is quite a cumbersome process and very prone to errors (which is why we usually let computers do this) and this gets problematic real quick when the matrices become larger. If you're still thirsty for more and you're curious about some more of the mathematical properties of matrices I strongly suggest you take a look at these <a href="https://www.khanacademy.org/math/algebra2/algebra-matrices" target="_blank">Khan Academy videos</a> about matrices. -</p> - -<p> - Anyways, now that we know how to multiply matrices together, we can start getting to the good stuff. -</p> - -<h1>Matrix-Vector multiplication</h1> -<p> - Up until now we've had our fair share of vectors. We used them to represent positions, colors and even texture coordinates. Let's move a bit further down the rabbit hole and tell you that a vector is basically a <code>Nx1</code> matrix where <code>N</code> is the vector's number of components (also known as an <def>N-dimensional</def> vector). If you think about it, it makes a lot of sense. Vectors are just like matrices an array of numbers, but with only 1 column. So, how does this new piece of information help us? Well, if we have a <code>MxN</code> matrix we can multiply this matrix with our <code>Nx1</code> vector, since the columns of the matrix are equal to the number of rows of the vector, thus matrix multiplication is defined. -</p> - -<p> - But why do we care if we can multiply matrices with a vector? Well, it just so happens that there are lots of interesting 2D/3D transformations we can place inside a matrix, and multiplying that matrix with a vector then <em>transforms</em> that vector. In case you're still a bit confused, let's start with a few examples and you'll soon see what we mean. -</p> - -<h2>Identity matrix</h2> -<p> - In OpenGL we usually work with <code>4x4</code> transformation matrices for several reasons and one of them is that most of the vectors are of size 4. The most simple transformation matrix that we can think of is the <def>identity matrix</def>. The identity matrix is an <code>NxN</code> matrix with only 0s except on its diagonal. As you'll see, this transformation matrix leaves a vector completely unharmed: - - \[ \begin{bmatrix} \color{red}1 & \color{red}0 & \color{red}0 & \color{red}0 \\ \color{green}0 & \color{green}1 & \color{green}0 & \color{green}0 \\ \color{blue}0 & \color{blue}0 & \color{blue}1 & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{bmatrix} 1 \\ 2 \\ 3 \\ 4 \end{bmatrix} = \begin{bmatrix} \color{red}1 \cdot 1 \\ \color{green}1 \cdot 2 \\ \color{blue}1 \cdot 3 \\ \color{purple}1 \cdot 4 \end{bmatrix} = \begin{bmatrix} 1 \\ 2 \\ 3 \\ 4 \end{bmatrix} \] - - The vector is completely untouched. This becomes obvious from the rules of multiplication: the first result element is each individual element of the first row of the matrix multiplied with each element of the vector. Since each of the row's elements are 0 except the first one, we get: \(\color{red}1\cdot1 + \color{red}0\cdot2 + \color{red}0\cdot3 + \color{red}0\cdot4 = 1\) and the same applies for the other 3 elements of the vector. -</p> - -<note> - You may be wondering what the use is of a transformation matrix that does not transform? The identity matrix is usually a starting point for generating other transformation matrices and if we dig even deeper into linear algebra, a very useful matrix for proving theorems and solving linear equations. -</note> - -<h2>Scaling</h2> -<p> - When we're scaling a vector we are increasing the length of the arrow by the amount we'd like to scale, keeping its direction the same. Since we're working in either 2 or 3 dimensions we can define scaling by a vector of 2 or 3 scaling variables, each scaling one axis (<code>x</code>, <code>y</code> or <code>z</code>). -</p> - -<p> - Let's try scaling the vector \(\color{red}{\bar{v}} = (3,2)\). We will scale the vector along the x-axis by <code>0.5</code>, thus making it twice as narrow; and we'll scale the vector by <code>2</code> along the y-axis, making it twice as high. Let's see what it looks like if we scale the vector by <code>(0.5,2)</code> as \(\color{blue}{\bar{s}}\): -</p> - - -<img src="/img/getting-started/vectors_scale.png" class="clean"/> - -<p> - Keep in mind that OpenGL usually operates in 3D space so for this 2D case we could set the z-axis scale to <code>1</code>, leaving it unharmed. The scaling operation we just performed is a <def>non-uniform</def> scale, because the scaling factor is not the same for each axis. If the scalar would be equal on all axes it would be called a <def>uniform scale</def>. -</p> - -<p> - Let's start building a transformation matrix that does the scaling for us. We saw from the identity matrix that each of the diagonal elements were multiplied with its corresponding vector element. What if we were to change the <code>1</code>s in the identity matrix to <code>3</code>s? In that case, we would be multiplying each of the vector elements by a value of <code>3</code> and thus effectively uniformly scale the vector by 3. If we represent the scaling variables as \( (\color{red}{S_1}, \color{green}{S_2}, \color{blue}{S_3}) \) we can define a scaling matrix on any vector \((x,y,z)\) as: - - \[\begin{bmatrix} \color{red}{S_1} & \color{red}0 & \color{red}0 & \color{red}0 \\ \color{green}0 & \color{green}{S_2} & \color{green}0 & \color{green}0 \\ \color{blue}0 & \color{blue}0 & \color{blue}{S_3} & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} \color{red}{S_1} \cdot x \\ \color{green}{S_2} \cdot y \\ \color{blue}{S_3} \cdot z \\ 1 \end{pmatrix} \] - - Note that we keep the 4th scaling value <code>1</code>. The <code>w</code> component is used for other purposes as we'll see later on. -</p> - -<h2>Translation</h2> -<p> - <def>Translation</def> is the process of adding another vector on top of the original vector to return a new vector with a different position, thus <em>moving</em> the vector based on a translation vector. We've already discussed vector addition so this shouldn't be too new. -</p> - -<p> - Just like the scaling matrix there are several locations on a 4-by-4 matrix that we can use to perform certain operations and for translation those are the top-3 values of the 4th column. If we represent the translation vector as \((\color{red}{T_x},\color{green}{T_y},\color{blue}{T_z})\) we can define the translation matrix by: - - \[\begin{bmatrix} \color{red}1 & \color{red}0 & \color{red}0 & \color{red}{T_x} \\ \color{green}0 & \color{green}1 & \color{green}0 & \color{green}{T_y} \\ \color{blue}0 & \color{blue}0 & \color{blue}1 & \color{blue}{T_z} \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} x + \color{red}{T_x} \\ y + \color{green}{T_y} \\ z + \color{blue}{T_z} \\ 1 \end{pmatrix} \] - - This works because all of the translation values are multiplied by the vector's <code>w</code> column and added to the vector's original values (remember the matrix-multiplication rules). This wouldn't have been possible with a 3-by-3 matrix. -</p> - -<note> - <strong>Homogeneous coordinates</strong><br/> - The <code>w</code> component of a vector is also known as a <def>homogeneous coordinate</def>. - To get the 3D vector from a homogeneous vector we divide the <code>x</code>, <code>y</code> and <code>z</code> coordinate by its <code>w</code> coordinate. We usually do not notice this since the <code>w</code> component is <code>1.0</code> most of the time. Using homogeneous coordinates has several advantages: it allows us to do matrix translations on 3D vectors (without a <code>w</code> component we can't translate vectors) and in the next chapter we'll use the <code>w</code> value to create 3D perspective.<br/> - <br/> - Also, whenever the homogeneous coordinate is equal to <code>0</code>, the vector is specifically known as a <def>direction vector</def> since a vector with a <code>w</code> coordinate of <code>0</code> cannot be translated. -</note> - -<p> - With a translation matrix we can move objects in any of the 3 axis directions (<code>x</code>, <code>y</code>, <code>z</code>), making it a very useful transformation matrix for our transformation toolkit. -</p> - - - -<h2>Rotation</h2> -<p> - The last few transformations were relatively easy to understand and visualize in 2D or 3D space, but rotations are a bit trickier. If you want to know exactly how these matrices are constructed I'd recommend that you watch the rotation items of Khan Academy's <a href="https://www.khanacademy.org/math/linear-algebra/matrix_transformations" target="_blank">linear algebra</a> videos. -</p> - -<p> - First let's define what a rotation of a vector actually is. A rotation in 2D or 3D is represented with an <def>angle</def>. An angle could be in degrees or radians where a whole circle has 360 degrees or 2 <a href="http://en.wikipedia.org/wiki/Pi" target="_blank">PI</a> radians. I prefer explaining rotations using degrees as we're generally more accustomed to them. - -<note> - Most rotation functions require an angle in radians, but luckily degrees are easily converted to radians: <br/> - <code>angle in degrees = angle in radians * (180 / PI) </code><br/> - <code>angle in radians = angle in degrees * (PI / 180) </code><br/> - Where <code>PI</code> equals (rounded) <code>3.14159265359</code>. -</note> - - Rotating half a circle rotates us 360/2 = 180 degrees and rotating 1/5th to the right means we rotate 360/5 = 72 degrees to the right. This is demonstrated for a basic 2D vector where \(\color{red}{\bar{v}}\) is rotated 72 degrees to the right, or clockwise, from \(\color{green}{\bar{k}}\): -</p> - - <img src="/img/getting-started/vectors_angle.png" class="clean" /> - -<p> - Rotations in 3D are specified with an angle <strong>and</strong> a <def>rotation axis</def>. The angle specified will rotate the object along the rotation axis given. Try to visualize this by spinning your head a certain degree while continually looking down a single rotation axis. When rotating 2D vectors in a 3D world for example, we set the rotation axis to the z-axis (try to visualize this). -</p> - -<p> - Using trigonometry it is possible to transform vectors to newly rotated vectors given an angle. This is usually done via a smart combination of the <code>sine</code> and <code>cosine</code> functions (commonly abbreviated to <code>sin</code> and <code>cos</code>). A discussion of how the rotation matrices are generated is out of the scope of this chapter. -</p> - -<p> - A rotation matrix is defined for each unit axis in 3D space where the angle is represented as the theta symbol \(\theta\). -</p> - - <p> - Rotation around the X-axis: - - \[\begin{bmatrix} \color{red}1 & \color{red}0 & \color{red}0 & \color{red}0 \\ \color{green}0 & \color{green}{\cos \theta} & - \color{green}{\sin \theta} & \color{green}0 \\ \color{blue}0 & \color{blue}{\sin \theta} & \color{blue}{\cos \theta} & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} x \\ \color{green}{\cos \theta} \cdot y - \color{green}{\sin \theta} \cdot z \\ \color{blue}{\sin \theta} \cdot y + \color{blue}{\cos \theta} \cdot z \\ 1 \end{pmatrix}\] - </p> - - <p> - Rotation around the Y-axis: - - \[\begin{bmatrix} \color{red}{\cos \theta} & \color{red}0 & \color{red}{\sin \theta} & \color{red}0 \\ \color{green}0 & \color{green}1 & \color{green}0 & \color{green}0 \\ - \color{blue}{\sin \theta} & \color{blue}0 & \color{blue}{\cos \theta} & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} \color{red}{\cos \theta} \cdot x + \color{red}{\sin \theta} \cdot z \\ y \\ - \color{blue}{\sin \theta} \cdot x + \color{blue}{\cos \theta} \cdot z \\ 1 \end{pmatrix} \] - </p> - - <p> - Rotation around the Z-axis: - - \[\begin{bmatrix} \color{red}{\cos \theta} & - \color{red}{\sin \theta} & \color{red}0 & \color{red}0 \\ \color{green}{\sin \theta} & \color{green}{\cos \theta} & \color{green}0 & \color{green}0 \\ \color{blue}0 & \color{blue}0 & \color{blue}1 & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \cdot \begin{pmatrix} x \\ y \\ z \\ 1 \end{pmatrix} = \begin{pmatrix} \color{red}{\cos \theta} \cdot x - \color{red}{\sin \theta} \cdot y \\ \color{green}{\sin \theta} \cdot x + \color{green}{\cos \theta} \cdot y \\ z \\ 1 \end{pmatrix} \] - </p> - - - <p> - Using the rotation matrices we can transform our position vectors around one of the three unit axes. To rotate around an arbitrary 3D axis we can combine all 3 them by first rotating around the X-axis, then Y and then Z for example. However, this quickly introduces a problem called <def>Gimbal lock</def>. We won't discuss the details, but a better solution is to rotate around an arbitrary unit axis e.g. <code>(0.662,0.2,0.722)</code> (note that this is a unit vector) right away instead of combining the rotation matrices. Such a (verbose) matrix exists and is given below with \((\color{red}{R_x}, \color{green}{R_y}, \color{blue}{R_z})\) as the arbitrary rotation axis: - - \[\begin{bmatrix} \cos \theta + \color{red}{R_x}^2(1 - \cos \theta) & \color{red}{R_x}\color{green}{R_y}(1 - \cos \theta) - \color{blue}{R_z} \sin \theta & \color{red}{R_x}\color{blue}{R_z}(1 - \cos \theta) + \color{green}{R_y} \sin \theta & 0 \\ \color{green}{R_y}\color{red}{R_x} (1 - \cos \theta) + \color{blue}{R_z} \sin \theta & \cos \theta + \color{green}{R_y}^2(1 - \cos \theta) & \color{green}{R_y}\color{blue}{R_z}(1 - \cos \theta) - \color{red}{R_x} \sin \theta & 0 \\ \color{blue}{R_z}\color{red}{R_x}(1 - \cos \theta) - \color{green}{R_y} \sin \theta & \color{blue}{R_z}\color{green}{R_y}(1 - \cos \theta) + \color{red}{R_x} \sin \theta & \cos \theta + \color{blue}{R_z}^2(1 - \cos \theta) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}\] - - - A mathematical discussion of generating such a matrix is out of the scope of this chapter. Keep in mind that even this matrix does not completely prevent gimbal lock (although it gets a lot harder). To truly prevent Gimbal locks we have to represent rotations using <def>quaternions</def>, that are not only safer, but also more computationally friendly. However, a discussion of quaternions is out of this chapter's scope. - </p> - -<h2>Combining matrices</h2> -<p> - The true power from using matrices for transformations is that we can combine multiple transformations in a single matrix thanks to matrix-matrix multiplication. Let's see if we can generate a transformation matrix that combines several transformations. Say we have a vector <code>(x,y,z)</code> and we want to scale it by 2 and then translate it by <code>(1,2,3)</code>. We need a translation and a scaling matrix for our required steps. The resulting transformation matrix would then look like: - - \[Trans . Scale = \begin{bmatrix} \color{red}1 & \color{red}0 & \color{red}0 & \color{red}1 \\ \color{green}0 & \color{green}1 & \color{green}0 & \color{green}2 \\ \color{blue}0 & \color{blue}0 & \color{blue}1 & \color{blue}3 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} . \begin{bmatrix} \color{red}2 & \color{red}0 & \color{red}0 & \color{red}0 \\ \color{green}0 & \color{green}2 & \color{green}0 & \color{green}0 \\ \color{blue}0 & \color{blue}0 & \color{blue}2 & \color{blue}0 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} = \begin{bmatrix} \color{red}2 & \color{red}0 & \color{red}0 & \color{red}1 \\ \color{green}0 & \color{green}2 & \color{green}0 & \color{green}2 \\ \color{blue}0 & \color{blue}0 & \color{blue}2 & \color{blue}3 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} \] - - Note that we first do a translation and then a scale transformation when multiplying matrices. Matrix multiplication is not commutative, which means their order is important. When multiplying matrices the right-most matrix is first multiplied with the vector so you should read the multiplications from right to left. It is advised to first do scaling operations, then rotations and lastly translations when combining matrices otherwise they may (negatively) affect each other. For example, if you would first do a translation and then scale, the translation vector would also scale! - </p> - -<p> - Running the final transformation matrix on our vector results in the following vector: - - \[\begin{bmatrix} \color{red}2 & \color{red}0 & \color{red}0 & \color{red}1 \\ \color{green}0 & \color{green}2 & \color{green}0 & \color{green}2 \\ \color{blue}0 & \color{blue}0 & \color{blue}2 & \color{blue}3 \\ \color{purple}0 & \color{purple}0 & \color{purple}0 & \color{purple}1 \end{bmatrix} . \begin{bmatrix} x \\ y \\ z \\ 1 \end{bmatrix} = \begin{bmatrix} \color{red}2x + \color{red}1 \\ \color{green}2y + \color{green}2 \\ \color{blue}2z + \color{blue}3 \\ 1 \end{bmatrix} \] - - Great! The vector is first scaled by two and then translated by <code>(1,2,3)</code>. - </p> - -<h1>In practice</h1> -<p> - Now that we've explained all the theory behind transformations, it's time to see how we can actually use this knowledge to our advantage. OpenGL does not have any form of matrix or vector knowledge built in, so we have to define our own mathematics classes and functions. In this book we'd rather abstract from all the tiny mathematical details and simply use pre-made mathematics libraries. Luckily, there is an easy-to-use and tailored-for-OpenGL mathematics library called GLM. - </p> - - <h2>GLM</h2> -<p> - <img src="/img/getting-started/glm.png" class="right"/> - GLM stands for Open<strong>GL</strong> <strong>M</strong>athematics and is a <em>header-only</em> library, which means that we only have to include the proper header files and we're done; no linking and compiling necessary. - GLM can be downloaded from their <a href="https://glm.g-truc.net/0.9.8/index.html" target="_blank">website</a>. Copy the root directory of the header files into your <em>includes</em> folder and let's get rolling. - </p> - -<!--<warning> - Since GLM version <code>0.9.9</code>, GLM default initializates matrix types to a 0-initalized matrix, instead of the identity matrix. From that version it is required to initialize matrix types as: <code>glm::mat4 mat = glm::mat4(1.0f)</code>. - - For consistency with the tutorials' code it's advised to use a version of GLM lower than <code>0.9.9</code> or initialize all matrices as mentioned above. -</warning> ---> - - <p> - Most of GLM's functionality that we need can be found in 3 headers files that we'll include as follows: - </p> - -<pre><code> -#include <glm/glm.hpp> -#include <glm/gtc/matrix_transform.hpp> -#include <glm/gtc/type_ptr.hpp> -</code></pre> - - <p> - Let's see if we can put our transformation knowledge to good use by translating a vector of <code>(1,0,0)</code> by <code>(1,1,0)</code> (note that we define it as a <code>glm::vec4</code> with its homogeneous coordinate set to <code>1.0</code>: - </p> - -<pre><code> -glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f); -glm::mat4 trans = glm::mat4(1.0f); -trans = <function id='55'>glm::translate</function>(trans, glm::vec3(1.0f, 1.0f, 0.0f)); -vec = trans * vec; -std::cout << vec.x << vec.y << vec.z << std::endl; -</code></pre> - - <p> - We first define a vector named <code>vec</code> using GLM's built-in vector class. Next we define a <code>mat4</code> and explicitly initialize it to the identity matrix by initializing the matrix's diagonals to <code>1.0</code>; if we do not initialize it to the identity matrix the matrix would be a null matrix (all elements <code>0</code>) and all subsequent matrix operations would end up a null matrix as well. -</p> - -<p> -The next step is to create a transformation matrix by passing our identity matrix to the <code><function id='55'>glm::translate</function></code> function, together with a translation vector (the given matrix is then multiplied with a translation matrix and the resulting matrix is returned). <br/> - Then we multiply our vector by the transformation matrix and output the result. If we still remember how matrix translation works then the resulting vector should be <code>(1+1,0+1,0+0)</code> which is <code>(2,1,0)</code>. This snippet of code outputs <code>210</code> so the translation matrix did its job. - </p> - - <p> - Let's do something more interesting and scale and rotate the container object from the previous chapter: - </p> - -<pre><code> -glm::mat4 trans = glm::mat4(1.0f); -trans = <function id='57'>glm::rotate</function>(trans, <function id='63'>glm::radians</function>(90.0f), glm::vec3(0.0, 0.0, 1.0)); -trans = <function id='56'>glm::scale</function>(trans, glm::vec3(0.5, 0.5, 0.5)); -</code></pre> - -<p> - First we scale the container by <code>0.5</code> on each axis and then rotate the container <code>90</code> degrees around the Z-axis. GLM expects its angles in radians so we convert the degrees to radians using <code><function id='63'>glm::radians</function></code>. Note that the textured rectangle is on the XY plane so we want to rotate around the Z-axis. Keep in mind that the axis that we rotate around should be a unit vector, so be sure to normalize the vector first if you're not rotating around the X, Y, or Z axis. Because we pass the matrix to each of GLM's functions, GLM automatically multiples the matrices together, resulting in a transformation matrix that combines all the transformations. - </p> - - <p> - The next big question is: how do we get the transformation matrix to the shaders? We shortly mentioned before that GLSL also has a <code>mat4</code> type. So we'll adapt the vertex shader to accept a <code>mat4</code> uniform variable and multiply the position vector by the matrix uniform: - </p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec2 aTexCoord; - -out vec2 TexCoord; - -uniform mat4 transform; - -void main() -{ - gl_Position = transform * vec4(aPos, 1.0f); - TexCoord = vec2(aTexCoord.x, aTexCoord.y); -} -</code></pre> - -<note> - GLSL also has <code>mat2</code> and <code>mat3</code> types that allow for swizzling-like operations just like vectors. All the aforementioned math operations (like scalar-matrix multiplication, matrix-vector multiplication and matrix-matrix multiplication) are allowed on the matrix types. Wherever special matrix operations are used we'll be sure to explain what's happening. -</note> - - <p> - We added the uniform and multiplied the position vector with the transformation matrix before passing it to <var>gl_Position</var>. Our container should now be twice as small and rotated <code>90</code> degrees (tilted to the left). We still need to pass the transformation matrix to the shader though: - </p> - -<pre><code> -unsigned int transformLoc = <function id='45'>glGetUniformLocation</function>(ourShader.ID, "transform"); -<function id='44'>glUniform</function>Matrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans)); -</code></pre> - -<p> - We first query the location of the uniform variable and then send the matrix data to the shaders using <fun><function id='44'>glUniform</function></fun> with <code>Matrix4fv</code> as its postfix. The first argument should be familiar by now which is the uniform's location. The second argument tells OpenGL how many matrices we'd like to send, which is <code>1</code>. The third argument asks us if we want to transpose our matrix, that is to swap the columns and rows. OpenGL developers often use an internal matrix layout called <def>column-major ordering</def> which is the default matrix layout in GLM so there is no need to transpose the matrices; we can keep it at <var>GL_FALSE</var>. The last parameter is the actual matrix data, but GLM stores their matrices' data in a way that doesn't always match OpenGL's expectations so we first convert the data with GLM's built-in function <fun>value_ptr</fun>. -</p> - -<p> - We created a transformation matrix, declared a uniform in the vertex shader and sent the matrix to the shaders where we transform our vertex coordinates. The result should look something like this: -</p> - - <img src="/img/getting-started/transformations.png" class="clean" /> - -<p> - Perfect! Our container is indeed tilted to the left and twice as small so the transformation was successful. Let's get a little more funky and see if we can rotate the container over time, and for fun we'll also reposition the container at the bottom-right side of the window. -To rotate the container over time we have to update the transformation matrix in the render loop because it needs to update each frame. We use GLFW's time function to get an angle over time: -</p> - -<pre><code> -glm::mat4 trans = glm::mat4(1.0f); -trans = <function id='55'>glm::translate</function>(trans, glm::vec3(0.5f, -0.5f, 0.0f)); -trans = <function id='57'>glm::rotate</function>(trans, (float)<function id='47'>glfwGetTime</function>(), glm::vec3(0.0f, 0.0f, 1.0f)); -</code></pre> - -<p> - Keep in mind that in the previous case we could declare the transformation matrix anywhere, but now we have to create it every iteration to continuously update the rotation. This means we have to re-create the transformation matrix in each iteration of the render loop. Usually when rendering scenes we have several transformation matrices that are re-created with new values each frame. -</p> - -<p> - Here we first rotate the container around the origin <code>(0,0,0)</code> and once it's rotated, we translate its rotated version to the bottom-right corner of the screen. Remember that the actual transformation order should be read in reverse: even though in code we first translate and then later rotate, the actual transformations first apply a rotation and then a translation. Understanding all these combinations of transformations and how they apply to objects is difficult to understand. Try and experiment with transformations like these and you'll quickly get a grasp of it. -</p> - - -<p> - If you did things right you should get the following result: -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/getting-started/transformations.mp4" type="video/mp4" /> - <img src="/img/getting-started/transformations2.png" class="clean"/> - </video> -</div> - - - <p> - And there you have it. A translated container that's rotated over time, all done by a single transformation matrix! Now you can see why matrices are such a powerful construct in graphics land. We can define an infinite amount of transformations and combine them all in a single matrix that we can re-use as often as we'd like. Using transformations like this in the vertex shader saves us the effort of re-defining the vertex data and saves us some processing time as well, since we don't have to re-send our data all the time (which is quite slow); all we need to do is update the transformation uniform. - </p> - -<p> - If you didn't get the right result or you're stuck somewhere else, take a look at the <a href="/code_viewer_gh.php?code=src/1.getting_started/5.1.transformations/transformations.cpp" target="_blank">source code</a> and the updated <a href="https://learnopengl.com/code_viewer_gh.php?code=includes/learnopengl/shader_m.h" target="_blank">shader</a> class. -</p> - - <p> - In the next chapter we'll discuss how we can use matrices to define different coordinate spaces for our vertices. This will be our first step into 3D graphics! - </p> - -<h2>Further reading</h2> -<ul> - <li><a href="https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab" target="_blank">Essence of Linear Algebra</a>: great video tutorial series by Grant Sanderson about the underlying mathematics of transformations and linear algebra.</li> - -</ul> - -<h2>Exercises</h2> -<p> - <ul> - <li>Using the last transformation on the container, try switching the order around by first rotating and then translating. See what happens and try to reason why this happens: <a href="/code_viewer_gh.php?code=src/1.getting_started/5.2.transformations_exercise1/transformations_exercise1.cpp" target="_blank">solution</a>.</li> - <li>Try drawing a second container with another call to <fun><function id='2'>glDrawElements</function></fun> but place it at a different position using transformations <strong>only</strong>. Make sure this second container is placed at the top-left of the window and instead of rotating, scale it over time (using the <code>sin</code> function is useful here; note that using <code>sin</code> will cause the object to invert as soon as a negative scale is applied): <a href="/code_viewer_gh.php?code=src/1.getting_started/5.2.transformations_exercise2/transformations_exercise2.cpp" target="_blank">solution</a>.</li> - </ul> -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/orig/Introduction.html b/orig/Introduction.html @@ -1,335 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Introduction</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Introduction</h1> -<h1 id="content-url" style='display:none;'>Introduction</h1> -<p> - Since you came here you probably want to learn the inner workings of computer graphics and do all the stuff the cool kids do by yourself. Doing things by yourself is extremely fun and resourceful and gives you a great understanding of graphics programming. However, there are a few items that need to be taken into consideration before starting your journey. -</p> - -<h2>Prerequisites</h2> -<p> - Since OpenGL is a graphics API and not a platform of its own, it requires a language to operate in and the language of choice is <code>C++</code>. Therefore a decent knowledge of the <code>C++</code> programming language is required for these chapters. However, I will try to explain most of the concepts used, including advanced <code>C++</code> topics where required so it is not required to be an expert in <code>C++</code>, but you should be able to write more than just a <code>'Hello World'</code> program. If you don't have much experience with <code>C++</code> I can recommend the free tutorials at <a href="http://www.learncpp.com" target="_blank">www.learncpp.com</a>. -</p> - -<p> - Also, we will be using some math (linear algebra, geometry, and trigonometry) along the way and I will try to explain all the required concepts of the math required. However, I'm not a mathematician by heart so even though my explanations may be easy to understand, they will most likely be incomplete. So where necessary I will provide pointers to good resources that explain the material in a more complete fashion. Don't be scared about the mathematical knowledge required before starting your journey into OpenGL; almost all the concepts can be understood with a basic mathematical background and I will try to keep the mathematics to a minimum where possible. Most of the functionality doesn't even require you to understand all the math as long as you know how to use it. -</p> - -<h2>Structure</h2> -<p> - LearnOpenGL is broken down into a number of general sections. Each section contains several chapters that each explain different concepts in large detail. Each of the chapters can be found at the menu to your left. The concepts are taught in a linear fashion (so it is advised to start from the top to the bottom, unless otherwise instructed) where each chapter explains the background theory and the practical aspects. -</p> - -<p> - To make the concepts easier to follow, and give them some added structure, the book contains <em>boxes</em>, <em>code blocks</em>, <em>color hints</em> and <em>function references</em>. -</p> - -<h3>Boxes</h3> -<note><strong>Green</strong> boxes encompasses some notes or useful features/hints about OpenGL or the subject at hand.</note> -<warning><strong>Red</strong> boxes will contain warnings or other features you have to be extra careful with.</warning> - -<h3>Code</h3> -<p> - You will find plenty of small pieces of code in the website that are located in dark-gray boxes with syntax-highlighted code as you can see below: -</p> - -<pre><code> -// This box contains code -</code></pre> - - <p> - Since these provide only snippets of code, wherever necessary I will provide a link to the entire source code required for a given subject. -</p> - -<h3>Color hints</h3> -<p> - Some words are displayed with a different color to make it extra clear these words portray a special meaning: -</p> - -<ul> - <li><def>Definition</def>: green words specify a definition i.e. an important aspect/name of something you're likely to hear more often.</li> - <li><fun>Program structure</fun>: red words specify function names or class names.</li> - <li><var>Variables</var>: blue words specify variables including all OpenGL constants.</li> -</ul> - -<h3>OpenGL Function references</h3> -<p> - A particularly well appreciated feature of LearnOpenGL is the ability to review most of OpenGL's functions wherever they show up in the content. Whenever a function is found in the content that is documented at the website, the function will show up with a slightly noticeable underline. You can hover the mouse over the function and after a small interval, a pop-up window will show relevant information about this function including a nice overview of what the function actually does. Hover your mouse over <fun><function id='60'>glEnable</function></fun> to see it in action. -</p> - -<p> - Now that you got a bit of a feel of the structure of the site, hop over to the Getting Started section to start your journey in OpenGL! -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/orig/dirs b/orig/dirs @@ -1,18 +0,0 @@ -Getting-started -Lighting -Model-Loading -Advanced-OpenGL -Advanced-Lighting -Advanced-Lighting/Shadows -Advanced-Lighting -PBR -PBR/IBL -In-Practice -In-Practice/2D-Game -In-Practice/2D-Game/Collisions -In-Practice/2D-Game -Guest-Articles -Guest-Articles/2020/OIT -Guest-Articles/2020 -Guest-Articles/2021 -Guest-Articles/2021/Scene diff --git a/orig/download b/orig/download @@ -1,71 +0,0 @@ - curl https://learnopengl.com/Introduction --create-dirs -o Introduction.html && sleep 0.5 - curl https://learnopengl.com/Getting-started/OpenGL --create-dirs -o Getting-started/OpenGL.html && sleep 0.5 - curl https://learnopengl.com/Getting-started/Creating-a-window --create-dirs -o Getting-started/Creating-a-window.html && sleep 0.5 - curl https://learnopengl.com/Getting-started/Hello-Window --create-dirs -o Getting-started/Hello-Window.html && sleep 0.5 - curl https://learnopengl.com/Getting-started/Hello-Triangle --create-dirs -o Getting-started/Hello-Triangle.html && sleep 0.5 - curl https://learnopengl.com/Getting-started/Shaders --create-dirs -o Getting-started/Shaders.html && sleep 0.5 - curl https://learnopengl.com/Getting-started/Textures --create-dirs -o Getting-started/Textures.html && sleep 0.5 - curl https://learnopengl.com/Getting-started/Transformations --create-dirs -o Getting-started/Transformations.html && sleep 0.5 - curl https://learnopengl.com/Getting-started/Coordinate-Systems --create-dirs -o Getting-started/Coordinate-Systems.html && sleep 0.5 - curl https://learnopengl.com/Getting-started/Camera --create-dirs -o Getting-started/Camera.html && sleep 0.5 - curl https://learnopengl.com/Getting-started/Review --create-dirs -o Getting-started/Review.html && sleep 0.5 - curl https://learnopengl.com/Lighting/Colors --create-dirs -o Lighting/Colors.html && sleep 0.5 - curl https://learnopengl.com/Lighting/Basic-Lighting --create-dirs -o Lighting/Basic-Lighting.html && sleep 0.5 - curl https://learnopengl.com/Lighting/Materials --create-dirs -o Lighting/Materials.html && sleep 0.5 - curl https://learnopengl.com/Lighting/Lighting-maps --create-dirs -o Lighting/Lighting-maps.html && sleep 0.5 - curl https://learnopengl.com/Lighting/Light-casters --create-dirs -o Lighting/Light-casters.html && sleep 0.5 - curl https://learnopengl.com/Lighting/Multiple-lights --create-dirs -o Lighting/Multiple-lights.html && sleep 0.5 - curl https://learnopengl.com/Lighting/Review --create-dirs -o Lighting/Review.html && sleep 0.5 - curl https://learnopengl.com/Model-Loading/Assimp --create-dirs -o Model-Loading/Assimp.html && sleep 0.5 - curl https://learnopengl.com/Model-Loading/Mesh --create-dirs -o Model-Loading/Mesh.html && sleep 0.5 - curl https://learnopengl.com/Model-Loading/Model --create-dirs -o Model-Loading/Model.html && sleep 0.5 - curl https://learnopengl.com/Advanced-OpenGL/Depth-testing --create-dirs -o Advanced-OpenGL/Depth-testing.html && sleep 0.5 - curl https://learnopengl.com/Advanced-OpenGL/Stencil-testing --create-dirs -o Advanced-OpenGL/Stencil-testing.html && sleep 0.5 - curl https://learnopengl.com/Advanced-OpenGL/Blending --create-dirs -o Advanced-OpenGL/Blending.html && sleep 0.5 - curl https://learnopengl.com/Advanced-OpenGL/Face-culling --create-dirs -o Advanced-OpenGL/Face-culling.html && sleep 0.5 - curl https://learnopengl.com/Advanced-OpenGL/Framebuffers --create-dirs -o Advanced-OpenGL/Framebuffers.html && sleep 0.5 - curl https://learnopengl.com/Advanced-OpenGL/Cubemaps --create-dirs -o Advanced-OpenGL/Cubemaps.html && sleep 0.5 - curl https://learnopengl.com/Advanced-OpenGL/Advanced-Data --create-dirs -o Advanced-OpenGL/Advanced-Data.html && sleep 0.5 - curl https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL --create-dirs -o Advanced-OpenGL/Advanced-GLSL.html && sleep 0.5 - curl https://learnopengl.com/Advanced-OpenGL/Geometry-Shader --create-dirs -o Advanced-OpenGL/Geometry-Shader.html && sleep 0.5 - curl https://learnopengl.com/Advanced-OpenGL/Instancing --create-dirs -o Advanced-OpenGL/Instancing.html && sleep 0.5 - curl https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing --create-dirs -o Advanced-OpenGL/Anti-Aliasing.html && sleep 0.5 - curl https://learnopengl.com/Advanced-Lighting/Advanced-Lighting --create-dirs -o Advanced-Lighting/Advanced-Lighting.html && sleep 0.5 - curl https://learnopengl.com/Advanced-Lighting/Gamma-Correction --create-dirs -o Advanced-Lighting/Gamma-Correction.html && sleep 0.5 - curl https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping --create-dirs -o Advanced-Lighting/Shadows/Shadow-Mapping.html && sleep 0.5 - curl https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows --create-dirs -o Advanced-Lighting/Shadows/Point-Shadows.html && sleep 0.5 - curl https://learnopengl.com/Advanced-Lighting/Normal-Mapping --create-dirs -o Advanced-Lighting/Normal-Mapping.html && sleep 0.5 - curl https://learnopengl.com/Advanced-Lighting/Parallax-Mapping --create-dirs -o Advanced-Lighting/Parallax-Mapping.html && sleep 0.5 - curl https://learnopengl.com/Advanced-Lighting/HDR --create-dirs -o Advanced-Lighting/HDR.html && sleep 0.5 - curl https://learnopengl.com/Advanced-Lighting/Bloom --create-dirs -o Advanced-Lighting/Bloom.html && sleep 0.5 - curl https://learnopengl.com/Advanced-Lighting/Deferred-Shading --create-dirs -o Advanced-Lighting/Deferred-Shading.html && sleep 0.5 - curl https://learnopengl.com/Advanced-Lighting/SSAO --create-dirs -o Advanced-Lighting/SSAO.html && sleep 0.5 - curl https://learnopengl.com/PBR/Theory --create-dirs -o PBR/Theory.html && sleep 0.5 - curl https://learnopengl.com/PBR/Lighting --create-dirs -o PBR/Lighting.html && sleep 0.5 - curl https://learnopengl.com/PBR/IBL/Diffuse-irradiance --create-dirs -o PBR/IBL/Diffuse-irradiance.html && sleep 0.5 - curl https://learnopengl.com/PBR/IBL/Specular-IBL --create-dirs -o PBR/IBL/Specular-IBL.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/Debugging --create-dirs -o In-Practice/Debugging.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/Text-Rendering --create-dirs -o In-Practice/Text-Rendering.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/2D-Game/Breakout --create-dirs -o In-Practice/2D-Game/Breakout.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/2D-Game/Setting-up --create-dirs -o In-Practice/2D-Game/Setting-up.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites --create-dirs -o In-Practice/2D-Game/Rendering-Sprites.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/2D-Game/Levels --create-dirs -o In-Practice/2D-Game/Levels.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball --create-dirs -o In-Practice/2D-Game/Collisions/Ball.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection --create-dirs -o In-Practice/2D-Game/Collisions/Collision-detection.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution --create-dirs -o In-Practice/2D-Game/Collisions/Collision-resolution.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/2D-Game/Particles --create-dirs -o In-Practice/2D-Game/Particles.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/2D-Game/Postprocessing --create-dirs -o In-Practice/2D-Game/Postprocessing.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/2D-Game/Powerups --create-dirs -o In-Practice/2D-Game/Powerups.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/2D-Game/Audio --create-dirs -o In-Practice/2D-Game/Audio.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/2D-Game/Render-text --create-dirs -o In-Practice/2D-Game/Render-text.html && sleep 0.5 - curl https://learnopengl.com/In-Practice/2D-Game/Final-thoughts --create-dirs -o In-Practice/2D-Game/Final-thoughts.html && sleep 0.5 - curl https://learnopengl.com/Guest-Articles/How-to-publish --create-dirs -o Guest-Articles/How-to-publish.html && sleep 0.5 - curl https://learnopengl.com/Guest-Articles/2020/OIT/Introduction --create-dirs -o Guest-Articles/2020/OIT/Introduction.html && sleep 0.5 - curl https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended --create-dirs -o Guest-Articles/2020/OIT/Weighted-Blended.html && sleep 0.5 - curl https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation --create-dirs -o Guest-Articles/2020/Skeletal-Animation.html && sleep 0.5 - curl https://learnopengl.com/Guest-Articles/2021/CSM --create-dirs -o Guest-Articles/2021/CSM.html && sleep 0.5 - curl https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph --create-dirs -o Guest-Articles/2021/Scene/Scene-Graph.html && sleep 0.5 - curl https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling --create-dirs -o Guest-Articles/2021/Scene/Frustum-Culling.html && sleep 0.5 - curl https://learnopengl.com/Code-repository --create-dirs -o Code-repository.html && sleep 0.5 - curl https://learnopengl.com/Translations --create-dirs -o Translations.html && sleep 0.5 - curl https://learnopengl.com/About --create-dirs -o About.html && sleep 0.5 diff --git a/orig/files b/orig/files @@ -1,71 +0,0 @@ -Introduction.html -Getting-started/OpenGL.html -Getting-started/Creating-a-window.html -Getting-started/Hello-Window.html -Getting-started/Hello-Triangle.html -Getting-started/Shaders.html -Getting-started/Textures.html -Getting-started/Transformations.html -Getting-started/Coordinate-Systems.html -Getting-started/Camera.html -Getting-started/Review.html -Lighting/Colors.html -Lighting/Basic-Lighting.html -Lighting/Materials.html -Lighting/Lighting-maps.html -Lighting/Light-casters.html -Lighting/Multiple-lights.html -Lighting/Review.html -Model-Loading/Assimp.html -Model-Loading/Mesh.html -Model-Loading/Model.html -Advanced-OpenGL/Depth-testing.html -Advanced-OpenGL/Stencil-testing.html -Advanced-OpenGL/Blending.html -Advanced-OpenGL/Face-culling.html -Advanced-OpenGL/Framebuffers.html -Advanced-OpenGL/Cubemaps.html -Advanced-OpenGL/Advanced-Data.html -Advanced-OpenGL/Advanced-GLSL.html -Advanced-OpenGL/Geometry-Shader.html -Advanced-OpenGL/Instancing.html -Advanced-OpenGL/Anti-Aliasing.html -Advanced-Lighting/Advanced-Lighting.html -Advanced-Lighting/Gamma-Correction.html -Advanced-Lighting/Shadows/Shadow-Mapping.html -Advanced-Lighting/Shadows/Point-Shadows.html -Advanced-Lighting/Normal-Mapping.html -Advanced-Lighting/Parallax-Mapping.html -Advanced-Lighting/HDR.html -Advanced-Lighting/Bloom.html -Advanced-Lighting/Deferred-Shading.html -Advanced-Lighting/SSAO.html -PBR/Theory.html -PBR/Lighting.html -PBR/IBL/Diffuse-irradiance.html -PBR/IBL/Specular-IBL.html -In-Practice/Debugging.html -In-Practice/Text-Rendering.html -In-Practice/2D-Game/Breakout.html -In-Practice/2D-Game/Setting-up.html -In-Practice/2D-Game/Rendering-Sprites.html -In-Practice/2D-Game/Levels.html -In-Practice/2D-Game/Collisions/Ball.html -In-Practice/2D-Game/Collisions/Collision-detection.html -In-Practice/2D-Game/Collisions/Collision-resolution.html -In-Practice/2D-Game/Particles.html -In-Practice/2D-Game/Postprocessing.html -In-Practice/2D-Game/Powerups.html -In-Practice/2D-Game/Audio.html -In-Practice/2D-Game/Render-text.html -In-Practice/2D-Game/Final-thoughts.html -Guest-Articles/How-to-publish.html -Guest-Articles/2020/OIT/Introduction.html -Guest-Articles/2020/OIT/Weighted-Blended.html -Guest-Articles/2020/Skeletal-Animation.html -Guest-Articles/2021/CSM.html -Guest-Articles/2021/Scene/Scene-Graph.html -Guest-Articles/2021/Scene/Frustum-Culling.html -Code-repository.html -Translations.html -About.html diff --git a/orig/url b/orig/url @@ -1,71 +0,0 @@ -https://learnopengl.com/Introduction -https://learnopengl.com/Getting-started/OpenGL -https://learnopengl.com/Getting-started/Creating-a-window -https://learnopengl.com/Getting-started/Hello-Window -https://learnopengl.com/Getting-started/Hello-Triangle -https://learnopengl.com/Getting-started/Shaders -https://learnopengl.com/Getting-started/Textures -https://learnopengl.com/Getting-started/Transformations -https://learnopengl.com/Getting-started/Coordinate-Systems -https://learnopengl.com/Getting-started/Camera -https://learnopengl.com/Getting-started/Review -https://learnopengl.com/Lighting/Colors -https://learnopengl.com/Lighting/Basic-Lighting -https://learnopengl.com/Lighting/Materials -https://learnopengl.com/Lighting/Lighting-maps -https://learnopengl.com/Lighting/Light-casters -https://learnopengl.com/Lighting/Multiple-lights -https://learnopengl.com/Lighting/Review -https://learnopengl.com/Model-Loading/Assimp -https://learnopengl.com/Model-Loading/Mesh -https://learnopengl.com/Model-Loading/Model -https://learnopengl.com/Advanced-OpenGL/Depth-testing -https://learnopengl.com/Advanced-OpenGL/Stencil-testing -https://learnopengl.com/Advanced-OpenGL/Blending -https://learnopengl.com/Advanced-OpenGL/Face-culling -https://learnopengl.com/Advanced-OpenGL/Framebuffers -https://learnopengl.com/Advanced-OpenGL/Cubemaps -https://learnopengl.com/Advanced-OpenGL/Advanced-Data -https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL -https://learnopengl.com/Advanced-OpenGL/Geometry-Shader -https://learnopengl.com/Advanced-OpenGL/Instancing -https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing -https://learnopengl.com/Advanced-Lighting/Advanced-Lighting -https://learnopengl.com/Advanced-Lighting/Gamma-Correction -https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping -https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows -https://learnopengl.com/Advanced-Lighting/Normal-Mapping -https://learnopengl.com/Advanced-Lighting/Parallax-Mapping -https://learnopengl.com/Advanced-Lighting/HDR -https://learnopengl.com/Advanced-Lighting/Bloom -https://learnopengl.com/Advanced-Lighting/Deferred-Shading -https://learnopengl.com/Advanced-Lighting/SSAO -https://learnopengl.com/PBR/Theory -https://learnopengl.com/PBR/Lighting -https://learnopengl.com/PBR/IBL/Diffuse-irradiance -https://learnopengl.com/PBR/IBL/Specular-IBL -https://learnopengl.com/In-Practice/Debugging -https://learnopengl.com/In-Practice/Text-Rendering -https://learnopengl.com/In-Practice/2D-Game/Breakout -https://learnopengl.com/In-Practice/2D-Game/Setting-up -https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites -https://learnopengl.com/In-Practice/2D-Game/Levels -https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball -https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection -https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution -https://learnopengl.com/In-Practice/2D-Game/Particles -https://learnopengl.com/In-Practice/2D-Game/Postprocessing -https://learnopengl.com/In-Practice/2D-Game/Powerups -https://learnopengl.com/In-Practice/2D-Game/Audio -https://learnopengl.com/In-Practice/2D-Game/Render-text -https://learnopengl.com/In-Practice/2D-Game/Final-thoughts -https://learnopengl.com/Guest-Articles/How-to-publish -https://learnopengl.com/Guest-Articles/2020/OIT/Introduction -https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended -https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation -https://learnopengl.com/Guest-Articles/2021/CSM -https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph -https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling -https://learnopengl.com/Code-repository -https://learnopengl.com/Translations -https://learnopengl.com/About diff --git a/translation/About.html b/translation/About.html @@ -1,300 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - About</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">About</h1> -<h1 id="content-url" style='display:none;'>About</h1> -<p> - Hi, my name is <a href="http://joeydevries.com" target="_blank">Joey de Vries</a> and I'm the sole author of LearnOpenGL. As you have probably guessed, I'm a computer graphics enthusiast. -</p> - -<p> - Computer graphics have always been a strong interest of mine, either because I enjoyed the visuals of movie effects and video games, or simply liked moving pixels. It wasn't until joining a computer science program at Utrecht University that I started to take programming and computer graphics seriously. I developed a strong eagerness to learn computer graphics (specifically OpenGL) and video game development, honing these skills as a hobby alongside my studies; using a multitude of online tutorials similar to sites like this. However, I always felt that the available online tutorials regarding OpenGL were either incomplete, contained errors or try to be overly complicated for no good reason. Therefore, it's always been in my mind to set up an OpenGL tutorial resource one day that tries to overcome these hurdles. -</p> - -<p> - Around June 2014 I published this website and started generating a large batch of content for everyone to enjoy. I am continuously striving to create one of the best OpenGL resources while also making it easy-to-understand and fun to learn. This means I will try to add as much content as I see fit and frequently revisit old chapters to improve their content. By now I have over 5 years of professional industry experience, both in WebGL/OpenGL and as a AAA game engine developer. This experience translates in continuous improvements on Learn OpenGL as I fix mistakes, improve explanations, and learn better approaches to teaching. Let me know what you think of the site in the comments or by contacting me <a href="mailto:learnopengl.com@gmail.com">personally</a> and let's try to together make this the number one resource for learning modern OpenGL! -</p> - -<p> - All code samples, unless explicitly stated otherwise, are licensed under the terms of the CC BY-NC 4.0 license as published by Creative Commons, either version 4 of the License, or (at your option) any later version. You can find a human-readable format of the license <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank">here</a> and the full license <a href="https://creativecommons.org/licenses/by-nc/4.0/legalcode" target="_blank">here</a>. -</p> - -<p> - Similarly, all images (and videos) are licensed under the terms of the CC BY 4.0 license as published by Creative Commons, either version 4 of the License, or (at your option) any later version. You can find a human-readable format of the license <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank">here</a> and the full license <a href="https://creativecommons.org/licenses/by/4.0/legalcode" target="_blank">here</a>. -</p> - -<p> - When attributing any of the licensed works, please include a copyright notice including a (hyper)link to the copyrighted work and a link to the specific license. Please also mention my full name (Joey de Vries), this website (or a link to the relevant article where applicable), and my personal twitter handle: <a href="https://twitter.com/JoeyDeVriez" target="_blank">https://twitter.com/JoeyDeVriez</a>. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-Lighting/Advanced-Lighting.html b/translation/Advanced-Lighting/Advanced-Lighting.html @@ -1,390 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Advanced Lighting</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Advanced Lighting</h1> -<h1 id="content-url" style='display:none;'>Advanced-Lighting/Advanced-Lighting</h1> -<p> - In the <a href="https://learnopengl.com/Lighting/Basic-Lighting" target="_blank">lighting</a> chapters we briefly introduced the Phong lighting model to bring a basic amount of realism into our scenes. The Phong model looks nice, but has a few nuances we'll focus on in this chapter. -</p> - -<h2>Blinn-Phong</h2> -<p> - Phong lighting is a great and very efficient approximation of lighting, but its specular reflections break down in certain conditions, specifically when the shininess property is low resulting in a large (rough) specular area. The image below shows what happens when we use a specular shininess exponent of <code>1.0</code> on a flat textured plane: -</p> - -<img src="/img/advanced-lighting/advanced_lighting_phong_limit.png" class="clean" alt="Result of Phong specular reflection with low exponent"/> - -<p> - You can see at the edges that the specular area is immediately cut off. The reason this happens is because the angle between the view and reflection vector doesn't go over 90 degrees. If the angle is larger than 90 degrees, the resulting dot product becomes negative and this results in a specular exponent of <code>0.0</code>. You're probably thinking this won't be a problem since we shouldn't get any light with angles higher than 90 degrees anyways, right? -</p> - -<p> - Wrong, this only applies to the diffuse component where an angle higher than 90 degrees between the normal and light source means the light source is below the lighted surface and thus the light's diffuse contribution should equal <code>0.0</code>. However, with specular lighting we're not measuring the angle between the light source and the normal, but between the view and reflection vector. Take a look at the following two images: -</p> - -<img src="/img/advanced-lighting/advanced_lighting_over_90.png" class="clean" alt="Image of Phong's reflection vectors being incorrect when larger than 90 degrees"/> - -<p> - Here the issue should become apparent. The left image shows Phong reflections as familiar, with \(\theta\) being less than 90 degrees. In the right image we can see that the angle \(\theta\) between the view and reflection vector is larger than 90 degrees which as a result nullifies the specular contribution. This generally isn't a problem since the view direction is far from the reflection direction, but if we use a low specular exponent the specular radius is large enough to have a contribution under these conditions. Since we're nullifying this contribution at angles larger than 90 degrees we get the artifact as seen in the first image. -</p> - -<p> - In 1977 the <def>Blinn-Phong</def> shading model was introduced by James F. Blinn as an extension to the Phong shading we've used so far. The Blinn-Phong model is largely similar, but approaches the specular model slightly different which as a result overcomes our problem. Instead of relying on a reflection vector we're using a so called <def>halfway vector</def> that is a unit vector exactly halfway between the view direction and the light direction. The closer this halfway vector aligns with the surface's normal vector, the higher the specular contribution. -</p> - -<img src="/img/advanced-lighting/advanced_lighting_halfway_vector.png" class="clean" alt="Illustration of Blinn-Phong's halfway vector"/> - -<p> - When the view direction is perfectly aligned with the (now imaginary) reflection vector, the halfway vector aligns perfectly with the normal vector. The closer the view direction is to the original reflection direction, the stronger the specular highlight. -</p> - -<p> - Here you can see that whatever direction the viewer looks from, the angle between the halfway vector and the surface normal never exceeds 90 degrees (unless the light is far below the surface of course). The results are slightly different from Phong reflections, but generally more visually plausible, especially with low specular exponents. The Blinn-Phong shading model is also the exact shading model used in the earlier fixed function pipeline of OpenGL. -</p> - -<p> - Getting the halfway vector is easy, we add the light's direction vector and view vector together and normalize the result: -</p> - -\[\bar{H} = \frac{\bar{L} + \bar{V}}{||\bar{L} + \bar{V}||}\] - -<p> - This translates to GLSL code as follows: -</p> - -<pre><code> -vec3 lightDir = normalize(lightPos - FragPos); -vec3 viewDir = normalize(viewPos - FragPos); -vec3 halfwayDir = normalize(lightDir + viewDir); -</code></pre> - -<p> - Then the actual calculation of the specular term becomes a clamped dot product between the surface normal and the halfway vector to get the cosine angle between them that we again raise to a specular shininess exponent: -</p> - -<pre><code> -float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess); -vec3 specular = lightColor * spec; -</code></pre> - -<p> - And there is nothing more to Blinn-Phong than what we just described. The only difference between Blinn-Phong and Phong specular reflection is that we now measure the angle between the normal and halfway vector instead of the angle between the view and reflection vector. -</p> - -<p> - With the introduction of the halfway vector we should no longer have the specular cutoff issue of Phong shading. The image below shows the specular area of both methods with a specular exponent of <code>0.5</code>: -</p> - - -<img src="/img/advanced-lighting/advanced_lighting_comparrison.png" alt="Comparison between Phong and Blinn-Phong shading with a low exponent"/> - -<p> - Another subtle difference between Phong and Blinn-Phong shading is that the angle between the halfway vector and the surface normal is often shorter than the angle between the view and reflection vector. As a result, to get visuals similar to Phong shading the specular shininess exponent has to be set a bit higher. A general rule of thumb is to set it between 2 and 4 times the Phong shininess exponent. -</p> - -<p> - Below is a comparison between both specular reflection models with the Phong exponent set to <code>8.0</code> and the Blinn-Phong component set to <code>32.0</code>: -</p> - -<img src="/img/advanced-lighting/advanced_lighting_comparrison2.png" alt="Comparison between Phong and Blinn-Phong shading with normal exponents"/> - -<p> - You can see that the Blinn-Phong specular exponent is bit sharper compared to Phong. It usually requires a bit of tweaking to get similar results as to what you previously had with Phong shading. It's worth it though as Blinn-Phong shading is generally more realistic compared to default Phong shading. -</p> - -<p> - Here we used a simple fragment shader that switches between regular Phong reflections and Blinn-Phong reflections: -</p> - -<pre><code> -void main() -{ - [...] - float spec = 0.0; - if(blinn) - { - vec3 halfwayDir = normalize(lightDir + viewDir); - spec = pow(max(dot(normal, halfwayDir), 0.0), 16.0); - } - else - { - vec3 reflectDir = reflect(-lightDir, normal); - spec = pow(max(dot(viewDir, reflectDir), 0.0), 8.0); - } -</code></pre> - -<p> - You can find the source code for the simple demo <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/1.advanced_lighting/advanced_lighting.cpp" target="_blank">here</a>. By pressing the <code>b</code> key, the demo switches from Phong to Blinn-Phong lighting and vica versa. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-Lighting/Bloom.html b/translation/Advanced-Lighting/Bloom.html @@ -1,613 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Bloom</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Bloom</h1> -<h1 id="content-url" style='display:none;'>Advanced-Lighting/Bloom</h1> -<p> - Bright light sources and brightly lit regions are often difficult to convey to the viewer as the intensity range of a monitor is limited. One way to distinguish bright light sources on a monitor is by making them glow; the light then <em>bleeds</em> around the light source. This effectively gives the viewer the illusion these light sources or bright regions are intensely bright. -</p> - -<p> - This light bleeding, or glow effect, is achieved with a post-processing effect called <def>Bloom</def>. Bloom gives all brightly lit regions of a scene a glow-like effect. An example of a scene with and without glow can be seen below (image courtesy of Epic Games): -</p> - -<img src="/img/advanced-lighting/bloom_example.png" "Example of Bloom or glow in OpenGL tutorial"/> - -<p> - Bloom gives noticeable visual cues about the brightness of objects. When done in a subtle fashion (which some games drastically fail to do) Bloom significantly boosts the lighting of your scene and allows for a large range of dramatic effects. -</p> - -<p> - Bloom works best in combination with <a href="https://learnopengl.com/Advanced-Lighting/HDR" target="_blank">HDR</a> rendering. A common misconception is that HDR is the same as Bloom as many people use the terms interchangeably. They are however completely different techniques used for different purposes. It is possible to implement Bloom with default 8-bit precision framebuffers, just as it is possible to use HDR without the Bloom effect. It is simply that HDR makes Bloom more effective to implement (as we'll later see). -</p> - -<p> - To implement Bloom, we render a lit scene as usual and extract both the scene's HDR color buffer and an image of the scene with only its bright regions visible. This extracted brightness image is then blurred and the result added on top of the original HDR scene image. -</p> - -<p> - Let's illustrate this process in a step by step fashion. We render a scene filled with 4 bright light sources, visualized as colored cubes. The colored light cubes have a brightness values between <code>1.5</code> and <code>15.0</code>. If we were to render this to an HDR color buffer the scene looks as follows: -</p> - - <img src="/img/advanced-lighting/bloom_scene.png" class="clean" alt="Image of a HDR scene where we need to add the bloom or glow effect in OpenGL"/> - -<p> - We take this HDR color buffer texture and extract all the fragments that exceed a certain brightness. This gives us an image that only show the bright colored regions as their fragment intensities exceeded a certain threshold: -</p> - - <img src="/img/advanced-lighting/bloom_extracted.png" class="clean" alt="Bright regions extracted of a scene for the bloom or glow post-processing effect in OpenGL"/> - -<p> - We then take this thresholded brightness texture and blur the result. The strength of the bloom effect is largely determined by the range and strength of the blur filter used. -</p> - - <img src="/img/advanced-lighting/bloom_blurred.png" class="clean" alt="Bright regions extracted for glow or bloom effect are blurred in OpenGL"/> - -<p> - The resulting blurred texture is what we use to get the glow or light-bleeding effect. This blurred texture is added on top of the original HDR scene texture. Because the bright regions are extended in both width and height due to the blur filter, the bright regions of the scene appear to glow or <em>bleed</em> light. -</p> - -<img src="/img/advanced-lighting/bloom_small.png" class="clean" alt="Example of the Bloom or Glow post-processing effect in OpenGL with HDR"/> - -<p> - Bloom by itself isn't a complicated technique, but difficult to get exactly right. Most of its visual quality is determined by the quality and type of blur filter used for blurring the extracted brightness regions. Simply tweaking the blur filter can drastically change the quality of the Bloom effect. -</p> - -<p> - Following these steps gives us the Bloom post-processing effect. The next image briefly summarizes the required steps for implementing Bloom: -</p> - - <img src="/img/advanced-lighting/bloom_steps.png" class="clean" alt="Steps required for implementing the bloom or glow post-processing effect in OpenGL"/> - -<p> - The first step requires us to extract all the bright colors of a scene based on some threshold. Let's first delve into that. -</p> - -<h2>Extracting bright color</h2> -<p> - The first step requires us to extract two images from a rendered scene. We could render the scene twice, both rendering to a different framebuffer with different shaders, but we can also use a neat little trick called <def>Multiple Render Targets (MRT)</def> that allows us to specify more than one fragment shader output; this gives us the option to extract the first two images in a single render pass. By specifying a layout location specifier before a fragment shader's output we can control to which color buffer a fragment shader writes to: -</p> - -<pre><code> -layout (location = 0) out vec4 FragColor; -layout (location = 1) out vec4 BrightColor; -</code></pre> - -<p> - This only works if we actually have multiple buffers to write to. As a requirement for using multiple fragment shader outputs we need multiple color buffers attached to the currently bound framebuffer object. You may remember from the <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">framebuffers</a> chapter that we can specify a color attachment number when linking a texture as a framebuffer's color buffer. Up until now we've always used <var>GL_COLOR_ATTACHMENT0</var>, but by also using <var>GL_COLOR_ATTACHMENT1</var> we can have two color buffers attached to a framebuffer object: -</p> - -<pre><code> -// set up floating point framebuffer to render scene to -unsigned int hdrFBO; -<function id='76'>glGenFramebuffers</function>(1, &hdrFBO); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, hdrFBO); -unsigned int colorBuffers[2]; -<function id='50'>glGenTextures</function>(2, colorBuffers); -for (unsigned int i = 0; i < 2; i++) -{ - <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, colorBuffers[i]); - <function id='52'>glTexImage2D</function>( - GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL - ); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - // attach texture to framebuffer - <function id='81'>glFramebufferTexture2D</function>( - GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, colorBuffers[i], 0 - ); -} -</code></pre> - -<p> - We do have to explicitly tell OpenGL we're rendering to multiple colorbuffers via <fun>glDrawBuffers</fun>. OpenGL, by default, only renders to a framebuffer's first color attachment, ignoring all others. We can do this by passing an array of color attachment enums that we'd like to render to in subsequent operations: -</p> - -<pre><code> -unsigned int attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; -glDrawBuffers(2, attachments); -</code></pre> - -<p> - When rendering into this framebuffer, whenever a fragment shader uses the layout location specifier, the respective color buffer is used to render the fragment to. This is great as this saves us an extra render pass for extracting bright regions as we can now directly extract them from the to-be-rendered fragment: -</p> - -<pre><code> -#version 330 core -layout (location = 0) out vec4 FragColor; -layout (location = 1) out vec4 BrightColor; - -[...] - -void main() -{ - [...] // first do normal lighting calculations and output results - FragColor = vec4(lighting, 1.0); - // check whether fragment output is higher than threshold, if so output as brightness color - float brightness = dot(FragColor.rgb, vec3(0.2126, 0.7152, 0.0722)); - if(brightness > 1.0) - BrightColor = vec4(FragColor.rgb, 1.0); - else - BrightColor = vec4(0.0, 0.0, 0.0, 1.0); -} -</code></pre> - -<p> - Here we first calculate lighting as normal and pass it to the first fragment shader's output variable <var>FragColor</var>. Then we use what is currently stored in <var>FragColor</var> to determine if its brightness exceeds a certain threshold. We calculate the brightness of a fragment by properly transforming it to grayscale first (by taking the dot product of both vectors we effectively multiply each individual component of both vectors and add the results together). If the brightness exceeds a certain threshold, we output the color to the second color buffer. We do the same for the light cubes. -</p> - -<p> - This also shows why Bloom works incredibly well with HDR rendering. Because we render in high dynamic range, color values can exceed <code>1.0</code> which allows us to specify a brightness threshold outside the default range, giving us much more control over what is considered bright. Without HDR we'd have to set the threshold lower than <code>1.0</code>, which is still possible, but regions are much quicker considered bright. This sometimes leads to the glow effect becoming too dominant (think of white glowing snow for example). -</p> - -<p> - With these two color buffers we have an image of the scene as normal, and an image of the extracted bright regions; all generated in a single render pass. -</p> - - <img src="/img/advanced-lighting/bloom_attachments.png" alt="Image of two colorbuffers obtained from a single render pass with multiple color attachments for the bloom or glow effect in OpenGL"/> - -<p> - With an image of the extracted bright regions we now need to blur the image. We can do this with a simple box filter as we've done in the post-processing section of the framebufers chapter, but we'd rather use a more advanced (and better-looking) blur filter called <def>Gaussian blur</def>. -</p> - -<h2>Gaussian blur</h2> -<p> - In the post-processing chapter's blur we took the average of all surrounding pixels of an image. While it does give us an easy blur, it doesn't give the best results. A Gaussian blur is based on the Gaussian curve which is commonly described as a <em>bell-shaped curve</em> giving high values close to its center that gradually wear off over distance. The Gaussian curve can be mathematically represented in different forms, but generally has the following shape: -</p> - - <img src="/img/advanced-lighting/bloom_gaussian.png" class="clean" alt="Image of a Gaussian Curve used for blurring a bloom or glow image in OpenGL"/> - -<p> - As the Gaussian curve has a larger area close to its center, using its values as weights to blur an image give more natural results as samples close by have a higher precedence. If we for instance sample a 32x32 box around a fragment, we use progressively smaller weights the larger the distance to the fragment; this gives a better and more realistic blur which is known as a <def>Gaussian blur</def>. -</p> - -<p> - To implement a Gaussian blur filter we'd need a two-dimensional box of weights that we can obtain from a 2 dimensional Gaussian curve equation. The problem with this approach however is that it quickly becomes extremely heavy on performance. Take a blur kernel of 32 by 32 for example, this would require us to sample a texture a total of 1024 times for each fragment! -</p> - -<p> - Luckily for us, the Gaussian equation has a very neat property that allows us to separate the two-dimensional equation into two smaller one-dimensional equations: one that describes the horizontal weights and the other that describes the vertical weights. We'd then first do a horizontal blur with the horizontal weights on the scene texture, and then on the resulting texture do a vertical blur. Due to this property the results are exactly the same, but this time saving us an incredible amount of performance as we'd now only have to do 32 + 32 samples compared to 1024! This is known as <def>two-pass Gaussian blur</def>. - </p> - - <img src="/img/advanced-lighting/bloom_gaussian_two_pass.png" class="clean" alt="Image of two-pass Gaussian blur with the same results as normal gaussian blur, but now saving a lot of performance in OpenGL"/> - -<p> - This does mean we need to blur an image at least two times and this works best with the use of framebuffer objects. Specifically for the two-pass Gaussian blur we're going to implement <em>ping-pong</em> framebuffers. That is a pair of framebuffers where we render and swap, a given number of times, the other framebuffer's color buffer into the current framebuffer's color buffer with an alternating shader effect. We basically continuously switch the framebuffer to render to and the texture to draw with. This allows us to first blur the scene's texture in the first framebuffer, then blur the first framebuffer's color buffer into the second framebuffer, and then the second framebuffer's color buffer into the first, and so on. -</p> - -<p> - Before we delve into the framebuffers let's first discuss the Gaussian blur's fragment shader: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec2 TexCoords; - -uniform sampler2D image; - -uniform bool horizontal; -uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); - -void main() -{ - vec2 tex_offset = 1.0 / textureSize(image, 0); // gets size of single texel - vec3 result = texture(image, TexCoords).rgb * weight[0]; // current fragment's contribution - if(horizontal) - { - for(int i = 1; i < 5; ++i) - { - result += texture(image, TexCoords + vec2(tex_offset.x * i, 0.0)).rgb * weight[i]; - result += texture(image, TexCoords - vec2(tex_offset.x * i, 0.0)).rgb * weight[i]; - } - } - else - { - for(int i = 1; i < 5; ++i) - { - result += texture(image, TexCoords + vec2(0.0, tex_offset.y * i)).rgb * weight[i]; - result += texture(image, TexCoords - vec2(0.0, tex_offset.y * i)).rgb * weight[i]; - } - } - FragColor = vec4(result, 1.0); -} -</code></pre> - -<p> - Here we take a relatively small sample of Gaussian weights that we each use to assign a specific weight to the horizontal or vertical samples around the current fragment. You can see that we split the blur filter into a horizontal and vertical section based on whatever value we set the <var>horizontal</var> uniform. We base the offset distance on the exact size of a texel obtained by the division of <code>1.0</code> over the size of the texture (a <code>vec2</code> from <fun>textureSize</fun>). -</p> - -<p> - For blurring an image we create two basic framebuffers, each with only a color buffer texture: -</p> - -<pre><code> -unsigned int pingpongFBO[2]; -unsigned int pingpongBuffer[2]; -<function id='76'>glGenFramebuffers</function>(2, pingpongFBO); -<function id='50'>glGenTextures</function>(2, pingpongBuffer); -for (unsigned int i = 0; i < 2; i++) -{ - <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, pingpongFBO[i]); - <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, pingpongBuffer[i]); - <function id='52'>glTexImage2D</function>( - GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL - ); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - <function id='81'>glFramebufferTexture2D</function>( - GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongBuffer[i], 0 - ); -} -</code></pre> - -<p> - Then after we've obtained an HDR texture and an extracted brightness texture, we first fill one of the ping-pong framebuffers with the brightness texture and then blur the image 10 times (5 times horizontally and 5 times vertically): -</p> - -<pre><code> -bool horizontal = true, first_iteration = true; -int amount = 10; -shaderBlur.use(); -for (unsigned int i = 0; i < amount; i++) -{ - <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, pingpongFBO[horizontal]); - shaderBlur.setInt("horizontal", horizontal); - <function id='48'>glBindTexture</function>( - GL_TEXTURE_2D, first_iteration ? colorBuffers[1] : pingpongBuffers[!horizontal] - ); - RenderQuad(); - horizontal = !horizontal; - if (first_iteration) - first_iteration = false; -} -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -</code></pre> - -<p> - Each iteration we bind one of the two framebuffers based on whether we want to blur horizontally or vertically and bind the other framebuffer's color buffer as the texture to blur. The first iteration we specifically bind the texture we'd like to blur (<var>brightnessTexture</var>) as both color buffers would else end up empty. By repeating this process 10 times, the brightness image ends up with a complete Gaussian blur that was repeated 5 times. This construct allows us to blur any image as often as we'd like; the more Gaussian blur iterations, the stronger the blur. -</p> - -<p> - By blurring the extracted brightness texture 5 times, we get a properly blurred image of all bright regions of a scene. -</p> - - <img src="/img/advanced-lighting/bloom_blurred_large.png" class="clean" alt="Blurred image using Gaussian Blur of extracted brightness regions for the glow or bloom effect in OpenGL"/> - -<p> - The last step to complete the Bloom effect is to combine this blurred brightness texture with the original scene's HDR texture. -</p> - -<h2>Blending both textures</h2> -<p> - With the scene's HDR texture and a blurred brightness texture of the scene we only need to combine the two to achieve the infamous Bloom or glow effect. In the final fragment shader (largely similar to the one we used in the <a href="https://learnopengl.com/Advanced-Lighting/HDR" target="_blank">HDR</a> chapter) we additively blend both textures: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec2 TexCoords; - -uniform sampler2D scene; -uniform sampler2D bloomBlur; -uniform float exposure; - -void main() -{ - const float gamma = 2.2; - vec3 hdrColor = texture(scene, TexCoords).rgb; - vec3 bloomColor = texture(bloomBlur, TexCoords).rgb; - hdrColor += bloomColor; // additive blending - // tone mapping - vec3 result = vec3(1.0) - exp(-hdrColor * exposure); - // also gamma correct while we're at it - result = pow(result, vec3(1.0 / gamma)); - FragColor = vec4(result, 1.0); -} -</code></pre> - -<p> - Interesting to note here is that we add the Bloom effect before we apply tone mapping. This way, the added brightness of bloom is also softly transformed to LDR range with better relative lighting as a result. -</p> - -<p> - With both textures added together, all bright areas of our scene now get a proper glow effect: -</p> - - <img src="/img/advanced-lighting/bloom.png" class="clean" alt="Example of the Bloom or Glow post-processing effect in OpenGL with HDR"/> - -<p> - The colored cubes now appear much more bright and give a better illusion as light emitting objects. This is a relatively simple scene so the Bloom effect isn't too impressive here, but in well lit scenes it can make a significant difference when properly configured. You can find the source code of this simple demo <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/7.bloom/bloom.cpp" target="_blank">here</a>. -</p> - -<p> - For this chapter we used a relatively simple Gaussian blur filter where we only take 5 samples in each direction. By taking more samples along a larger radius or repeating the blur filter an extra number of times we can improve the blur effect. As the quality of the blur directly correlates to the quality of the Bloom effect, improving the blur step can make a significant improvement. Some of these improvements combine blur filters with varying sized blur kernels or use multiple Gaussian curves to selectively combine weights. The additional resources from Kalogirou and Epic Games discuss how to significantly improve the Bloom effect by improving the Gaussian blur. -</p> - -<h2>Additional resources</h2> -<ul> - <li><a href="http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/" target="_blank">Efficient Gaussian Blur with linear sampling</a>: descirbes the Gaussian blur very well and how to improve its performance using OpenGL's bilinear texture sampling.</li> - <li><a href="https://udk-legacy.unrealengine.com/udk/Three/Bloom.html" target="_blank">Bloom Post Process Effect</a>: article from Epic Games about improving the Bloom effect by combining multiple Gaussian curves for its weights.</li> - <li><a href="http://kalogirou.net/2006/05/20/how-to-do-good-bloom-for-hdr-rendering/" target="_blank">How to do good Bloom for HDR rendering</a>: Article from Kalogirou that describes how to improve the Bloom effect using a better Gaussian blur method.</li> - </ul> - - - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-Lighting/Deferred-Shading.html b/translation/Advanced-Lighting/Deferred-Shading.html @@ -1,779 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Deferred Shading</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Deferred Shading</h1> -<h1 id="content-url" style='display:none;'>Advanced-Lighting/Deferred-Shading</h1> -<p> - The way we did lighting so far was called <def>forward rendering</def> or <def>forward shading</def>. A straightforward approach where we render an object and light it according to all light sources in a scene. We do this for every object individually for each object in the scene. While quite easy to understand and implement it is also quite heavy on performance as each rendered object has to iterate over each light source for every rendered fragment, which is a lot! Forward rendering also tends to waste a lot of fragment shader runs in scenes with a high depth complexity (multiple objects cover the same screen pixel) as fragment shader outputs are overwritten. -</p> - -<p> - <def>Deferred shading</def> or <def>deferred rendering</def> aims to overcome these issues by drastically changing the way we render objects. This gives us several new options to significantly optimize scenes with large numbers of lights, allowing us to render hundreds (or even thousands) of lights with an acceptable framerate. The following image is a scene with 1847 point lights rendered with deferred shading (image courtesy of Hannes Nevalainen); something that wouldn't be possible with forward rendering. -</p> - -<img src="/img/advanced-lighting/deferred_example.png" alt="Example of the power of deferred shading in OpenGL as we can easily render 1000s lights with an acceptable framerate"/> - -<p> - Deferred shading is based on the idea that we <em>defer</em> or <em>postpone</em> most of the heavy rendering (like lighting) to a later stage. Deferred shading consists of two passes: in the first pass, called the <def>geometry pass</def>, we render the scene once and retrieve all kinds of geometrical information from the objects that we store in a collection of textures called the <def>G-buffer</def>; think of position vectors, color vectors, normal vectors, and/or specular values. The geometric information of a scene stored in the <def>G-buffer</def> is then later used for (more complex) lighting calculations. Below is the content of a G-buffer of a single frame: -</p> - - <img src="/img/advanced-lighting/deferred_g_buffer.png" alt="An example of a G-Buffer filled with geometrical data of a scene in OpenGL"/> - -<p> - We use the textures from the G-buffer in a second pass called the <def>lighting pass</def> where we render a screen-filled quad and calculate the scene's lighting for each fragment using the geometrical information stored in the G-buffer; pixel by pixel we iterate over the G-buffer. Instead of taking each object all the way from the vertex shader to the fragment shader, we decouple its advanced fragment processes to a later stage. The lighting calculations are exactly the same, but this time we take all required input variables from the corresponding G-buffer textures, instead of the vertex shader (plus some uniform variables). -</p> - -<p> - The image below nicely illustrates the process of deferred shading. -</p> - - <img src="/img/advanced-lighting/deferred_overview.png" class="clean" alt="Overview of the deferred shading technique in OpenGL"/> - -<p> - A major advantage of this approach is that whatever fragment ends up in the G-buffer is the actual fragment information that ends up as a screen pixel. The depth test already concluded this fragment to be the last and top-most fragment. This ensures that for each pixel we process in the lighting pass, we only calculate lighting once. Furthermore, deferred rendering opens up the possibility for further optimizations that allow us to render a much larger amount of light sources compared to forward rendering. -</p> - -<p> - It also comes with some disadvantages though as the G-buffer requires us to store a relatively large amount of scene data in its texture color buffers. This eats memory, especially since scene data like position vectors require a high precision. Another disadvantage is that it doesn't support blending (as we only have information of the top-most fragment) and MSAA no longer works. There are several workarounds for this that we'll get to at the end of the chapter. -</p> - -<p> - Filling the G-buffer (in the geometry pass) isn't too expensive as we directly store object information like position, color, or normals into a framebuffer with a small or zero amount of processing. By using <def>multiple render targets</def> (MRT) we can even do all of this in a single render pass. -</p> - -<h2>The G-buffer</h2> -<p> - The <def>G-buffer</def> is the collective term of all textures used to store lighting-relevant data for the final lighting pass. Let's take this moment to briefly review all the data we need to light a fragment with forward rendering: -</p> - -<ul> - <li>A 3D world-space <strong>position</strong> vector to calculate the (interpolated) fragment position variable used for <var>lightDir</var> and <var>viewDir</var>. </li> - <li>An RGB diffuse <strong>color</strong> vector also known as <def>albedo</def>.</li> - <li>A 3D <strong>normal</strong> vector for determining a surface's slope.</li> - <li>A <strong>specular intensity</strong> float.</li> - <li>All light source position and color vectors.</li> - <li>The player or viewer's position vector.</li> -</ul> - -<p> - With these (per-fragment) variables at our disposal we are able to calculate the (Blinn-)Phong lighting we're accustomed to. The light source positions and colors, and the player's view position, can be configured using uniform variables, but the other variables are all fragment specific. If we can somehow pass the exact same data to the final deferred lighting pass we can calculate the same lighting effects, even though we're rendering fragments of a 2D quad. -</p> - -<p> - There is no limit in OpenGL to what we can store in a texture so it makes sense to store all per-fragment data in one or multiple screen-filled textures of the G-buffer and use these later in the lighting pass. As the G-buffer textures will have the same size as the lighting pass's 2D quad, we get the exact same fragment data we'd had in a forward rendering setting, but this time in the lighting pass; there is a one on one mapping. -</p> - -<p> - In pseudocode the entire process will look a bit like this: -</p> - -<pre><code> -while(...) // render loop -{ - // 1. geometry pass: render all geometric/color data to g-buffer - <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, gBuffer); - <function id='13'><function id='10'>glClear</function>Color</function>(0.0, 0.0, 0.0, 1.0); // keep it black so it doesn't leak into g-buffer - <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - gBufferShader.use(); - for(Object obj : Objects) - { - ConfigureShaderTransformsAndUniforms(); - obj.Draw(); - } - // 2. lighting pass: use g-buffer to calculate the scene's lighting - <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); - lightingPassShader.use(); - BindAllGBufferTextures(); - SetLightingUniforms(); - RenderQuad(); -} -</code></pre> - -<p> - The data we'll need to store of each fragment is a <strong>position</strong> vector, a <strong>normal</strong> vector, a <strong>color</strong> vector, and a <strong>specular intensity</strong> value. In the geometry pass we need to render all objects of the scene and store these data components in the G-buffer. We can again use <def>multiple render targets</def> to render to multiple color buffers in a single render pass; this was briefly discussed in the <a href="https://learnopengl.com/Advanced-Lighting/Bloom" target="_blank">Bloom</a> chapter. -</p> - -<p> - For the geometry pass we'll need to initialize a framebuffer object that we'll call <var>gBuffer</var> that has multiple color buffers attached and a single depth renderbuffer object. For the position and normal texture we'd preferably use a high-precision texture (16 or 32-bit float per component). For the albedo and specular values we'll be fine with the default texture precision (8-bit precision per component). Note that we use <var>GL_RGBA16F</var> over <var>GL_RGB16F</var> as GPUs generally prefer 4-component formats over 3-component formats due to byte alignment; some drivers may fail to complete the framebuffer otherwise. -</p> - -<pre><code> -unsigned int gBuffer; -<function id='76'>glGenFramebuffers</function>(1, &gBuffer); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, gBuffer); -unsigned int gPosition, gNormal, gColorSpec; - -// - position color buffer -<function id='50'>glGenTextures</function>(1, &gPosition); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gPosition); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); -<function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0); - -// - normal color buffer -<function id='50'>glGenTextures</function>(1, &gNormal); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gNormal); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); -<function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0); - -// - color + specular color buffer -<function id='50'>glGenTextures</function>(1, &gAlbedoSpec); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gAlbedoSpec); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); -<function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0); - -// - tell OpenGL which color attachments we'll use (of this framebuffer) for rendering -unsigned int attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }; -glDrawBuffers(3, attachments); - -// then also add render buffer object as depth buffer and check for completeness. -[...] -</code></pre> - -<p> - Since we use multiple render targets, we have to explicitly tell OpenGL which of the color buffers associated with <var>GBuffer</var> we'd like to render to with <fun>glDrawBuffers</fun>. Also interesting to note here is we combine the color and specular intensity data in a single <code>RGBA</code> texture; this saves us from having to declare an additional color buffer texture. As your deferred shading pipeline gets more complex and needs more data you'll quickly find new ways to combine data in individual textures. -</p> - -<p> - Next we need to render into the G-buffer. Assuming each object has a diffuse, normal, and specular texture we'd use something like the following fragment shader to render into the G-buffer: -</p> - -<pre><code> -#version 330 core -layout (location = 0) out vec3 gPosition; -layout (location = 1) out vec3 gNormal; -layout (location = 2) out vec4 gAlbedoSpec; - -in vec2 TexCoords; -in vec3 FragPos; -in vec3 Normal; - -uniform sampler2D texture_diffuse1; -uniform sampler2D texture_specular1; - -void main() -{ - // store the fragment position vector in the first gbuffer texture - gPosition = FragPos; - // also store the per-fragment normals into the gbuffer - gNormal = normalize(Normal); - // and the diffuse per-fragment color - gAlbedoSpec.rgb = texture(texture_diffuse1, TexCoords).rgb; - // store specular intensity in gAlbedoSpec's alpha component - gAlbedoSpec.a = texture(texture_specular1, TexCoords).r; -} -</code></pre> - -<p> - As we use multiple render targets, the layout specifier tells OpenGL to which color buffer of the active framebuffer we render to. Note that we do not store the specular intensity into a single color buffer texture as we can store its single float value in the alpha component of one of the other color buffer textures. -</p> - - -<warning> - Keep in mind that with lighting calculations it is extremely important to keep all relevant variables in the same coordinate space. In this case we store (and calculate) all variables in world-space. -</warning> - -<p> - If we'd now were to render a large collection of backpack objects into the <var>gBuffer</var> framebuffer and visualize its content by projecting each color buffer one by one onto a screen-filled quad we'd see something like this: -</p> - - <img src="/img/advanced-lighting/deferred_g_buffer.png" alt="Image of a G-Buffer in OpenGL with several backpacks"/> - -<p> - Try to visualize that the world-space position and normal vectors are indeed correct. For instance, the normal vectors pointing to the right would be more aligned to a red color, similarly for position vectors that point from the scene's origin to the right. As soon as you're satisfied with the content of the G-buffer it's time to move to the next step: the lighting pass. -</p> - -<h2>The deferred lighting pass</h2> -<p> - With a large collection of fragment data in the G-Buffer at our disposal we have the option to completely calculate the scene's final lit colors. We do this by iterating over each of the G-Buffer textures pixel by pixel and use their content as input to the lighting algorithms. Because the G-buffer texture values all represent the final transformed fragment values we only have to do the expensive lighting operations once per pixel. This is especially useful in complex scenes where we'd easily invoke multiple expensive fragment shader calls per pixel in a forward rendering setting. -</p> - -<p> - For the lighting pass we're going to render a 2D screen-filled quad (a bit like a post-processing effect) and execute an expensive lighting fragment shader on each pixel: -</p> - -<pre><code> -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -<function id='49'>glActiveTexture</function>(GL_TEXTURE0); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gPosition); -<function id='49'>glActiveTexture</function>(GL_TEXTURE1); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gNormal); -<function id='49'>glActiveTexture</function>(GL_TEXTURE2); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gAlbedoSpec); -// also send light relevant uniforms -shaderLightingPass.use(); -SendAllLightUniformsToShader(shaderLightingPass); -shaderLightingPass.setVec3("viewPos", camera.Position); -RenderQuad(); -</code></pre> - -<p> - We bind all relevant textures of the G-buffer before rendering and also send the lighting-relevant uniform variables to the shader. -</p> - -<p> - The fragment shader of the lighting pass is largely similar to the lighting chapter shaders we've used so far. What is new is the method in which we obtain the lighting's input variables, which we now directly sample from the G-buffer: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec2 TexCoords; - -uniform sampler2D gPosition; -uniform sampler2D gNormal; -uniform sampler2D gAlbedoSpec; - -struct Light { - vec3 Position; - vec3 Color; -}; -const int NR_LIGHTS = 32; -uniform Light lights[NR_LIGHTS]; -uniform vec3 viewPos; - -void main() -{ - // retrieve data from G-buffer - vec3 FragPos = texture(gPosition, TexCoords).rgb; - vec3 Normal = texture(gNormal, TexCoords).rgb; - vec3 Albedo = texture(gAlbedoSpec, TexCoords).rgb; - float Specular = texture(gAlbedoSpec, TexCoords).a; - - // then calculate lighting as usual - vec3 lighting = Albedo * 0.1; // hard-coded ambient component - vec3 viewDir = normalize(viewPos - FragPos); - for(int i = 0; i < NR_LIGHTS; ++i) - { - // diffuse - vec3 lightDir = normalize(lights[i].Position - FragPos); - vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Albedo * lights[i].Color; - lighting += diffuse; - } - - FragColor = vec4(lighting, 1.0); -} -</code></pre> - -<p> - The lighting pass shader accepts 3 uniform textures that represent the G-buffer and hold all the data we've stored in the geometry pass. If we were to sample these with the current fragment's texture coordinates we'd get the exact same fragment values as if we were rendering the geometry directly. Note that we retrieve both the <var>Albedo</var> color and the <var>Specular</var> intensity from the single <var>gAlbedoSpec</var> texture. -</p> - -<p> - As we now have the per-fragment variables (and the relevant uniform variables) necessary to calculate Blinn-Phong lighting, we don't have to make any changes to the lighting code. The only thing we change in deferred shading here is the method of obtaining lighting input variables. -</p> - -<p> - Running a simple demo with a total of <code>32</code> small lights looks a bit like this: -</p> - - <img src="/img/advanced-lighting/deferred_shading.png" class="clean" alt="Example of Deferred Shading in OpenGL"/> - -<p> - One of the disadvantages of deferred shading is that it is not possible to do <a href="https://learnopengl.com/Advanced-OpenGL/Blending" target="_blank">blending</a> as all values in the G-buffer are from single fragments, and blending operates on the combination of multiple fragments. Another disadvantage is that deferred shading forces you to use the same lighting algorithm for most of your scene's lighting; you can somehow alleviate this a bit by including more material-specific data in the G-buffer. -</p> - -<p> - To overcome these disadvantages (especially blending) we often split the renderer into two parts: one deferred rendering part, and the other a forward rendering part specifically meant for blending or special shader effects not suited for a deferred rendering pipeline. To illustrate how this works, we'll render the light sources as small cubes using a forward renderer as the light cubes require a special shader (simply output a single light color). -</p> - -<h2>Combining deferred rendering with forward rendering</h2> -<p> - Say we want to render each of the light sources as a 3D cube positioned at the light source's position emitting the color of the light. A first idea that comes to mind is to simply forward render all the light sources on top of the deferred lighting quad at the end of the deferred shading pipeline. So basically render the cubes as we'd normally do, but only after we've finished the deferred rendering operations. In code this will look a bit like this: -</p> - -<pre><code> -// deferred lighting pass -[...] -RenderQuad(); - -// now render all light cubes with forward rendering as we'd normally do -shaderLightBox.use(); -shaderLightBox.setMat4("projection", projection); -shaderLightBox.setMat4("view", view); -for (unsigned int i = 0; i < lightPositions.size(); i++) -{ - model = glm::mat4(1.0f); - model = <function id='55'>glm::translate</function>(model, lightPositions[i]); - model = <function id='56'>glm::scale</function>(model, glm::vec3(0.25f)); - shaderLightBox.setMat4("model", model); - shaderLightBox.setVec3("lightColor", lightColors[i]); - RenderCube(); -} -</code></pre> - -<p> - However, these rendered cubes do not take any of the stored geometry depth of the deferred renderer into account and are, as a result, always rendered on top of the previously rendered objects; this isn't the result we were looking for. -</p> - - <img src="/img/advanced-lighting/deferred_lights_no_depth.png" class="clean" alt="Image of deferred rendering with forward rendering where we didn't copy depth buffer data and lights are rendered on top of all geometry in OpenGL"/> - -<p> - What we need to do, is first copy the depth information stored in the geometry pass into the default framebuffer's depth buffer and only then render the light cubes. This way the light cubes' fragments are only rendered when on top of the previously rendered geometry. -</p> - -<p> - We can copy the content of a framebuffer to the content of another framebuffer with the help of <fun><function id='103'>glBlitFramebuffer</function></fun>, a function we also used in the <a href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing" target="_blank">anti-aliasing</a> chapter to resolve multisampled framebuffers. The <fun><function id='103'>glBlitFramebuffer</function></fun> function allows us to copy a user-defined region of a framebuffer to a user-defined region of another framebuffer. -</p> - -<p> - We stored the depth of all the objects rendered in the deferred geometry pass in the <var>gBuffer</var> FBO. If we were to copy the content of its depth buffer to the depth buffer of the default framebuffer, the light cubes would then render as if all of the scene's geometry was rendered with forward rendering. As briefly explained in the anti-aliasing chapter, we have to specify a framebuffer as the read framebuffer and similarly specify a framebuffer as the write framebuffer: -</p> - -<pre><code> -<function id='77'>glBindFramebuffer</function>(GL_READ_FRAMEBUFFER, gBuffer); -<function id='77'>glBindFramebuffer</function>(GL_DRAW_FRAMEBUFFER, 0); // write to default framebuffer -<function id='103'>glBlitFramebuffer</function>( - 0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST -); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -// now render light cubes as before -[...] -</code></pre> - -<p> - Here we copy the entire read framebuffer's depth buffer content to the default framebuffer's depth buffer; this can similarly be done for color buffers and stencil buffers. If we then render the light cubes, the cubes indeed render correctly over the scene's geometry: -</p> - - - <img src="/img/advanced-lighting/deferred_lights_depth.png" class="clean" alt="Image of deferred rendering with forward rendering where we copied the depth buffer data and lights are rendered properly with all geometry in OpenGL"/> - -<p> - You can find the full source code of the demo <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/8.1.deferred_shading/deferred_shading.cpp" target="_blank">here</a>. -</p> - -<p> - With this approach we can easily combine deferred shading with forward shading. This is great as we can now still apply blending and render objects that require special shader effects, something that isn't possible in a pure deferred rendering context. -</p> - -<h2>A larger number of lights</h2> -<p> - What deferred rendering is often praised for, is its ability to render an enormous amount of light sources without a heavy cost on performance. Deferred rendering by itself doesn't allow for a very large amount of light sources as we'd still have to calculate each fragment's lighting component for each of the scene's light sources. What makes a large amount of light sources possible is a very neat optimization we can apply to the deferred rendering pipeline: that of <def>light volumes</def>. -</p> - -<p> - Normally when we render a fragment in a large lit scene we'd calculate the contribution of <strong>each</strong> light source in a scene, regardless of their distance to the fragment. A large portion of these light sources will never reach the fragment, so why waste all these lighting computations? -</p> - -<p> - The idea behind light volumes is to calculate the radius, or volume, of a light source i.e. the area where its light is able to reach fragments. As most light sources use some form of attenuation, we can use that to calculate the maximum distance or radius their light is able to reach. We then only do the expensive lighting calculations if a fragment is inside one or more of these light volumes. This can save us a considerable amount of computation as we now only calculate lighting where it's necessary. -</p> - -<p> - The trick to this approach is mostly figuring out the size or radius of the light volume of a light source. -</p> - -<h3>Calculating a light's volume or radius</h3> -<p> - To obtain a light's volume radius we have to solve the attenuation equation for when its light contribution becomes <code>0.0</code>. For the attenuation function we'll use the function introduced in the <a href="https://learnopengl.com/Lighting/Light-casters" target="_blank">light casters</a> chapter: -</p> - - \[F_{light} = \frac{I}{K_c + K_l * d + K_q * d^2}\] - -<p> - What we want to do is solve this equation for when \(F_{light}\) is <code>0.0</code>. However, this equation will never exactly reach the value <code>0.0</code>, so there won't be a solution. What we can do however, is not solve the equation for <code>0.0</code>, but solve it for a brightness value that is close to <code>0.0</code> but still perceived as dark. The brightness value of \(5/256\) would be acceptable for this chapter's demo scene; divided by 256 as the default 8-bit framebuffer can only display that many intensities per component. -</p> - -<note> - The attenuation function used is mostly dark in its visible range. If we were to limit it to an even darker brightness than \(5/256\), the light volume would become too large and thus less effective. As long as a user cannot see a sudden cut-off of a light source at its volume borders we'll be fine. Of course this always depends on the type of scene; a higher brightness threshold results in smaller light volumes and thus a better efficiency, but can produce noticeable artifacts where lighting seems to break at a volume's borders. -</note> - -<p> - The attenuation equation we have to solve becomes: -</p> - - \[\frac{5}{256} = \frac{I_{max}}{Attenuation}\] - -<p> - Here \(I_{max}\) is the light source's brightest color component. We use a light source's brightest color component as solving the equation for a light's brightest intensity value best reflects the ideal light volume radius. -</p> - -<p> - From here on we continue solving the equation: -</p> - - \[\frac{5}{256} * Attenuation = I_{max} \] - - \[5 * Attenuation = I_{max} * 256 \] - - \[Attenuation = I_{max} * \frac{256}{5} \] - - \[K_c + K_l * d + K_q * d^2 = I_{max} * \frac{256}{5} \] - - \[K_q * d^2 + K_l * d + K_c - I_{max} * \frac{256}{5} = 0 \] - -<p> - The last equation is an equation of the form \(ax^2 + bx + c = 0\), which we can solve using the quadratic equation: -</p> - - \[x = \frac{-K_l + \sqrt{K_l^2 - 4 * K_q * (K_c - I_{max} * \frac{256}{5})}}{2 * K_q} \] - -<p> - This gives us a general equation that allows us to calculate \(x\) i.e. the light volume's radius for the light source given a constant, linear, and quadratic parameter: -</p> - -<pre><code> -float constant = 1.0; -float linear = 0.7; -float quadratic = 1.8; -float lightMax = std::fmaxf(std::fmaxf(lightColor.r, lightColor.g), lightColor.b); -float radius = - (-linear + std::sqrtf(linear * linear - 4 * quadratic * (constant - (256.0 / 5.0) * lightMax))) - / (2 * quadratic); -</code></pre> - -<p> - We calculate this radius for each light source of the scene and use it to only calculate lighting for that light source if a fragment is inside the light source's volume. Below is the updated lighting pass fragment shader that takes the calculated light volumes into account. Note that this approach is merely done for teaching purposes and not viable in a practical setting as we'll soon discuss: -</p> - -<pre><code> -struct Light { - [...] - float Radius; -}; - -void main() -{ - [...] - for(int i = 0; i < NR_LIGHTS; ++i) - { - // calculate distance between light source and current fragment - float distance = length(lights[i].Position - FragPos); - if(distance < lights[i].Radius) - { - // do expensive lighting - [...] - } - } -} -</code></pre> - -<p> - The results are exactly the same as before, but this time each light only calculates lighting for the light sources in which volume it resides. -</p> - -<p> - You can find the final source code of the demo <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/8.2.deferred_shading_volumes/deferred_shading_volumes.cpp" target="_blank">here</a>. -</p> - -<h3>How we really use light volumes</h3> -<p> - The fragment shader shown above doesn't really work in practice and only illustrates how we can <em>sort of</em> use a light's volume to reduce lighting calculations. The reality is that your GPU and GLSL are pretty bad at optimizing loops and branches. The reason for this is that shader execution on the GPU is highly parallel and most architectures have a requirement that for large collection of threads they need to run the exact same shader code for it to be efficient. This often means that a shader is run that executes <strong>all</strong> branches of an <code>if</code> statement to ensure the shader runs are the same for that group of threads, making our previous <em>radius check</em> optimization completely useless; we'd still calculate lighting for all light sources! -</p> - -<p> - The appropriate approach to using light volumes is to render actual spheres, scaled by the light volume radius. The centers of these spheres are positioned at the light source's position, and as it is scaled by the light volume radius the sphere exactly encompasses the light's visible volume. This is where the trick comes in: we use the deferred lighting shader for rendering the spheres. As a rendered sphere produces fragment shader invocations that exactly match the pixels the light source affects, we only render the relevant pixels and skip all other pixels. The image below illustrates this: -</p> - - <img src="/img/advanced-lighting/deferred_light_volume_rendered.png" class="clean" alt="Image of a light volume rendered with a deferred fragment shader in OpenGL"/> - -<p> - This is done for each light source in the scene, and the resulting fragments are additively blended together. The result is then the exact same scene as before, but this time rendering only the relevant fragments per light source. This effectively reduces the computations from <code>nr_objects * nr_lights</code> to <code>nr_objects + nr_lights</code>, which makes it incredibly efficient in scenes with a large number of lights. This approach is what makes deferred rendering so suitable for rendering a large number of lights. -</p> - -<p> - There is still an issue with this approach: face culling should be enabled (otherwise we'd render a light's effect twice) and when it is enabled the user may enter a light source's volume after which the volume isn't rendered anymore (due to back-face culling), removing the light source's influence; we can solve that by only rendering the spheres' back faces. -</p> - - <p> - Rendering light volumes does take its toll on performance, and while it is generally much faster than normal deferred shading for rendering a large number of lights, there's still more we can optimize. Two other popular (and more efficient) extensions on top of deferred shading exist called <def>deferred lighting</def> and <def>tile-based deferred shading</def>. These are even more efficient at rendering large amounts of light and also allow for relatively efficient MSAA. -</p> - -<h2>Deferred rendering vs forward rendering</h2> -<p> - By itself (without light volumes), deferred shading is a nice optimization as each pixel only runs a single fragment shader, compared to forward rendering where we'd often run the fragment shader multiple times per pixel. Deferred rendering does come with a few disadvantages though: a large memory overhead, no MSAA, and blending still has to be done with forward rendering. -</p> - -<p> - When you have a small scene and not too many lights, deferred rendering is not necessarily faster and sometimes even slower as the overhead then outweighs the benefits of deferred rendering. In more complex scenes, deferred rendering quickly becomes a significant optimization; especially with the more advanced optimization extensions. In addition, some render effects (especially post-processing effects) become cheaper on a deferred render pipeline as a lot of scene inputs are already available from the g-buffer. -</p> - -<p> - As a final note I'd like to mention that basically all effects that can be accomplished with forward rendering can also be implemented in a deferred rendering context; this often only requires a small translation step. For instance, if we want to use normal mapping in a deferred renderer, we'd change the geometry pass shaders to output a world-space normal extracted from a normal map (using a TBN matrix) instead of the surface normal; the lighting calculations in the lighting pass don't need to change at all. And if you want parallax mapping to work, you'd want to first displace the texture coordinates in the geometry pass before sampling an object's diffuse, specular, and normal textures. Once you understand the idea behind deferred rendering, it's not too difficult to get creative. -</p> - -<h2>Additional resources</h2> -<ul> - <li><a href="http://ogldev.atspace.co.uk/www/tutorial35/tutorial35.html" target="_blank">Tutorial 35: Deferred Shading - Part 1</a>: a three-part deferred shading tutorial by OGLDev.</li> - <li><a href="https://software.intel.com/sites/default/files/m/d/4/1/d/8/lauritzen_deferred_shading_siggraph_2010.pdf" target="_blank">Deferred Rendering for Current and -Future Rendering Pipelines</a>: slides by Andrew Lauritzen discussing high-level tile-based deferred shading and deferred lighting.</li> - </ul> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-Lighting/Gamma-Correction.html b/translation/Advanced-Lighting/Gamma-Correction.html @@ -1,486 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Gamma Correction</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Gamma Correction</h1> -<h1 id="content-url" style='display:none;'>Advanced-Lighting/Gamma-Correction</h1> -<p> - As soon as we compute the final pixel colors of the scene we will have to display them on a monitor. In the old days of digital imaging most monitors were cathode-ray tube (CRT) monitors. These monitors had the physical property that twice the input voltage did not result in twice the amount of brightness. Doubling the input voltage resulted in a brightness equal to an exponential relationship of roughly 2.2 known as the <def>gamma</def> of a monitor. This happens to (coincidently) also closely match how human beings measure brightness as brightness is also displayed with a similar (inverse) power relationship. To better understand what this all means take a look at the following image: -</p> - -<img src="/img/advanced-lighting/gamma_correction_brightness.png" alt="Linear encodings of display with and without gamma correction"/> - -<p> - The top line looks like the correct brightness scale to the human eye, doubling the brightness (from 0.1 to 0.2 for example) does indeed look like it's twice as bright with nice consistent differences. However, when we're talking about the physical brightness of light e.g. amount of photons leaving a light source, the bottom scale actually displays the correct brightness. At the bottom scale, doubling the brightness returns the correct physical brightness, but since our eyes perceive brightness differently (more susceptible to changes in dark colors) it looks weird. -</p> - -<p> - Because the human eyes prefer to see brightness colors according to the top scale, monitors (still today) use a power relationship for displaying output colors so that the original physical brightness colors are mapped to the non-linear brightness colors in the top scale. -</p> - -<p> - This non-linear mapping of monitors does output more pleasing brightness results for our eyes, but when it comes to rendering graphics there is one issue: all the color and brightness options we configure in our applications are based on what we perceive from the monitor and thus all the options are actually non-linear brightness/color options. Take a look at the graph below: -</p> - -<img src="/img/advanced-lighting/gamma_correction_gamma_curves.png" alt="Gamme curves"/> - -<p> - The dotted line represents color/light values in linear space and the solid line represents the color space that monitors display. If we double a color in linear space, its result is indeed double the value. For instance, take a light's color vector (0.5, 0.0, 0.0) which represents a semi-dark red light. If we would double this light in linear space it would become (1.0, 0.0, 0.0) as you can see in the graph. However, the original color gets displayed on the monitor as (0.218, 0.0, 0.0) as you can see from the graph. Here's where the issues start to rise: once we double the dark-red light in linear space, it actually becomes more than 4.5 times as bright on the monitor! -</p> - -<p> - Up until this chapter we have assumed we were working in linear space, but we've actually been working in the monitor's output space so all colors and lighting variables we configured weren't physically correct, but merely looked (sort of) right on our monitor. For this reason, we (and artists) generally set lighting values way brighter than they should be (since the monitor darkens them) which as a result makes most linear-space calculations incorrect. Note that the monitor (CRT) and linear graph both start and end at the same position; it is the intermediate values that are darkened by the display. -</p> - -<p> - Because colors are configured based on the display's output, all intermediate (lighting) calculations in linear-space are physically incorrect. This becomes more obvious as more advanced lighting algorithms are in place, as you can see in the image below: -</p> - - <img src="/img/advanced-lighting/gamma_correction_example.png" alt="Example of gamma correction w/ and without on advanced rendering"/> - -<p> - You can see that with gamma correction, the (updated) color values work more nicely together and darker areas show more details. Overall, a better image quality with a few small modifications. -</p> - -<p> - Without properly correcting this monitor gamma, the lighting looks wrong and artists will have a hard time getting realistic and good-looking results. The solution is to apply <def>gamma correction</def>. -</p> - -<h2>Gamma correction</h2> -<p> - The idea of gamma correction is to apply the inverse of the monitor's gamma to the final output color before displaying to the monitor. Looking back at the gamma curve graph earlier this chapter we see another <em>dashed</em> line that is the inverse of the monitor's gamma curve. We multiply each of the linear output colors by this inverse gamma curve (making them brighter) and as soon as the colors are displayed on the monitor, the monitor's gamma curve is applied and the resulting colors become linear. We effectively brighten the intermediate colors so that as soon as the monitor darkens them, it balances all out. -</p> - - -<p> - Let's give another example. Say we again have the dark-red color \((0.5, 0.0, 0.0)\). Before displaying this color to the monitor we first apply the gamma correction curve to the color value. Linear colors displayed by a monitor are roughly scaled to a power of \(2.2\) so the inverse requires scaling the colors by a power of \(1/2.2\). The gamma-corrected dark-red color thus becomes \((0.5, 0.0, 0.0)^{1/2.2} = (0.5, 0.0, 0.0)^{0.45} = (0.73, 0.0, 0.0)\). The corrected colors are then fed to the monitor and as a result the color is displayed as \((0.73, 0.0, 0.0)^{2.2} = (0.5, 0.0, 0.0)\). You can see that by using gamma-correction, the monitor now finally displays the colors as we linearly set them in the application. -</p> - -<note> -A gamma value of 2.2 is a default gamma value that roughly estimates the average gamma of most displays. The color space as a result of this gamma of 2.2 is called the <def>sRGB</def> color space (not 100% exact, but close). Each monitor has their own gamma curves, but a gamma value of 2.2 gives good results on most monitors. For this reason, games often allow players to change the game's gamma setting as it varies slightly per monitor. -</note> - -<p> - There are two ways to apply gamma correction to your scene: -</p> - -<ul> - <li>By using OpenGL's built-in sRGB framebuffer support.</li> - <li>By doing the gamma correction ourselves in the fragment shader(s).</li> -</ul> - -<p> - The first option is probably the easiest, but also gives you less control. By enabling <var>GL_FRAMEBUFFER_SRGB</var> you tell OpenGL that each subsequent drawing command should first gamma correct colors (from the sRGB color space) before storing them in color buffer(s). The sRGB is a color space that roughly corresponds to a gamma of 2.2 and a standard for most devices. After enabling <var>GL_FRAMEBUFFER_SRGB</var>, OpenGL automatically performs gamma correction after each fragment shader run to all subsequent framebuffers, including the default framebuffer. -</p> - -<p> - Enabling <var>GL_FRAMEBUFFER_SRGB</var> is as simple as calling <fun><function id='60'>glEnable</function></fun>: -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_FRAMEBUFFER_SRGB); -</code></pre> - -<p> - From now on your rendered images will be gamma corrected and as this is done by the hardware it is completely free. Something you should keep in mind with this approach (and the other approach) is that gamma correction (also) transforms the colors from linear space to non-linear space so it is very important you only do gamma correction at the last and final step. If you gamma-correct your colors before the final output, all subsequent operations on those colors will operate on incorrect values. For instance, if you use multiple framebuffers you probably want intermediate results passed in between framebuffers to remain in linear-space and only have the last framebuffer apply gamma correction before being sent to the monitor. -</p> - -<p> - The second approach requires a bit more work, but also gives us complete control over the gamma operations. We apply gamma correction at the end of each relevant fragment shader run so the final colors end up gamma corrected before being sent out to the monitor: -</p> - -<pre><code> -void main() -{ - // do super fancy lighting in linear space - [...] - // apply gamma correction - float gamma = 2.2; - FragColor.rgb = pow(fragColor.rgb, vec3(1.0/gamma)); -} -</code></pre> - -<p> - The last line of code effectively raises each individual color component of <var>fragColor</var> to <code>1.0/gamma</code>, correcting the output color of this fragment shader run. -</p> - -<p> - An issue with this approach is that in order to be consistent you have to apply gamma correction to each fragment shader that contributes to the final output. If you have a dozen fragment shaders for multiple objects, you have to add the gamma correction code to each of these shaders. An easier solution would be to introduce a post-processing stage in your render loop and apply gamma correction on the post-processed quad as a final step which you'd only have to do once. -</p> - -<p> - That one line represents the technical implementation of gamma correction. Not all too impressive, but there are a few extra things you have to consider when doing gamma correction. -</p> - - -<h2>sRGB textures</h2> -<p> - Because monitors display colors with gamma applied, whenever you draw, edit, or paint a picture on your computer you are picking colors based on what you see on the monitor. This effectively means all the pictures you create or edit are not in linear space, but in sRGB space e.g. doubling a dark-red color on your screen based on perceived brightness, does not equal double the red component. -</p> - -<p> - As a result, when texture artists create art by eye, all the textures' values are in sRGB space so if we use those textures as they are in our rendering application we have to take this into account. Before we knew about gamma correction this wasn't really an issue, because the textures looked good in sRGB space which is the same space we worked in; the textures were displayed exactly as they are which was fine. However, now that we're displaying everything in linear space, the texture colors will be off as the following image shows: -</p> - -<img src="/img/advanced-lighting/gamma_correction_srgbtextures.png" alt="Comparrison between working in linear space with sRGB textures and linear-space textures"/> - -<p> - The texture image is way too bright and this happens because it is actually gamma corrected twice! Think about it, when we create an image based on what we see on the monitor, we effectively gamma correct the color values of an image so that it looks right on the monitor. Because we then again gamma correct in the renderer, the image ends up way too bright. -</p> - -<p> - To fix this issue we have to make sure texture artists work in linear space. However, since it's easier to work in sRGB space and most tools don't even properly support linear texturing, this is probably not the preferred solution. -</p> - -<p> - The other solution is to re-correct or transform these sRGB textures to linear space before doing any calculations on their color values. We can do this as follows: -</p> - -<pre><code> -float gamma = 2.2; -vec3 diffuseColor = pow(texture(diffuse, texCoords).rgb, vec3(gamma)); -</code></pre> - -<p> - To do this for each texture in sRGB space is quite troublesome though. Luckily OpenGL gives us yet another solution to our problems by giving us the <var>GL_SRGB</var> and <var>GL_SRGB_ALPHA</var> internal texture formats. -</p> - -<p> - If we create a texture in OpenGL with any of these two sRGB texture formats, OpenGL will automatically correct the colors to linear-space as soon as we use them, allowing us to properly work in linear space. We can specify a texture as an sRGB texture as follows: -</p> - -<pre><code> -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); -</code></pre> - -<p> - If you also want to include alpha components in your texture you'll have to specify the texture's internal format as <var>GL_SRGB_ALPHA</var>. -</p> - -<p> - You should be careful when specifying your textures in sRGB space as not all textures will actually be in sRGB space. Textures used for coloring objects (like diffuse textures) are almost always in sRGB space. Textures used for retrieving lighting parameters (like <a href="https://learnopengl.com/Lighting/Lighting-maps" target="_blank">specular maps</a> and <a href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping" target="_blank">normal maps</a>) are almost always in linear space, so if you were to configure these as sRGB textures the lighting will look odd. Be careful in which textures you specify as sRGB. -</p> - -<p> - With our diffuse textures specified as sRGB textures you get the visual output you'd expect again, but this time everything is gamma corrected only once. -</p> - -<h2>Attenuation</h2> -<p> - Something else that's different with gamma correction is lighting attenuation. In the real physical world, lighting attenuates closely inversely proportional to the squared distance from a light source. In normal English it simply means that the light strength is reduced over the distance to the light source squared, like below: -</p> - -<pre><code> -float attenuation = 1.0 / (distance * distance); -</code></pre> - -<p> - However, when using this equation the attenuation effect is usually way too strong, giving lights a small radius that doesn't look physically right. For that reason other attenuation functions were used (like we discussed in the <a href="https://learnopengl.com/Lighting/Basic-Lighting" target="_blank">basic lighting</a> chapter) that give much more control, or the linear equivalent is used: -</p> - -<pre><code> -float attenuation = 1.0 / distance; -</code></pre> - -<p> - The linear equivalent gives more plausible results compared to its quadratic variant without gamma correction, but when we enable gamma correction the linear attenuation looks too weak and the physically correct quadratic attenuation suddenly gives the better results. The image below shows the differences: -</p> - - <img src="/img/advanced-lighting/gamma_correction_attenuation.png" alt="Attenuation differences between gamma corrected and uncorrected scene."/> - -<p> - The cause of this difference is that light attenuation functions change brightness, and as we weren't visualizing our scene in linear space we chose the attenuation functions that looked best on our monitor, but weren't physically correct. Think of the squared attenuation function: if we were to use this function without gamma correction, the attenuation function effectively becomes: \((1.0 / distance^2)^{2.2}\) when displayed on a monitor. This creates a much larger attenuation from what we originally anticipated. This also explains why the linear equivalent makes much more sense without gamma correction as this effectively becomes \((1.0 / distance)^{2.2} = 1.0 / distance^{2.2}\) which resembles its physical equivalent a lot more. -</p> - -<note> - The more advanced attenuation function we discussed in the <a href="https://learnopengl.com/Lighting/Basic-Lighting" target="_blank">basic lighting</a> chapter still has its place in gamma corrected scenes as it gives more control over the exact attenuation (but of course requires different parameters in a gamma corrected scene). -</note> - -<p> - You can find the source code of this simple demo scene <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/2.gamma_correction/gamma_correction.cpp" target="_blank">here</a>. By pressing the spacebar we switch between a gamma corrected and un-corrected scene with both scenes using their texture and attenuation equivalents. It's not the most impressive demo, but it does show how to actually apply all techniques. -</p> - -<p> - To summarize, gamma correction allows us to do all our shader/lighting calculations in linear space. Because linear space makes sense in the physical world, most physical equations now actually give good results (like real light attenuation). The more advanced your lighting becomes, the easier it is to get good looking (and realistic) results with gamma correction. That is also why it's advised to only really tweak your lighting parameters as soon as you have gamma correction in place. -</p> - - -<h2>Additional resources</h2> -<ul> - <li><a href="http://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/" target="_blank">What every coder should know about gamma</a>: a well written in-depth article by John Novak about gamma correction.</li> - <li><a href="http://www.cambridgeincolour.com/tutorials/gamma-correction.htm" target="_blank">www.cambridgeincolour.com</a>: more about gamma and gamma correction. </li> - <li><a href="http://blog.wolfire.com/2010/02/Gamma-correct-lighting" target="_blank">blog.wolfire.com</a>: blog post by David Rosen about the benefit of gamma correction in graphics rendering. </li> - <li><a href="http://renderwonk.com/blog/index.php/archive/adventures-with-gamma-correct-rendering/" target="_blank">renderwonk.com</a>: some extra practical considerations. </li> - -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-Lighting/HDR.html b/translation/Advanced-Lighting/HDR.html @@ -1,488 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - HDR</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">HDR</h1> -<h1 id="content-url" style='display:none;'>Advanced-Lighting/HDR</h1> -<p> - Brightness and color values, by default, are clamped between <code>0.0</code> and <code>1.0</code> when stored into a framebuffer. This, at first seemingly innocent, statement caused us to always specify light and color values somewhere in this range, trying to make them fit into the scene. This works oké and gives decent results, but what happens if we walk in a really bright area with multiple bright light sources that as a total sum exceed <code>1.0</code>? The answer is that all fragments that have a brightness or color sum over <code>1.0</code> get clamped to <code>1.0</code>, which isn't pretty to look at: -</p> - -<img src="/img/advanced-lighting/hdr_clamped.png" class="clean" alt="Color values clamped in bright areas"/> - -<p> - Due to a large number of fragments' color values getting clamped to <code>1.0</code>, each of the bright fragments have the exact same white color value in large regions, losing a significant amount of detail and giving it a fake look. -</p> - -<p> - A solution to this problem would be to reduce the strength of the light sources and ensure no area of fragments in your scene ends up brighter than <code>1.0</code>; this is not a good solution as this forces you to use unrealistic lighting parameters. A better approach is to allow color values to temporarily exceed <code>1.0</code> and transform them back to the original range of <code>0.0</code> and <code>1.0</code> as a final step, but without losing detail. -</p> - -<p> - Monitors (non-HDR) are limited to display colors in the range of <code>0.0</code> and <code>1.0</code>, but there is no such limitation in lighting equations. By allowing fragment colors to exceed <code>1.0</code> we have a much higher range of color values available to work in known as <def>high dynamic range</def> (HDR). With high dynamic range, bright things can be really bright, dark things can be really dark, and details can be seen in both. -</p> - -<p> - High dynamic range was originally only used for photography where a photographer takes multiple pictures of the same scene with varying exposure levels, capturing a large range of color values. Combining these forms a HDR image where a large range of details are visible based on the combined exposure levels, or a specific exposure it is viewed with. For instance, the following image (credits to Colin Smith) shows a lot of detail at brightly lit regions with a low exposure (look at the window), but these details are gone with a high exposure. However, a high exposure now reveals a great amount of detail at darker regions that weren't previously visible. -</p> - - <img src="/img/advanced-lighting/hdr_image.png" alt="HDR image showing multiple exposure levels and their respective details"/> - -<p> - This is also very similar to how the human eye works and the basis of high dynamic range rendering. When there is little light, the human eye adapts itself so the darker parts become more visible and similarly for bright areas. It's like the human eye has an automatic exposure slider based on the scene's brightness. -</p> - -<p> - High dynamic range rendering works a bit like that. We allow for a much larger range of color values to render to, collecting a large range of dark and bright details of a scene, and at the end we transform all the HDR values back to the <def>low dynamic range</def> (LDR) of [<code>0.0</code>, <code>1.0</code>]. This process of converting HDR values to LDR values is called <def>tone mapping</def> and a large collection of tone mapping algorithms exist that aim to preserve most HDR details during the conversion process. These tone mapping algorithms often involve an exposure parameter that selectively favors dark or bright regions. -</p> - -<p> - When it comes to real-time rendering, high dynamic range allows us to not only exceed the LDR range of [<code>0.0</code>, <code>1.0</code>] and preserve more detail, but also gives us the ability to specify a light source's intensity by their <em>real</em> intensities. For instance, the sun has a much higher intensity than something like a flashlight so why not configure the sun as such (e.g. a diffuse brightness of <code>100.0</code>). This allows us to more properly configure a scene's lighting with more realistic lighting parameters, something that wouldn't be possible with LDR rendering as they'd then directly get clamped to <code>1.0</code>. -</p> - -<p> - As (non-HDR) monitors only display colors in the range between <code>0.0</code> and <code>1.0</code> we do need to transform the currently high dynamic range of color values back to the monitor's range. Simply re-transforming the colors back with a simple average wouldn't do us much good as brighter areas then become a lot more dominant. What we can do, is use different equations and/or curves to transform the HDR values back to LDR that give us complete control over the scene's brightness. This is the process earlier denoted as tone mapping and the final step of HDR rendering. -</p> - -<h2>Floating point framebuffers</h2> -<p> - To implement high dynamic range rendering we need some way to prevent color values getting clamped after each fragment shader run. When framebuffers use a normalized fixed-point color format (like <var>GL_RGB</var>) as their color buffer's internal format, OpenGL automatically clamps the values between <code>0.0</code> and <code>1.0</code> before storing them in the framebuffer. This operation holds for most types of framebuffer formats, except for floating point formats. -</p> - -<p> - When the internal format of a framebuffer's color buffer is specified as <var>GL_RGB16F</var>, <var>GL_RGBA16F</var>, <var>GL_RGB32F</var>, or <var>GL_RGBA32F</var> the framebuffer is known as a <def>floating point framebuffer</def> that can store floating point values outside the default range of <code>0.0</code> and <code>1.0</code>. This is perfect for rendering in high dynamic range! -</p> - -<p> - To create a floating point framebuffer the only thing we need to change is its color buffer's internal format parameter: -</p> - -<pre><code> -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, colorBuffer); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL); -</code></pre> - -<p> - The default framebuffer of OpenGL (by default) only takes up 8 bits per color component. With a floating point framebuffer with 32 bits per color component (when using <var>GL_RGB32F</var> or <var>GL_RGBA32F</var>) we're using 4 times more memory for storing color values. As 32 bits isn't really necessary (unless you need a high level of precision) using <var>GL_RGBA16F</var> will suffice. -</p> - -<p> - With a floating point color buffer attached to a framebuffer we can now render the scene into this framebuffer knowing color values won't get clamped between <code>0.0</code> and <code>1.0</code>. In this chapter's example demo we first render a lit scene into the floating point framebuffer and then display the framebuffer's color buffer on a screen-filled quad; it'll look a bit like this: -</p> - -<pre><code> -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, hdrFBO); - <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - // [...] render (lit) scene -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); - -// now render hdr color buffer to 2D screen-filling quad with tone mapping shader -hdrShader.use(); -<function id='49'>glActiveTexture</function>(GL_TEXTURE0); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, hdrColorBufferTexture); -RenderQuad(); -</code></pre> - -<p> - Here a scene's color values are filled into a floating point color buffer which can contain any arbitrary color value, possibly exceeding <code>1.0</code>. For this chapter, a simple demo scene was created with a large stretched cube acting as a tunnel with four point lights, one being extremely bright positioned at the tunnel's end: -</p> - -<pre><code> -std::vector<glm::vec3> lightColors; -lightColors.push_back(glm::vec3(200.0f, 200.0f, 200.0f)); -lightColors.push_back(glm::vec3(0.1f, 0.0f, 0.0f)); -lightColors.push_back(glm::vec3(0.0f, 0.0f, 0.2f)); -lightColors.push_back(glm::vec3(0.0f, 0.1f, 0.0f)); -</code></pre> - -<p> - Rendering to a floating point framebuffer is exactly the same as we would normally render into a framebuffer. What is new is <var>hdrShader</var>'s fragment shader that renders the final 2D quad with the floating point color buffer texture attached. Let's first define a simple pass-through fragment shader: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec2 TexCoords; - -uniform sampler2D hdrBuffer; - -void main() -{ - vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb; - FragColor = vec4(hdrColor, 1.0); -} -</code></pre> - -<p> - Here we directly sample the floating point color buffer and use its color value as the fragment shader's output. However, as the 2D quad's output is directly rendered into the default framebuffer, all the fragment shader's output values will still end up clamped between <code>0.0</code> and <code>1.0</code> even though we have several values in the floating point color texture exceeding <code>1.0</code>. -</p> - - <img src="/img/advanced-lighting/hdr_direct.png" alt="Direct rendering of floating point color values to the default framebuffer without tone mapping."/> - -<p> - It becomes clear the intense light values at the end of the tunnel are clamped to <code>1.0</code> as a large portion of it is completely white, effectively losing all lighting details in the process. As we directly write HDR values to an LDR output buffer it is as if we have no HDR enabled in the first place. What we need to do is transform all the floating point color values into the <code>0.0</code> - <code>1.0</code> range without losing any of its details. We need to apply a process called <def>tone mapping</def>. -</p> - -<h2>Tone mapping</h2> -<p> - Tone mapping is the process of transforming floating point color values to the expected [<code>0.0</code>, <code>1.0</code>] range known as low dynamic range without losing too much detail, often accompanied with a specific stylistic color balance. -</p> - -<p> - One of the more simple tone mapping algorithms is <def>Reinhard tone mapping</def> that involves dividing the entire HDR color values to LDR color values. The Reinhard tone mapping algorithm evenly balances out all brightness values onto LDR. We include Reinhard tone mapping into the previous fragment shader and also add a <a href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction" target="_blank">gamma correction</a> filter for good measure (including the use of sRGB textures): -</p> - -<pre><code> -void main() -{ - const float gamma = 2.2; - vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb; - - // reinhard tone mapping - vec3 mapped = hdrColor / (hdrColor + vec3(1.0)); - // gamma correction - mapped = pow(mapped, vec3(1.0 / gamma)); - - FragColor = vec4(mapped, 1.0); -} -</code></pre> - -<p> - With Reinhard tone mapping applied we no longer lose any detail at the bright areas of our scene. It does tend to slightly favor brighter areas, making darker regions seem less detailed and distinct: -</p> - - <img src="/img/advanced-lighting/hdr_reinhard.png" class="clean" alt="Reinhard tone mapping algorithm applied with HDR rendering in OpenGL"/> - -<p> - Here you can again see details at the end of the tunnel as the wood texture pattern becomes visible again. With this relatively simple tone mapping algorithm we can properly see the entire range of HDR values stored in the floating point framebuffer, giving us precise control over the scene's lighting without losing details. -</p> - -<note> - Note that we could also directly tone map at the end of our lighting shader, not needing any floating point framebuffer at all! However, as scenes get more complex you'll frequently find the need to store intermediate HDR results as floating point buffers so this is a good exercise. -</note> - -<p> - Another interesting use of tone mapping is to allow the use of an exposure parameter. You probably remember from the introduction that HDR images contain a lot of details visible at different exposure levels. If we have a scene that features a day and night cycle it makes sense to use a lower exposure at daylight and a higher exposure at night time, similar to how the human eye adapts. With such an exposure parameter it allows us to configure lighting parameters that work both at day and night under different lighting conditions as we only have to change the exposure parameter. -</p> - -<p> - A relatively simple exposure tone mapping algorithm looks as follows: -</p> - -<pre><code> -uniform float exposure; - -void main() -{ - const float gamma = 2.2; - vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb; - - // exposure tone mapping - vec3 mapped = vec3(1.0) - exp(-hdrColor * exposure); - // gamma correction - mapped = pow(mapped, vec3(1.0 / gamma)); - - FragColor = vec4(mapped, 1.0); -} -</code></pre> - -<p> - Here we defined an <var>exposure</var> uniform that defaults at <code>1.0</code> and allows us to more precisely specify whether we'd like to focus more on dark or bright regions of the HDR color values. For instance, with high exposure values the darker areas of the tunnel show significantly more detail. In contrast, a low exposure largely removes the dark region details, but allows us to see more detail in the bright areas of a scene. Take a look at the image below to see the tunnel at multiple exposure levels: -</p> - - <img src="/img/advanced-lighting/hdr_exposure.png" alt="Multiple exposure levels of HDR tone mapping in OpenGL"/> - -<p> - This image clearly shows the benefit of high dynamic range rendering. By changing the exposure level we get to see a lot of details of our scene, that would've been otherwise lost with low dynamic range rendering. Take the end of the tunnel for example. With a normal exposure the wood structure is barely visible, but with a low exposure the detailed wooden patterns are clearly visible. The same holds for the wooden patterns close by that are more visible with a high exposure. -</p> - -<p> - You can find the source code of the demo <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/6.hdr/hdr.cpp" target="_blank">here</a>. -</p> - -<h3>More HDR</h3> -<p> - The two tone mapping algorithms shown are only a few of a large collection of (more advanced) tone mapping algorithms of which each has their own strengths and weaknesses. Some tone mapping algorithms favor certain colors/intensities above others and some algorithms display both the low and high exposure colors at the same time to create more colorful and detailed images. There is also a collection of techniques known as <def>automatic exposure adjustment</def> or <def>eye adaptation</def> techniques that determine the brightness of the scene in the previous frame and (slowly) adapt the exposure parameter such that the scene gets brighter in dark areas or darker in bright areas mimicking the human eye. -</p> - -<p> - The real benefit of HDR rendering really shows itself in large and complex scenes with heavy lighting algorithms. As it is difficult to create such a complex demo scene for teaching purposes while keeping it accessible, the chapter's demo scene is small and lacks detail. While relatively simple it does show some of the benefits of HDR rendering: no details are lost in high and dark regions as they can be restored with tone mapping, the addition of multiple lights doesn't cause clamped regions, and light values can be specified by real brightness values not being limited by LDR values. Furthermore, HDR rendering also makes several other interesting effects more feasible and realistic; one of these effects is <def>bloom</def> that we'll discuss in the next <a href="https://learnopengl.com/Advanced-Lighting/Bloom" target="_blank">next</a> chapter. -</p> - -<h2>Additional resources</h2> -<ul> - <li><a href="http://gamedev.stackexchange.com/questions/62836/does-hdr-rendering-have-any-benefits-if-bloom-wont-be-applied" target="_blank">Does HDR rendering have any benefits if bloom won't be applied?</a>: a stackexchange question that features a great lengthy answer describing some of the benefits of HDR rendering.</li> - <li><a href="http://photo.stackexchange.com/questions/7630/what-is-tone-mapping-how-does-it-relate-to-hdr" target="_blank">What is tone mapping? How does it relate to HDR?</a>: another interesting answer with great reference images to explain tone mapping.</li> -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-Lighting/Normal-Mapping.html b/translation/Advanced-Lighting/Normal-Mapping.html @@ -1,793 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Normal Mapping</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Normal Mapping</h1> -<h1 id="content-url" style='display:none;'>Advanced-Lighting/Normal-Mapping</h1> -<p> - All of our scenes are filled with meshes, each consisting of hundreds or maybe thousands of triangles. We boosted the realism by wrapping 2D textures on these flat triangles, hiding the fact that the polygons are just tiny flat triangles. Textures help, but when you take a good close look at the meshes it is still quite easy to see the underlying flat surfaces. Most real-life surface aren't flat however and exhibit a lot of (bumpy) details. -</p> - -<p> - For instance, take a brick surface. A brick surface is quite a rough surface and obviously not completely flat: it contains sunken cement stripes and a lot of detailed little holes and cracks. If we were to view such a brick surface in a lit scene the immersion gets easily broken. Below we can see a brick texture applied to a flat surface lit by a point light. -</p> - -<img src="/img/advanced-lighting/normal_mapping_flat.png" class="clean" alt="Brick surface lighted by point light in OpenGL. It's not too realistic; its flat structures is now quite obvious"/> - -<p> - The lighting doesn't take any of the small cracks and holes into account and completely ignores the deep stripes between the bricks; the surface looks perfectly flat. We can partly fix the flat look by using a specular map to pretend some surfaces are less lit due to depth or other details, but that's more of a hack than a real solution. What we need is some way to inform the lighting system about all the little depth-like details of the surface. -</p> - -<p> - If we think about this from a light's perspective: how comes the surface is lit as a completely flat surface? The answer is the surface's normal vector. From the lighting technique's point of view, the only way it determines the shape of an object is by its perpendicular normal vector. The brick surface only has a single normal vector, and as a result the surface is uniformly lit based on this normal vector's direction. What if we, instead of a per-surface normal that is the same for each fragment, use a per-fragment normal that is different for each fragment? This way we can slightly deviate the normal vector based on a surface's little details; this gives the illusion the surface is a lot more complex: -</p> - - <img src="/img/advanced-lighting/normal_mapping_surfaces.png" class="clean" alt="Surfaces displaying per-surface normal and per-fragment normals for normal mapping in OpenGL"/> - -<p> - By using per-fragment normals we can trick the lighting into believing a surface consists of tiny little planes (perpendicular to the normal vectors) giving the surface an enormous boost in detail. This technique to use per-fragment normals compared to per-surface normals is called <def>normal mapping</def> or <def>bump mapping</def>. Applied to the brick plane it looks a bit like this: -</p> - -<img src="/img/advanced-lighting/normal_mapping_compare.png" alt="Surface without and with normal mapping in OpenGL"/> - -<p> - As you can see, it gives an enormous boost in detail and for a relatively low cost. Since we only change the normal vectors per fragment there is no need to change the lighting equation. We now pass a per-fragment normal, instead of an interpolated surface normal, to the lighting algorithm. The lighting then does the rest. -</p> - -<h2>Normal mapping</h2> -<p> - To get normal mapping to work we're going to need a per-fragment normal. Similar to what we did with diffuse and specular maps we can use a 2D texture to store per-fragment normal data. This way we can sample a 2D texture to get a normal vector for that specific fragment. - </p> - - <p> - While normal vectors are geometric entities and textures are generally only used for color information, storing normal vectors in a texture may not be immediately obvious. If you think about color vectors in a texture they are represented as a 3D vector with an <code>r</code>, <code>g</code>, and <code>b</code> component. We can similarly store a normal vector's <code>x</code>, <code>y</code> and <code>z</code> component in the respective color components. Normal vectors range between <code>-1</code> and <code>1</code> so they're first mapped to [<code>0</code>,<code>1</code>]: -</p> - -<pre><code> -vec3 rgb_normal = normal * 0.5 + 0.5; // transforms from [-1,1] to [0,1] -</code></pre> - -<p> - With normal vectors transformed to an RGB color component like this, we can store a per-fragment normal derived from the shape of a surface onto a 2D texture. An example <def>normal map</def> of the brick surface at the start of this chapter is shown below: -</p> - - <img src="/img/advanced-lighting/normal_mapping_normal_map.png" alt="Image of a normal map in OpenGL normal mapping"/> - -<p> - This (and almost all normal maps you find online) will have a blue-ish tint. This is because the normals are all closely pointing outwards towards the positive z-axis \((0, 0, 1)\): a blue-ish color. The deviations in color represent normal vectors that are slightly offset from the general positive z direction, giving a sense of depth to the texture. For example, you can see that at the top of each brick the color tends to be more greenish, which makes sense as the top side of a brick would have normals pointing more in the positive y direction \((0, 1, 0)\) which happens to be the color green! -</p> - -<p> - With a simple plane, looking at the positive z-axis, we can take <a href="/img/textures/brickwall.jpg" target="_blank">this</a> diffuse texture and <a href="/img/textures/brickwall_normal.jpg" target="_blank">this</a> normal map to render the image from the previous section. Note that the linked normal map is different from the one shown above. The reason for this is that OpenGL reads texture coordinates with the y (or v) coordinate reversed from how textures are generally created. The linked normal map thus has its y (or green) component inversed (you can see the green colors are now pointing downwards); if you fail to take this into account, the lighting will be incorrect. Load both textures, bind them to the proper texture units, and render a plane with the following changes in the lighting fragment shader: -</p> - -<pre><code> -uniform sampler2D normalMap; - -void main() -{ - // obtain normal from normal map in range [0,1] - normal = texture(normalMap, fs_in.TexCoords).rgb; - // transform normal vector to range [-1,1] - normal = normalize(normal * 2.0 - 1.0); - - [...] - // proceed with lighting as normal -} -</code></pre> - -<p> - Here we reverse the process of mapping normals to RGB colors by remapping the sampled normal color from [<code>0</code>,<code>1</code>] back to [<code>-1</code>,<code>1</code>] and then use the sampled normal vectors for the upcoming lighting calculations. In this case we used a Blinn-Phong shader. -</p> - -<p> - By slowly moving the light source over time you really get a sense of depth using the normal map. Running this normal mapping example gives the exact results as shown at the start of this chapter: -</p> - -<img src="/img/advanced-lighting/normal_mapping_correct.png" class="clean" alt="Surface without and with normal mapping in OpenGL"/> - -<p> - There is one issue however that greatly limits this use of normal maps. The normal map we used had normal vectors that all pointed somewhat in the positive z direction. This worked because the plane's surface normal was also pointing in the positive z direction. However, what would happen if we used the same normal map on a plane laying on the ground with a surface normal vector pointing in the positive y direction? -</p> - - <img src="/img/advanced-lighting/normal_mapping_ground.png" class="clean" alt="Image of plane with normal mapping without tangent space transformation, looks off in OpenGL"/> - -<p> - The lighting doesn't look right! This happens because the sampled normals of this plane still roughly point in the positive z direction even though they should mostly point in the positive y direction. As a result, the lighting thinks the surface's normals are the same as before when the plane was pointing towards the positive z direction; the lighting is incorrect. The image below shows what the sampled normals approximately look like on this surface: -</p> - - <img src="/img/advanced-lighting/normal_mapping_ground_normals.png" class="clean" alt="Image of plane with normal mapping without tangent space transformation with displayed normals, looks off in OpenGL"/> - -<p> - You can see that all the normals point somewhat in the positive z direction even though they should be pointing towards the positive y direction. One solution to this problem is to define a normal map for each possible direction of the surface; in the case of a cube we would need 6 normal maps. However, with advanced meshes that can have more than hundreds of possible surface directions this becomes an infeasible approach. -</p> - -<p> - A different solution exists that does all the lighting in a different coordinate space: a coordinate space where the normal map vectors always point towards the positive z direction; all other lighting vectors are then transformed relative to this positive z direction. This way we can always use the same normal map, regardless of orientation. This coordinate space is called <def>tangent space</def>. -</p> - -<h2>Tangent space</h2> -<p> - Normal vectors in a normal map are expressed in tangent space where normals always point roughly in the positive z direction. Tangent space is a space that's local to the surface of a triangle: the normals are relative to the local reference frame of the individual triangles. Think of it as the local space of the normal map's vectors; they're all defined pointing in the positive z direction regardless of the final transformed direction. Using a specific matrix we can then transform normal vectors from this <em>local</em> tangent space to world or view coordinates, orienting them along the final mapped surface's direction. -</p> - -<p> - Let's say we have the incorrect normal mapped surface from the previous section looking in the positive y direction. The normal map is defined in tangent space, so one way to solve the problem is to calculate a matrix to transform normals from tangent space to a different space such that they're aligned with the surface's normal direction: the normal vectors are then all pointing roughly in the positive y direction. The great thing about tangent space is that we can calculate this matrix for any type of surface so that we can properly align the tangent space's z direction to the surface's normal direction. -</p> - -<p> - Such a matrix is called a <def>TBN</def> matrix where the letters depict a <def>Tangent</def>, <def>Bitangent</def> and <def>Normal</def> vector. These are the vectors we need to construct this matrix. To construct such a <em>change-of-basis</em> matrix, that transforms a tangent-space vector to a different coordinate space, we need three perpendicular vectors that are aligned along the surface of a normal map: an up, right, and forward vector; similar to what we did in the <a href="https://learnopengl.com/Getting-Started/Camera" target="_blank">camera</a> chapter. -</p> - -<p> - We already know the up vector, which is the surface's normal vector. The right and forward vector are the tangent and bitangent vector respectively. The following image of a surface shows all three vectors on a surface: -</p> - - <img src="/img/advanced-lighting/normal_mapping_tbn_vectors.png" class="clean" alt="Normal mapping tangent, bitangent and normal vectors on a surface in OpenGL"/> - -<p> - Calculating the tangent and bitangent vectors is not as straightforward as the normal vector. We can see from the image that the direction of the normal map's tangent and bitangent vector align with the direction in which we define a surface's texture coordinates. We'll use this fact to calculate tangent and bitangent vectors for each surface. Retrieving them does require a bit of math; take a look at the following image: -</p> - - <img src="/img/advanced-lighting/normal_mapping_surface_edges.png" class="clean" alt="Edges of a surface in OpenGL required for calculating TBN matrix"/> - -<p> - From the image we can see that the texture coordinate differences of an edge \(E_2\) of a triangle (denoted as \(\Delta U_2\) and \(\Delta V_2\)) are expressed in the same direction as the tangent vector \(T\) and bitangent vector \(B\). Because of this we can write both displayed edges \(E_1\) and \(E_2\) of the triangle as a linear combination of the tangent vector \(T\) and the bitangent vector \(B\): -</p> - - \[E_1 = \Delta U_1T + \Delta V_1B\] - \[E_2 = \Delta U_2T + \Delta V_2B\] - -<p> - Which we can also write as: -</p> - - \[(E_{1x}, E_{1y}, E_{1z}) = \Delta U_1(T_x, T_y, T_z) + \Delta V_1(B_x, B_y, B_z)\] - \[(E_{2x}, E_{2y}, E_{2z}) = \Delta U_2(T_x, T_y, T_z) + \Delta V_2(B_x, B_y, B_z)\] - -<p> - We can calculate \(E\) as the difference vector between two triangle positions, and \(\Delta U\) and \(\Delta V\) as their texture coordinate differences. We're then left with two unknowns (tangent \(T\) and bitangent \(B\)) and two equations. You may remember from your algebra classes that this allows us to solve for \(T\) and \(B\). -</p> - -<p> - The last equation allows us to write it in a different form: that of matrix multiplication: -</p> - - \[\begin{bmatrix} E_{1x} & E_{1y} & E_{1z} \\ E_{2x} & E_{2y} & E_{2z} \end{bmatrix} = \begin{bmatrix} \Delta U_1 & \Delta V_1 \\ \Delta U_2 & \Delta V_2 \end{bmatrix} \begin{bmatrix} T_x & T_y & T_z \\ B_x & B_y & B_z \end{bmatrix} \] - -<p> - Try to visualize the matrix multiplications in your head and confirm that this is indeed the same equation. An advantage of rewriting the equations in matrix form is that solving for \(T\) and \(B\) is easier to understand. If we multiply both sides of the equations by the inverse of the \(\Delta U \Delta V\) matrix we get: -</p> - - \[ \begin{bmatrix} \Delta U_1 & \Delta V_1 \\ \Delta U_2 & \Delta V_2 \end{bmatrix}^{-1} \begin{bmatrix} E_{1x} & E_{1y} & E_{1z} \\ E_{2x} & E_{2y} & E_{2z} \end{bmatrix} = \begin{bmatrix} T_x & T_y & T_z \\ B_x & B_y & B_z \end{bmatrix} \] - -<p> - This allows us to solve for \(T\) and \(B\). This does require us to calculate the inverse of the delta texture coordinate matrix. I won't go into the mathematical details of calculating a matrix' inverse, but it roughly translates to 1 over the determinant of the matrix, multiplied by its adjugate matrix: -</p> - \[ \begin{bmatrix} T_x & T_y & T_z \\ B_x & B_y & B_z \end{bmatrix} = \frac{1}{\Delta U_1 \Delta V_2 - \Delta U_2 \Delta V_1} \begin{bmatrix} \Delta V_2 & -\Delta V_1 \\ -\Delta U_2 & \Delta U_1 \end{bmatrix} \begin{bmatrix} E_{1x} & E_{1y} & E_{1z} \\ E_{2x} & E_{2y} & E_{2z} \end{bmatrix} \] - -<p> - This final equation gives us a formula for calculating the tangent vector \(T\) and bitangent vector \(B\) from a triangle's two edges and its texture coordinates. -</p> - -<p> - Don't worry if you do not fully understand the mathematics behind this. As long as you understand that we can calculate tangents and bitangents from a triangle's vertices and its texture coordinates (since texture coordinates are in the same space as tangent vectors) you're halfway there. -</p> - -<h3>Manual calculation of tangents and bitangents</h3> -<p> - In the previous demo we had a simple normal mapped plane facing the positive z direction. This time we want to implement normal mapping using tangent space so we can orient this plane however we want and normal mapping would still work. Using the previously discussed mathematics we're going to manually calculate this surface's tangent and bitangent vectors. -</p> - -<p> - Let's assume the plane is built up from the following vectors (with 1, 2, 3 and 1, 3, 4 as its two triangles): -</p> - -<pre><code> -// positions -glm::vec3 pos1(-1.0, 1.0, 0.0); -glm::vec3 pos2(-1.0, -1.0, 0.0); -glm::vec3 pos3( 1.0, -1.0, 0.0); -glm::vec3 pos4( 1.0, 1.0, 0.0); -// texture coordinates -glm::vec2 uv1(0.0, 1.0); -glm::vec2 uv2(0.0, 0.0); -glm::vec2 uv3(1.0, 0.0); -glm::vec2 uv4(1.0, 1.0); -// normal vector -glm::vec3 nm(0.0, 0.0, 1.0); -</code></pre> - -<p> - We first calculate the first triangle's edges and delta UV coordinates: -</p> - -<pre><code> -glm::vec3 edge1 = pos2 - pos1; -glm::vec3 edge2 = pos3 - pos1; -glm::vec2 deltaUV1 = uv2 - uv1; -glm::vec2 deltaUV2 = uv3 - uv1; -</code></pre> - -<p> - With the required data for calculating tangents and bitangents we can start following the equation from the previous section: -</p> - -<pre><code> -float f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y); - -tangent1.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x); -tangent1.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y); -tangent1.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z); - -bitangent1.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x); -bitangent1.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y); -bitangent1.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z); - -[...] // similar procedure for calculating tangent/bitangent for plane's second triangle -</code></pre> - -<p> - Here we first pre-calculate the fractional part of the equation as <var>f</var> and then for each vector component we do the corresponding matrix multiplication multiplied by <var>f</var>. If you compare this code with the final equation you can see it is a direct translation. Because a triangle is always a flat shape, we only need to calculate a single tangent/bitangent pair per triangle as they will be the same for each of the triangle's vertices. -</p> - -<p> - The resulting tangent and bitangent vector should have a value of (<code>1</code>,<code>0</code>,<code>0</code>) and (<code>0</code>,<code>1</code>,<code>0</code>) respectively that together with the normal (<code>0</code>,<code>0</code>,<code>1</code>) forms an orthogonal TBN matrix. Visualized on the plane, the TBN vectors would look like this: -</p> - - <img src="/img/advanced-lighting/normal_mapping_tbn_shown.png" class="clean" alt="Image of TBN vectors visualized on a plane in OpenGL"/> - -<p> - With tangent and bitangent vectors defined per vertex we can start implementing <em>proper</em> normal mapping. -</p> - -<h3>Tangent space normal mapping</h3> -<p> - To get normal mapping working, we first have to create a TBN matrix in the shaders. To do that, we pass the earlier calculated tangent and bitangent vectors to the vertex shader as vertex attributes: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec3 aNormal; -layout (location = 2) in vec2 aTexCoords; -layout (location = 3) in vec3 aTangent; -layout (location = 4) in vec3 aBitangent; -</code></pre> - -<p> - Then within the vertex shader's <fun>main</fun> function we create the TBN matrix: -</p> - -<pre><code> -void main() -{ - [...] - vec3 T = normalize(vec3(model * vec4(aTangent, 0.0))); - vec3 B = normalize(vec3(model * vec4(aBitangent, 0.0))); - vec3 N = normalize(vec3(model * vec4(aNormal, 0.0))); - mat3 TBN = mat3(T, B, N); -} -</code></pre> - -<p> - Here we first transform all the TBN vectors to the coordinate system we'd like to work in, which in this case is world-space as we multiply them with the <var>model</var> matrix. Then we create the actual TBN matrix by directly supplying <fun>mat3</fun>'s constructor with the relevant column vectors. Note that if we want to be really precise, we would multiply the TBN vectors with the normal matrix as we only care about the orientation of the vectors. -</p> - -<note> - Technically there is no need for the <var>bitangent</var> variable in the vertex shader. All three TBN vectors are perpendicular to each other so we can calculate the <var>bitangent</var> ourselves in the vertex shader by taking the cross product of the <var>T</var> and <var>N</var> vector: <code>vec3 B = cross(N, T);</code> -</note> - -<p> - So now that we have a TBN matrix, how are we going to use it? There are two ways we can use a TBN matrix for normal mapping, and we'll demonstrate both of them: -</p> - -<ol> - <li>We take the TBN matrix that transforms any vector from tangent to world space, give it to the fragment shader, and transform the sampled normal from tangent space to world space using the TBN matrix; the normal is then in the same space as the other lighting variables.</li> - <li>We take the inverse of the TBN matrix that transforms any vector from world space to tangent space, and use this matrix to transform not the normal, but the other relevant lighting variables to tangent space; the normal is then again in the same space as the other lighting variables.</li> -</ol> - -<p> - Let's review the first case. The normal vector we sample from the normal map is expressed in tangent space whereas the other lighting vectors (light and view direction) are expressed in world space. By passing the TBN matrix to the fragment shader we can multiply the sampled tangent space normal with this TBN matrix to transform the normal vector to the same reference space as the other lighting vectors. This way, all the lighting calculations (specifically the dot product) make sense. -</p> - -<p> - Sending the TBN matrix to the fragment shader is easy: -</p> - -<pre><code> -out VS_OUT { - vec3 FragPos; - vec2 TexCoords; - mat3 TBN; -} vs_out; - -void main() -{ - [...] - vs_out.TBN = mat3(T, B, N); -} -</code></pre> - -<p> - In the fragment shader we similarly take a <code>mat3</code> as an input variable: -</p> - -<pre><code> -in VS_OUT { - vec3 FragPos; - vec2 TexCoords; - mat3 TBN; -} fs_in; -</code></pre> - -<p> - With this TBN matrix we can now update the normal mapping code to include the tangent-to-world space transformation: -</p> - -<pre class="cpp"><code> -normal = texture(normalMap, fs_in.TexCoords).rgb; -normal = normal * 2.0 - 1.0; -normal = normalize(fs_in.TBN * normal); -</code></pre> - -<p> - Because the resulting <var>normal</var> is now in world space, there is no need to change any of the other fragment shader code as the lighting code assumes the normal vector to be in world space. -</p> - -<p> - Let's also review the second case, where we take the inverse of the TBN matrix to transform all relevant world-space vectors to the space the sampled normal vectors are in: tangent space. The construction of the TBN matrix remains the same, but we first invert the matrix before sending it to the fragment shader: -</p> - -<pre><code> -vs_out.TBN = transpose(mat3(T, B, N)); -</code></pre> - -<p> - Note that we use the <fun>transpose</fun> function instead of the <fun>inverse</fun> function here. A great property of orthogonal matrices (each axis is a perpendicular unit vector) is that the transpose of an orthogonal matrix equals its inverse. This is a great property as <fun>inverse</fun> is expensive and a transpose isn't. -</p> - -<p> - Within the fragment shader we do not transform the normal vector, but we transform the other relevant vectors to tangent space, namely the <var>lightDir</var> and <var>viewDir</var> vectors. That way, each vector is in the same coordinate space: tangent space. -</p> - -<pre><code> -void main() -{ - vec3 normal = texture(normalMap, fs_in.TexCoords).rgb; - normal = normalize(normal * 2.0 - 1.0); - - vec3 lightDir = fs_in.TBN * normalize(lightPos - fs_in.FragPos); - vec3 viewDir = fs_in.TBN * normalize(viewPos - fs_in.FragPos); - [...] -} -</code></pre> - -<p> - The second approach looks like more work and also requires matrix multiplications in the fragment shader, so why would we bother with the second approach? -</p> - -<p> - Well, transforming vectors from world to tangent space has an added advantage in that we can transform all the relevant lighting vectors to tangent space in the vertex shader instead of in the fragment shader. This works, because <var>lightPos</var> and <var>viewPos</var> don't update every fragment run, and for <var>fs_in.FragPos</var> we can calculate its tangent-space position in the vertex shader and let fragment interpolation do its work. There is effectively no need to transform a vector to tangent space in the fragment shader, while it is necessary with the first approach as sampled normal vectors are specific to each fragment shader run. -</p> - -<p> - So instead of sending the inverse of the TBN matrix to the fragment shader, we send a tangent-space light position, view position, and vertex position to the fragment shader. This saves us from having to do matrix multiplications in the fragment shader. This is a nice optimization as the vertex shader runs considerably less often than the fragment shader. This is also the reason why this approach is often the preferred approach. -</p> - -<pre><code> -out VS_OUT { - vec3 FragPos; - vec2 TexCoords; - vec3 TangentLightPos; - vec3 TangentViewPos; - vec3 TangentFragPos; -} vs_out; - -uniform vec3 lightPos; -uniform vec3 viewPos; - -[...] - -void main() -{ - [...] - mat3 TBN = transpose(mat3(T, B, N)); - vs_out.TangentLightPos = TBN * lightPos; - vs_out.TangentViewPos = TBN * viewPos; - vs_out.TangentFragPos = TBN * vec3(model * vec4(aPos, 1.0)); -} -</code></pre> - -<p> - In the fragment shader we then use these new input variables to calculate lighting in tangent space. As the normal vector is already in tangent space, the lighting makes sense. -</p> - -<p> - With normal mapping applied in tangent space, we should get similar results to what we had at the start of this chapter. This time however, we can orient our plane in any way we'd like and the lighting would still be correct: -</p> - -<pre><code> -glm::mat4 model = glm::mat4(1.0f); -model = <function id='57'>glm::rotate</function>(model, (float)<function id='47'>glfwGetTime</function>() * -10.0f, glm::normalize(glm::vec3(1.0, 0.0, 1.0))); -shader.setMat4("model", model); -RenderQuad(); -</code></pre> - -<p> - Which indeed looks like proper normal mapping: -</p> - - <img src="/img/advanced-lighting/normal_mapping_correct_tangent.png" class="clean" alt="Correct normal mapping with tangent space transformations in OpenGL"/> - -<p> - You can find the source code <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/4.normal_mapping/normal_mapping.cpp" target="_blank">here</a>. -</p> - -<h2>Complex objects</h2> -<p> - We've demonstrated how we can use normal mapping, together with tangent space transformations, by manually calculating the tangent and bitangent vectors. Luckily for us, having to manually calculate these tangent and bitangent vectors is not something we do too often. Most of the time you implement it once in a custom model loader, or in our case use a <a href="https://learnopengl.com/Model-Loading/Assimp" target="_blank">model loader</a> using Assimp. -</p> - -<p> - Assimp has a very useful configuration bit we can set when loading a model called <var>aiProcess_CalcTangentSpace</var>. When the <var>aiProcess_CalcTangentSpace</var> bit is supplied to Assimp's <fun>ReadFile</fun> function, Assimp calculates smooth tangent and bitangent vectors for each of the loaded vertices, similarly to how we did it in this chapter. -</p> - -<pre><code> -const aiScene *scene = importer.ReadFile( - path, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace -); -</code></pre> - -<p> - Within Assimp we can then retrieve the calculated tangents via: -</p> - -<pre><code> -vector.x = mesh->mTangents[i].x; -vector.y = mesh->mTangents[i].y; -vector.z = mesh->mTangents[i].z; -vertex.Tangent = vector; -</code></pre> - -<p> - Then you'll have to update the model loader to also load normal maps from a textured model. The wavefront object format (.obj) exports normal maps slightly different from Assimp's conventions as <var>aiTextureType_NORMAL</var> doesn't load normal maps, while <var>aiTextureType_HEIGHT</var> does: -</p> - -<pre><code> -vector<Texture> normalMaps = loadMaterialTextures(material, aiTextureType_HEIGHT, "texture_normal"); -</code></pre> - -<p> - Of course, this is different for each type of loaded model and file format. -</p> - - <!--It's also important to realize that <var>aiProcess_CalcTangentSpace</var> doesn't always work. Calculating tangents is based on texture coordinates and some 3D artists do certain texture tricks like mirroring a texture surface over a model; this gives incorrect results when the mirroring is not taken into account. The nanosuit model for instance doesn't produce proper tangents as it has mirrored texture coordinates. Assimp gives us a multiplication factor (<code>1</code> or <code>-1</code>) in the tangent's <code>w</code> coordinate that we can use to multiply the bitangent with to account for the mirroring. -</p>--> - -<p> - Running the application on a model with specular and normal maps, using an updated model loader, gives the following result: -</p> - - <img src="/img/advanced-lighting/normal_mapping_complex_compare.png" alt="Normal mapping in OpenGL on a complex object loaded with Assimp"/> - -<p> - As you can see, normal mapping boosts the detail of an object by an incredible amount without too much extra cost. -</p> - -<p> - Using normal maps is also a great way to boost performance. Before normal mapping, you had to use a large number of vertices to get a high number of detail on a mesh. With normal mapping, we can get the same level of detail on a mesh using a lot less vertices. The image below from Paolo Cignoni shows a nice comparison of both methods: -</p> - - <img src="/img/advanced-lighting/normal_mapping_comparison.png" alt="Comparrison of visualizing details on a mesh with and without normal mapping"/> - -<p> - The details on both the high-vertex mesh and the low-vertex mesh with normal mapping are almost indistinguishable. So normal mapping doesn't only look nice, it's a great tool to replace high-vertex meshes with low-vertex meshes without losing (too much) detail. -</p> - -<h2>One last thing</h2> -<p> - There is one last trick left to discuss that slightly improves quality without too much extra cost. -</p> - -<p> - When tangent vectors are calculated on larger meshes that share a considerable amount of vertices, the tangent vectors are generally averaged to give nice and smooth results. A problem with this approach is that the three TBN vectors could end up non-perpendicular, which means the resulting TBN matrix would no longer be orthogonal. Normal mapping would only be slightly off with a non-orthogonal TBN matrix, but it's still something we can improve. -</p> - -<p> - Using a mathematical trick called the <def>Gram-Schmidt process</def>, we can <def>re-orthogonalize</def> the TBN vectors such that each vector is again perpendicular to the other vectors. Within the vertex shader we would do it like this: -</p> - -<pre><code> -vec3 T = normalize(vec3(model * vec4(aTangent, 0.0))); -vec3 N = normalize(vec3(model * vec4(aNormal, 0.0))); -// re-orthogonalize T with respect to N -T = normalize(T - dot(T, N) * N); -// then retrieve perpendicular vector B with the cross product of T and N -vec3 B = cross(N, T); - -mat3 TBN = mat3(T, B, N) -</code></pre> - -<p> - This, albeit by a little, generally improves the normal mapping results with a little extra cost. Take a look at the end of the <em>Normal Mapping Mathematics</em> video in the additional resources for a great explanation of how this process actually works. -</p> - -<h2>Additional resources</h2> - <ul> - <li><a href="http://ogldev.atspace.co.uk/www/tutorial26/tutorial26.html" target="_blank">Tutorial 26: Normal Mapping</a>: normal mapping tutorial by ogldev.</li> - <li><a href="https://www.youtube.com/watch?v=LIOPYmknj5Q" target="_blank">How Normal Mapping Works</a>: a nice video tutorial of how normal mapping works by TheBennyBox.</li> - <li><a href="https://www.youtube.com/watch?v=4FaWLgsctqY" target="_blank">Normal Mapping Mathematics</a>: a similar video by TheBennyBox about the mathematics behind normal mapping.</li> - <li><a href="http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/" target="_blank">Tutorial 13: Normal Mapping</a>: normal mapping tutorial by opengl-tutorial.org.</li> -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-Lighting/Parallax-Mapping.html b/translation/Advanced-Lighting/Parallax-Mapping.html @@ -1,664 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Parallax Mapping</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Parallax Mapping</h1> -<h1 id="content-url" style='display:none;'>Advanced-Lighting/Parallax-Mapping</h1> -<p> - Parallax mapping is a technique similar to normal mapping, but based on different principles. Just like normal mapping it is a technique that significantly boosts a textured surface's detail and gives it a sense of depth. While also an illusion, parallax mapping is a lot better in conveying a sense of depth and together with normal mapping gives incredibly realistic results. While parallax mapping isn't necessarily a technique directly related to (advanced) lighting, I'll still discuss it here as the technique is a logical follow-up of normal mapping. Note that getting an understanding of normal mapping, specifically tangent space, is strongly advised before learning parallax mapping. -</p> - -<p> - Parallax mapping is closely related to the family of <def>displacement mapping</def> techniques that <em>displace</em> or <em>offset</em> vertices based on geometrical information stored inside a texture. One way to do this, is to take a plane with roughly 1000 vertices and displace each of these vertices based on a value in a texture that tells us the height of the plane at that specific area. Such a texture that contains height values per texel is called a <def>height map</def>. An example height map derived from the geometric properties of a simple brick surface looks a bit like this: -</p> - -<img src="/img/advanced-lighting/parallax_mapping_height_map.png" alt="Height map used in OpenGL for parallax mapping"/> - -<p> - When spanned over a plane, each vertex is displaced based on the sampled height value in the height map, transforming a flat plane to a rough bumpy surface based on a material's geometric properties. For instance, taking a flat plane displaced with the above heightmap results in the following image: -</p> - - <img src="/img/advanced-lighting/parallax_mapping_plane_heightmap.png" class="clean" alt="Height map applied to simple plane"/> - -<p> - A problem with displacing vertices this way is that a plane needs to contain a huge amount of triangles to get a realistic displacement, otherwise the displacement looks too blocky. As each flat surface may then require over 10000 vertices this quickly becomes computationally infeasible. What if we could somehow achieve similar realism without the need of extra vertices? In fact, what if I were to tell you that the previously shown displaced surface is actually rendered with only 2 triangles. This brick surface shown is rendered with <def>parallax mapping</def>, a displacement mapping technique that doesn't require extra vertex data to convey depth, but (similar to normal mapping) uses a clever technique to trick the user. -</p> - -<p> - The idea behind parallax mapping is to alter the texture coordinates in such a way that it looks like a fragment's surface is higher or lower than it actually is, all based on the view direction and a heightmap. To understand how it works, take a look at the following image of our brick surface: - </p> - - <img src="/img/advanced-lighting/parallax_mapping_plane_height.png" class="clean" alt="Diagram of how parallax mapping works in OpenGL"/> - -<p> - Here the rough red line represents the values in the heightmap as the geometric surface representation of the brick surface and the vector \(\color{orange}{\bar{V}}\) represents the surface to view direction (<var>viewDir</var>). If the plane would have actual displacement, the viewer would see the surface at point \(\color{blue}B\). However, as our plane has no actual displacement the view direction is calculated from point \(\color{green}A\) as we'd expect. Parallax mapping aims to offset the texture coordinates at fragment position \(\color{green}A\) in such a way that we get texture coordinates at point \(\color{blue}B\). We then use the texture coordinates at point \(\color{blue}B\) for all subsequent texture samples, making it look like the viewer is actually looking at point \(\color{blue}B\). -</p> - -<p> - The trick is to figure out how to get the texture coordinates at point \(\color{blue}B\) from point \(\color{green}A\). Parallax mapping tries to solve this by scaling the fragment-to-view direction vector \(\color{orange}{\bar{V}}\) by the height at fragment \(\color{green}A\). So we're scaling the length of \(\color{orange}{\bar{V}}\) to be equal to a sampled value from the heightmap \(\color{green}{H(A)}\) at fragment position \(\color{green}A\). The image below shows this scaled vector \(\color{brown}{\bar{P}}\): -</p> - - <img src="/img/advanced-lighting/parallax_mapping_scaled_height.png" class="clean" alt="Diagram of how parallax mapping works in OpenGL with vector scaled by fragment's height."/> - -<p> - We then take this vector \(\color{brown}{\bar{P}}\) and take its vector coordinates that align with the plane as the texture coordinate offset. This works because vector \(\color{brown}{\bar{P}}\) is calculated using a height value from the heightmap. So the higher a fragment's height, the more it effectively gets displaced. -</p> - -<p> - This little trick gives good results most of the time, but it is still a really crude approximation to get to point \(\color{blue}B\). When heights change rapidly over a surface the results tend to look unrealistic as the vector \(\color{brown}{\bar{P}}\) will not end up close to \(\color{blue}B\) as you can see below: -</p> - - <img src="/img/advanced-lighting/parallax_mapping_incorrect_p.png" class="clean" alt="Diagram of why basic parallax mapping gives incorrect result at steep height changes."/> - -<p> - Another issue with parallax mapping is that it's difficult to figure out which coordinates to retrieve from \(\color{brown}{\bar{P}}\) when the surface is arbitrarily rotated in some way. We'd rather do this in a different coordinate space where the <code>x</code> and <code>y</code> component of vector \(\color{brown}{\bar{P}}\) always align with the texture's surface. If you've followed along in the <a href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping" target="_blank">normal mapping</a> chapter you probably guessed how we can accomplish this. And yes, we would like to do parallax mapping in tangent space. -</p> - -<p> - By transforming the fragment-to-view direction vector \(\color{orange}{\bar{V}}\) to tangent space, the transformed \(\color{brown}{\bar{P}}\) vector will have its <code>x</code> and <code>y</code> component aligned to the surface's tangent and bitangent vectors. As the tangent and bitangent vectors are pointing in the same direction as the surface's texture coordinates we can take the <code>x</code> and <code>y</code> components of \(\color{brown}{\bar{P}}\) as the texture coordinate offset, regardless of the surface's orientation. -</p> - -<p> - But enough about the theory, let's get our feet wet and start implementing actual parallax mapping. -</p> - -<h2>Parallax mapping</h2> -<p> - For parallax mapping we're going to use a simple 2D plane for which we calculated its tangent and bitangent vectors before sending it to the GPU; similar to what we did in the normal mapping chapter. Onto the plane we're going to attach a <a href="/img/textures/bricks2.jpg" target="_blank">diffuse texture</a>, a <a href="/img/textures/bricks2_normal.jpg" target="_blank">normal map</a>, and a <a href="/img/textures/bricks2_disp.jpg" target="_blank">displacement map</a> that you can download from their urls. For this example we're going to use parallax mapping in conjunction with normal mapping. Because parallax mapping gives the illusion of displacing a surface, the illusion breaks when the lighting doesn't match. As normal maps are often generated from heightmaps, using a normal map together with the heightmap makes sure the lighting is in place with the displacement. -</p> - -<p> - You may have already noted that the displacement map linked above is the inverse of the heightmap shown at the start of this chapter. With parallax mapping it makes more sense to use the inverse of the heightmap as it's easier to fake depth than height on flat surfaces. This slightly changes how we perceive parallax mapping as shown below: -</p> - -<img src="/img/advanced-lighting/parallax_mapping_depth.png" class="clean" alt="Parallax mapping using a depth map instead of a heightmap"/> - -<p> - We again have a points \(\color{green}A\) and \(\color{blue}B\), but this time we obtain vector \(\color{brown}{\bar{P}}\) by <strong>subtracting</strong> vector \(\color{orange}{\bar{V}}\) from the texture coordinates at point \(\color{green}A\). We can obtain depth values instead of height values by subtracting the sampled heightmap values from <code>1.0</code> in the shaders, or by simply inversing its texture values in image-editing software as we did with the depthmap linked above. -</p> - - -<p> - Parallax mapping is implemented in the fragment shader as the displacement effect is different all over a triangle's surface. In the fragment shader we're then going to need to calculate the fragment-to-view direction vector \(\color{orange}{\bar{V}}\) so we need the view position and a fragment position in tangent space. In the normal mapping chapter we already had a vertex shader that sends these vectors in tangent space so we can take an exact copy of that chapter's vertex shader: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec3 aNormal; -layout (location = 2) in vec2 aTexCoords; -layout (location = 3) in vec3 aTangent; -layout (location = 4) in vec3 aBitangent; - -out VS_OUT { - vec3 FragPos; - vec2 TexCoords; - vec3 TangentLightPos; - vec3 TangentViewPos; - vec3 TangentFragPos; -} vs_out; - -uniform mat4 projection; -uniform mat4 view; -uniform mat4 model; - -uniform vec3 lightPos; -uniform vec3 viewPos; - -void main() -{ - gl_Position = projection * view * model * vec4(aPos, 1.0); - vs_out.FragPos = vec3(model * vec4(aPos, 1.0)); - vs_out.TexCoords = aTexCoords; - - vec3 T = normalize(mat3(model) * aTangent); - vec3 B = normalize(mat3(model) * aBitangent); - vec3 N = normalize(mat3(model) * aNormal); - mat3 TBN = transpose(mat3(T, B, N)); - - vs_out.TangentLightPos = TBN * lightPos; - vs_out.TangentViewPos = TBN * viewPos; - vs_out.TangentFragPos = TBN * vs_out.FragPos; -} -</code></pre> - -<p> - Within the fragment shader we then implement the parallax mapping logic. The fragment shader looks a bit like this: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in VS_OUT { - vec3 FragPos; - vec2 TexCoords; - vec3 TangentLightPos; - vec3 TangentViewPos; - vec3 TangentFragPos; -} fs_in; - -uniform sampler2D diffuseMap; -uniform sampler2D normalMap; -uniform sampler2D depthMap; - -uniform float height_scale; - -vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir); - -void main() -{ - // offset texture coordinates with Parallax Mapping - vec3 viewDir = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos); - vec2 texCoords = ParallaxMapping(fs_in.TexCoords, viewDir); - - // then sample textures with new texture coords - vec3 diffuse = texture(diffuseMap, texCoords); - vec3 normal = texture(normalMap, texCoords); - normal = normalize(normal * 2.0 - 1.0); - // proceed with lighting code - [...] -} - -</code></pre> - -<p> - We defined a function called <fun>ParallaxMapping</fun> that takes as input the fragment's texture coordinates and the fragment-to-view direction \(\color{orange}{\bar{V}}\) in tangent space. The function returns the displaced texture coordinates. We then use these <em>displaced</em> texture coordinates as the texture coordinates for sampling the diffuse and normal map. As a result, the fragment's diffuse and normal vector correctly corresponds to the surface's displaced geometry. -</p> - -<p> - Let's take a look inside the <fun>ParallaxMapping</fun> function: -</p> - -<pre><code> -vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir) -{ - float height = texture(depthMap, texCoords).r; - vec2 p = viewDir.xy / viewDir.z * (height * height_scale); - return texCoords - p; -} -</code></pre> - -<p> - This relatively simple function is a direct translation of what we've discussed so far. We take the original texture coordinates <var>texCoords</var> and use these to sample the height (or depth) from the <var>depthMap</var> at the current fragment \(\color{green}{A}\) as \(\color{green}{H(A)}\). We then calculate \(\color{brown}{\bar{P}}\) as the <code>x</code> and <code>y</code> component of the tangent-space <var>viewDir</var> vector divided by its <code>z</code> component and scaled by \(\color{green}{H(A)}\). We also introduced a <var>height_scale</var> uniform for some extra control as the parallax effect is usually too strong without an extra scale parameter. We then subtract this vector \(\color{brown}{\bar{P}}\) from the texture coordinates to get the final displaced texture coordinates. - </p> - -<p> - What is interesting to note here is the division of <var>viewDir.xy</var> by <var>viewDir.z</var>. As the <var>viewDir</var> vector is normalized, <var>viewDir.z</var> will be somewhere in the range between <code>0.0</code> and <code>1.0</code>. When <var>viewDir</var> is largely parallel to the surface, its <code>z</code> component is close to <code>0.0</code> and the division returns a much larger vector \(\color{brown}{\bar{P}}\) compared to when <var>viewDir</var> is largely perpendicular to the surface. We're adjusting the size of \(\color{brown}{\bar{P}}\) in such a way that it offsets the texture coordinates at a larger scale when looking at a surface from an angle compared to when looking at it from the top; this gives more realistic results at angles. <br/> - Some prefer to leave the division by <var>viewDir.z</var> out of the equation as default Parallax Mapping could produce undesirable results at angles; the technique is then called <def>Parallax Mapping with Offset Limiting</def>. Choosing which technique to pick is usually a matter of personal preference. -</p> - -<p> - The resulting texture coordinates are then used to sample the other textures (diffuse and normal) and this gives a very neat displaced effect as you can see below with a <var>height_scale</var> of roughly <code>0.1</code>: -</p> - -<img src="/img/advanced-lighting/parallax_mapping.png" alt="Image of parallax mapping in OpenGL"/> - -<p> - Here you can see the difference between normal mapping and parallax mapping combined with normal mapping. Because parallax mapping tries to simulate depth it is actually possible to have bricks overlap other bricks based on the direction you view them. -</p> - -<p> - You can still see a few weird border artifacts at the edge of the parallax mapped plane. This happens because at the edges of the plane the displaced texture coordinates can oversample outside the range [<code>0</code>, <code>1</code>]. This gives unrealistic results based on the texture's wrapping mode(s). A cool trick to solve this issue is to discard the fragment whenever it samples outside the default texture coordinate range: -</p> - -<pre><code> -texCoords = ParallaxMapping(fs_in.TexCoords, viewDir); -if(texCoords.x > 1.0 || texCoords.y > 1.0 || texCoords.x < 0.0 || texCoords.y < 0.0) - discard; -</code></pre> - -<p> - All fragments with (displaced) texture coordinates outside the default range are discarded and Parallax Mapping then gives proper result around the edges of a surface. Note that this trick doesn't work on all types of surfaces, but when applied to a plane it gives great results: -</p> - - <img src="/img/advanced-lighting/parallax_mapping_edge_fix.png" class="clean" alt="Parallax mapping with fragments discarded at the borders, fixing edge artifacts in OpenGL"/> - -<p> - You can find the source code <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/5.1.parallax_mapping/parallax_mapping.cpp" target="_blank">here</a>. -</p> - -<p> - It looks great and is quite fast as well as we only need a single extra texture sample for parallax mapping to work. It does come with a few issues though as it sort of breaks down when looking at it from an angle (similar to normal mapping) and gives incorrect results with steep height changes, as you can see below: -</p> - - <img src="/img/advanced-lighting/parallax_mapping_issues.png" alt="Three images displaying the issues with standard parallax mapping: breaks down at angles and incorrect results with steep height changes."/> - -<p> - The reason that it doesn't work properly at times is that it's just a crude approximation of displacement mapping. There are some extra tricks however that still allows us to get almost perfect results with steep height changes, even when looking at an angle. For instance, what if we instead of one sample take multiple samples to find the closest point to \(\color{blue}B\)? -</p> - -<h2>Steep Parallax Mapping</h2> -<p> - Steep Parallax Mapping is an extension on top of Parallax Mapping in that it uses the same principles, but instead of 1 sample it takes multiple samples to better pinpoint vector \(\color{brown}{\bar{P}}\) to \(\color{blue}B\). This gives much better results, even with steep height changes, as the accuracy of the technique is improved by the number of samples. -</p> - -<p> - The general idea of Steep Parallax Mapping is that it divides the total depth range into multiple layers of the same height/depth. For each of these layers we sample the depthmap, shifting the texture coordinates along the direction of \(\color{brown}{\bar{P}}\), until we find a sampled depth value that is less than the depth value of the current layer. Take a look at the following image: -</p> - - <img src="/img/advanced-lighting/parallax_mapping_steep_parallax_mapping_diagram.png" class="clean" alt="Diagram of how steep Parallax Mapping works in OpenGL"/> - -<p> - We traverse the depth layers from the top down and for each layer we compare its depth value to the depth value stored in the depthmap. If the layer's depth value is less than the depthmap's value it means this layer's part of vector \(\color{brown}{\bar{P}}\) is not below the surface. We continue this process until the layer's depth is higher than the value stored in the depthmap: this point is then below the (displaced) geometric surface. -</p> - -<p> - In this example we can see that the depthmap value at the second layer (D(2) = 0.73) is lower than the second layer's depth value <code>0.4</code> so we continue. In the next iteration, the layer's depth value <code>0.6</code> is higher than the depthmap's sampled depth value (D(3) = 0.37). We can thus assume vector \(\color{brown}{\bar{P}}\) at the third layer to be the most viable position of the displaced geometry. We then take the texture coordinate offset \(T_3\) from vector \(\color{brown}{\bar{P_3}}\) to displace the fragment's texture coordinates. You can see how the accuracy increases with more depth layers. -</p> - -<p> - To implement this technique we only have to change the <fun>ParallaxMapping</fun> function as we already have all the variables we need: -</p> - -<pre><code> -vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir) -{ - // number of depth layers - const float numLayers = 10; - // calculate the size of each layer - float layerDepth = 1.0 / numLayers; - // depth of current layer - float currentLayerDepth = 0.0; - // the amount to shift the texture coordinates per layer (from vector P) - vec2 P = viewDir.xy * height_scale; - vec2 deltaTexCoords = P / numLayers; - - [...] -} -</code></pre> - -<p> - Here we first set things up: we specify the number of layers, calculate the depth offset of each layer, and finally calculate the texture coordinate offset that we have to shift along the direction of \(\color{brown}{\bar{P}}\) per layer. -</p> - -<p> - We then iterate through all the layers, starting from the top, until we find a depthmap value less than the layer's depth value: -</p> - -<pre><code> -// get initial values -vec2 currentTexCoords = texCoords; -float currentDepthMapValue = texture(depthMap, currentTexCoords).r; - -while(currentLayerDepth < currentDepthMapValue) -{ - // shift texture coordinates along direction of P - currentTexCoords -= deltaTexCoords; - // get depthmap value at current texture coordinates - currentDepthMapValue = texture(depthMap, currentTexCoords).r; - // get depth of next layer - currentLayerDepth += layerDepth; -} - -return currentTexCoords; -</code></pre> - -<p> - Here we loop over each depth layer and stop until we find the texture coordinate offset along vector \(\color{brown}{\bar{P}}\) that first returns a depth that's below the (displaced) surface. The resulting offset is subtracted from the fragment's texture coordinates to get a final displaced texture coordinate vector, this time with much more accuracy compared to traditional parallax mapping. -</p> - -<p> - With around <code>10</code> samples the brick surface already looks more viable even when looking at it from an angle, but steep parallax mapping really shines when having a complex surface with steep height changes; like the earlier displayed wooden toy surface: -</p> - - <img src="/img/advanced-lighting/parallax_mapping_steep_parallax_mapping.png" class="clean" alt="Steep Parallax Mapping implemented in OpenGL"/> - -<p> - We can improve the algorithm a bit by exploiting one of Parallax Mapping's properties. When looking straight onto a surface there isn't much texture displacement going on while there is a lot of displacement when looking at a surface from an angle (visualize the view direction on both cases). By taking less samples when looking straight at a surface and more samples when looking at an angle we only sample the necessary amount: -</p> - -<pre><code> -const float minLayers = 8.0; -const float maxLayers = 32.0; -float numLayers = mix(maxLayers, minLayers, max(dot(vec3(0.0, 0.0, 1.0), viewDir), 0.0)); -</code></pre> - -<p> - Here we take the dot product of <var>viewDir</var> and the positive z direction and use its result to align the number of samples to <var>minLayers</var> or <var>maxLayers</var> based on the angle we're looking towards a surface (note that the positive z direction equals the surface's normal vector in tangent space). If we were to look at a direction parallel to the surface we'd use a total of <code>32</code> layers. -</p> - -<p> - You can find the updated source code <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/5.2.steep_parallax_mapping/steep_parallax_mapping.cpp" target="_blank">here</a>. You can also find the wooden toy box surface here: <a href="/img/textures/wood.png" target="_blank">diffuse</a>, <a href="/img/textures/toy_box_normal.png" target="_blank">normal</a> and <a href="/img/textures/toy_box_disp.png" target="_blank">depth</a>. -</p> - -<p> - Steep Parallax Mapping also comes with its problems though. Because the technique is based on a finite number of samples, we get aliasing effects and the clear distinctions between layers can easily be spotted: -</p> - - <img src="/img/advanced-lighting/parallax_mapping_steep_artifact.png" class="clean" alt="The visible layers of Steep Parallax Mapping can easily be detected with small numbers"/> - -<p> - We can reduce the issue by taking a larger number of samples, but this quickly becomes too heavy a burden on performance. There are several approaches that aim to fix this issue by not taking the first position that's below the (displaced) surface, but by <em>interpolating</em> between the position's two closest depth layers to find a much closer match to \(\color{blue}B\). -</p> - -<p> - Two of the more popular of these approaches are called <def>Relief Parallax Mapping</def> and <def>Parallax Occlusion Mapping</def> of which Relief Parallax Mapping gives the most accurate results, but is also more performance heavy compared to Parallax Occlusion Mapping. Because Parallax Occlusion Mapping gives almost the same results as Relief Parallax Mapping and is also more efficient it is often the preferred approach. -</p> - -<h2>Parallax Occlusion Mapping</h2> -<p> - Parallax Occlusion Mapping is based on the same principles as Steep Parallax Mapping, but instead of taking the texture coordinates of the first depth layer after a collision, we're going to linearly interpolate between the depth layer after and before the collision. We base the weight of the linear interpolation on how far the surface's height is from the depth layer's value of both layers. Take a look at the following picture to get a grasp of how it works: -</p> - -<img src="/img/advanced-lighting/parallax_mapping_parallax_occlusion_mapping_diagram.png" class="clean" alt="How Parallax Occlusion Mapping works in OpenGL"/> - -<p> - As you can see, it's largely similar to Steep Parallax Mapping with as an extra step the linear interpolation between the two depth layers' texture coordinates surrounding the intersected point. This is again an approximation, but significantly more accurate than Steep Parallax Mapping. -</p> - -<p> - The code for Parallax Occlusion Mapping is an extension on top of Steep Parallax Mapping and not too difficult: -</p> - -<pre><code> -[...] // steep parallax mapping code here - -// get texture coordinates before collision (reverse operations) -vec2 prevTexCoords = currentTexCoords + deltaTexCoords; - -// get depth after and before collision for linear interpolation -float afterDepth = currentDepthMapValue - currentLayerDepth; -float beforeDepth = texture(depthMap, prevTexCoords).r - currentLayerDepth + layerDepth; - -// interpolation of texture coordinates -float weight = afterDepth / (afterDepth - beforeDepth); -vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight); - -return finalTexCoords; -</code></pre> - -<p> - After we found the depth layer after intersecting the (displaced) surface geometry, we also retrieve the texture coordinates of the depth layer before intersection. Then we calculate the distance of the (displaced) geometry's depth from the corresponding depth layers and interpolate between these two values. The linear interpolation is a basic interpolation between both layer's texture coordinates. The function then finally returns the final interpolated texture coordinates. -</p> - -<p> - Parallax Occlusion Mapping gives surprisingly good results and although some slight artifacts and aliasing issues are still visible, it's a generally a good trade-off and only really visible when heavily zoomed in or looking at very steep angles. -</p> - - <img src="/img/advanced-lighting/parallax_mapping_parallax_occlusion_mapping.png" alt="Image of Parallax Occlusion Mapping in OpenGL"/> - -<p> - You can find the source code <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/5.3.parallax_occlusion_mapping/parallax_occlusion_mapping.cpp" target="_blank">here</a>. -</p> - -<p> - Parallax Mapping is a great technique to boost the detail of your scene, but does come with a few artifacts you'll have to consider when using it. Most often, parallax mapping is used on floor or wall-like surfaces where it's not as easy to determine the surface's outline and the viewing angle is most often roughly perpendicular to the surface. This way, the artifacts of Parallax Mapping aren't as noticeable and make it an incredibly interesting technique for boosting your objects' details. -</p> - -<h2>Additional resources</h2> -<ul> - <li><a href="http://sunandblackcat.com/tipFullView.php?topicid=28" target="_blank">Parallax Occlusion Mapping in GLSL</a>: great parallax mapping tutorial by sunandblackcat.com.</li> - <li><a href="https://www.youtube.com/watch?v=xvOT62L-fQI" target="_blank">How Parallax Displacement Mapping Works</a>: a nice video tutorial of how parallax mapping works by TheBennyBox.</li> -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-Lighting/SSAO.html b/translation/Advanced-Lighting/SSAO.html @@ -1,889 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - SSAO</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">SSAO</h1> -<h1 id="content-url" style='display:none;'>Advanced-Lighting/SSAO</h1> -<p> - We've briefly touched the topic in the basic lighting chapter: ambient lighting. Ambient lighting is a fixed light constant we add to the overall lighting of a scene to simulate the <def>scattering</def> of light. In reality, light scatters in all kinds of directions with varying intensities so the indirectly lit parts of a scene should also have varying intensities. One type of indirect lighting approximation is called <def>ambient occlusion</def> that tries to approximate indirect lighting by darkening creases, holes, and surfaces that are close to each other. These areas are largely occluded by surrounding geometry and thus light rays have fewer places to escape to, hence the areas appear darker. Take a look at the corners and creases of your room to see that the light there seems just a little darker. -</p> - -<p> - Below is an example image of a scene with and without ambient occlusion. Notice how especially between the creases, the (ambient) light is more occluded: -</p> - -<img src="/img/advanced-lighting/ssao_example.png" alt="Example image of SSAO with and without"/> - -<p> - While not an incredibly obvious effect, the image with ambient occlusion enabled does feel a lot more realistic due to these small occlusion-like details, giving the entire scene a greater feel of depth. -</p> - -<p> - Ambient occlusion techniques are expensive as they have to take surrounding geometry into account. One could shoot a large number of rays for each point in space to determine its amount of occlusion, but that quickly becomes computationally infeasible for real-time solutions. In 2007, Crytek published a technique called <def>screen-space ambient occlusion</def> (SSAO) for use in their title <em>Crysis</em>. The technique uses a scene's depth buffer in screen-space to determine the amount of occlusion instead of real geometrical data. This approach is incredibly fast compared to real ambient occlusion and gives plausible results, making it the de-facto standard for approximating real-time ambient occlusion. -</p> - -<p> - The basics behind screen-space ambient occlusion are simple: for each fragment on a screen-filled quad we calculate an <def>occlusion factor</def> based on the fragment's surrounding depth values. The occlusion factor is then used to reduce or nullify the fragment's ambient lighting component. The occlusion factor is obtained by taking multiple depth samples in a sphere sample kernel surrounding the fragment position and compare each of the samples with the current fragment's depth value. The number of samples that have a higher depth value than the fragment's depth represents the occlusion factor. -</p> - -<img src="/img/advanced-lighting/ssao_crysis_circle.png" class="clean" alt="Image of circle based SSAO technique as done by Crysis"/> - -<p> - Each of the gray depth samples that are inside geometry contribute to the total occlusion factor; the more samples we find inside geometry, the less ambient lighting the fragment should eventually receive. -</p> - -<p> - It is clear the quality and precision of the effect directly relates to the number of surrounding samples we take. If the sample count is too low, the precision drastically reduces and we get an artifact called <def>banding</def>; if it is too high, we lose performance. We can reduce the amount of samples we have to test by introducing some randomness into the sample kernel. By randomly rotating the sample kernel each fragment we can get high quality results with a much smaller amount of samples. This does come at a price as the randomness introduces a noticeable <def>noise pattern</def> that we'll have to fix by blurring the results. Below is an image (courtesy of <a href="http://john-chapman-graphics.blogspot.com/" target="_blank">John Chapman</a>) showcasing the banding effect and the effect randomness has on the results: -</p> - -<img src="/img/advanced-lighting/ssao_banding_noise.jpg" alt="The SSAO image quality with multiple samples and a blur added"/> - -<p> - As you can see, even though we get noticeable banding on the SSAO results due to a low sample count, by introducing some randomness the banding effects are completely gone. -</p> - -<p> - The SSAO method developed by Crytek had a certain visual style. Because the sample kernel used was a sphere, it caused flat walls to look gray as half of the kernel samples end up being in the surrounding geometry. Below is an image of Crysis's screen-space ambient occlusion that clearly portrays this gray feel: -</p> - -<img src="/img/advanced-lighting/ssao_crysis.jpg" alt="Screen space ambient occlusion in the Crysis game by Crytek showing a gray feel due to them using a sphere kernel instead of a normal oriented hemisphere sample kernel in OpenGL"/> - -<p> - For that reason we won't be using a sphere sample kernel, but rather a hemisphere sample kernel oriented along a surface's normal vector. -</p> - - <img src="/img/advanced-lighting/ssao_hemisphere.png" class="clean" alt="Image of normal oriented hemisphere sample kernel for SSAO in OpenGL"/> - - <p> - By sampling around this <def>normal-oriented hemisphere</def> we do not consider the fragment's underlying geometry to be a contribution to the occlusion factor. This removes the gray-feel of ambient occlusion and generally produces more realistic results. - This chapter's technique is based on this normal-oriented hemisphere method and a slightly modified version of John Chapman's brilliant <a href="http://john-chapman-graphics.blogspot.nl/2013/01/ssao-tutorial.html" target="_blank">SSAO tutorial</a>. - </p> - -<h2>Sample buffers</h2> -<p> - SSAO requires geometrical info as we need some way to determine the occlusion factor of a fragment. For each fragment, we're going to need the following data: -</p> - -<ul> - <li>A per-fragment <strong>position</strong> vector.</li> - <li>A per-fragment <strong>normal</strong> vector.</li> - <li>A per-fragment <strong>albedo</strong> color.</li> - <li>A <strong>sample kernel</strong>.</li> - <li>A per-fragment <strong>random rotation</strong> vector used to rotate the sample kernel.</li> -</ul> - -<p> - Using a per-fragment view-space position we can orient a sample hemisphere kernel around the fragment's view-space surface normal and use this kernel to sample the position buffer texture at varying offsets. For each per-fragment kernel sample we compare its depth with its depth in the position buffer to determine the amount of occlusion. The resulting occlusion factor is then used to limit the final ambient lighting component. By also including a per-fragment rotation vector we can significantly reduce the number of samples we'll need to take as we'll soon see. -</p> - - <img src="/img/advanced-lighting/ssao_overview.png" class="clean" alt="An overview of the SSAO screen-space OpenGL technique"/> - -<p> - As SSAO is a screen-space technique we calculate its effect on each fragment on a screen-filled 2D quad. This does mean we have no geometrical information of the scene. What we could do, is render the geometrical per-fragment data into screen-space textures that we then later send to the SSAO shader so we have access to the per-fragment geometrical data. If you've followed along with the previous chapter you'll realize this looks quite like a deferred renderer's G-buffer setup. For that reason SSAO is perfectly suited in combination with deferred rendering as we already have the position and normal vectors in the G-buffer. -</p> - -<note> - In this chapter we're going to implement SSAO on top of a slightly simplified version of the deferred renderer from the <a href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading" target="_blank">deferred shading</a> chapter. If you're not sure what deferred shading is, be sure to first read up on that. - </note> - -<p> - As we should have per-fragment position and normal data available from the scene objects, the fragment shader of the geometry stage is fairly simple: -</p> - -<pre><code> -#version 330 core -layout (location = 0) out vec4 gPosition; -layout (location = 1) out vec3 gNormal; -layout (location = 2) out vec4 gAlbedoSpec; - -in vec2 TexCoords; -in vec3 FragPos; -in vec3 Normal; - -void main() -{ - // store the fragment position vector in the first gbuffer texture - gPosition = FragPos; - // also store the per-fragment normals into the gbuffer - gNormal = normalize(Normal); - // and the diffuse per-fragment color, ignore specular - gAlbedoSpec.rgb = vec3(0.95); -} -</code></pre> - -<p> - Since SSAO is a screen-space technique where occlusion is calculated from the visible view, it makes sense to implement the algorithm in view-space. Therefore, <var>FragPos</var> and <var>Normal</var> as supplied by the geometry stage's vertex shader are transformed to view space (multiplied by the view matrix as well). -</p> - -<note> - It is possible to reconstruct the position vectors from depth values alone, using some clever tricks as Matt Pettineo described in his <a href="https://mynameismjp.wordpress.com/2010/09/05/position-from-depth-3/" target="_blank">blog</a>. This requires a few extra calculations in the shaders, but saves us from having to store position data in the G-buffer (which costs a lot of memory). For the sake of a more simple example, we'll leave these optimizations out of the chapter. -</note> - -<p> - The <var>gPosition</var> color buffer texture is configured as follows: -</p> - -<pre><code> -<function id='50'>glGenTextures</function>(1, &gPosition); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gPosition); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -</code></pre> - -<p> - This gives us a position texture that we can use to obtain depth values for each of the kernel samples. Note that we store the positions in a floating point data format; this way position values aren't clamped to [<code>0.0</code>,<code>1.0</code>] and we need the higher precision. Also note the texture wrapping method of <var>GL_CLAMP_TO_EDGE</var>. This ensures we don't accidentally oversample position/depth values in screen-space outside the texture's default coordinate region. -</p> - -<p> - Next, we need the actual hemisphere sample kernel and some method to randomly rotate it. -</p> - -<h2>Normal-oriented hemisphere</h2> -<p> - We need to generate a number of samples oriented along the normal of a surface. As we briefly discussed at the start of this chapter, we want to generate samples that form a hemisphere. As it is difficult nor plausible to generate a sample kernel for each surface normal direction, we're going to generate a sample kernel in <a href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping" target="_blank">tangent space</a>, with the normal vector pointing in the positive z direction. -</p> - - <img src="/img/advanced-lighting/ssao_hemisphere.png" class="clean" alt="Image of normal oriented hemisphere sample kernel for use in SSAO in OpenGL"/> - -<p> - Assuming we have a unit hemisphere, we can obtain a sample kernel with a maximum of <code>64</code> sample values as follows: -</p> - -<pre><code> -std::uniform_real_distribution<float> randomFloats(0.0, 1.0); // random floats between [0.0, 1.0] -std::default_random_engine generator; -std::vector<glm::vec3> ssaoKernel; -for (unsigned int i = 0; i < 64; ++i) -{ - glm::vec3 sample( - randomFloats(generator) * 2.0 - 1.0, - randomFloats(generator) * 2.0 - 1.0, - randomFloats(generator) - ); - sample = glm::normalize(sample); - sample *= randomFloats(generator); - ssaoKernel.push_back(sample); -} -</code></pre> - -<p> - We vary the <code>x</code> and <code>y</code> direction in tangent space between <code>-1.0</code> and <code>1.0</code>, and vary the z direction of the samples between <code>0.0</code> and <code>1.0</code> (if we varied the z direction between <code>-1.0</code> and <code>1.0</code> as well we'd have a sphere sample kernel). As the sample kernel will be oriented along the surface normal, the resulting sample vectors will all end up in the hemisphere. -</p> - -<p> - Currently, all samples are randomly distributed in the sample kernel, but we'd rather place a larger weight on occlusions close to the actual fragment. We want to distribute more kernel samples closer to the origin. We can do this with an accelerating interpolation function: -</p> - -<pre><code> - float scale = (float)i / 64.0; - scale = lerp(0.1f, 1.0f, scale * scale); - sample *= scale; - ssaoKernel.push_back(sample); -} -</code></pre> - -<p> - Where <fun>lerp</fun> is defined as: -</p> - -<pre><code> -float lerp(float a, float b, float f) -{ - return a + f * (b - a); -} -</code></pre> - -<p> - This gives us a kernel distribution that places most samples closer to its origin. -</p> - -<img src="/img/advanced-lighting/ssao_kernel_weight.png" class="clean" alt="SSAO Sample kernels (normal oriented hemisphere) with samples more closer aligned to the fragment's center position in OpenGL"/> - - -<p> - Each of the kernel samples will be used to offset the view-space fragment position to sample surrounding geometry. We do need quite a lot of samples in view-space in order to get realistic results, which may be too heavy on performance. However, if we can introduce some semi-random rotation/noise on a per-fragment basis, we can significantly reduce the number of samples required. -</p> - -<h2>Random kernel rotations</h2> -<p> - By introducing some randomness onto the sample kernels we largely reduce the number of samples necessary to get good results. We could create a random rotation vector for each fragment of a scene, but that quickly eats up memory. It makes more sense to create a small texture of random rotation vectors that we tile over the screen. -</p> - -<p> - We create a 4x4 array of random rotation vectors oriented around the tangent-space surface normal: -</p> - -<pre><code> -std::vector<glm::vec3> ssaoNoise; -for (unsigned int i = 0; i < 16; i++) -{ - glm::vec3 noise( - randomFloats(generator) * 2.0 - 1.0, - randomFloats(generator) * 2.0 - 1.0, - 0.0f); - ssaoNoise.push_back(noise); -} -</code></pre> - -<p> - As the sample kernel is oriented along the positive z direction in tangent space, we leave the <code>z</code> component at <code>0.0</code> so we rotate around the <code>z</code> axis. -</p> - -<p> - We then create a 4x4 texture that holds the random rotation vectors; make sure to set its wrapping method to <var>GL_REPEAT</var> so it properly tiles over the screen. -</p> - -<pre><code> -unsigned int noiseTexture; -<function id='50'>glGenTextures</function>(1, &noiseTexture); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, noiseTexture); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA16F, 4, 4, 0, GL_RGB, GL_FLOAT, &ssaoNoise[0]); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); -</code></pre> - -<p> - We now have all the relevant input data we need to implement SSAO. -</p> - -<h2>The SSAO shader</h2> -<p> - The SSAO shader runs on a 2D screen-filled quad that calculates the occlusion value for each of its fragments. As we need to store the result of the SSAO stage (for use in the final lighting shader), we create yet another framebuffer object: - </p> - -<pre><code> -unsigned int ssaoFBO; -<function id='76'>glGenFramebuffers</function>(1, &ssaoFBO); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, ssaoFBO); - -unsigned int ssaoColorBuffer; -<function id='50'>glGenTextures</function>(1, &ssaoColorBuffer); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, ssaoColorBuffer); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RED, SCR_WIDTH, SCR_HEIGHT, 0, GL_RED, GL_FLOAT, NULL); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - -<function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ssaoColorBuffer, 0); -</code></pre> - -<p> - As the ambient occlusion result is a single grayscale value we'll only need a texture's red component, so we set the color buffer's internal format to <var>GL_RED</var>. -</p> - -<p> - The complete process for rendering SSAO then looks a bit like this: -</p> - -<pre><code> -// geometry pass: render stuff into G-buffer -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, gBuffer); - [...] -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); - -// use G-buffer to render SSAO texture -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, ssaoFBO); - <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT); - <function id='49'>glActiveTexture</function>(GL_TEXTURE0); - <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gPosition); - <function id='49'>glActiveTexture</function>(GL_TEXTURE1); - <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, gNormal); - <function id='49'>glActiveTexture</function>(GL_TEXTURE2); - <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, noiseTexture); - shaderSSAO.use(); - SendKernelSamplesToShader(); - shaderSSAO.setMat4("projection", projection); - RenderQuad(); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); - -// lighting pass: render scene lighting -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -shaderLightingPass.use(); -[...] -<function id='49'>glActiveTexture</function>(GL_TEXTURE3); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, ssaoColorBuffer); -[...] -RenderQuad(); -</code></pre> - -<p> - The <var>shaderSSAO</var> shader takes as input the relevant G-buffer textures, the noise texture, and the normal-oriented hemisphere kernel samples: -</p> - -<pre><code> -#version 330 core -out float FragColor; - -in vec2 TexCoords; - -uniform sampler2D gPosition; -uniform sampler2D gNormal; -uniform sampler2D texNoise; - -uniform vec3 samples[64]; -uniform mat4 projection; - -// tile noise texture over screen, based on screen dimensions divided by noise size -const vec2 noiseScale = vec2(800.0/4.0, 600.0/4.0); // screen = 800x600 - -void main() -{ - [...] -} -</code></pre> - -<p> - Interesting to note here is the <var>noiseScale</var> variable. We want to tile the noise texture all over the screen, but as the <var>TexCoords</var> vary between <code>0.0</code> and <code>1.0</code>, the <var>texNoise</var> texture won't tile at all. So we'll calculate the required amount to scale <var>TexCoords</var> by dividing the screen's dimensions by the noise texture size. -</p> - -<pre><code> -vec3 fragPos = texture(gPosition, TexCoords).xyz; -vec3 normal = texture(gNormal, TexCoords).rgb; -vec3 randomVec = texture(texNoise, TexCoords * noiseScale).xyz; -</code></pre> - -<p> - As we set the tiling parameters of <var>texNoise</var> to <var>GL_REPEAT</var>, the random values will be repeated all over the screen. Together with the <var>fragPos</var> and <var>normal</var> vector, we then have enough data to create a TBN matrix that transforms any vector from tangent-space to view-space: -</p> - -<pre><code> -vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal)); -vec3 bitangent = cross(normal, tangent); -mat3 TBN = mat3(tangent, bitangent, normal); -</code></pre> - -<p> - Using a process called the <def>Gramm-Schmidt process</def> we create an orthogonal basis, each time slightly tilted based on the value of <var>randomVec</var>. Note that because we use a random vector for constructing the tangent vector, there is no need to have the TBN matrix exactly aligned to the geometry's surface, thus no need for per-vertex tangent (and bitangent) vectors. -</p> - -<p> - Next we iterate over each of the kernel samples, transform the samples from tangent to view-space, add them to the current fragment position, and compare the fragment position's depth with the sample depth stored in the view-space position buffer. Let's discuss this in a step-by-step fashion: -</p> - -<pre><code> -float occlusion = 0.0; -for(int i = 0; i < kernelSize; ++i) -{ - // get sample position - vec3 samplePos = TBN * samples[i]; // from tangent to view-space - samplePos = fragPos + samplePos * radius; - - [...] -} -</code></pre> - -<p> - Here <var>kernelSize</var> and <var>radius</var> are variables that we can use to tweak the effect; in this case a value of <var>64</var> and <var>0.5</var> respectively. - For each iteration we first transform the respective sample to view-space. We then add the view-space kernel offset sample to the view-space fragment position. Then we multiply the offset sample by <var>radius</var> to increase (or decrease) the effective sample radius of SSAO. -</p> - -<p> - Next we want to transform <var>sample</var> to screen-space so we can sample the position/depth value of <var>sample</var> as if we were rendering its position directly to the screen. As the vector is currently in view-space, we'll transform it to clip-space first using the <var>projection</var> matrix uniform: -</p> - -<pre><code> -vec4 offset = vec4(samplePos, 1.0); -offset = projection * offset; // from view to clip-space -offset.xyz /= offset.w; // perspective divide -offset.xyz = offset.xyz * 0.5 + 0.5; // transform to range 0.0 - 1.0 -</code></pre> - -<p> - After the variable is transformed to clip-space, we perform the perspective divide step by dividing its <code>xyz</code> components with its <code>w</code> component. The resulting normalized device coordinates are then transformed to the [<code>0.0</code>, <code>1.0</code>] range so we can use them to sample the position texture: -</p> - -<pre><code> -float sampleDepth = texture(gPosition, offset.xy).z; -</code></pre> - -<p> - We use the <var>offset</var> vector's <code>x</code> and <code>y</code> component to sample the position texture to retrieve the depth (or <code>z</code> value) of the sample position as seen from the viewer's perspective (the first non-occluded visible fragment). We then check if the sample's current depth value is larger than the stored depth value and if so, we add to the final contribution factor: -</p> - -<pre class="cpp"><code> -occlusion += (sampleDepth >= samplePos.z + bias ? 1.0 : 0.0); -</code></pre> - -<p> - Note that we add a small <code>bias</code> here to the original fragment's depth value (set to <code>0.025</code> in this example). A bias isn't always necessary, but it helps visually tweak the SSAO effect and solves acne effects that may occur based on the scene's complexity. -</p> - -<p> - We're not completely finished yet as there is still a small issue we have to take into account. Whenever a fragment is tested for ambient occlusion that is aligned close to the edge of a surface, it will also consider depth values of surfaces far behind the test surface; these values will (incorrectly) contribute to the occlusion factor. We can solve this by introducing a range check as the following image (courtesy of <a href="http://john-chapman-graphics.blogspot.com/" target="_blank">John Chapman</a>) illustrates: -</p> - - <img src="/img/advanced-lighting/ssao_range_check.png" alt="Image with and without range check of SSAO surface in OpenGL"/> - -<p> - We introduce a range check that makes sure a fragment contributes to the occlusion factor if its depth values is within the sample's radius. We change the last line to: -</p> - -<pre><code> -float rangeCheck = smoothstep(0.0, 1.0, radius / abs(fragPos.z - sampleDepth)); -occlusion += (sampleDepth >= samplePos.z + bias ? 1.0 : 0.0) * rangeCheck; -</code></pre> - -<p> - Here we used GLSL's <fun>smoothstep</fun> function that smoothly interpolates its third parameter between the first and second parameter's range, returning <code>0.0</code> if less than or equal to its first parameter and <code>1.0</code> if equal or higher to its second parameter. If the depth difference ends up between <var>radius</var>, its value gets smoothly interpolated between <code>0.0</code> and <code>1.0</code> by the following curve: -</p> - - <img src="/img/advanced-lighting/ssao_smoothstep.png" class="clean" alt="Image of smoothstep function in OpenGL used for rangecheck in SSAO in OpenGL"/> - -<p> - If we were to use a hard cut-off range check that would abruptly remove occlusion contributions if the depth values are outside <var>radius</var>, we'd see obvious (unattractive) borders at where the range check is applied. -</p> - -<p> - As a final step we normalize the occlusion contribution by the size of the kernel and output the results. Note that we subtract the occlusion factor from <code>1.0</code> so we can directly use the occlusion factor to scale the ambient lighting component. -</p> - -<pre class="cpp"><code> -} -occlusion = 1.0 - (occlusion / kernelSize); -FragColor = occlusion; -</code></pre> - -<p> - If we'd imagine a scene where our favorite backpack model is taking a little nap, the ambient occlusion shader produces the following texture: -</p> - -<img src="/img/advanced-lighting/ssao_without_blur.png" class="clean" alt="Image of SSAO shader result in OpenGL"/> - -<p> - As we can see, ambient occlusion gives a great sense of depth. With just the ambient occlusion texture we can already clearly see the model is indeed laying on the floor, instead of hovering slightly above it. -</p> - -<p> - It still doesn't look perfect, as the repeating pattern of the noise texture is clearly visible. To create a smooth ambient occlusion result we need to blur the ambient occlusion texture. -</p> - -<h2>Ambient occlusion blur</h2> -<p> - Between the SSAO pass and the lighting pass, we first want to blur the SSAO texture. So let's create yet another framebuffer object for storing the blur result: -</p> - -<pre><code> -unsigned int ssaoBlurFBO, ssaoColorBufferBlur; -<function id='76'>glGenFramebuffers</function>(1, &ssaoBlurFBO); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, ssaoBlurFBO); -<function id='50'>glGenTextures</function>(1, &ssaoColorBufferBlur); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, ssaoColorBufferBlur); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RED, SCR_WIDTH, SCR_HEIGHT, 0, GL_RED, GL_FLOAT, NULL); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); -<function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, ssaoColorBufferBlur, 0); -</code></pre> - -<p> - Because the tiled random vector texture gives us a consistent randomness, we can use this property to our advantage to create a simple blur shader: -</p> - -<pre><code> -#version 330 core -out float FragColor; - -in vec2 TexCoords; - -uniform sampler2D ssaoInput; - -void main() { - vec2 texelSize = 1.0 / vec2(textureSize(ssaoInput, 0)); - float result = 0.0; - for (int x = -2; x < 2; ++x) - { - for (int y = -2; y < 2; ++y) - { - vec2 offset = vec2(float(x), float(y)) * texelSize; - result += texture(ssaoInput, TexCoords + offset).r; - } - } - FragColor = result / (4.0 * 4.0); -} -</code></pre> - -<p> - Here we traverse the surrounding SSAO texels between <code>-2.0</code> and <code>2.0</code>, sampling the SSAO texture an amount identical to the noise texture's dimensions. We offset each texture coordinate by the exact size of a single texel using <fun>textureSize</fun> that returns a <code>vec2</code> of the given texture's dimensions. We average the obtained results to get a simple, but effective blur: -</p> - - <img src="/img/advanced-lighting/ssao.png" class="clean" alt="Image of SSAO texture with blur applied in OpenGL"/> - -<p> - And there we go, a texture with per-fragment ambient occlusion data; ready for use in the lighting pass. -</p> - -<h2>Applying ambient occlusion</h2> -<p> - Applying the occlusion factors to the lighting equation is incredibly easy: all we have to do is multiply the per-fragment ambient occlusion factor to the lighting's ambient component and we're done. If we take the Blinn-Phong deferred lighting shader of the previous chapter and adjust it a bit, we get the following fragment shader: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec2 TexCoords; - -uniform sampler2D gPosition; -uniform sampler2D gNormal; -uniform sampler2D gAlbedo; -uniform sampler2D ssao; - -struct Light { - vec3 Position; - vec3 Color; - - float Linear; - float Quadratic; - float Radius; -}; -uniform Light light; - -void main() -{ - // retrieve data from gbuffer - vec3 FragPos = texture(gPosition, TexCoords).rgb; - vec3 Normal = texture(gNormal, TexCoords).rgb; - vec3 Diffuse = texture(gAlbedo, TexCoords).rgb; - float AmbientOcclusion = texture(ssao, TexCoords).r; - - // blinn-phong (in view-space) - vec3 ambient = vec3(0.3 * Diffuse * AmbientOcclusion); // here we add occlusion factor - vec3 lighting = ambient; - vec3 viewDir = normalize(-FragPos); // viewpos is (0.0.0) in view-space - // diffuse - vec3 lightDir = normalize(light.Position - FragPos); - vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Diffuse * light.Color; - // specular - vec3 halfwayDir = normalize(lightDir + viewDir); - float spec = pow(max(dot(Normal, halfwayDir), 0.0), 8.0); - vec3 specular = light.Color * spec; - // attenuation - float dist = length(light.Position - FragPos); - float attenuation = 1.0 / (1.0 + light.Linear * dist + light.Quadratic * dist * dist); - diffuse *= attenuation; - specular *= attenuation; - lighting += diffuse + specular; - - FragColor = vec4(lighting, 1.0); -} -</code></pre> - -<p> - The only thing (aside from the change to view-space) we really changed is the multiplication of the scene's ambient component by <var>AmbientOcclusion</var>. With a single blue-ish point light in the scene we'd get the following result: -</p> - -<img src="/img/advanced-lighting/ssao_final.png" class="clean" alt="Image of SSAO applied in OpenGL"/> - -<p> - You can find the full source code of the demo scene <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/9.ssao/ssao.cpp" target="_blank">here</a>. -</p> - -<!--<ul> - <li><strong>geometry</strong>: <a href="/code_viewer.php?code=advanced-lighting/ssao_geometry&type=vertex" target="_blank">vertex</a>, <a href="/code_viewer.php?code=advanced-lighting/ssao_geometry&type=fragment" target="_blank">fragment</a>.</li> - <li><strong>SSAO</strong>: <a href="/code_viewer.php?code=advanced-lighting/ssao&type=vertex" target="_blank">vertex</a>, <a href="/code_viewer.php?code=advanced-lighting/ssao&type=fragment" target="_blank">fragment</a>.</li> - <li><strong>blur</strong>: <a href="/code_viewer.php?code=advanced-lighting/ssao&type=vertex" target="_blank">vertex</a>, <a href="/code_viewer.php?code=advanced-lighting/ssao_blur&type=fragment" target="_blank">fragment</a>.</li> - <li><strong>lighting</strong>: <a href="/code_viewer.php?code=advanced-lighting/ssao&type=vertex" target="_blank">vertex</a>, <a href="/code_viewer.php?code=advanced-lighting/ssao_lighting&type=fragment" target="_blank">fragment</a>.</li> -</ul> ---> - -<p> - Screen-space ambient occlusion is a highly customizable effect that relies heavily on tweaking its parameters based on the type of scene. There is no perfect combination of parameters for every type of scene. Some scenes only work with a small radius, while other scenes require a larger radius and a larger sample count for them to look realistic. The current demo uses <code>64</code> samples, which is a bit much; play around with a smaller kernel size and try to get good results. -</p> - -<p> - Some parameters you can tweak (by using uniforms for example): kernel size, radius, bias, and/or the size of the noise kernel. You can also raise the final occlusion value to a user-defined power to increase its strength: -</p> - -<pre><code> -occlusion = 1.0 - (occlusion / kernelSize); -FragColor = pow(occlusion, power); -</code></pre> - -<p> - Play around with different scenes and different parameters to appreciate the customizability of SSAO.</p> - -<p> - Even though SSAO is a subtle effect that isn't too clearly noticeable, it adds a great deal of realism to properly lit scenes and is definitely a technique you'd want to have in your toolkit. -</p> - -<h2>Additional resources</h2> -<ul> - <li><a href="http://john-chapman-graphics.blogspot.nl/2013/01/ssao-tutorial.html" target="_blank">SSAO Tutorial</a>: excellent SSAO tutorial by John Chapman; a large portion of this chapter's code and techniques are based of his article.</li> - <li><a href="https://mtnphil.wordpress.com/2013/06/26/know-your-ssao-artifacts/" target="_blank">Know your SSAO artifacts</a>: great article about improving SSAO specific artifacts.</li> - <li><a href="http://ogldev.atspace.co.uk/www/tutorial46/tutorial46.html" target="_blank">SSAO With Depth Reconstruction</a>: extension tutorial on top of SSAO from OGLDev about reconstructing position vectors from depth alone, saving us from storing the expensive position vectors in the G-buffer.</li> - </ul> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-Lighting/Shadows/Point-Shadows.html b/translation/Advanced-Lighting/Shadows/Point-Shadows.html @@ -1,861 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Point Shadows</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Point Shadows</h1> -<h1 id="content-url" style='display:none;'>Advanced-Lighting/Shadows/Point-Shadows</h1> -<p> - In the last chapter we learned to create dynamic shadows with shadow mapping. It works great, but it's mostly suited for directional (or spot) lights as the shadows are generated only in the direction of the light source. It is therefore also known as <def>directional shadow mapping</def> as the depth (or shadow) map is generated from only the direction the light is looking at. -</p> - -<p> - What this chapter will focus on is the generation of dynamic shadows in all surrounding directions. The technique we're using is perfect for point lights as a real point light would cast shadows in all directions. This technique is known as point (light) shadows or more formerly as <def>omnidirectional shadow maps</def>. -</p> - -<note> - This chapter builds upon the previous <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping" target="_blank">shadow mapping</a> chapter so unless you're familiar with traditional shadow mapping it is advised to read the shadow mapping chapter first. -</note> - -<p> - The technique is mostly similar to directional shadow mapping: we generate a depth map from the light's perspective(s), sample the depth map based on the current fragment position, and compare each fragment with the stored depth value to see whether it is in shadow. The main difference between directional shadow mapping and omnidirectional shadow mapping is the depth map we use. -</p> - -<p> - The depth map we need requires rendering a scene from all surrounding directions of a point light and as such a normal 2D depth map won't work; what if we were to use a <a href="https://learnopengl.com/Advanced-OpenGL/Cubemaps" target="_blank">cubemap</a> instead? Because a cubemap can store full environment data with only 6 faces, it is possible to render the entire scene to each of the faces of a cubemap and sample these as the point light's surrounding depth values. -</p> - -<img src="/img/advanced-lighting/point_shadows_diagram.png" class="clean" alt="Image of how omnidrectional shadow mapping or point shadows work"/> - -<p> - The generated depth cubemap is then passed to the lighting fragment shader that samples the cubemap with a direction vector to obtain the closest depth (from the light's perspective) at that fragment. Most of the complicated stuff we've already discussed in the shadow mapping chapter. What makes this technique a bit more difficult is the depth cubemap generation. -</p> - -<h2>Generating the depth cubemap</h2> -<p> - To create a cubemap of a light's surrounding depth values we have to render the scene 6 times: once for each face. One (quite obvious) way to do this, is render the scene 6 times with 6 different view matrices, each time attaching a different cubemap face to the framebuffer object. This would look something like this: - </p> - -<pre><code> -for(unsigned int i = 0; i < 6; i++) -{ - GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X + i; - <function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, face, depthCubemap, 0); - BindViewMatrix(lightViewMatrices[i]); - RenderScene(); -} -</code></pre> - -<p> - This can be quite expensive though as a lot of render calls are necessary for this single depth map. In this chapter we're going to use an alternative (more organized) approach using a little trick in the geometry shader that allows us to build the depth cubemap with just a single render pass. -</p> - -<p> - First, we'll need to create a cubemap: -</p> - -<pre><code> -unsigned int depthCubemap; -<function id='50'>glGenTextures</function>(1, &depthCubemap); -</code></pre> - -<p> - And assign each of the single cubemap faces a 2D depth-valued texture image: -</p> - -<pre><code> -const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024; -<function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, depthCubemap); -for (unsigned int i = 0; i < 6; ++i) - <function id='52'>glTexImage2D</function>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT, - SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); -</code></pre> - -<p> - And don't forget to set the texture parameters: -</p> - -<pre><code> -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); -</code></pre> - -<p> - Normally we'd attach a single face of a cubemap texture to the framebuffer object and render the scene 6 times, each time switching the depth buffer target of the framebuffer to a different cubemap face. Since we're going to use a geometry shader, that allows us to render to all faces in a single pass, we can directly attach the cubemap as a framebuffer's depth attachment with <fun>glFramebufferTexture</fun>: -</p> - -<pre class="cpp"><code> -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, depthMapFBO); -glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthCubemap, 0); -glDrawBuffer(GL_NONE); -glReadBuffer(GL_NONE); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -</code></pre> - -<p> - Again, note the call to <fun>glDrawBuffer</fun> and <fun>glReadBuffer</fun>: we only care about depth values when generating a depth cubemap so we have to explicitly tell OpenGL this framebuffer object does not render to a color buffer. -</p> - -<p> - With omnidirectional shadow maps we have two render passes: first, we generate the depth cubemap and second, we use the depth cubemap in the normal render pass to add shadows to the scene. This process looks a bit like this: -</p> - -<pre><code> -// 1. first render to depth cubemap -<function id='22'>glViewport</function>(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, depthMapFBO); - <function id='10'>glClear</function>(GL_DEPTH_BUFFER_BIT); - ConfigureShaderAndMatrices(); - RenderScene(); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -// 2. then render scene as normal with shadow mapping (using depth cubemap) -<function id='22'>glViewport</function>(0, 0, SCR_WIDTH, SCR_HEIGHT); -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -ConfigureShaderAndMatrices(); -<function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, depthCubemap); -RenderScene(); -</code></pre> - -<p> - The process is exactly the same as with default shadow mapping, although this time we render to and use a cubemap depth texture compared to a 2D depth texture. -</p> - -<h3>Light space transform</h3> -<p> - With the framebuffer and cubemap set, we need some way to transform all the scene's geometry to the relevant light spaces in all 6 directions of the light. Just like the <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping" target="_blank">shadow mapping</a> chapter we're going to need a light space transformation matrix \(T\), but this time one for each face. -</p> - -<p> - Each light space transformation matrix contains both a projection and a view matrix. For the projection matrix we're going to use a perspective projection matrix; the light source represents a point in space so perspective projection makes most sense. Each light space transformation matrix uses the same projection matrix: -</p> - -<pre><code> -float aspect = (float)SHADOW_WIDTH/(float)SHADOW_HEIGHT; -float near = 1.0f; -float far = 25.0f; -glm::mat4 shadowProj = <function id='58'>glm::perspective</function>(<function id='63'>glm::radians</function>(90.0f), aspect, near, far); -</code></pre> - -<p> - Important to note here is the field of view parameter of <fun><function id='58'>glm::perspective</function></fun> that we set to 90 degrees. By setting this to 90 degrees we make sure the viewing field is exactly large enough to fill a single face of the cubemap such that all faces align correctly to each other at the edges. -</p> - -<p> - As the projection matrix does not change per direction we can re-use it for each of the 6 transformation matrices. We do need a different view matrix per direction. With <fun><function id='62'>glm::lookAt</function></fun> we create 6 view directions, each looking at one face direction of the cubemap in the order: right, left, top, bottom, near and far. -</p> - -<pre><code> -std::vector<glm::mat4> shadowTransforms; -shadowTransforms.push_back(shadowProj * - <function id='62'>glm::lookAt</function>(lightPos, lightPos + glm::vec3( 1.0, 0.0, 0.0), glm::vec3(0.0,-1.0, 0.0)); -shadowTransforms.push_back(shadowProj * - <function id='62'>glm::lookAt</function>(lightPos, lightPos + glm::vec3(-1.0, 0.0, 0.0), glm::vec3(0.0,-1.0, 0.0)); -shadowTransforms.push_back(shadowProj * - <function id='62'>glm::lookAt</function>(lightPos, lightPos + glm::vec3( 0.0, 1.0, 0.0), glm::vec3(0.0, 0.0, 1.0)); -shadowTransforms.push_back(shadowProj * - <function id='62'>glm::lookAt</function>(lightPos, lightPos + glm::vec3( 0.0,-1.0, 0.0), glm::vec3(0.0, 0.0,-1.0)); -shadowTransforms.push_back(shadowProj * - <function id='62'>glm::lookAt</function>(lightPos, lightPos + glm::vec3( 0.0, 0.0, 1.0), glm::vec3(0.0,-1.0, 0.0)); -shadowTransforms.push_back(shadowProj * - <function id='62'>glm::lookAt</function>(lightPos, lightPos + glm::vec3( 0.0, 0.0,-1.0), glm::vec3(0.0,-1.0, 0.0)); -</code></pre> - -<p> - Here we create 6 view matrices and multiply them with the projection matrix to get a total of 6 different light space transformation matrices. The <code>target</code> parameter of <fun><function id='62'>glm::lookAt</function></fun> each looks into the direction of a single cubemap face. -</p> - -<p> - These transformation matrices are sent to the shaders that render the depth into the cubemap. -</p> - -<h3>Depth shaders</h3> -<p> - To render depth values to a depth cubemap we're going to need a total of three shaders: a vertex and fragment shader, and a <a href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader" target="_blank">geometry shader</a> in between. -</p> - -<p> - The geometry shader will be the shader responsible for transforming all world-space vertices to the 6 different light spaces. Therefore, the vertex shader simply transforms vertices to world-space and directs them to the geometry shader: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; - -uniform mat4 model; - -void main() -{ - gl_Position = model * vec4(aPos, 1.0); -} -</code></pre> - -<p> - The geometry shader will take as input 3 triangle vertices and a uniform array of light space transformation matrices. The geometry shader is responsible for transforming the vertices to the light spaces; this is also where it gets interesting. - </p> - - <p> - The geometry shader has a built-in variable called <var>gl_Layer</var> that specifies which cubemap face to emit a primitive to. When left alone, the geometry shader just sends its primitives further down the pipeline as usual, but when we update this variable we can control to which cubemap face we render to for each primitive. This of course only works when we have a cubemap texture attached to the active framebuffer. -</p> - -<pre><code> -#version 330 core -layout (triangles) in; -layout (triangle_strip, max_vertices=18) out; - -uniform mat4 shadowMatrices[6]; - -out vec4 FragPos; // FragPos from GS (output per emitvertex) - -void main() -{ - for(int face = 0; face < 6; ++face) - { - gl_Layer = face; // built-in variable that specifies to which face we render. - for(int i = 0; i < 3; ++i) // for each triangle vertex - { - FragPos = gl_in[i].gl_Position; - gl_Position = shadowMatrices[face] * FragPos; - EmitVertex(); - } - EndPrimitive(); - } -} -</code></pre> - -<p> - This geometry shader is relatively straightforward. We take as input a triangle, and output a total of 6 triangles (6 * 3 equals 18 vertices). In the <fun>main</fun> function we iterate over 6 cubemap faces where we specify each face as the output face by storing the face integer into <var>gl_Layer</var>. We then generate the output triangles by transforming each world-space input vertex to the relevant light space by multiplying <var>FragPos</var> with the face's light-space transformation matrix. Note that we also sent the resulting <var>FragPos</var> variable to the fragment shader that we'll need to calculate a depth value. -</p> - -<p> - In the last chapter we used an empty fragment shader and let OpenGL figure out the depth values of the depth map. This time we're going to calculate our own (linear) depth as the linear distance between each closest fragment position and the light source's position. Calculating our own depth values makes the later shadow calculations a bit more intuitive. -</p> - -<pre><code> -#version 330 core -in vec4 FragPos; - -uniform vec3 lightPos; -uniform float far_plane; - -void main() -{ - // get distance between fragment and light source - float lightDistance = length(FragPos.xyz - lightPos); - - // map to [0;1] range by dividing by far_plane - lightDistance = lightDistance / far_plane; - - // write this as modified depth - gl_FragDepth = lightDistance; -} -</code></pre> - -<p> - The fragment shader takes as input the <var>FragPos</var> from the geometry shader, the light's position vector, and the frustum's far plane value. Here we take the distance between the fragment and the light source, map it to the [<code>0</code>,<code>1</code>] range and write it as the fragment's depth value. -</p> - -<p> - Rendering the scene with these shaders and the cubemap-attached framebuffer object active should give you a completely filled depth cubemap for the second pass's shadow calculations. -</p> - -<h2>Omnidirectional shadow maps</h2> -<p> - With everything set up it is time to render the actual omnidirectional shadows. The procedure is similar to the directional shadow mapping chapter, although this time we bind a cubemap texture instead of a 2D texture and also pass the light projection's far plane variable to the shaders. -</p> - -<pre><code> -<function id='22'>glViewport</function>(0, 0, SCR_WIDTH, SCR_HEIGHT); -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -shader.use(); -// ... send uniforms to shader (including light's far_plane value) -<function id='49'>glActiveTexture</function>(GL_TEXTURE0); -<function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, depthCubemap); -// ... bind other textures -RenderScene(); -</code></pre> - -<p> - Here the <fun>renderScene</fun> function renders a few cubes in a large cube room scattered around a light source at the center of the scene. -</p> - -<p> - The vertex and fragment shader are mostly similar to the original shadow mapping shaders: the difference being that the fragment shader no longer requires a fragment position in light space as we can now sample the depth values with a direction vector. -</p> - -<p> - Because of this, the vertex shader doesn't needs to transform its position vectors to light space so we can remove the <var>FragPosLightSpace</var> variable: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec3 aNormal; -layout (location = 2) in vec2 aTexCoords; - -out vec2 TexCoords; - -out VS_OUT { - vec3 FragPos; - vec3 Normal; - vec2 TexCoords; -} vs_out; - -uniform mat4 projection; -uniform mat4 view; -uniform mat4 model; - -void main() -{ - vs_out.FragPos = vec3(model * vec4(aPos, 1.0)); - vs_out.Normal = transpose(inverse(mat3(model))) * aNormal; - vs_out.TexCoords = aTexCoords; - gl_Position = projection * view * model * vec4(aPos, 1.0); -} -</code></pre> - -<p> - The fragment shader's Blinn-Phong lighting code is exactly the same as we had before with a shadow multiplication at the end: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in VS_OUT { - vec3 FragPos; - vec3 Normal; - vec2 TexCoords; -} fs_in; - -uniform sampler2D diffuseTexture; -uniform samplerCube depthMap; - -uniform vec3 lightPos; -uniform vec3 viewPos; - -uniform float far_plane; - -float ShadowCalculation(vec3 fragPos) -{ - [...] -} - -void main() -{ - vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb; - vec3 normal = normalize(fs_in.Normal); - vec3 lightColor = vec3(0.3); - // ambient - vec3 ambient = 0.3 * color; - // diffuse - vec3 lightDir = normalize(lightPos - fs_in.FragPos); - float diff = max(dot(lightDir, normal), 0.0); - vec3 diffuse = diff * lightColor; - // specular - vec3 viewDir = normalize(viewPos - fs_in.FragPos); - vec3 reflectDir = reflect(-lightDir, normal); - float spec = 0.0; - vec3 halfwayDir = normalize(lightDir + viewDir); - spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0); - vec3 specular = spec * lightColor; - // calculate shadow - float shadow = ShadowCalculation(fs_in.FragPos); - vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color; - - FragColor = vec4(lighting, 1.0); -} -</code></pre> - -<p> - There are a few subtle differences: the lighting code is the same, but we now have a <code>samplerCube</code> uniform and the <fun>ShadowCalculation</fun> function takes the current fragment's position as its argument instead of the fragment position in light space. We now also include the light frustum's <var>far_plane</var> value that we'll later need. -</p> - -<p> - The biggest difference is in the content of the <fun>ShadowCalculation</fun> function that now samples depth values from a cubemap instead of a 2D texture. Let's discuss its content step by step. -</p> - -<p> - The first thing we have to do is retrieve the depth of the cubemap. You may remember from the cubemap section of this chapter that we stored the depth as the linear distance between the fragment and the light position; we're taking a similar approach here: -</p> - -<pre><code> -float ShadowCalculation(vec3 fragPos) -{ - vec3 fragToLight = fragPos - lightPos; - float closestDepth = texture(depthMap, fragToLight).r; -} -</code></pre> - -<p> - Here we take the difference vector between the fragment's position and the light's position and use that vector as a direction vector to sample the cubemap. The direction vector doesn't need to be a unit vector to sample from a cubemap so there's no need to normalize it. The resulting <var>closestDepth</var> value is the normalized depth value between the light source and its closest visible fragment. -</p> - -<p> - The <var>closestDepth</var> value is currently in the range [<code>0</code>,<code>1</code>] so we first transform it back to [<code>0</code>,<code>far_plane</code>] by multiplying it with <var>far_plane</var>. -</p> - -<pre><code> -closestDepth *= far_plane; -</code></pre> - -<p> - Next we retrieve the depth value between the current fragment and the light source, which we can easily obtain by taking the length of <var>fragToLight</var> due to how we calculated depth values in the cubemap: -</p> - -<pre><code> -float currentDepth = length(fragToLight); -</code></pre> - -<p> - This returns a depth value in the same (or larger) range as <var>closestDepth</var>. -</p> - -<p> - Now we can compare both depth values to see which is closer than the other and determine whether the current fragment is in shadow. We also include a shadow bias so we don't get shadow acne as discussed in the <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping" target="_blank">previous</a> chapter. -</p> - -<pre><code> -float bias = 0.05; -float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0; -</code></pre> - -<p> - The complete <fun>ShadowCalculation</fun> then becomes: -</p> - -<pre><code> -float ShadowCalculation(vec3 fragPos) -{ - // get vector between fragment position and light position - vec3 fragToLight = fragPos - lightPos; - // use the light to fragment vector to sample from the depth map - float closestDepth = texture(depthMap, fragToLight).r; - // it is currently in linear range between [0,1]. Re-transform back to original value - closestDepth *= far_plane; - // now get current linear depth as the length between the fragment and light position - float currentDepth = length(fragToLight); - // now test for shadows - float bias = 0.05; - float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0; - - return shadow; -} -</code></pre> - -<p> - With these shaders we already get pretty good shadows and this time in all surrounding directions from a point light. With a point light positioned at the center of a simple scene it'll look a bit like this: -</p> - - <img src="/img/advanced-lighting/point_shadows.png" class="clean" alt="Omnidirectional point shadow maps in OpenGL"/> - -<p> - You can find the source code of this demo <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/3.2.1.point_shadows/point_shadows.cpp" target="_blank">here</a>. -</p> - -<h3>Visualizing cubemap depth buffer</h3> -<p> - If you're somewhat like me you probably didn't get this right on the first try so it makes sense to do some debugging, with one of the obvious checks being validating whether the depth map was built correctly. A simple trick to visualize the depth buffer is to take the <var>closestDepth</var> variable in the <fun>ShadowCalculation</fun> function and display that variable as: -</p> - -<pre><code> -FragColor = vec4(vec3(closestDepth / far_plane), 1.0); -</code></pre> - -<p> - The result is a grayed out scene where each color represents the linear depth values of the scene: -</p> - - <img src="/img/advanced-lighting/point_shadows_depth_cubemap.png" class="clean" alt="Visualized depth cube map of omnidrectional shadow maps"/> - -<p> - You can also see the to-be shadowed regions on the outside wall. If it looks somewhat similar, you know the depth cubemap was properly generated. -</p> - -<h2>PCF</h2> -<p> - Since omnidirectional shadow maps are based on the same principles of traditional shadow mapping it also has the same resolution dependent artifacts. If you zoom in close enough you can again see jagged edges. <def>Percentage-closer filtering</def> or PCF allows us to smooth out these jagged edges by filtering multiple samples around the fragment position and average the results. -</p> - -<p> - If we take the same simple PCF filter of the previous chapter and add a third dimension we get: -</p> - -<pre><code> -float shadow = 0.0; -float bias = 0.05; -float samples = 4.0; -float offset = 0.1; -for(float x = -offset; x < offset; x += offset / (samples * 0.5)) -{ - for(float y = -offset; y < offset; y += offset / (samples * 0.5)) - { - for(float z = -offset; z < offset; z += offset / (samples * 0.5)) - { - float closestDepth = texture(depthMap, fragToLight + vec3(x, y, z)).r; - closestDepth *= far_plane; // undo mapping [0;1] - if(currentDepth - bias > closestDepth) - shadow += 1.0; - } - } -} -shadow /= (samples * samples * samples); -</code></pre> - -<p> - The code isn't that different from the traditional shadow mapping code. We calculate and add texture offsets dynamically for each axis based on a fixed number of samples. For each sample we repeat the original shadow process on the offsetted sample direction and average the results at the end. -</p> - -<p> - The shadows now look more soft and smooth and give more plausible results. -</p> - - <img src="/img/advanced-lighting/point_shadows_soft.png" class="clean" alt="Soft shades with omnidirectional shadow mapping in OpenGL using PCF"/> - -<p> - However, with <var>samples</var> set to <code>4.0</code> we take a total of <code>64</code> samples each fragment which is a lot! -</p> - -<p> - As most of these samples are redundant in that they sample close to the original direction vector it may make more sense to only sample in perpendicular directions of the sample direction vector. However as there is no (easy) way to figure out which sub-directions are redundant this becomes difficult. One trick we can use is to take an array of offset directions that are all roughly separable e.g. each of them points in completely different directions. This will significantly reduce the number of sub-directions that are close together. Below we have such an array of a maximum of <code>20</code> offset directions: -</p> - -<pre><code> -vec3 sampleOffsetDirections[20] = vec3[] -( - vec3( 1, 1, 1), vec3( 1, -1, 1), vec3(-1, -1, 1), vec3(-1, 1, 1), - vec3( 1, 1, -1), vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1), - vec3( 1, 1, 0), vec3( 1, -1, 0), vec3(-1, -1, 0), vec3(-1, 1, 0), - vec3( 1, 0, 1), vec3(-1, 0, 1), vec3( 1, 0, -1), vec3(-1, 0, -1), - vec3( 0, 1, 1), vec3( 0, -1, 1), vec3( 0, -1, -1), vec3( 0, 1, -1) -); -</code></pre> - -<p> - From this we can adapt the PCF algorithm to take a fixed amount of samples from <var>sampleOffsetDirections</var> and use these to sample the cubemap. The advantage here is that we need a lot less samples to get visually similar results. -</p> - -<pre><code> -float shadow = 0.0; -float bias = 0.15; -int samples = 20; -float viewDistance = length(viewPos - fragPos); -float diskRadius = 0.05; -for(int i = 0; i < samples; ++i) -{ - float closestDepth = texture(depthMap, fragToLight + sampleOffsetDirections[i] * diskRadius).r; - closestDepth *= far_plane; // undo mapping [0;1] - if(currentDepth - bias > closestDepth) - shadow += 1.0; -} -shadow /= float(samples); -</code></pre> - -<p> - Here we add multiple offsets, scaled by some <var>diskRadius</var>, around the original <var>fragToLight</var> direction vector to sample from the cubemap. -</p> - -<p> - Another interesting trick we can apply here is that we can change <var>diskRadius</var> based on the distance of the viewer to the fragment, making the shadows softer when far away and sharper when close by. -</p> - -<pre><code> -float diskRadius = (1.0 + (viewDistance / far_plane)) / 25.0; -</code></pre> - -<p> - The results of the updated PCF algorithm gives just as good, if not better, results of soft shadows: -</p> - -<img src="/img/advanced-lighting/point_shadows_soft_better.png" class="clean" alt="Soft shades with omnidirectional shadow mapping in OpenGL using PCF, more efficient"/> - -<p> - Of course, the <var>bias</var> we add to each sample is highly based on context and will always require tweaking based on the scene you're working with. Play around with all the values and see how they affect the scene. -</p> - -<p> - You can find the final code here: <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/3.2.2.point_shadows_soft/point_shadows_soft.cpp" target="_blank">here</a>. -</p> - -<p> - I should mention that using geometry shaders to generate a depth map isn't necessarily faster than rendering the scene 6 times for each face. Using a geometry shader like this has its own performance penalties that may outweigh the performance gain of using one in the first place. This of course depends on the type of environment, the specific video card drivers, and plenty of other factors. So if you really care about pushing the most out of your system, make sure to profile both methods and select the more efficient one for your scene. -</p> - -<h2>Additional resources</h2> - <ul> - <li><a href="http://www.sunandblackcat.com/tipFullView.php?l=eng&topicid=36" target="_blank">Shadow Mapping for point light sources in OpenGL</a>: omnidirectional shadow mapping tutorial by sunandblackcat.</li> - <li><a href="http://ogldev.atspace.co.uk/www/tutorial43/tutorial43.html" target="_blank">Multipass Shadow Mapping With Point Lights</a>: omnidirectional shadow mapping tutorial by ogldev.</li> - <li><a href="http://www.cg.tuwien.ac.at/~husky/RTR/OmnidirShadows-whyCaps.pdf" target="_blank">Omni-directional Shadows</a>: a nice set of slides about omnidirectional shadow mapping by Peter Houska.</li> -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-Lighting/Shadows/Shadow-Mapping.html b/translation/Advanced-Lighting/Shadows/Shadow-Mapping.html @@ -1,982 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Shadow Mapping</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Shadow Mapping</h1> -<h1 id="content-url" style='display:none;'>Advanced-Lighting/Shadows/Shadow-Mapping</h1> -<p> - Shadows are a result of the absence of light due to occlusion. When a light source's light rays do not hit an object because it gets occluded by some other object, the object is in shadow. Shadows add a great deal of realism to a lit scene and make it easier for a viewer to observe spatial relationships between objects. They give a greater sense of depth to our scene and objects. For example, take a look at the following image of a scene with and without shadows: - -</p> - -<img src="/img/advanced-lighting/shadow_mapping_with_without.png" alt="comparrison of shadows in a scene with and without in OpenGL"/> - -<p> - You can see that with shadows it becomes much more obvious how the objects relate to each other. For instance, the fact that one of the cubes is floating above the others is only really noticeable when we have shadows. -</p> - -<p> - Shadows are a bit tricky to implement though, specifically because in current real-time (rasterized graphics) research a perfect shadow algorithm hasn't been developed yet. There are several good shadow approximation techniques, but they all have their little quirks and annoyances which we have to take into account. -</p> - -<p> - One technique used by most videogames that gives decent results and is relatively easy to implement is <def>shadow mapping</def>. Shadow mapping is not too difficult to understand, doesn't cost too much in performance and quite easily extends into more advanced algorithms (like <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows" target="_blank">Omnidirectional Shadow Maps</a> and Cascaded Shadow Maps). -</p> - -<h2>Shadow mapping</h2> -<p> - The idea behind shadow mapping is quite simple: we render the scene from the light's point of view and everything we see from the light's perspective is lit and everything we can't see must be in shadow. Imagine a floor section with a large box between itself and a light source. Since the light source will see this box and not the floor section when looking in its direction that specific floor section should be in shadow. -</p> - - <img src="/img/advanced-lighting/shadow_mapping_theory.png" class="clean" alt="Shadow mapping illustrated."/> - -<p> - Here all the blue lines represent the fragments that the light source can see. The occluded fragments are shown as black lines: these are rendered as being shadowed. If we were to draw a line or <def>ray</def> from the light source to a fragment on the right-most box we can see the ray first hits the floating container before hitting the right-most container. As a result, the floating container's fragment is lit and the right-most container's fragment is not lit and thus in shadow. -</p> - -<p> - We want to get the point on the ray where it first hit an object and compare this <em>closest point</em> to other points on this ray. We then do a basic test to see if a test point's ray position is further down the ray than the closest point and if so, the test point must be in shadow. Iterating through possibly thousands of light rays from such a light source is an extremely inefficient approach and doesn't lend itself too well for real-time rendering. We can do something similar, but without casting light rays. Instead, we use something we're quite familiar with: the depth buffer. -</p> - -<p> - You may remember from the <a href="https://learnopengl.com/Advanced-OpenGL/Depth-testing" target="_blank">depth testing</a> chapter that a value in the depth buffer corresponds to the depth of a fragment clamped to [0,1] from the camera's point of view. What if we were to render the scene from the light's perspective and store the resulting depth values in a texture? This way, we can sample the closest depth values as seen from the light's perspective. After all, the depth values show the first fragment visible from the light's perspective. We store all these depth values in a texture that we call a <def>depth map</def> or <def>shadow map</def>. -</p> - - <img src="/img/advanced-lighting/shadow_mapping_theory_spaces.png" class="clean" alt="Different coordinate transforms / spaces for shadow mapping."/> - -<p> - The left image shows a directional light source (all light rays are parallel) casting a shadow on the surface below the cube. Using the depth values stored in the depth map we find the closest point and use that to determine whether fragments are in shadow. We create the depth map by rendering the scene (from the light's perspective) using a view and projection matrix specific to that light source. This projection and view matrix together form a transformation \(T\) that transforms any 3D position to the light's (visible) coordinate space. -</p> - -<note> - A directional light doesn't have a position as it's modelled to be infinitely far away. However, for the sake of shadow mapping we need to render the scene from a light's perspective and thus render the scene from a position somewhere along the lines of the light direction. -</note> - - <p> - In the right image we see the same directional light and the viewer. We render a fragment at point \(\bar{\color{red}{P}}\) for which we have to determine whether it is in shadow. To do this, we first transform point \(\bar{\color{red}{P}}\) to the light's coordinate space using \(T\). Since point \(\bar{\color{red}{P}}\) is now as seen from the light's perspective, its <code>z</code> coordinate corresponds to its depth which in this example is <code>0.9</code>. Using point \(\bar{\color{red}{P}}\) we can also index the depth/shadow map to obtain the closest visible depth from the light's perspective, which is at point \(\bar{\color{green}{C}}\) with a sampled depth of <code>0.4</code>. Since indexing the depth map returns a depth smaller than the depth at point \(\bar{\color{red}{P}}\) we can conclude point \(\bar{\color{red}{P}}\) is occluded and thus in shadow. -</p> - - -<p> - Shadow mapping therefore consists of two passes: first we render the depth map, and in the second pass we render the scene as normal and use the generated depth map to calculate whether fragments are in shadow. It may sound a bit complicated, but as soon as we walk through the technique step-by-step it'll likely start to make sense. -</p> - -<h2>The depth map</h2> -<p> - The first pass requires us to generate a depth map. The depth map is the depth texture as rendered from the light's perspective that we'll be using for testing for shadows. Because we need to store the rendered result of a scene into a texture we're going to need <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">framebuffers</a> again. -</p> - -<p> - First we'll create a framebuffer object for rendering the depth map: -</p> - -<pre><code> -unsigned int depthMapFBO; -<function id='76'>glGenFramebuffers</function>(1, &depthMapFBO); -</code></pre> - -<p> - Next we create a 2D texture that we'll use as the framebuffer's depth buffer: -</p> - -<pre><code> -const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024; - -unsigned int depthMap; -<function id='50'>glGenTextures</function>(1, &depthMap); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, depthMap); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, - SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); -</code></pre> - -<p> - Generating the depth map shouldn't look too complicated. Because we only care about depth values we specify the texture's formats as <var>GL_DEPTH_COMPONENT</var>. We also give the texture a width and height of <code>1024</code>: this is the resolution of the depth map. -</p> - -<p> - With the generated depth texture we can attach it as the framebuffer's depth buffer: -</p> - -<pre class="cpp"><code> -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, depthMapFBO); -<function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0); -glDrawBuffer(GL_NONE); -glReadBuffer(GL_NONE); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -</code></pre> - -<p> - We only need the depth information when rendering the scene from the light's perspective so there is no need for a color buffer. A framebuffer object however is not complete without a color buffer so we need to explicitly tell OpenGL we're not going to render any color data. We do this by setting both the read and draw buffer to <var>GL_NONE</var> with <fun>glDrawBuffer</fun> and <fun>glReadbuffer</fun>. -</p> - -<p> - With a properly configured framebuffer that renders depth values to a texture we can start the first pass: generate the depth map. When combined with the second pass, the complete rendering stage will look a bit like this: -</p> - -<pre><code> -// 1. first render to depth map -<function id='22'>glViewport</function>(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, depthMapFBO); - <function id='10'>glClear</function>(GL_DEPTH_BUFFER_BIT); - ConfigureShaderAndMatrices(); - RenderScene(); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -// 2. then render scene as normal with shadow mapping (using depth map) -<function id='22'>glViewport</function>(0, 0, SCR_WIDTH, SCR_HEIGHT); -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -ConfigureShaderAndMatrices(); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, depthMap); -RenderScene(); -</code></pre> - -<p> - This code left out some details, but it'll give you the general idea of shadow mapping. What is important to note here are the calls to <fun><function id='22'>glViewport</function></fun>. Because shadow maps often have a different resolution compared to what we originally render the scene in (usually the window resolution), we need to change the viewport parameters to accommodate for the size of the shadow map. If we forget to update the viewport parameters, the resulting depth map will be either incomplete or too small. -</p> - -<h3>Light space transform</h3> -<p> - An unknown in the previous snippet of code is the <fun>ConfigureShaderAndMatrices</fun> function. In the second pass this is business as usual: make sure proper projection and view matrices are set, and set the relevant model matrices per object. However, in the first pass we need to use a different projection and view matrix to render the scene from the light's point of view. -</p> - -<p> - Because we're modelling a directional light source, all its light rays are parallel. For this reason, we're going to use an orthographic projection matrix for the light source where there is no perspective deform: -</p> - -<pre><code> -float near_plane = 1.0f, far_plane = 7.5f; -glm::mat4 lightProjection = <function id='59'>glm::ortho</function>(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane); -</code></pre> - -<p> - Here is an example orthographic projection matrix as used in this chapter's demo scene. Because a projection matrix indirectly determines the range of what is visible (e.g. what is not clipped) you want to make sure the size of the projection frustum correctly contains the objects you want to be in the depth map. When objects or fragments are not in the depth map they will not produce shadows. -</p> - -<p> - To create a view matrix to transform each object so they're visible from the light's point of view, we're going to use the infamous <fun><function id='62'>glm::lookAt</function></fun> function; this time with the light source's position looking at the scene's center. -</p> - -<pre><code> -glm::mat4 lightView = <function id='62'>glm::lookAt</function>(glm::vec3(-2.0f, 4.0f, -1.0f), - glm::vec3( 0.0f, 0.0f, 0.0f), - glm::vec3( 0.0f, 1.0f, 0.0f)); -</code></pre> - -<p> - Combining these two gives us a light space transformation matrix that transforms each world-space vector into the space as visible from the light source; exactly what we need to render the depth map. -</p> - -<pre><code> -glm::mat4 lightSpaceMatrix = lightProjection * lightView; -</code></pre> - -<p> - This <var>lightSpaceMatrix</var> is the transformation matrix that we earlier denoted as \(T\). With this <var>lightSpaceMatrix</var>, we can render the scene as usual as long as we give each shader the light-space equivalents of the projection and view matrices. However, we only care about depth values and not all the expensive fragment (lighting) calculations. To save performance we're going to use a different, but much simpler shader for rendering to the depth map. -</p> - -<h3>Render to depth map</h3> -<p> - When we render the scene from the light's perspective we'd much rather use a simple shader that only transforms the vertices to light space and not much more. For such a simple shader called <var>simpleDepthShader</var> we'll use the following vertex shader: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; - -uniform mat4 lightSpaceMatrix; -uniform mat4 model; - -void main() -{ - gl_Position = lightSpaceMatrix * model * vec4(aPos, 1.0); -} -</code></pre> - -<p> - This vertex shader takes a per-object model, a vertex, and transforms all vertices to light space using <var>lightSpaceMatrix</var>. -</p> - -<p> - Since we have no color buffer and disabled the draw and read buffers, the resulting fragments do not require any processing so we can simply use an empty fragment shader: -</p> - -<pre><code> -#version 330 core - -void main() -{ - // gl_FragDepth = gl_FragCoord.z; -} -</code></pre> - -<p> - This empty fragment shader does no processing whatsoever, and at the end of its run the depth buffer is updated. We could explicitly set the depth by uncommenting its one line, but this is effectively what happens behind the scene anyways. -</p> - -<p> - Rendering the depth/shadow map now effectively becomes: -</p> - -<pre><code> -simpleDepthShader.use(); -<function id='44'>glUniform</function>Matrix4fv(lightSpaceMatrixLocation, 1, GL_FALSE, glm::value_ptr(lightSpaceMatrix)); - -<function id='22'>glViewport</function>(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, depthMapFBO); - <function id='10'>glClear</function>(GL_DEPTH_BUFFER_BIT); - RenderScene(simpleDepthShader); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -</code></pre> - -<p> - Here the <fun>RenderScene</fun> function takes a shader program, calls all relevant drawing functions and sets the corresponding model matrices where necessary. -</p> - -<p> - The result is a nicely filled depth buffer holding the closest depth of each visible fragment from the light's perspective. By rendering this texture onto a 2D quad that fills the screen (similar to what we did in the post-processing section at the end of the <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">framebuffers</a> chapter) we get something like this: -</p> - -<img src="/img/advanced-lighting/shadow_mapping_depth_map.png" class="clean" alt="Depth (or shadow) map of shadow mapping technique"/> - -<p> - For rendering the depth map onto a quad we used the following fragment shader: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec2 TexCoords; - -uniform sampler2D depthMap; - -void main() -{ - float depthValue = texture(depthMap, TexCoords).r; - FragColor = vec4(vec3(depthValue), 1.0); -} -</code></pre> - -<p> - Note that there are some subtle changes when displaying depth using a perspective projection matrix instead of an orthographic projection matrix as depth is non-linear when using perspective projection. At the end of this chapter we'll discuss some of these subtle differences. -</p> - -<p> - You can find the source code for rendering a scene to a depth map <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/3.1.1.shadow_mapping_depth/shadow_mapping_depth.cpp" target="_blank">here</a>. -</p> - -<h2>Rendering shadows</h2> -<p> - With a properly generated depth map we can start rendering the actual shadows. The code to check if a fragment is in shadow is (quite obviously) executed in the fragment shader, but we do the light-space transformation in the vertex shader: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec3 aNormal; -layout (location = 2) in vec2 aTexCoords; - -out VS_OUT { - vec3 FragPos; - vec3 Normal; - vec2 TexCoords; - vec4 FragPosLightSpace; -} vs_out; - -uniform mat4 projection; -uniform mat4 view; -uniform mat4 model; -uniform mat4 lightSpaceMatrix; - -void main() -{ - vs_out.FragPos = vec3(model * vec4(aPos, 1.0)); - vs_out.Normal = transpose(inverse(mat3(model))) * aNormal; - vs_out.TexCoords = aTexCoords; - vs_out.FragPosLightSpace = lightSpaceMatrix * vec4(vs_out.FragPos, 1.0); - gl_Position = projection * view * vec4(vs_out.FragPos, 1.0); -} -</code></pre> - -<p> - What is new here is the extra output vector <var>FragPosLightSpace</var>. We take the same <var>lightSpaceMatrix</var> (used to transform vertices to light space in the depth map stage) and transform the world-space vertex position to light space for use in the fragment shader. -</p> - -<p> - The main fragment shader we'll use to render the scene uses the Blinn-Phong lighting model. Within the fragment shader we then calculate a <var>shadow</var> value that is either <code>1.0</code> when the fragment is in shadow or <code>0.0</code> when not in shadow. The resulting <var>diffuse</var> and <var>specular</var> components are then multiplied by this shadow component. Because shadows are rarely completely dark (due to light scattering) we leave the <var>ambient</var> component out of the shadow multiplications. -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in VS_OUT { - vec3 FragPos; - vec3 Normal; - vec2 TexCoords; - vec4 FragPosLightSpace; -} fs_in; - -uniform sampler2D diffuseTexture; -uniform sampler2D shadowMap; - -uniform vec3 lightPos; -uniform vec3 viewPos; - -float ShadowCalculation(vec4 fragPosLightSpace) -{ - [...] -} - -void main() -{ - vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb; - vec3 normal = normalize(fs_in.Normal); - vec3 lightColor = vec3(1.0); - // ambient - vec3 ambient = 0.15 * color; - // diffuse - vec3 lightDir = normalize(lightPos - fs_in.FragPos); - float diff = max(dot(lightDir, normal), 0.0); - vec3 diffuse = diff * lightColor; - // specular - vec3 viewDir = normalize(viewPos - fs_in.FragPos); - float spec = 0.0; - vec3 halfwayDir = normalize(lightDir + viewDir); - spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0); - vec3 specular = spec * lightColor; - // calculate shadow - float shadow = ShadowCalculation(fs_in.FragPosLightSpace); - vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color; - - FragColor = vec4(lighting, 1.0); -} -</code></pre> - -<p> - The fragment shader is largely a copy from what we used in the <a href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting" target="_blank">advanced lighting</a> chapter, but with an added shadow calculation. We declared a function <fun>ShadowCalculation</fun> that does most of the shadow work. At the end of the fragment shader, we multiply the diffuse and specular contributions by the inverse of the <var>shadow</var> component e.g. how much the fragment is <em>not</em> in shadow. This fragment shader takes as extra input the light-space fragment position and the depth map generated from the first render pass. -</p> - -<p> - The first thing to do to check whether a fragment is in shadow, is transform the light-space fragment position in clip-space to normalized device coordinates. When we output a clip-space vertex position to <var>gl_Position</var> in the vertex shader, OpenGL automatically does a perspective divide e.g. transform clip-space coordinates in the range [<code>-w</code>,<code>w</code>] to [<code>-1</code>,<code>1</code>] by dividing the <code>x</code>, <code>y</code> and <code>z</code> component by the vector's <code>w</code> component. As the clip-space <var>FragPosLightSpace</var> is not passed to the fragment shader through <var>gl_Position</var>, we have to do this perspective divide ourselves: -</p> - -<pre><code> -float ShadowCalculation(vec4 fragPosLightSpace) -{ - // perform perspective divide - vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; - [...] -} -</code></pre> - -<p> - This returns the fragment's light-space position in the range [<code>-1</code>,<code>1</code>]. -</p> - - <note> - When using an orthographic projection matrix the <code>w</code> component of a vertex remains untouched so this step is actually quite meaningless. However, it is necessary when using perspective projection so keeping this line ensures it works with both projection matrices. - </note> - -<p> - Because the depth from the depth map is in the range [<code>0</code>,<code>1</code>] and we also want to use <var>projCoords</var> to sample from the depth map, we transform the NDC coordinates to the range [<code>0</code>,<code>1</code>]: -</p> - -<pre class="cpp"><code> -projCoords = projCoords * 0.5 + 0.5; -</code></pre> - -<p> - With these projected coordinates we can sample the depth map as the resulting [<code>0</code>,<code>1</code>] coordinates from <var>projCoords</var> directly correspond to the transformed NDC coordinates from the first render pass. This gives us the closest depth from the light's point of view: -</p> - -<pre><code> -float closestDepth = texture(shadowMap, projCoords.xy).r; -</code></pre> - -<p> - To get the current depth at this fragment we simply retrieve the projected vector's <code>z</code> coordinate which equals the depth of this fragment from the light's perspective. -</p> - -<pre><code> -float currentDepth = projCoords.z; -</code></pre> - -<p> - The actual comparison is then simply a check whether <var>currentDepth</var> is higher than <var>closestDepth</var> and if so, the fragment is in shadow: -</p> - -<pre><code> -float shadow = currentDepth > closestDepth ? 1.0 : 0.0; -</code></pre> - -<p> - The complete <fun>ShadowCalculation</fun> function then becomes: -</p> - -<pre><code> -float ShadowCalculation(vec4 fragPosLightSpace) -{ - // perform perspective divide - vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; - // transform to [0,1] range - projCoords = projCoords * 0.5 + 0.5; - // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords) - float closestDepth = texture(shadowMap, projCoords.xy).r; - // get depth of current fragment from light's perspective - float currentDepth = projCoords.z; - // check whether current frag pos is in shadow - float shadow = currentDepth > closestDepth ? 1.0 : 0.0; - - return shadow; -} -</code></pre> - -<p> - Activating this shader, binding the proper textures, and activating the default projection and view matrices in the second render pass should give you a result similar to the image below: -</p> - - <img src="/img/advanced-lighting/shadow_mapping_shadows.png" class="clean" alt="Shadow mapped images, without improvements."/> - -<p> - If you did things right you should indeed see (albeit with quite a few artifacts) shadows on the floor and the cubes. You can find the source code of the demo application <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/3.1.2.shadow_mapping_base/shadow_mapping_base.cpp" target="_blank">here</a>. -</p> - -<h2>Improving shadow maps</h2> -<p> - We managed to get the basics of shadow mapping working, but as you can we're not there yet due to several (clearly visible) artifacts related to shadow mapping we need to fix. We'll focus on fixing these artifacts in the next sections. -</p> - -<h3>Shadow acne</h3> -<p> - It is obvious something is wrong from the previous image. A closer zoom shows us a very obvious Moiré-like pattern: -</p> - - <img src="/img/advanced-lighting/shadow_mapping_acne.png" alt="Image of shadow acne as Moiré pattern with shadow mapping"/> - -<p> - We can see a large part of the floor quad rendered with obvious black lines in an alternating fashion. This shadow mapping artifact is called <def>shadow acne</def> and can be explained by the following image: -</p> - -<img src="/img/advanced-lighting/shadow_mapping_acne_diagram.png" class="clean" alt="Shadow acne explained"/> - -<p> - Because the shadow map is limited by resolution, multiple fragments can sample the same value from the depth map when they're relatively far away from the light source. The image shows the floor where each yellow tilted panel represents a single texel of the depth map. As you can see, several fragments sample the same depth sample. - </p> - -<p> - While this is generally okay, it becomes an issue when the light source looks at an angle towards the surface as in that case the depth map is also rendered from an angle. Several fragments then access the same tilted depth texel while some are above and some below the floor; we get a shadow discrepancy. Because of this, some fragments are considered to be in shadow and some are not, giving the striped pattern from the image. -</p> - -<p> - We can solve this issue with a small little hack called a <def>shadow bias</def> where we simply offset the depth of the surface (or the shadow map) by a small bias amount such that the fragments are not incorrectly considered above the surface. -</p> - - <img src="/img/advanced-lighting/shadow_mapping_acne_bias.png" class="clean" alt="Shadow mapping, with shadow acne fixed using shadow bias."/> - -<p> - With the bias applied, all the samples get a depth smaller than the surface's depth and thus the entire surface is correctly lit without any shadows. We can implement such a bias as follows: -</p> - -<pre><code> -float bias = 0.005; -float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0; -</code></pre> - -<p> - A shadow bias of <code>0.005</code> solves the issues of our scene by a large extent, but you can imagine the bias value is highly dependent on the angle between the light source and the surface. If the surface would have a steep angle to the light source, the shadows may still display shadow acne. A more solid approach would be to change the amount of bias based on the surface angle towards the light: something we can solve with the dot product: -</p> - -<pre><code> -float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005); -</code></pre> - -<p> - Here we have a maximum bias of <code>0.05</code> and a minimum of <code>0.005</code> based on the surface's normal and light direction. This way, surfaces like the floor that are almost perpendicular to the light source get a small bias, while surfaces like the cube's side-faces get a much larger bias. The following image shows the same scene but now with a shadow bias: -</p> - - - <img src="/img/advanced-lighting/shadow_mapping_with_bias.png" class="clean" alt="Shadow mapped images, with (sloped) shadow bias applied."/> - -<p> - Choosing the correct bias value(s) requires some tweaking as this will be different for each scene, but most of the time it's simply a matter of slowly incrementing the bias until all acne is removed. -</p> - -<h3>Peter panning</h3> -<p> - A disadvantage of using a shadow bias is that you're applying an offset to the actual depth of objects. As a result, the bias may become large enough to see a visible offset of shadows compared to the actual object locations as you can see below (with an exaggerated bias value): -</p> - - <img src="/img/advanced-lighting/shadow_mapping_peter_panning.png" class="clean" alt="Peter panning with shadow mapping implementation"/> - -<p> - This shadow artifact is called <def>peter panning</def> since objects seem slightly <em>detached</em> from their shadows. We can use a little trick to solve most of the peter panning issue by using front face culling when rendering the depth map. You may remember from the <a href="https://learnopengl.com/Advanced-OpenGL/Face-Culling" target="_blank">face culling</a> chapter that OpenGL by default culls back-faces. By telling OpenGL we want to cull front faces during the shadow map stage we're switching that order around. -</p> - -<p> - Because we only need depth values for the depth map it shouldn't matter for solid objects whether we take the depth of their front faces or their back faces. Using their back face depths doesn't give wrong results as it doesn't matter if we have shadows inside objects; we can't see there anyways. -</p> - - <img src="/img/advanced-lighting/shadow_mapping_culling.png" class="clean" alt="Shadow mapping showing how front face culling helps solve peter panning."/> - -<p> - To fix peter panning we cull all front faces during the shadow map generation. Note that you need to enable <var>GL_CULL_FACE</var> first. -</p> - -<pre><code> -<function id='74'>glCullFace</function>(GL_FRONT); -RenderSceneToDepthMap(); -<function id='74'>glCullFace</function>(GL_BACK); // don't forget to reset original culling face -</code></pre> - -<p> - This effectively solves the peter panning issues, but <strong>only for solid</strong> objects that actually have an inside without openings. In our scene for example, this works perfectly fine on the cubes. However, on the floor it won't work as well as culling the front face completely removes the floor from the equation. The floor is a single plane and would thus be completely culled. If one wants to solve peter panning with this trick, care has to be taken to only cull the front faces of objects where it makes sense. -</p> - -<p> - Another consideration is that objects that are close to the shadow receiver (like the distant cube) may still give incorrect results. However, with normal bias values you can generally avoid peter panning. -</p> - -<h3>Over sampling</h3> -<p> - Another visual discrepancy which you may like or dislike is that regions outside the light's visible frustum are considered to be in shadow while they're (usually) not. This happens because projected coordinates outside the light's frustum are higher than <code>1.0</code> and will thus sample the depth texture outside its default range of [<code>0</code>,<code>1</code>]. Based on the texture's wrapping method, we will get incorrect depth results not based on the real depth values from the light source. -</p> - - <img src="/img/advanced-lighting/shadow_mapping_outside_frustum.png" class="clean" alt="Shadow mapping with edges of depth map visible, texture wrapping"/> -<p> - You can see in the image that there is some sort of imaginary region of light, and a large part outside this area is in shadow; this area represents the size of the depth map projected onto the floor. The reason this happens is that we earlier set the depth map's wrapping options to <var>GL_REPEAT</var>. -</p> - -<p> - What we'd rather have is that all coordinates outside the depth map's range have a depth of <code>1.0</code> which as a result means these coordinates will never be in shadow (as no object will have a depth larger than <code>1.0</code>). We can do this by configuring a texture border color and set the depth map's texture wrap options to <var>GL_CLAMP_TO_BORDER</var>: -</p> - -<pre><code> -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); -float borderColor[] = { 1.0f, 1.0f, 1.0f, 1.0f }; -<function id='15'>glTexParameter</function>fv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); -</code></pre> - -<p> - Now whenever we sample outside the depth map's [<code>0</code>,<code>1</code>] coordinate range, the <fun>texture</fun> function will always return a depth of <code>1.0</code>, producing a <var>shadow</var> value of <code>0.0</code>. The result now looks more plausible: -</p> - - <img src="/img/advanced-lighting/shadow_mapping_clamp_edge.png" class="clean" alt="Shadow mapping with texture wrapping set to clamp to border color"/> - -<p> - There seems to still be one part showing a dark region. Those are the coordinates outside the far plane of the light's orthographic frustum. You can see that this dark region always occurs at the far end of the light source's frustum by looking at the shadow directions. -</p> - -<p> - A light-space projected fragment coordinate is further than the light's far plane when its <code>z</code> coordinate is larger than <code>1.0</code>. In that case the <var>GL_CLAMP_TO_BORDER</var> wrapping method doesn't work anymore as we compare the coordinate's <code>z</code> component with the depth map values; this always returns true for <code>z</code> larger than <code>1.0</code>. -</p> - -<p> - The fix for this is also relatively easy as we simply force the <var>shadow</var> value to <code>0.0</code> whenever the projected vector's <code>z</code> coordinate is larger than <code>1.0</code>: -</p> - -<pre><code> -float ShadowCalculation(vec4 fragPosLightSpace) -{ - [...] - if(projCoords.z > 1.0) - shadow = 0.0; - - return shadow; -} -</code></pre> - -<p> - Checking the far plane and clamping the depth map to a manually specified border color solves the over-sampling of the depth map. This finally gives us the result we are looking for: -</p> - -<img src="/img/advanced-lighting/shadow_mapping_over_sampling_fixed.png" class="clean" alt="Shadow mapping with over sampling fixed with border clamp to color and far plane fix."/> - -<p> - The result of all this does mean that we only have shadows where the projected fragment coordinates sit inside the depth map range so anything outside the light frustum will have no visible shadows. As games usually make sure this only occurs in the distance it is a much more plausible effect than the obvious black regions we had before. -</p> - -<h2>PCF</h2> -<p> - The shadows right now are a nice addition to the scenery, but it's still not exactly what we want. If you were to zoom in on the shadows the resolution dependency of shadow mapping quickly becomes apparent. -</p> - - <img src="/img/advanced-lighting/shadow_mapping_zoom.png" alt="Zoomed in of shadows with shadow mappign technique shows jagged edges."/> - -<p> - Because the depth map has a fixed resolution, the depth frequently usually spans more than one fragment per texel. As a result, multiple fragments sample the same depth value from the depth map and come to the same shadow conclusions, which produces these jagged blocky edges. -</p> - -<p> - You can reduce these blocky shadows by increasing the depth map resolution, or by trying to fit the light frustum as closely to the scene as possible. -</p> - -<p> - Another (partial) solution to these jagged edges is called PCF, or <def>percentage-closer filtering</def>, which is a term that hosts many different filtering functions that produce <em>softer</em> shadows, making them appear less blocky or hard. The idea is to sample more than once from the depth map, each time with slightly different texture coordinates. For each individual sample we check whether it is in shadow or not. All the sub-results are then combined and averaged and we get a nice soft looking shadow. -</p> - -<p> - One simple implementation of PCF is to simply sample the surrounding texels of the depth map and average the results: -</p> - -<pre><code> -float shadow = 0.0; -vec2 texelSize = 1.0 / textureSize(shadowMap, 0); -for(int x = -1; x <= 1; ++x) -{ - for(int y = -1; y <= 1; ++y) - { - float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r; - shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0; - } -} -shadow /= 9.0; -</code></pre> - -<p> - Here <fun>textureSize</fun> returns a <code>vec2</code> of the width and height of the given sampler texture at mipmap level <code>0</code>. 1 divided over this returns the size of a single texel that we use to offset the texture coordinates, making sure each new sample samples a different depth value. Here we sample 9 values around the projected coordinate's <code>x</code> and <code>y</code> value, test for shadow occlusion, and finally average the results by the total number of samples taken. -</p> - -<p> - By using more samples and/or varying the <var>texelSize</var> variable you can increase the quality of the soft shadows. Below you can see the shadows with simple PCF applied: -</p> - - <img src="/img/advanced-lighting/shadow_mapping_soft_shadows.png" alt="Soft shadows with PCF using shadow mapping"/> - -<p> - From a distance the shadows look a lot better and less hard. If you zoom in you can still see the resolution artifacts of shadow mapping, but in general this gives good results for most applications. -</p> - -<p> - You can find the complete source code of the example <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/3.1.3.shadow_mapping/shadow_mapping.cpp" target="_blank">here</a>. -</p> - -<p> - There is actually much more to PCF and quite a few techniques to considerably improve the quality of soft shadows, but for the sake of this chapter's length we'll leave that for a later discussion. -</p> - -<h2>Orthographic vs perspective</h2> -<p> - There is a difference between rendering the depth map with an orthographic or a perspective projection matrix. An orthographic projection matrix does not deform the scene with perspective so all view/light rays are parallel. This makes it a great projection matrix for directional lights. A perspective projection matrix however does deform all vertices based on perspective which gives different results. The following image shows the different shadow regions of both projection methods: -</p> - -<img src="/img/advanced-lighting/shadow_mapping_projection.png" class="clean" alt="Shadow mapping difference between orthographic and perspective projection."/> - -<p> - Perspective projections make most sense for light sources that have actual locations, unlike directional lights. Perspective projections are most often used with spotlights and point lights, while orthographic projections are used for directional lights. -</p> - -<p> - Another subtle difference with using a perspective projection matrix is that visualizing the depth buffer will often give an almost completely white result. This happens because with perspective projection the depth is transformed to non-linear depth values with most of its noticeable range close to the near plane. To be able to properly view the depth values as we did with the orthographic projection you first want to transform the non-linear depth values to linear as we discussed in the <a href="https://learnopengl.com/Advanced-OpenGL/Depth-testing" target="_blank">depth testing</a> chapter: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec2 TexCoords; - -uniform sampler2D depthMap; -uniform float near_plane; -uniform float far_plane; - -float LinearizeDepth(float depth) -{ - float z = depth * 2.0 - 1.0; // Back to NDC - return (2.0 * near_plane * far_plane) / (far_plane + near_plane - z * (far_plane - near_plane)); -} - -void main() -{ - float depthValue = texture(depthMap, TexCoords).r; - FragColor = vec4(vec3(LinearizeDepth(depthValue) / far_plane), 1.0); // perspective - // FragColor = vec4(vec3(depthValue), 1.0); // orthographic -} -</code></pre> - -<p> - This shows depth values similar to what we've seen with orthographic projection. Note that this is only useful for debugging; the depth checks remain the same with orthographic or projection matrices as the relative depths do not change. -</p> - -<h2>Additional resources</h2> - <ul> - <li><a href="http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/" target="_blank">Tutorial 16 : Shadow mapping</a>: similar shadow mapping tutorial by opengl-tutorial.org with a few extra notes.</li> - <li><a href="http://ogldev.atspace.co.uk/www/tutorial23/tutorial23.html" target="_blank">Shadow Mapping - Part 1</a>: another shadow mapping tutorial by ogldev.</li> - <li><a href="https://www.youtube.com/watch?v=EsccgeUpdsM" target="_blank">How Shadow Mapping Works</a>: a 3-part YouTube tutorial by TheBennyBox on shadow mapping and its implementation.</li> - <li><a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ee416324%28v=vs.85%29.aspx" target="_blank">Common Techniques to Improve Shadow Depth Maps</a>: a great article by Microsoft listing a large number of techniques to improve the quality of shadow maps. -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-OpenGL/Advanced-Data.html b/translation/Advanced-OpenGL/Advanced-Data.html @@ -1,411 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Advanced Data</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Advanced Data</h1> -<h1 id="content-url" style='display:none;'>Advanced-OpenGL/Advanced-Data</h1> -<p> - Throughout most chapters we've been extensively using buffers in OpenGL to store data on the GPU. This chapter we'll briefly discuss a few alternative approaches to managing buffers. -</p> - -<p> - A buffer in OpenGL is, at its core, an object that manages a certain piece of GPU memory and nothing more. We give meaning to a buffer when binding it to a specific <def>buffer target</def>. A buffer is only a vertex array buffer when we bind it to <var>GL_ARRAY_BUFFER</var>, but we could just as easily bind it to <var>GL_ELEMENT_ARRAY_BUFFER</var>. OpenGL internally stores a reference to the buffer per target and, based on the target, processes the buffer differently. -</p> - -<p> - So far we've been filling the buffer's memory by calling <fun><function id='31'>glBufferData</function></fun>, which allocates a piece of GPU memory and adds data into this memory. If we were to pass <code>NULL</code> as its data argument, the function would only allocate memory and not fill it. This is useful if we first want to <em>reserve</em> a specific amount of memory and later come back to this buffer. -</p> - -<p> - Instead of filling the entire buffer with one function call we can also fill specific regions of the buffer by calling <fun><function id='90'>glBufferSubData</function></fun>. This function expects a buffer target, an offset, the size of the data and the actual data as its arguments. What's new with this function is that we can now give an offset that specifies from <em>where</em> we want to fill the buffer. This allows us to insert/update only certain parts of the buffer's memory. Do note that the buffer should have enough allocated memory so a call to <fun><function id='31'>glBufferData</function></fun> is necessary before calling <fun><function id='90'>glBufferSubData</function></fun> on the buffer. -</p> - -<pre><code> -<function id='90'>glBufferSubData</function>(GL_ARRAY_BUFFER, 24, sizeof(data), &data); // Range: [24, 24 + sizeof(data)] -</code></pre> - -<p> - Yet another method for getting data into a buffer is to ask for a pointer to the buffer's memory and directly copy the data in memory yourself. By calling <fun><function id='91'>glMapBuffer</function></fun> OpenGL returns a pointer to the currently bound buffer's memory for us to operate on: -</p> - -<pre><code> -float data[] = { - 0.5f, 1.0f, -0.35f - [...] -}; -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, buffer); -// get pointer -void *ptr = <function id='91'>glMapBuffer</function>(GL_ARRAY_BUFFER, GL_WRITE_ONLY); -// now copy data into memory -memcpy(ptr, data, sizeof(data)); -// make sure to tell OpenGL we're done with the pointer -<function id='92'>glUnmapBuffer</function>(GL_ARRAY_BUFFER); -</code></pre> - -<p> - By telling OpenGL we're finished with the pointer operations via <fun><function id='92'>glUnmapBuffer</function></fun>, OpenGL knows you're done. By unmapping, the pointer becomes invalid and the function returns <var>GL_TRUE</var> if OpenGL was able to map your data successfully to the buffer. -</p> - -<p> - Using <fun><function id='91'>glMapBuffer</function></fun> is useful for directly mapping data to a buffer, without first storing it in temporary memory. Think of directly reading data from file and copying it into the buffer's memory. -</p> - -<h2>Batching vertex attributes</h2> -<p> - Using <fun><function id='30'>glVertexAttribPointer</function></fun> we were able to specify the attribute layout of the vertex array buffer's content. Within the vertex array buffer we <def>interleaved</def> the attributes; that is, we placed the position, normal and/or texture coordinates next to each other in memory for each vertex. Now that we know a bit more about buffers we can take a different approach. -</p> - -<p> - What we could also do is batch all the vector data into large chunks per attribute type instead of interleaving them. Instead of an interleaved layout <code>123123123123</code> we take a batched approach <code>111122223333</code>. -</p> - -<p> - When loading vertex data from file you generally retrieve an array of positions, an array of normals and/or an array of texture coordinates. It may cost some effort to combine these arrays into one large array of interleaved data. Taking the batching approach is then an easier solution that we can easily implement using <fun><function id='90'>glBufferSubData</function></fun>: -</p> - -<pre><code> -float positions[] = { ... }; -float normals[] = { ... }; -float tex[] = { ... }; -// fill buffer -<function id='90'>glBufferSubData</function>(GL_ARRAY_BUFFER, 0, sizeof(positions), &positions); -<function id='90'>glBufferSubData</function>(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), &normals); -<function id='90'>glBufferSubData</function>(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(tex), &tex); -</code></pre> - -<p> - This way we can directly transfer the attribute arrays as a whole into the buffer without first having to process them. We could have also combined them in one large array and fill the buffer right away using <fun><function id='31'>glBufferData</function></fun>, but using <fun><function id='90'>glBufferSubData</function></fun> lends itself perfectly for tasks like these. -</p> -<p> - We'll also have to update the vertex attribute pointers to reflect these changes: -</p> - -<pre><code> -<function id='30'>glVertexAttribPointer</function>(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0); -<function id='30'>glVertexAttribPointer</function>(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions))); -<function id='30'>glVertexAttribPointer</function>( - 2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals))); -</code></pre> - -<p> - Note that the <code>stride</code> parameter is equal to the size of the vertex attribute, since the next vertex attribute vector can be found directly after its 3 (or 2) components. -</p> - -<p> - This gives us yet another approach of setting and specifying vertex attributes. Using either approach is feasible, it is mostly a more organized way to set vertex attributes. However, the interleaved approach is still the recommended approach as the vertex attributes for each vertex shader run are then closely aligned in memory. -</p> - -<h2>Copying buffers</h2> -<p> - Once your buffers are filled with data you may want to share that data with other buffers or perhaps copy the buffer's content into another buffer. The function <fun><function id='93'>glCopyBufferSubData</function></fun> allows us to copy the data from one buffer to another buffer with relative ease. The function's prototype is as follows: -</p> - -<pre><code> -void <function id='93'>glCopyBufferSubData</function>(GLenum readtarget, GLenum writetarget, GLintptr readoffset, - GLintptr writeoffset, GLsizeiptr size); -</code></pre> - -<p> - The <code>readtarget</code> and <code>writetarget</code> parameters expect to give the buffer targets that we want to copy from and to. We could for example copy from a <var>VERTEX_ARRAY_BUFFER</var> buffer to a <var>VERTEX_ELEMENT_ARRAY_BUFFER</var> buffer by specifying those buffer targets as the read and write targets respectively. The buffers currently bound to those buffer targets will then be affected. -</p> - -<p> - But what if we wanted to read and write data into two different buffers that are both vertex array buffers? We can't bind two buffers at the same time to the same buffer target. For this reason, and this reason alone, OpenGL gives us two more buffer targets called <var>GL_COPY_READ_BUFFER</var> and <var>GL_COPY_WRITE_BUFFER</var>. We then bind the buffers of our choice to these new buffer targets and set those targets as the <code>readtarget</code> and <code>writetarget</code> argument. -</p> - -<p> - <fun><function id='93'>glCopyBufferSubData</function></fun> then reads data of a given <code>size</code> from a given <code>readoffset</code> and writes it into the <code>writetarget</code> buffer at <code>writeoffset</code>. An example of copying the content of two vertex array buffers is shown below: -</p> - -<pre><code> -<function id='32'>glBindBuffer</function>(GL_COPY_READ_BUFFER, vbo1); -<function id='32'>glBindBuffer</function>(GL_COPY_WRITE_BUFFER, vbo2); -<function id='93'>glCopyBufferSubData</function>(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 8 * sizeof(float)); -</code></pre> - -<p> - We could've also done this by only binding the <code>writetarget</code> buffer to one of the new buffer target types: -</p> - -<pre><code> -float vertexData[] = { ... }; -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, vbo1); -<function id='32'>glBindBuffer</function>(GL_COPY_WRITE_BUFFER, vbo2); -<function id='93'>glCopyBufferSubData</function>(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 8 * sizeof(float)); -</code></pre> - - -<p> - With some extra knowledge about how to manipulate buffers we can already use them in more interesting ways. The further you get in OpenGL, the more useful these new buffer methods start to become. In the <a href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL" target="_blank">next</a> chapter, where we'll discuss <def>uniform buffer objects</def>, we'll make good use of <fun><function id='90'>glBufferSubData</function></fun>. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-OpenGL/Advanced-GLSL.html b/translation/Advanced-OpenGL/Advanced-GLSL.html @@ -1,903 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Advanced GLSL</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Advanced GLSL</h1> -<h1 id="content-url" style='display:none;'>Advanced-OpenGL/Advanced-GLSL</h1> -<p> - 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. -</p> - -<p> - 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>. -</p> - -<h1>GLSL's built-in variables</h1> -<p> - 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>. -</p> - -<p> - 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>. -</p> - -<h2>Vertex shader variables</h2> -<p> - 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. -</p> - -<h3>gl_PointSize</h3> -<p> - 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. -</p> - -<p> - 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. -</p> - -<p> - 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>: -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_PROGRAM_POINT_SIZE); -</code></pre> - -<p> - 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. -</p> - -<pre><code> -void main() -{ - gl_Position = projection * view * model * vec4(aPos, 1.0); - gl_PointSize = gl_Position.z; -} -</code></pre> - -<p> - The result is that the points we've drawn are rendered larger the more we move away from them: -</p> - -<img src="/img/advanced/advanced_glsl_pointsize.png" class="clean" alt="Points in OpenGL drawn with their gl_PointSize influenced in the vertex shader"/> - -<p> - You can imagine that varying the point size per vertex is interesting for techniques like particle generation. -</p> - -<h3>gl_VertexID</h3> -<p> - 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>. -</p> - -<p> - 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. -</p> - -<h2>Fragment shader variables</h2> -<p> - 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>. -</p> - -<h3>gl_FragCoord</h3> -<p> - 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. -</p> - -<p> - 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. -</p> - -<p> - 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: -</p> - -<pre><code> -void main() -{ - if(gl_FragCoord.x < 400) - FragColor = vec4(1.0, 0.0, 0.0, 1.0); - else - FragColor = vec4(0.0, 1.0, 0.0, 1.0); -} -</code></pre> - -<p> - 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. -</p> - -<img src="/img/advanced/advanced_glsl_fragcoord.png" class="clean" alt="Cube in OpenGL drawn with 2 colors using gl_FragCoord"/> - -<p> - 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. -</p> - -<h3>gl_FrontFacing</h3> -<p> - 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. -</p> - -<p> - 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: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec2 TexCoords; - -uniform sampler2D frontTexture; -uniform sampler2D backTexture; - -void main() -{ - if(gl_FrontFacing) - FragColor = texture(frontTexture, TexCoords); - else - FragColor = texture(backTexture, TexCoords); -} -</code></pre> - -<p> - If we take a peek inside the container we can now see a different texture being used. -</p> - -<img src="/img/advanced/advanced_glsl_frontfacing.png" class="clean" alt="OpenGL container using two different textures via gl_FrontFacing"/> - -<p> - 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. -</p> - -<h3>gl_FragDepth</h3> -<p> - 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. -</p> - -<p> - 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: -</p> - -<pre><code> -gl_FragDepth = 0.0; // this fragment now has a depth value of 0.0 -</code></pre> - -<p> - 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>. -</p> - -<p> - 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. -</p> - -<p> - 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>: -</p> - -<pre><code> -layout (depth_<condition>) out float gl_FragDepth; -</code></pre> - -<p> - This <code>condition</code> can take the following values: -</p> - -<table> - <tr> - <th>Condition</th> - <th>Description</th> - </tr> - <tr> - <td><code>any</code></td> - <td>The default value. Early depth testing is disabled.</td> - </tr> - <tr> - <td><code>greater</code></td> - <td>You can only make the depth value larger compared to <code>gl_FragCoord.z</code>.</td> - </tr> - <tr> - <td><code>less</code></td> - <td>You can only make the depth value smaller compared to <code>gl_FragCoord.z</code>.</td> - </tr> - <tr> - <td><code>unchanged</code></td> - <td>If you write to <code>gl_FragDepth</code>, you will write exactly <code>gl_FragCoord.z</code>.</td> - </tr> -</table> - -<p> - 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>. -</p> - -<p> - 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: -</p> - -<pre><code> -#version 420 core // note the GLSL version! -out vec4 FragColor; -layout (depth_greater) out float gl_FragDepth; - -void main() -{ - FragColor = vec4(1.0); - gl_FragDepth = gl_FragCoord.z + 0.1; -} -</code></pre> - -<p> - Do note that this feature is only available from OpenGL version 4.2 or higher. -</p> - -<h1>Interface blocks</h1> -<p> - 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. -</p> - -<p> - 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. -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec2 aTexCoords; - -uniform mat4 model; -uniform mat4 view; -uniform mat4 projection; - -out VS_OUT -{ - vec2 TexCoords; -} vs_out; - -void main() -{ - gl_Position = projection * view * model * vec4(aPos, 1.0); - vs_out.TexCoords = aTexCoords; -} -</code></pre> - -<p> - 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. -</p> - -<p> - 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. -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in VS_OUT -{ - vec2 TexCoords; -} fs_in; - -uniform sampler2D texture; - -void main() -{ - FragColor = texture(texture, fs_in.TexCoords); -} -</code></pre> - -<p> - 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. -</p> - -<h1>Uniform buffer objects</h1> -<p> - 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. -</p> - -<p> - 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. -</p> - -<p> - 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>: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; - -layout (std140) uniform Matrices -{ - mat4 projection; - mat4 view; -}; - -uniform mat4 model; - -void main() -{ - gl_Position = projection * view * model * vec4(aPos, 1.0); -} -</code></pre> - -<p> - 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. -</p> - -<p> - 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. -</p> - -<p> - 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>. -</p> - -<h2>Uniform block layout</h2> -<p> - 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. -</p> - -<p> - Imagine the following uniform block in a shader: -</p> - -<pre><code> -layout (std140) uniform ExampleBlock -{ - float value; - vec3 vector; - mat4 matrix; - float values[3]; - bool boolean; - int integer; -}; -</code></pre> - -<p> - 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. -</p> - -<p> - 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. -</p> - -<p> - 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. -</p> - -<p> - 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. -</p> - -<p> - 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>. -</p> - -<table> - <tr> - <th>Type</th> - <th>Layout rule</th> - </tr> - - <tr> - <td>Scalar e.g. <fun>int</fun> or <fun>bool</fun></td> - <td>Each scalar has a base alignment of N.</td> - </tr> - <tr> - <td>Vector</td> - <td>Either 2N or 4N. This means that a <fun>vec3</fun> has a base alignment of 4N.</td> - </tr> - <tr> - <td>Array of scalars or vectors</td> - <td>Each element has a base alignment equal to that of a <fun>vec4</fun>.</td> - </tr> - <tr> - <td>Matrices</td> - <td>Stored as a large array of column vectors, where each of those vectors has a base alignment of <fun>vec4</fun>.</td> - </tr> - <tr> - <td>Struct</td> - <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> - </tr> -</table> - -<p> - 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: -</p> - -<pre><code> -layout (std140) uniform ExampleBlock -{ - // base alignment // aligned offset - float value; // 4 // 0 - vec3 vector; // 16 // 16 (offset must be multiple of 16 so 4->16) - mat4 matrix; // 16 // 32 (column 0) - // 16 // 48 (column 1) - // 16 // 64 (column 2) - // 16 // 80 (column 3) - float values[3]; // 16 // 96 (values[0]) - // 16 // 112 (values[1]) - // 16 // 128 (values[2]) - bool boolean; // 4 // 144 - int integer; // 4 // 148 -}; -</code></pre> - -<p> - 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. -</p> - -<p> - 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. -</p> - -<h2>Using uniform buffers</h2> -<p> - We've defined uniform blocks and specified their memory layout, but we haven't discussed how to actually use them yet. -</p> - -<p> - 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>. -</p> - -<pre><code> -unsigned int uboExampleBlock; -<function id='12'>glGenBuffers</function>(1, &uboExampleBlock); -<function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, uboExampleBlock); -<function id='31'>glBufferData</function>(GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); // allocate 152 bytes of memory -<function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, 0); -</code></pre> - -<p> - 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? -</p> - -<p> - 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: -</p> - -<img src="/img/advanced/advanced_glsl_binding_points.png" class="clean" alt="Diagram of uniform binding points in OpenGL"/> - -<p> - 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. -</p> - -<p> - 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: -</p> - -<pre><code> -unsigned int lights_index = <function id='94'>glGetUniformBlockIndex</function>(shaderA.ID, "Lights"); -<function id='95'><function id='44'>glUniform</function>BlockBinding</function>(shaderA.ID, lights_index, 2); -</code></pre> - -<p> - Note that we have to repeat this process for <strong>each</strong> shader. -</p> - -<note> - 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: -<pre class="cpp"><code> -layout(std140, binding = 2) uniform Lights { ... }; -</code></pre> -</note> - -<p> - 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>. -</p> - -<pre><code> -<function id='96'><function id='32'>glBindBuffer</function>Base</function>(GL_UNIFORM_BUFFER, 2, uboExampleBlock); -// or -<function id='97'><function id='32'>glBindBuffer</function>Range</function>(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152); -</code></pre> - -<p> - 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. -</p> - -<p> - 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: -</p> - -<pre><code> -<function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, uboExampleBlock); -int b = true; // bools in GLSL are represented as 4 bytes, so we store it in an integer -<function id='90'>glBufferSubData</function>(GL_UNIFORM_BUFFER, 144, 4, &b); -<function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, 0); -</code></pre> - -<p> - And the same procedure applies for all the other uniform variables inside the uniform block, but with different range arguments. -</p> - -<h2>A simple example</h2> -<p> - 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. -</p> - -<p> - 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. -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; - -layout (std140) uniform Matrices -{ - mat4 projection; - mat4 view; -}; -uniform mat4 model; - -void main() -{ - gl_Position = projection * view * model * vec4(aPos, 1.0); -} -</code></pre> - -<p> - 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. -</p> - -<p> - 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: -</p> - -<pre><code> -unsigned int uniformBlockIndexRed = <function id='94'>glGetUniformBlockIndex</function>(shaderRed.ID, "Matrices"); -unsigned int uniformBlockIndexGreen = <function id='94'>glGetUniformBlockIndex</function>(shaderGreen.ID, "Matrices"); -unsigned int uniformBlockIndexBlue = <function id='94'>glGetUniformBlockIndex</function>(shaderBlue.ID, "Matrices"); -unsigned int uniformBlockIndexYellow = <function id='94'>glGetUniformBlockIndex</function>(shaderYellow.ID, "Matrices"); - -<function id='95'><function id='44'>glUniform</function>BlockBinding</function>(shaderRed.ID, uniformBlockIndexRed, 0); -<function id='95'><function id='44'>glUniform</function>BlockBinding</function>(shaderGreen.ID, uniformBlockIndexGreen, 0); -<function id='95'><function id='44'>glUniform</function>BlockBinding</function>(shaderBlue.ID, uniformBlockIndexBlue, 0); -<function id='95'><function id='44'>glUniform</function>BlockBinding</function>(shaderYellow.ID, uniformBlockIndexYellow, 0); -</code></pre> - -<p> - Next we create the actual uniform buffer object and bind that buffer to binding point <code>0</code>: -</p> - -<pre><code> -unsigned int uboMatrices -<function id='12'>glGenBuffers</function>(1, &uboMatrices); - -<function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, uboMatrices); -<function id='31'>glBufferData</function>(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW); -<function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, 0); - -<function id='97'><function id='32'>glBindBuffer</function>Range</function>(GL_UNIFORM_BUFFER, 0, uboMatrices, 0, 2 * sizeof(glm::mat4)); -</code></pre> - -<p> - 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>. -</p> - -<p> - 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: -</p> - -<pre><code> -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); -<function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, uboMatrices); -<function id='90'>glBufferSubData</function>(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(projection)); -<function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, 0); -</code></pre> - -<p> - 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: -</p> - -<pre><code> -glm::mat4 view = camera.GetViewMatrix(); -<function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, uboMatrices); -<function id='90'>glBufferSubData</function>(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view)); -<function id='32'>glBindBuffer</function>(GL_UNIFORM_BUFFER, 0); -</code></pre> - -<p> - 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: -</p> - -<pre><code> -<function id='27'>glBindVertexArray</function>(cubeVAO); -shaderRed.use(); -glm::mat4 model = glm::mat4(1.0f); -model = <function id='55'>glm::translate</function>(model, glm::vec3(-0.75f, 0.75f, 0.0f)); // move top-left -shaderRed.setMat4("model", model); -<function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 36); -// ... draw Green Cube -// ... draw Blue Cube -// ... draw Yellow Cube -</code></pre> - -<p> - 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: -</p> - -<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"/> - -<p> - 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. -</p> - -<p> - 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>. -</p> - -<p> - 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. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-OpenGL/Anti-Aliasing.html b/translation/Advanced-OpenGL/Anti-Aliasing.html @@ -1,559 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Anti Aliasing</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Anti Aliasing</h1> -<h1 id="content-url" style='display:none;'>Advanced-OpenGL/Anti-Aliasing</h1> -<p> - Somewhere in your adventurous rendering journey you probably came across some jagged saw-like patterns along the edges of your models. The reason these <def>jagged edges</def> appear is due to how the rasterizer transforms the vertex data into actual fragments behind the scene. An example of what these jagged edges look like can already be seen when drawing a simple cube: -</p> - -<img src="/img/advanced/anti_aliasing_aliasing.png" class="clean" alt="Container with visible aliasing"/> - -<p> - While not immediately visible, if you take a closer look at the edges of the cube you'll see a jagged pattern. If we zoom in you'd see the following: -</p> - -<img src="/img/advanced/anti_aliasing_zoomed.png" alt="Zoomed in on contanier with visible aliasing"/> - -<p> - This is clearly not something we want in a final version of an application. This effect, of clearly seeing the pixel formations an edge is composed of, is called <def>aliasing</def>. There are quite a few techniques out there called <def>anti-aliasing</def> techniques that fight this aliasing behavior by producing <em>smoother</em> edges. -</p> - -<p> - At first we had a technique called <def>super sample anti-aliasing</def> (SSAA) that temporarily uses a much higher resolution render buffer to render the scene in (super sampling). Then when the full scene is rendered, the resolution is downsampled back to the normal resolution. This <em>extra</em> resolution was used to prevent these jagged edges. While it did provide us with a solution to the aliasing problem, it came with a major performance drawback since we have to draw <strong>a lot</strong> more fragments than usual. This technique therefore only had a short glory moment. -</p> - -<p> - This technique did give birth to a more modern technique called <def>multisample anti-aliasing</def> or MSAA that borrows from the concepts behind SSAA while implementing a much more efficient approach. In this chapter we'll be extensively discussing this MSAA technique that is built-in in OpenGL. -</p> - -<h2>Multisampling</h2> -<p> - To understand what multisampling is and how it works into solving the aliasing problem we first need to delve a bit further into the inner workings of OpenGL's rasterizer. -</p> - -<p> - The rasterizer is the combination of all algorithms and processes that sit between your final processed vertices and the fragment shader. The rasterizer takes all vertices belonging to a single primitive and transforms this to a set of fragments. Vertex coordinates can theoretically have any coordinate, but fragments can't since they are bound by the resolution of your screen. There will almost never be a one-on-one mapping between vertex coordinates and fragments, so the rasterizer has to determine in some way what fragment/screen-coordinate each specific vertex will end up at. -</p> - -<img src="/img/advanced/anti_aliasing_rasterization.png" alt="Image of a triangle being rasterized in OpenGL"/> - -<p> - Here we see a grid of screen pixels where the center of each pixel contains a <def>sample point</def> that is used to determine if a pixel is covered by the triangle. The red sample points are covered by the triangle and a fragment will be generated for that covered pixel. Even though some parts of the triangle edges still enter certain screen pixels, the pixel's sample point is not covered by the inside of the triangle so this pixel won't be influenced by any fragment shader. -</p> - -<p> - You can probably already figure out the origin of aliasing right now. The complete rendered version of the triangle would look like this on your screen: -</p> - -<img src="/img/advanced/anti_aliasing_rasterization_filled.png" alt="Filled triangle as a result of rasterization in OpenGL"/> - -<p> - Due to the limited amount of screen pixels, some pixels will be rendered along an edge and some won't. The result is that we're rendering primitives with non-smooth edges giving rise to the jagged edges we've seen before. -</p> - -<p> - What multisampling does, is not use a single sampling point for determining coverage of the triangle, but multiple sample points (guess where it got its name from). Instead of a single sample point at the center of each pixel we're going to place <code>4</code> <def>subsamples</def> in a general pattern and use those to determine pixel coverage. -</p> - -<img src="/img/advanced/anti_aliasing_sample_points.png" class="clean" alt="Multisampling in OpenGL"/> - -<p> - The left side of the image shows how we would normally determine the coverage of a triangle. This specific pixel won't run a fragment shader (and thus remains blank) since its sample point wasn't covered by the triangle. The right side of the image shows a multisampled version where each pixel contains <code>4</code> sample points. Here we can see that only <code>2</code> of the sample points cover the triangle. -</p> - -<note> - The amount of sample points can be any number we'd like with more samples giving us better coverage precision. -</note> - -<p> - This is where multisampling becomes interesting. We determined that <code>2</code> subsamples were covered by the triangle so the next step is to determine a color for this specific pixel. Our initial guess would be that we run the fragment shader for each covered subsample and later average the colors of each subsample per pixel. In this case we'd run the fragment shader twice on the interpolated vertex data at each subsample and store the resulting color in those sample points. This is (fortunately) <strong>not</strong> how it works, because this would mean we need to run a lot more fragment shaders than without multisampling, drastically reducing performance. -</p> - -<p> - How MSAA really works is that the fragment shader is only run <strong>once</strong> per pixel (for each primitive) regardless of how many subsamples the triangle covers; the fragment shader runs with the vertex data interpolated to the <strong>center</strong> of the pixel. MSAA then uses a larger depth/stencil buffer to determine subsample coverage. The number of subsamples covered determines how much the pixel color contributes to the framebuffer. Because only 2 of the 4 samples were covered in the previous image, half of the triangle's color is mixed with the framebuffer color (in this case the clear color) resulting in a light blue-ish color. -</p> - -<p> - The result is a higher resolution buffer (with higher resolution depth/stencil) where all the primitive edges now produce a smoother pattern. Let's see what multisampling looks like when we determine the coverage of the earlier triangle: -</p> - -<img src="/img/advanced/anti_aliasing_rasterization_samples.png" alt="Rasterization of triangle with multisampling in OpenGL"/> - -<p> - Here each pixel contains 4 subsamples (the irrelevant samples were hidden) where the blue subsamples are covered by the triangle and the gray sample points aren't. Within the inner region of the triangle all pixels will run the fragment shader once where its color output is stored directly in the framebuffer (assuming no blending). At the inner edges of the triangle however not all subsamples will be covered so the result of the fragment shader won't fully contribute to the framebuffer. Based on the number of covered samples, more or less of the triangle fragment's color ends up at that pixel. -</p> - -<p> - For each pixel, the less subsamples are part of the triangle, the less it takes the color of the triangle. If we were to fill in the actual pixel colors we get the following image: -</p> - -<img src="/img/advanced/anti_aliasing_rasterization_samples_filled.png" alt="Rasterized triangle with multisampling in OpenGL"/> - -<p> - The hard edges of the triangle are now surrounded by colors slightly lighter than the actual edge color, which causes the edge to appear smooth when viewed from a distance. -</p> - -<p> - Depth and stencil values are stored per subsample and, even though we only run the fragment shader once, color values are stored per subsample as well for the case of multiple triangles overlapping a single pixel. For depth testing the vertex's depth value is interpolated to each subsample before running the depth test, and for stencil testing we store the stencil values per subsample. This does mean that the size of the buffers are now increased by the amount of subsamples per pixel. -</p> - -<p> - What we've discussed so far is a basic overview of how multisampled anti-aliasing works behind the scenes. The actual logic behind the rasterizer is a bit more complicated, but this brief description should be enough to understand the concept and logic behind multisampled anti-aliasing; enough to delve into the practical aspects. -</p> - -<h2>MSAA in OpenGL</h2> -<p> - If we want to use MSAA in OpenGL we need to use a buffer that is able to store more than one sample value per pixel. We need a new type of buffer that can store a given amount of multisamples and this is called a <def>multisample buffer</def>. -</p> - -<p> - Most windowing systems are able to provide us a multisample buffer instead of a default buffer. GLFW also gives us this functionality and all we need to do is <em>hint</em> GLFW that we'd like to use a multisample buffer with N samples instead of a normal buffer by calling <fun><function id='18'>glfwWindowHint</function></fun> before creating the window: -</p> - -<pre class="cpp"><code> -<function id='18'>glfwWindowHint</function>(GLFW_SAMPLES, 4); -</code></pre> - -<p> - When we now call <fun><function id='20'>glfwCreateWindow</function></fun> we create a rendering window, but this time with a buffer containing 4 subsamples per screen coordinate. This does mean that the size of the buffer is increased by 4. -</p> - -<p> - Now that we asked GLFW for multisampled buffers we need to enable multisampling by calling <fun><function id='60'>glEnable</function></fun> with <var>GL_MULTISAMPLE</var>. On most OpenGL drivers, multisampling is enabled by default so this call is then a bit redundant, but it's usually a good idea to enable it anyways. This way all OpenGL implementations have multisampling enabled. -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_MULTISAMPLE); -</code></pre> - -<p> - Because the actual multisampling algorithms are implemented in the rasterizer in your OpenGL drivers there's not much else we need to do. If we now were to render the green cube from the start of this chapter we should see smoother edges: -</p> - -<img src="/img/advanced/anti_aliasing_multisampled.png" class="clean" alt="Image of a multisampled cube in OpenGL"/> - -<p> - The cube does indeed look a lot smoother and the same will apply for any other object you're drawing in your scene. You can find the source code for this simple example <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/11.1.anti_aliasing_msaa/anti_aliasing_msaa.cpp" target="_blank">here</a>. -</p> - -<h2>Off-screen MSAA</h2> -<p> - Because GLFW takes care of creating the multisampled buffers, enabling MSAA is quite easy. If we want to use our own framebuffers however, we have to generate the multisampled buffers ourselves; now we <strong>do</strong> need to take care of creating multisampled buffers. -</p> - -<p> - There are two ways we can create multisampled buffers to act as attachments for framebuffers: texture attachments and renderbuffer attachments. Quite similar to normal attachments like we've discussed in the <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">framebuffers</a> chapter. -</p> - -<h3>Multisampled texture attachments</h3> -<p> - To create a texture that supports storage of multiple sample points we use <fun><function id='101'><function id='52'>glTexImage2D</function>Multisample</function></fun> instead of <fun><function id='52'>glTexImage2D</function></fun> that accepts <var>GL_TEXTURE_2D_MULTISAPLE</var> as its texture target: -</p> - -<pre class="cpp"><code> -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D_MULTISAMPLE, tex); -<function id='101'><function id='52'>glTexImage2D</function>Multisample</function>(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGB, width, height, GL_TRUE); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D_MULTISAMPLE, 0); -</code></pre> - -<p> - The second argument sets the number of samples we'd like the texture to have. If the last argument is set to <var>GL_TRUE</var>, the image will use identical sample locations and the same number of subsamples for each texel. -</p> - -<p> - To attach a multisampled texture to a framebuffer we use <fun><function id='81'>glFramebufferTexture2D</function></fun>, but this time with <var>GL_TEXTURE_2D_MULTISAMPLE</var> as the texture type: -</p> - -<pre class="cpp"><code> -<function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0); -</code></pre> - -<p> - The currently bound framebuffer now has a multisampled color buffer in the form of a texture image. -</p> - -<h3>Multisampled renderbuffer objects</h3> -<p> - Like textures, creating a multisampled renderbuffer object isn't difficult. It is even quite easy since all we need to change is <fun><function id='88'>glRenderbufferStorage</function></fun> to <fun><function id='102'><function id='88'>glRenderbufferStorage</function>Multisample</function></fun> when we configure the (currently bound) renderbuffer's memory storage: -</p> - -<pre class="cpp"><code> -<function id='102'><function id='88'>glRenderbufferStorage</function>Multisample</function>(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, width, height); -</code></pre> - -<p> - The one thing that changed here is the extra second parameter where we set the amount of samples we'd like to use; 4 in this particular case. -</p> - -<h3>Render to multisampled framebuffer</h3> -<p> - Rendering to a multisampled framebuffer is straightforward. Whenever we draw anything while the framebuffer object is bound, the rasterizer will take care of all the multisample operations. However, because a multisampled buffer is a bit special, we can't directly use the buffer for other operations like sampling it in a shader. -</p> - -<p> - A multisampled image contains much more information than a normal image so what we need to do is downscale or <def>resolve</def> the image. Resolving a multisampled framebuffer is generally done through <fun><function id='103'>glBlitFramebuffer</function></fun> that copies a region from one framebuffer to the other while also resolving any multisampled buffers. -</p> - -<p> - <fun><function id='103'>glBlitFramebuffer</function></fun> transfers a given <def>source</def> region defined by 4 screen-space coordinates to a given <def>target</def> region also defined by 4 screen-space coordinates. You may remember from the <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">framebuffers</a> chapter that if we bind to <var>GL_FRAMEBUFFER</var> we're binding to both the read and draw framebuffer targets. We could also bind to those targets individually by binding framebuffers to <var>GL_READ_FRAMEBUFFER</var> and <var>GL_DRAW_FRAMEBUFFER</var> respectively. The <fun><function id='103'>glBlitFramebuffer</function></fun> function reads from those two targets to determine which is the source and which is the target framebuffer. We could then transfer the multisampled framebuffer output to the actual screen by <def>blitting</def> the image to the default framebuffer like so: -</p> - -<pre class="cpp"><code> -<function id='77'>glBindFramebuffer</function>(GL_READ_FRAMEBUFFER, multisampledFBO); -<function id='77'>glBindFramebuffer</function>(GL_DRAW_FRAMEBUFFER, 0); -<function id='103'>glBlitFramebuffer</function>(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); -</code></pre> - -<p> - If we then were to render the same application we should get the same output: a lime-green cube displayed with MSAA and again showing significantly less jagged edges: -</p> - -<img src="/img/advanced/anti_aliasing_multisampled.png" class="clean" alt="Image of a multisampled cube in OpenGL"/> - -<p> - You can find the source code <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/11.2.anti_aliasing_offscreen/anti_aliasing_offscreen.cpp" target="_blank">here</a>. -</p> - -<p> - But what if we wanted to use the texture result of a multisampled framebuffer to do stuff like post-processing? We can't directly use the multisampled texture(s) in the fragment shader. What we can do however is blit the multisampled buffer(s) to a different FBO with a non-multisampled texture attachment. We then use this ordinary color attachment texture for post-processing, effectively post-processing an image rendered via multisampling. This does mean we have to generate a new FBO that acts solely as an intermediate framebuffer object to resolve the multisampled buffer into; a normal 2D texture we can use in the fragment shader. This process looks a bit like this in pseudocode: -</p> - -<pre><code> -unsigned int msFBO = CreateFBOWithMultiSampledAttachments(); -// then create another FBO with a normal texture color attachment -[...] -<function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0); -[...] -while(!<function id='14'>glfwWindowShouldClose</function>(window)) -{ - [...] - - <function id='77'>glBindFramebuffer</function>(msFBO); - ClearFrameBuffer(); - DrawScene(); - // now resolve multisampled buffer(s) into intermediate FBO - <function id='77'>glBindFramebuffer</function>(GL_READ_FRAMEBUFFER, msFBO); - <function id='77'>glBindFramebuffer</function>(GL_DRAW_FRAMEBUFFER, intermediateFBO); - <function id='103'>glBlitFramebuffer</function>(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); - // now scene is stored as 2D texture image, so use that image for post-processing - <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); - ClearFramebuffer(); - <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, screenTexture); - DrawPostProcessingQuad(); - - [...] -} -</code></pre> - -<p> - If we then implement this into the post-processing code of the <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">framebuffers</a> chapter we're able to create all kinds of cool post-processing effects on a texture of a scene with (almost) no jagged edges. With a grayscale postprocessing filter applied it'll look something like this: -</p> - -<img src="/img/advanced/anti_aliasing_post_processing.png" class="clean" alt="Image of post-processing on a scene drawn with MSAA in OpenGL"/> - -<note> - Because the screen texture is a normal (non-multisampled) texture again, some post-processing filters like <em>edge-detection</em> will introduce jagged edges again. To accommodate for this you could blur the texture afterwards or create your own anti-aliasing algorithm. -</note> - -<p> - You can see that when we want to combine multisampling with off-screen rendering we need to take care of some extra steps. The steps are worth the extra effort though since multisampling significantly boosts the visual quality of your scene. Do note that enabling multisampling can noticeably reduce performance the more samples you use. -</p> - -<h2>Custom Anti-Aliasing algorithm</h2> -<p> - It is possible to directly pass a multisampled texture image to a fragment shader instead of first resolving it. GLSL gives us the option to sample the texture image per subsample so we can create our own custom anti-aliasing algorithms. -</p> - -<p> - To get a texture value per subsample you'd have to define the texture uniform sampler as a <fun>sampler2DMS</fun> instead of the usual <fun>sampler2D</fun>: -</p> - -<pre><code> -uniform sampler2DMS screenTextureMS; -</code></pre> - - <p> - Using the <fun>texelFetch</fun> function it is then possible to retrieve the color value per sample: - </p> - -<pre><code> -vec4 colorSample = texelFetch(screenTextureMS, TexCoords, 3); // 4th subsample -</code></pre> - -<p> - We won't go into the details of creating custom anti-aliasing techniques here, but this may be enough to get started on building one yourself. -</p> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-OpenGL/Blending.html b/translation/Advanced-OpenGL/Blending.html @@ -1,691 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Blending</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Blending</h1> -<h1 id="content-url" style='display:none;'>Advanced-OpenGL/Blending</h1> -<p> - <def>Blending</def> in OpenGL is commonly known as the technique to implement <def>transparency</def> within objects. Transparency is all about objects (or parts of them) not having a solid color, but having a combination of colors from the object itself and any other object behind it with varying intensity. A colored glass window is a transparent object; the glass has a color of its own, but the resulting color contains the colors of all the objects behind the glass as well. This is also where the name blending comes from, since we <def>blend</def> several pixel colors (from different objects) to a single color. Transparency thus allows us to see through objects. -</p> - -<img src="/img/advanced/blending_transparency.png" class="clean" alt="Image of full transparent window and partially transparent window"/> - -<p> - Transparent objects can be completely transparent (letting all colors through) or partially transparent (letting colors through, but also some of its own colors). The amount of transparency of an object is defined by its color's <def>alpha</def> value. The alpha color value is the 4th component of a color vector that you've probably seen quite often now. Up until this chapter, we've always kept this 4th component at a value of <code>1.0</code> giving the object <code>0.0</code> transparency. An alpha value of <code>0.0</code> would result in the object having complete transparency. An alpha value of <code>0.5</code> tells us the object's color consist of 50% of its own color and 50% of the colors behind the object. -</p> - -<p> - The textures we've used so far all consisted of <code>3</code> color components: red, green and blue, but some textures also have an embedded alpha channel that contains an <def>alpha</def> value per texel. This alpha value tells us exactly which parts of the texture have transparency and by how much. For example, the following <a href="/img/advanced/blending_transparent_window.png" target="_blank">window texture</a> has an alpha value of <code>0.25</code> at its glass part and an alpha value of <code>0.0</code> at its corners. The glass part would normally be completely red, but since it has 75% transparency it largely shows the page's background through it, making it seem a lot less red: -</p> - -<img src="/img/advanced/blending_transparent_window.png" class="clean" alt="Texture image of window with transparency"/> - -<p> - We'll soon be adding this windowed texture to the scene from the depth testing chapter, but first we'll discuss an easier technique to implement transparency for pixels that are either fully transparent or fully opaque. -</p> - -<h2>Discarding fragments</h2> -<p> - Some effects do not care about partial transparency, but either want to show something or nothing at all based on the color value of a texture. Think of grass; to create something like grass with little effort you generally paste a grass texture onto a 2D quad and place that quad into your scene. However, grass isn't exactly shaped like a 2D square so you only want to display some parts of the grass texture and ignore the others. -</p> - -<p> - The following <a href="/img/textures/grass.png" target="_blank">texture</a> is exactly such a texture where it either is full opaque (an alpha value of <code>1.0</code>) or it is fully transparent (an alpha value of <code>0.0</code>) and nothing in between. You can see that wherever there is no grass, the image shows the page's background color instead of its own. -</p> - -<img src="/img/textures/grass.png" class="clean" style="width:384px; height:384px;" alt="Texture image of grass with transparency"/> - -<p> - So when adding vegetation to a scene we don't want to see a square image of grass, but rather only show the actual grass and see through the rest of the image. We want to <def>discard</def> the fragments that show the transparent parts of the texture, not storing that fragment into the color buffer. -</p> - -<p> - Before we get into that we first need to learn how to load a transparent texture. To load textures with alpha values there's not much we need to change. <code>stb_image</code> automatically loads an image's alpha channel if it's available, but we do need to tell OpenGL our texture now uses an alpha channel in the texture generation procedure: -</p> - -<pre class="cpp"><code> -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); -</code></pre> - -<p> - Also make sure that you retrieve all <code>4</code> color components of the texture in the fragment shader, not just the RGB components: -</p> - -<pre><code> -void main() -{ - // FragColor = vec4(vec3(texture(texture1, TexCoords)), 1.0); - FragColor = texture(texture1, TexCoords); -} -</code></pre> - -<p> - Now that we know how to load transparent textures it's time to put it to the test by adding several of these leaves of grass throughout the basic scene introduced in the <a href="https://learnopengl.com/Advanced-OpenGL/Depth-testing" target="_blank">depth testing</a> chapter. -</p> - -<p> - We create a small <code>vector</code> array where we add several <code>glm::vec3</code> vectors to represent the location of the grass leaves: -</p> - -<pre><code> -vector<glm::vec3> vegetation; -vegetation.push_back(glm::vec3(-1.5f, 0.0f, -0.48f)); -vegetation.push_back(glm::vec3( 1.5f, 0.0f, 0.51f)); -vegetation.push_back(glm::vec3( 0.0f, 0.0f, 0.7f)); -vegetation.push_back(glm::vec3(-0.3f, 0.0f, -2.3f)); -vegetation.push_back(glm::vec3( 0.5f, 0.0f, -0.6f)); -</code></pre> - -<p> - Each of the grass objects is rendered as a single quad with the grass texture attached to it. It's not a perfect 3D representation of grass, but it's a lot more efficient than loading and rendering a large number of complex models. With a few tricks like adding randomized rotations and scales you can get pretty convincing results with quads. -</p> - -<p> -Because the grass texture is going to be displayed on a quad object we'll need to create another VAO again, fill the VBO, and set the appropriate vertex attribute pointers. Then after we've rendered the floor and the two cubes we're going to render the grass leaves: -</p> - -<pre><code> -<function id='27'>glBindVertexArray</function>(vegetationVAO); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, grassTexture); -for(unsigned int i = 0; i < vegetation.size(); i++) -{ - model = glm::mat4(1.0f); - model = <function id='55'>glm::translate</function>(model, vegetation[i]); - shader.setMat4("model", model); - <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); -} -</code></pre> - -<p> - Running the application will now look a bit like this: -</p> - -<img src="/img/advanced/blending_no_discard.png" class="clean" alt="Not discarding transparent parts of texture results in weird artifacts in OpenGL"/> - -<p> - This happens because OpenGL by default does not know what to do with alpha values, nor when to discard them. We have to manually do this ourselves. Luckily this is quite easy thanks to the use of shaders. GLSL gives us the <code>discard</code> command that (once called) ensures the fragment will not be further processed and thus not end up into the color buffer. Thanks to this command we can check whether a fragment has an alpha value below a certain threshold and if so, discard the fragment as if it had never been processed: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec2 TexCoords; - -uniform sampler2D texture1; - -void main() -{ - vec4 texColor = texture(texture1, TexCoords); - if(texColor.a < 0.1) - discard; - FragColor = texColor; -} -</code></pre> - - -<p> - Here we check if the sampled texture color contains an alpha value lower than a threshold of <code>0.1</code> and if so, discard the fragment. This fragment shader ensures us that it only renders fragments that are not (almost) completely transparent. Now it'll look like it should: -</p> - - -<img src="/img/advanced/blending_discard.png" class="clean" alt="Image of grass leaves rendered with fragment discarding in OpenGL"/> - -<note> - Note that when sampling textures at their borders, OpenGL interpolates the border values with the next repeated value of the texture (because we set its wrapping parameters to <var>GL_REPEAT</var> by default). This is usually okay, but since we're using transparent values, the top of the texture image gets its transparent value interpolated with the bottom border's solid color value. The result is then a slightly semi-transparent colored border you may see wrapped around your textured quad. To prevent this, set the texture wrapping method to <var>GL_CLAMP_TO_EDGE</var> whenever you use alpha textures that you don't want to repeat: - -<pre><code> -<function id='15'>glTexParameter</function>i( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -</code></pre> -</note> - -<p> - You can find the source code <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/3.1.blending_discard/blending_discard.cpp" target="_blank">here</a>. -</p> - - -<h2>Blending</h2> -<p> - While discarding fragments is great and all, it doesn't give us the flexibility to render semi-transparent images; we either render the fragment or completely discard it. To render images with different levels of transparency we have to enable <def>blending</def>. Like most of OpenGL's functionality we can enable blending by enabling <var>GL_BLEND</var>: -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_BLEND); -</code></pre> - -<p> - Now that we've enabled blending we need to tell OpenGL <strong>how</strong> it should actually blend. -</p> - -<p> - Blending in OpenGL happens with the following equation: -</p> - -\begin{equation}\bar{C}_{result} = \bar{\color{green}C}_{source} * \color{green}F_{source} + \bar{\color{red}C}_{destination} * \color{red}F_{destination}\end{equation} - -<ul> - <li>\(\bar{\color{green}C}_{source}\): the source color vector. This is the color output of the fragment shader.</li> - <li>\(\bar{\color{red}C}_{destination}\): the destination color vector. This is the color vector that is currently stored in the color buffer.</li> - <li>\(\color{green}F_{source}\): the source factor value. Sets the impact of the alpha value on the source color.</li> - <li>\(\color{red}F_{destination}\): the destination factor value. Sets the impact of the alpha value on the destination color.</li> -</ul> - -<p> - After the fragment shader has run and all the tests have passed, this <def>blend equation</def> is let loose on the fragment's color output and with whatever is currently in the color buffer. The source and destination colors will automatically be set by OpenGL, but the source and destination factor can be set to a value of our choosing. Let's start with a simple example: -</p> - -<img src="/img/advanced/blending_equation.png" class="clean" alt="Two squares where one has alpha value lower than 1"/> - -<p> - We have two squares where we want to draw the semi-transparent green square on top of the red square. The red square will be the destination color (and thus should be first in the color buffer) and we are now going to draw the green square over the red square. -</p> - -<p> - The question then arises: what do we set the factor values to? Well, we at least want to multiply the green square with its alpha value so we want to set the \(F_{src}\) equal to the alpha value of the source color vector which is <code>0.6</code>. Then it makes sense to let the destination square have a contribution equal to the remainder of the alpha value. If the green square contributes 60% to the final color we want the red square to contribute 40% of the final color e.g. <code>1.0 - 0.6</code>. So we set \(F_{destination}\) equal to one minus the alpha value of the source color vector. The equation thus becomes: -</p> - -\begin{equation}\bar{C}_{result} = \begin{pmatrix} \color{red}{0.0} \\ \color{green}{1.0} \\ \color{blue}{0.0} \\ \color{purple}{0.6} \end{pmatrix} * \color{green}{0.6} + \begin{pmatrix} \color{red}{1.0} \\ \color{green}{0.0} \\ \color{blue}{0.0} \\ \color{purple}{1.0} \end{pmatrix} * (\color{red}{1 - 0.6}) \end{equation} - -<p> - The result is that the combined square fragments contain a color that is 60% green and 40% red: -</p> - -<img src="/img/advanced/blending_equation_mixed.png" class="clean" alt="Two containers where one has alpha value lower than 1"/> - -<p> - The resulting color is then stored in the color buffer, replacing the previous color. -</p> - -<p> - So this is great and all, but how do we actually tell OpenGL to use factors like that? Well it just so happens that there is a function for this called <fun><function id='70'>glBlendFunc</function></fun>. -</p> - -<p> - The <fun><function id='70'>glBlendFunc</function>(GLenum sfactor, GLenum dfactor)</fun> function expects two parameters that set the option for the <def>source</def> and <def>destination factor</def>. OpenGL defined quite a few options for us to set of which we'll list the most common options below. Note that the constant color vector \(\bar{\color{blue}C}_{constant}\) can be separately set via the <fun><function id='73'>glBlendColor</function></fun> function. -</p> - -<table> - <tr> - <th>Option</th> - <th>Value</th> - </tr> - <tr> - <td><code>GL_ZERO</code></td> - <td>Factor is equal to \(0\).</td> - </tr> - <tr> - <td><code>GL_ONE</code></td> - <td>Factor is equal to \(1\).</td> - </tr> - <tr> - <td><code>GL_SRC_COLOR</code></td> - <td>Factor is equal to the source color vector \(\bar{\color{green}C}_{source}\).</td> - </tr> - <tr> - <td><code>GL_ONE_MINUS_SRC_COLOR</code></td> - <td>Factor is equal to \(1\) minus the source color vector: \(1 - \bar{\color{green}C}_{source}\). </td> - </tr><tr> - <td><code>GL_DST_COLOR</code></td> - <td>Factor is equal to the destination color vector \(\bar{\color{red}C}_{destination}\)</td> - </tr> - <tr> - <td><code>GL_ONE_MINUS_DST_COLOR</code></td> - <td>Factor is equal to \(1\) minus the destination color vector: \(1 - \bar{\color{red}C}_{destination}\).</td> - </tr> - <tr> - <td><code>GL_SRC_ALPHA</code></td> - <td>Factor is equal to the \(alpha\) component of the source color vector \(\bar{\color{green}C}_{source}\). </td> - </tr> - <tr> - <td><code>GL_ONE_MINUS_SRC_ALPHA</code></td> - <td>Factor is equal to \(1 - alpha\) of the source color vector \(\bar{\color{green}C}_{source}\).</td> - </tr> - <tr> - <td><code>GL_DST_ALPHA</code></td> - <td>Factor is equal to the \(alpha\) component of the destination color vector \(\bar{\color{red}C}_{destination}\). </td> - </tr> - <tr> - <td><code>GL_ONE_MINUS_DST_ALPHA</code></td> - <td>Factor is equal to \(1 - alpha\) of the destination color vector \(\bar{\color{red}C}_{destination}\).</td> - </tr> - <tr> - <td><code>GL_CONSTANT_COLOR</code></td> - <td>Factor is equal to the constant color vector \(\bar{\color{blue}C}_{constant}\). </td> - </tr> - <tr> - <td><code>GL_ONE_MINUS_CONSTANT_COLOR</code></td> - <td>Factor is equal to \(1\) - the constant color vector \(\bar{\color{blue}C}_{constant}\).</td> - </tr> - <tr> - <td><code>GL_CONSTANT_ALPHA</code></td> - <td>Factor is equal to the \(alpha\) component of the constant color vector \(\bar{\color{blue}C}_{constant}\). </td> - </tr> - <tr> - <td><code>GL_ONE_MINUS_CONSTANT_ALPHA</code></td> - <td>Factor is equal to \(1 - alpha\) of the constant color vector \(\bar{\color{blue}C}_{constant}\).</td> - </tr> -</table> - -<p> - To get the blending result of our little two square example, we want to take the \(alpha\) of the source color vector for the source factor and \(1 - alpha\) of the same color vector for the destination factor. This translates to <fun><function id='70'>glBlendFunc</function></fun> as follows: -</p> - -<pre><code> -<function id='70'>glBlendFunc</function>(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -</code></pre> - -<p> - It is also possible to set different options for the RGB and alpha channel individually using <fun><function id='71'><function id='70'>glBlendFunc</function>Separate</function></fun>: -</p> - -<pre><code> -<function id='71'><function id='70'>glBlendFunc</function>Separate</function>(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO); -</code></pre> - -<p> - This function sets the RGB components as we've set them previously, but only lets the resulting alpha component be influenced by the source's alpha value. -</p> - -<p> - OpenGL gives us even more flexibility by allowing us to change the operator between the source and destination part of the equation. Right now, the source and destination components are added together, but we could also subtract them if we want. <fun><function id='72'>glBlendEquation</function>(GLenum mode)</fun> allows us to set this operation and has 5 possible options: -</p> - -<ul> - <li><code>GL_FUNC_ADD</code>: the default, adds both colors to each other: \(\bar{C}_{result} = \color{green}{Src} + \color{red}{Dst}\).</li> - <li><code>GL_FUNC_SUBTRACT</code>: subtracts both colors from each other: \(\bar{C}_{result} = \color{green}{Src} - \color{red}{Dst}\).</li> - <li><code>GL_FUNC_REVERSE_SUBTRACT</code>: subtracts both colors, but reverses order: \(\bar{C}_{result} = \color{red}{Dst} - \color{green}{Src}\).</li> - <li><code>GL_MIN</code>: takes the component-wise minimum of both colors: \(\bar{C}_{result} = min(\color{red}{Dst}, \color{green}{Src})\).</li> - <li><code>GL_MAX</code>: takes the component-wise maximum of both colors: \(\bar{C}_{result} = max(\color{red}{Dst}, \color{green}{Src})\).</li> -</ul> - -<p> - Usually we can simply omit a call to <fun><function id='72'>glBlendEquation</function></fun> because <var>GL_FUNC_ADD</var> is the preferred blending equation for most operations, but if you're really trying your best to break the mainstream circuit any of the other equations could suit your needs. -</p> - -<h2>Rendering semi-transparent textures</h2> -<p> - Now that we know how OpenGL works with regards to blending it's time to put our knowledge to the test by adding several semi-transparent windows. We'll be using the same scene as in the start of this chapter, but instead of rendering a grass texture we're now going to use the <a href="/img/advanced/blending_transparent_window.png" target="_blank">transparent window</a> texture from the start of this chapter. -</p> - -<p> - First, during initialization we enable blending and set the appropriate blending function: -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_BLEND); -<function id='70'>glBlendFunc</function>(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -</code></pre> - -<p> - Since we enabled blending there is no need to discard fragments so we'll reset the fragment shader to its original version: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec2 TexCoords; - -uniform sampler2D texture1; - -void main() -{ - FragColor = texture(texture1, TexCoords); -} -</code></pre> - -<p> - This time (whenever OpenGL renders a fragment) it combines the current fragment's color with the fragment color currently in the color buffer based on the alpha value of <var>FragColor</var>. Since the glass part of the window texture is semi-transparent we should be able to see the rest of the scene by looking through this window. -</p> - - -<img src="/img/advanced/blending_incorrect_order.png" class="clean" alt="A blended scene in OpenGL where order is incorrect."/> - -<p> - If you take a closer look however, you may notice something is off. The transparent parts of the front window are occluding the windows in the background. Why is this happening? -</p> - -<p> - The reason for this is that depth testing works a bit tricky combined with blending. When writing to the depth buffer, the depth test does not care if the fragment has transparency or not, so the transparent parts are written to the depth buffer as any other value. The result is that the background windows are tested on depth as any other opaque object would be, ignoring transparency. Even though the transparent part should show the windows behind it, the depth test discards them. -</p> - -<p> - So we cannot simply render the windows however we want and expect the depth buffer to solve all our issues for us; this is also where blending gets a little nasty. To make sure the windows show the windows behind them, we have to draw the windows in the background first. This means we have to manually sort the windows from furthest to nearest and draw them accordingly ourselves. -</p> - -<note> - Note that with fully transparent objects like the grass leaves we have the option to discard the transparent fragments instead of blending them, saving us a few of these headaches (no depth issues). -</note> - -<h2>Don't break the order</h2> -<p> - To make blending work for multiple objects we have to draw the most distant object first and the closest object last. The normal non-blended objects can still be drawn as normal using the depth buffer so they don't have to be sorted. We do have to make sure they are drawn first before drawing the (sorted) transparent objects. When drawing a scene with non-transparent and transparent objects the general outline is usually as follows: -</p> - -<ol> - <li>Draw all opaque objects first.</li> - <li>Sort all the transparent objects.</li> - <li>Draw all the transparent objects in sorted order.</li> -</ol> - -<p> - One way of sorting the transparent objects is to retrieve the distance of an object from the viewer's perspective. This can be achieved by taking the distance between the camera's position vector and the object's position vector. We then store this distance together with the corresponding position vector in a <fun>map</fun> data structure from the STL library. A <fun>map</fun> automatically sorts its values based on its keys, so once we've added all positions with their distance as the key they're automatically sorted on their distance value: -</p> - -<pre><code> -std::map<float, glm::vec3> sorted; -for (unsigned int i = 0; i < windows.size(); i++) -{ - float distance = glm::length(camera.Position - windows[i]); - sorted[distance] = windows[i]; -} -</code></pre> - -<p> - The result is a sorted container object that stores each of the window positions based on their <var>distance</var> key value from lowest to highest distance. -</p> - -<p> - Then, this time when rendering, we take each of the map's values in reverse order (from farthest to nearest) and then draw the corresponding windows in correct order: -</p> - -<pre><code> -for(std::map<float,glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it) -{ - model = glm::mat4(1.0f); - model = <function id='55'>glm::translate</function>(model, it->second); - shader.setMat4("model", model); - <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); -} -</code></pre> - -<p> - We take a reverse iterator from the <fun>map</fun> to iterate through each of the items in reverse order and then translate each window quad to the corresponding window position. This relatively simple approach to sorting transparent objects fixes the previous problem and now the scene looks like this: -</p> - -<img src="/img/advanced/blending_sorted.png" class="clean" alt="Image of an OpenGL scene with blending enabled, objects are sorted from far to near"/> - -<p> - You can find the complete source code with sorting <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/3.2.blending_sort/blending_sorted.cpp" target="_blank">here</a>. -</p> - -<p> - While this approach of sorting the objects by their distance works well for this specific scenario, it doesn't take rotations, scaling or any other transformation into account and weirdly shaped objects need a different metric than simply a position vector. -</p> - -<p> - Sorting objects in your scene is a difficult feat that depends greatly on the type of scene you have, let alone the extra processing power it costs. Completely rendering a scene with solid and transparent objects isn't all that easy. There are more advanced techniques like <def>order independent transparency</def> but these are out of the scope of this chapter. For now you'll have to live with normally blending your objects, but if you're careful and know the limitations you can get pretty decent blending implementations. -</p> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-OpenGL/Cubemaps.html b/translation/Advanced-OpenGL/Cubemaps.html @@ -1,829 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Cubemaps</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Cubemaps</h1> -<h1 id="content-url" style='display:none;'>Advanced-OpenGL/Cubemaps</h1> -<p> - We've been using 2D textures for a while now, but there are more texture types we haven't explored yet and in this chapter we'll discuss a texture type that is a combination of multiple textures mapped into one: a <def>cube map</def>. -</p> - -<p> - A cubemap is a texture that contains 6 individual 2D textures that each form one side of a cube: a textured cube. You may be wondering what the point is of such a cube? Why bother combining 6 individual textures into a single entity instead of just using 6 individual textures? Well, cube maps have the useful property that they can be indexed/sampled using a direction vector. Imagine we have a 1x1x1 unit cube with the origin of a direction vector residing at its center. Sampling a texture value from the cube map with an orange direction vector looks a bit like this: -</p> - -<img src="/img/advanced/cubemaps_sampling.png" class="clean" alt="Indexing/Sampling from a cubemap in OpenGL"/> - -<note> - The magnitude of the direction vector doesn't matter. As long as a direction is supplied, OpenGL retrieves the corresponding texels that the direction hits (eventually) and returns the properly sampled texture value. -</note> - -<p> - If we imagine we have a cube shape that we attach such a cubemap to, this direction vector would be similar to the (interpolated) local vertex position of the cube. This way we can sample the cubemap using the cube's actual position vectors as long as the cube is centered on the origin. We thus consider all vertex positions of the cube to be its texture coordinates when sampling a cubemap. The result is a texture coordinate that accesses the proper individual <def>face</def> texture of the cubemap. -</p> - -<h2>Creating a cubemap</h2> -<p> - A cubemap is a texture like any other texture, so to create one we generate a texture and bind it to the proper texture target before we do any further texture operations. This time binding it to <var>GL_TEXTURE_CUBE_MAP</var>: -</p> - -<pre class="cpp"><code> -unsigned int textureID; -<function id='50'>glGenTextures</function>(1, &textureID); -<function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, textureID); -</code></pre> - -<p> - Because a cubemap contains 6 textures, one for each face, we have to call <fun><function id='52'>glTexImage2D</function></fun> six times with their parameters set similarly to the previous chapters. This time however, we have to set the texture <em>target</em> parameter to match a specific face of the cubemap, telling OpenGL which side of the cubemap we're creating a texture for. This means we have to call <fun><function id='52'>glTexImage2D</function></fun> once for each face of the cubemap. -</p> - -<p> - Since we have 6 faces OpenGL gives us 6 special texture targets for targeting a face of the cubemap: -</p> - -<table> - <tr> - <th>Texture target</th> - <th>Orientation</th> - </tr> - <tr> - <td><code>GL_TEXTURE_CUBE_MAP_POSITIVE_X</code></td> - <td>Right</td> - </tr> - <tr> - <td><code>GL_TEXTURE_CUBE_MAP_NEGATIVE_X</code></td> - <td>Left</td> - </tr> - <tr> - <td><code>GL_TEXTURE_CUBE_MAP_POSITIVE_Y</code></td> - <td>Top</td> - </tr> - <tr> - <td><code>GL_TEXTURE_CUBE_MAP_NEGATIVE_Y</code></td> - <td>Bottom</td> - </tr> - <tr> - <td><code>GL_TEXTURE_CUBE_MAP_POSITIVE_Z</code></td> - <td>Back</td> - </tr> - <tr> - <td><code>GL_TEXTURE_CUBE_MAP_NEGATIVE_Z</code></td> - <td>Front</td> - </tr> -</table> - -<p> - Like many of OpenGL's enums, their behind-the-scenes <fun>int</fun> value is linearly incremented, so if we were to have an array or vector of texture locations we could loop over them by starting with <var>GL_TEXTURE_CUBE_MAP_POSITIVE_X</var> and incrementing the enum by 1 each iteration, effectively looping through all the texture targets: -</p> - -<pre><code> -int width, height, nrChannels; -unsigned char *data; -for(unsigned int i = 0; i < textures_faces.size(); i++) -{ - data = stbi_load(textures_faces[i].c_str(), &width, &height, &nrChannels, 0); - <function id='52'>glTexImage2D</function>( - GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, - 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data - ); -} -</code></pre> - -<p> - Here we have a <fun>vector</fun> called <var>textures_faces</var> that contain the locations of all the textures required for the cubemap in the order as given in the table. This generates a texture for each face of the currently bound cubemap. -</p> - -<p> - Because a cubemap is a texture like any other texture, we will also specify its wrapping and filtering methods: -</p> - -<pre><code> -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); -</code></pre> - -<p> - Don't be scared by the <var>GL_TEXTURE_WRAP_R</var>, this simply sets the wrapping method for the texture's <code>R</code> coordinate which corresponds to the texture's 3rd dimension (like <code>z</code> for positions). We set the wrapping method to <var>GL_CLAMP_TO_EDGE</var> since texture coordinates that are exactly between two faces may not hit an exact face (due to some hardware limitations) so by using <var>GL_CLAMP_TO_EDGE</var> OpenGL always returns their edge values whenever we sample between faces. -</p> - -<p> - Then before drawing the objects that will use the cubemap, we activate the corresponding texture unit and bind the cubemap before rendering; not much of a difference compared to normal 2D textures. -</p> - -<p> - Within the fragment shader we also have to use a different sampler of the type <code>samplerCube</code> that we sample from using the <fun>texture</fun> function, but this time using a <code>vec3</code> direction vector instead of a <code>vec2</code>. An example of fragment shader using a cubemap looks like this: -</p> - -<pre><code> -in vec3 textureDir; // direction vector representing a 3D texture coordinate -uniform samplerCube cubemap; // cubemap texture sampler - -void main() -{ - FragColor = texture(cubemap, textureDir); -} -</code></pre> - - -<p> - That is still great and all, but why bother? Well, it just so happens that there are quite a few interesting techniques that are a lot easier to implement with a cubemap. One of those techniques is creating a <def>skybox</def>. -</p> - -<h1>Skybox</h1> -<p> - A skybox is a (large) cube that encompasses the entire scene and contains 6 images of a surrounding environment, giving the player the illusion that the environment he's in is actually much larger than it actually is. Some examples of skyboxes used in videogames are images of mountains, of clouds, or of a starry night sky. An example of a skybox, using starry night sky images, can be seen in the following screenshot of the third elder scrolls game: -</p> - -<img src="/img/advanced/cubemaps_morrowind.jpg" alt="Image of morrowind with a skybox"/> - -<p> - You probably guessed by now that skyboxes like this suit cubemaps perfectly: we have a cube that has 6 faces and needs to be textured per face. In the previous image they used several images of a night sky to give the illusion the player is in some large universe while he's actually inside a tiny little box. -</p> - -<p> - There are usually enough resources online where you could find skyboxes like that. These skybox images usually have the following pattern: -</p> - -<img src="/img/advanced/cubemaps_skybox.png" class="clean" alt="Image of a skybox for a cubemap in OpenGL"/> - -<p> - If you would fold those 6 sides into a cube you'd get the completely textured cube that simulates a large landscape. Some resources provide the skybox in a format like that in which case you'd have to manually extract the 6 face images, but in most cases they're provided as 6 single texture images. -</p> - -<p> - This particular (high-quality) skybox is what we'll use for our scene and can be downloaded <a href="/img/textures/skybox.zip" target="_blank">here</a>. -</p> - -<h2>Loading a skybox</h2> -<p> - Since a skybox is by itself just a cubemap, loading a skybox isn't too different from what we've seen at the start of this chapter. To load the skybox we're going to use the following function that accepts a <fun>vector</fun> of 6 texture locations: -</p> - -<pre><code> -unsigned int loadCubemap(vector<std::string> faces) -{ - unsigned int textureID; - <function id='50'>glGenTextures</function>(1, &textureID); - <function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, textureID); - - int width, height, nrChannels; - for (unsigned int i = 0; i < faces.size(); i++) - { - unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0); - if (data) - { - <function id='52'>glTexImage2D</function>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, - 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data - ); - stbi_image_free(data); - } - else - { - std::cout << "Cubemap tex failed to load at path: " << faces[i] << std::endl; - stbi_image_free(data); - } - } - <function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - - return textureID; -} -</code></pre> - -<p> - The function itself shouldn't be too surprising. It is basically all the cubemap code we've seen in the previous section, but combined in a single manageable function. -</p> - -<p> - Now, before we call this function we'll load the appropriate texture paths in a vector in the order as specified by the cubemap enums: -</p> - -<pre><code> -vector<std::string> faces; -{ - "right.jpg", - "left.jpg", - "top.jpg", - "bottom.jpg", - "front.jpg", - "back.jpg" -}; -unsigned int cubemapTexture = loadCubemap(faces); -</code></pre> - -<p> - We loaded the skybox as a cubemap with <var>cubemapTexture</var> as its id. We can now finally bind it to a cube to replace that lame clear color we've been using all this time. -</p> - -<h2>Displaying a skybox</h2> -<p> - Because a skybox is drawn on a cube we'll need another VAO, VBO and a fresh set of vertices like any other 3D object. You can get its vertex data <a href="/code_viewer.php?code=advanced/cubemaps_skybox_data" target="_blank">here</a>. -</p> - -<p> - A cubemap used to texture a 3D cube can be sampled using the local positions of the cube as its texture coordinates. When a cube is centered on the origin (0,0,0) each of its position vectors is also a direction vector from the origin. This direction vector is exactly what we need to get the corresponding texture value at that specific cube's position. For this reason we only need to supply position vectors and don't need texture coordinates. -</p> - -<p> - To render the skybox we'll need a new set of shaders which aren't too complicated. Because we only have one vertex attribute the vertex shader is quite simple: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; - -out vec3 TexCoords; - -uniform mat4 projection; -uniform mat4 view; - -void main() -{ - TexCoords = aPos; - gl_Position = projection * view * vec4(aPos, 1.0); -} -</code></pre> - -<p> - The interesting part of this vertex shader is that we set the incoming local position vector as the outcoming texture coordinate for (interpolated) use in the fragment shader. The fragment shader then takes these as input to sample a <code>samplerCube</code>: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec3 TexCoords; - -uniform samplerCube skybox; - -void main() -{ - FragColor = texture(skybox, TexCoords); -} -</code></pre> - -<p> - The fragment shader is relatively straightforward. We take the vertex attribute's interpolated position vector as the texture's direction vector and use it to sample the texture values from the cubemap. -</p> - -<p> - Rendering the skybox is easy now that we have a cubemap texture, we simply bind the cubemap texture and the <var>skybox</var> sampler is automatically filled with the skybox cubemap. To draw the skybox we're going to draw it as the first object in the scene and disable depth writing. This way the skybox will always be drawn at the background of all the other objects since the unit cube is most likely smaller than the rest of the scene. -</p> - -<pre><code> -<function id='65'>glDepthMask</function>(GL_FALSE); -skyboxShader.use(); -// ... set view and projection matrix -<function id='27'>glBindVertexArray</function>(skyboxVAO); -<function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, cubemapTexture); -<function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 36); -<function id='65'>glDepthMask</function>(GL_TRUE); -// ... draw rest of the scene -</code></pre> - -<p> - If you run this you will get into difficulties though. We want the skybox to be centered around the player so that no matter how far the player moves, the skybox won't get any closer, giving the impression the surrounding environment is extremely large. The current view matrix however transforms all the skybox's positions by rotating, scaling and translating them, so if the player moves, the cubemap moves as well! We want to remove the translation part of the view matrix so only rotation will affect the skybox's position vectors. -</p> - -<p> - You may remember from the <a href="https://learnopengl.com/Lighting/Basic-Lighting" target="_blank">basic lighting</a> chapter that we can remove the translation section of transformation matrices by taking the upper-left 3x3 matrix of the 4x4 matrix. We can achieve this by converting the view matrix to a 3x3 matrix (removing translation) and converting it back to a 4x4 matrix: -</p> - -<pre><code> -glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix())); -</code></pre> - -<p> - This removes any translation, but keeps all rotation transformations so the user can still look around the scene. -</p> - -<p> - The result is a scene that instantly looks enormous due to our skybox. If you'd fly around the basic container you immediately get a sense of scale which dramatically improves the realism of the scene. The result looks something like this: -</p> - -<img src="/img/advanced/cubemaps_skybox_result.png" class="clean" alt="Image of a skybox in an OpenGL scene"/> - -<p> - Try experimenting with different skyboxes and see how they can have an enormous impact on the look and feel of your scene. -</p> - -<h2>An optimization</h2> -<p> - Right now we've rendered the skybox first before we rendered all the other objects in the scene. This works great, but isn't too efficient. If we render the skybox first we're running the fragment shader for each pixel on the screen even though only a small part of the skybox will eventually be visible; fragments that could have easily been discarded using <def>early depth testing</def> saving us valuable bandwidth. -</p> - -<p> - So to give us a slight performance boost we're going to render the skybox last. This way, the depth buffer is completely filled with all the scene's depth values so we only have to render the skybox's fragments wherever the early depth test passes, greatly reducing the number of fragment shader calls. The problem is that the skybox will most likely render on top of all other objects since it's only a 1x1x1 cube, succeeding most depth tests. Simply rendering it without depth testing is not a solution since the skybox will then still overwrite all the other objects in the scene as it's rendered last. We need to trick the depth buffer into believing that the skybox has the maximum depth value of <code>1.0</code> so that it fails the depth test wherever there's a different object in front of it. -</p> - -<p> - In the <a href="https://learnopengl.com/Getting-started/Coordinate-Systems" target="_blank">coordinate systems</a> chapter we said that <em>perspective division</em> is performed after the vertex shader has run, dividing the <var>gl_Position</var>'s <code>xyz</code> coordinates by its <code>w</code> component. We also know from the <a href="https://learnopengl.com/Advanced-OpenGL/Depth-testing" target="_blank">depth testing</a> chapter that the <code>z</code> component of the resulting division is equal to that vertex's depth value. Using this information we can set the <code>z</code> component of the output position equal to its <code>w</code> component which will result in a <code>z</code> component that is always equal to <code>1.0</code>, because when the perspective division is applied its <code>z</code> component translates to <code>w</code> / <code>w</code> = <code>1.0</code>: -</p> - -<pre><code> -void main() -{ - TexCoords = aPos; - vec4 pos = projection * view * vec4(aPos, 1.0); - gl_Position = pos.xyww; -} -</code></pre> - -<p> - The resulting <em>normalized device coordinates</em> will then always have a <code>z</code> value equal to <code>1.0</code>: the maximum depth value. The skybox will as a result only be rendered wherever there are no objects visible (only then it will pass the depth test, everything else is in front of the skybox). -</p> - -<p> - We do have to change the depth function a little by setting it to <var>GL_LEQUAL</var> instead of the default <var>GL_LESS</var>. The depth buffer will be filled with values of <code>1.0</code> for the skybox, so we need to make sure the skybox passes the depth tests with values <em>less than or equal</em> to the depth buffer instead of <em>less than</em>. -</p> - -<p> - You can find the more optimized version of the source code <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/6.1.cubemaps_skybox/cubemaps_skybox.cpp" target="_blank">here</a>. -</p> - -<h1>Environment mapping</h1> -<p> - We now have the entire surrounding environment mapped in a single texture object and we could use that information for more than just a skybox. Using a cubemap with an environment, we could give objects reflective or refractive properties. Techniques that use an environment cubemap like this are called <def>environment mapping</def> techniques and the two most popular ones are <def>reflection</def> and <def>refraction</def>. -</p> - -<h2>Reflection</h2> -<p> - Reflection is the property that an object (or part of an object) <def>reflects</def> its surrounding environment e.g. the object's colors are more or less equal to its environment based on the angle of the viewer. A mirror for example is a reflective object: it reflects its surroundings based on the viewer's angle. -</p> - -<p> - The basics of reflection are not that difficult. The following image shows how we can calculate a <def>reflection vector</def> and use that vector to sample from a cubemap: -</p> - -<img src="/img/advanced/cubemaps_reflection_theory.png" class="clean" alt="Image of how to calculate reflection."/> - -<p> - We calculate a reflection vector \(\color{green}{\bar{R}}\) around the object's normal vector \(\color{red}{\bar{N}}\) based on the view direction vector \(\color{gray}{\bar{I}}\). We can calculate this reflection vector using GLSL's built-in <fun>reflect</fun> function. The resulting vector \(\color{green}{\bar{R}}\) is then used as a direction vector to index/sample the cubemap, returning a color value of the environment. The resulting effect is that the object seems to reflect the skybox. -</p> - -<p> - Since we already have a skybox setup in our scene, creating reflections isn't too difficult. We'll change the fragment shader used by the container to give the container reflective properties: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec3 Normal; -in vec3 Position; - -uniform vec3 cameraPos; -uniform samplerCube skybox; - -void main() -{ - vec3 I = normalize(Position - cameraPos); - vec3 R = reflect(I, normalize(Normal)); - FragColor = vec4(texture(skybox, R).rgb, 1.0); -} -</code></pre> - -<p> - We first calculate the view/camera direction vector <var>I</var> and use this to calculate the reflect vector <var>R</var> which we then use to sample from the skybox cubemap. Note that we have the fragment's interpolated <var>Normal</var> and <var>Position</var> variable again so we'll need to adjust the vertex shader as well: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec3 aNormal; - -out vec3 Normal; -out vec3 Position; - -uniform mat4 model; -uniform mat4 view; -uniform mat4 projection; - -void main() -{ - Normal = mat3(transpose(inverse(model))) * aNormal; - Position = vec3(model * vec4(aPos, 1.0)); - gl_Position = projection * view * vec4(Position, 1.0); -} -</code></pre> - -<p> - We're using normal vectors so we'll want to transform them with a normal matrix again. The <var>Position</var> output vector is a world-space position vector. This <var>Position</var> output of the vertex shader is used to calculate the view direction vector in the fragment shader. -</p> - -<p> - Because we're using normals you'll want to update the <a href="/code_viewer.php?code=lighting/basic_lighting_vertex_data" target="_blank">vertex data</a> and update the attribute pointers as well. Also make sure to set the <var>cameraPos</var> uniform. -</p> - -<p> - Then we also want to bind the cubemap texture before rendering the container: -</p> - -<pre class="cpp"><code> -<function id='27'>glBindVertexArray</function>(cubeVAO); -<function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, skyboxTexture); -<function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 36); -</code></pre> - -<p> - Compiling and running your code gives you a container that acts like a perfect mirror. The surrounding skybox is perfectly reflected on the container: -</p> - -<img src="/img/advanced/cubemaps_reflection.png" class="clean" alt="Image of a cube reflecting a skybox via cubemaps via environment mapping."/> - -<p> - You can find the full source code <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/6.2.cubemaps_environment_mapping/cubemaps_environment_mapping.cpp" target="_blank">here</a>. -</p> - -<p> - When reflection is applied to an entire object (like the container) the object looks as if it has a high reflective material like steel or chrome. If we were to load a more interesting object (like the backpack model from the <a href="https://learnopengl.com/Model-Loading/Model" target="_blank">model loading</a> chapters) we'd get the effect that the object looks to be entirely made out of chrome: -</p> - -<img src="/img/advanced/cubemaps_reflection_nanosuit.png" alt="Image of a Backpack model reflecting a skybox via cubemaps via environment mapping."/> - -<p> - This looks quite awesome, but in reality most models aren't all completely reflective. We could for instance introduce <def>reflection maps</def> that give the models another extra level of detail. Just like diffuse and specular maps, reflection maps are texture images that we can sample to determine the reflectivity of a fragment. Using these reflection maps we can determine which parts of the model show reflection and by what intensity. <!--In the exercise of this chapter it's up to you to introduce reflection maps in the model loader we created earlier, significantly boosting the detail of the 3D object.--> -</p> - -<h2>Refraction</h2> -<p> - Another form of environment mapping is called <def>refraction</def> and is similar to reflection. Refraction is the change in direction of light due to the change of the material the light flows through. Refraction is what we commonly see with water-like surfaces where the light doesn't enter straight through, but bends a little. It's like looking at your arm when it's halfway in the water. -</p> - -<p> - Refraction is described by <a href="http://en.wikipedia.org/wiki/Snell%27s_law" target="_blank">Snell's law</a> that with environment maps looks a bit like this: -</p> - -<img src="/img/advanced/cubemaps_refraction_theory.png" class="clean" alt="Image explaining refraction of light for use with cubemaps."/> - -<p> - Again, we have a view vector \(\color{gray}{\bar{I}}\), a normal vector \(\color{red}{\bar{N}}\) and this time a resulting refraction vector \(\color{green}{\bar{R}}\). As you can see, the direction of the view vector is slightly bend. This resulting bended vector \(\color{green}{\bar{R}}\) is then used to sample from the cubemap. -</p> - -<p> - Refraction is fairly easy to implement using GLSL's built-in <fun>refract</fun> function that expects a normal vector, a view direction, and a ratio between both materials' <def>refractive indices</def>. -</p> - -<p> - The refractive index determines the amount light distorts/bends in a material where each material has its own refractive index. A list of the most common refractive indices are given in the following table: -</p> - -<table> - <tr> - <th>Material</th> - <th>Refractive index</th> - </tr> - <tr> - <td>Air</td> - <td>1.00</td> - </tr> - <tr> - <td>Water</td> - <td>1.33</td> - </tr> - <tr> - <td>Ice</td> - <td>1.309</td> - </tr> - <tr> - <td>Glass</td> - <td>1.52</td> - </tr> - <tr> - <td>Diamond</td> - <td>2.42</td> - </tr> -</table> - -<p> - We use these refractive indices to calculate the ratio between both materials the light passes through. In our case, the light/view ray goes from <em>air</em> to <em>glass</em> (if we assume the object is made of glass) so the ratio becomes \(\frac{1.00}{1.52} = 0.658\). -</p> - -<p> - We already have the cubemap bound, supplied the vertex data with normals, and set the camera position as a uniform. The only thing we have to change is the fragment shader: -</p> - -<pre><code> -void main() -{ - float ratio = 1.00 / 1.52; - vec3 I = normalize(Position - cameraPos); - vec3 R = refract(I, normalize(Normal), ratio); - FragColor = vec4(texture(skybox, R).rgb, 1.0); -} -</code></pre> - -<p> - By changing the refractive indices you can create completely different visual results. Compiling the application and running the results on the container object is not so interesting though as it doesn't really show the effect refraction has aside that it acts as a magnifying glass right now. Using the same shaders on the loaded 3D model however does show us the effect we're looking for: a glass-like object. -</p> - -<img src="/img/advanced/cubemaps_refraction.png" alt="Image of environment maps using refraction in OpenGL"/> - -<p> - You can imagine that with the right combination of lighting, reflection, refraction and vertex movement, you can create pretty neat water graphics. Do note that for physically accurate results we should refract the light <strong>again</strong> when it leaves the object; now we simply used single-sided refraction which is fine for most purposes. -</p> - -<h2>Dynamic environment maps</h2> -<p> - Right now we've been using a static combination of images as the skybox, which looks great, but it doesn't include the actual 3D scene with possibly moving objects. We didn't really notice this so far, because we only used a single object. If we had a mirror-like objects with multiple surrounding objects, only the skybox would be visible in the mirror as if it was the only object in the scene. -</p> - -<p> - Using framebuffers it is possible to create a texture of the scene for all 6 different angles from the object in question and store those in a cubemap each frame. We can then use this (dynamically generated) cubemap to create realistic reflection and refractive surfaces that include all other objects. This is called <def>dynamic environment mapping</def>, because we dynamically create a cubemap of an object's surroundings and use that as its environment map. -</p> - -<p> - While it looks great, it has one enormous disadvantage: we have to render the scene 6 times per object using an environment map, which is an enormous performance penalty on your application. Modern applications try to use the skybox as much as possible and where possible pre-render cubemaps wherever they can to still sort-of create dynamic environment maps. While dynamic environment mapping is a great technique, it requires a lot of clever tricks and hacks to get it working in an actual rendering application without too many performance drops. -</p> - - - -<!--<h2>Exercises</h2> -<ul> - <li>Try to introduce reflection maps into the model loader we created in the <a href="https://learnopengl.com/Model-Loading/Assimp" target="_blank">model loading</a> chapters. You can find the upgraded nanosuit model with reflection maps included <a href="/objects/nanosuit_reflection.zip" target="_blank">here</a>. There are a few things to note though:</li> - <ul> - <li>Assimp doesn't really seem to like reflection maps in most object formats so we cheated a little by storing the reflection maps as <em>ambient maps</em>. You can then load the reflection maps by specifying <var>aiTextureType_AMBIENT</var> as the texture type when loading materials.</li> - <li>I sort of hastily created reflection map textures from the specular texture images, so the reflection maps won't map exactly to the model in some places :).</li> - <li>Since the model loader by itself already takes up 3 texture units in the shader, you'll have to bind the skybox to a 4th texture unit since we'll also sample from the skybox in the same shader.</li> - </ul> - <li>If you did things right it'll look something like <a href="/img/advanced/cubemaps_reflection_map.png" target="_blank">this</a>. - </li> -</ul> ---> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-OpenGL/Depth-testing.html b/translation/Advanced-OpenGL/Depth-testing.html @@ -1,571 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Depth testing</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Depth testing</h1> -<h1 id="content-url" style='display:none;'>Advanced-OpenGL/Depth-testing</h1> -<p> - In the <a href="https://learnopengl.com/Getting-started/Coordinate-Systems" target="_blank">coordinate systems</a> chapter we've rendered a 3D container and made use of a <def>depth buffer</def> to prevent triangles rendering in the front while they're supposed to be behind other triangles. In this chapter we're going to elaborate a bit more on those <def>depth values</def> the depth buffer (or z-buffer) stores and how it actually determines if a fragment is in front. -</p> - -<p> - The depth-buffer is a buffer that, just like the <def>color buffer</def> (that stores all the fragment colors: the visual output), stores information per fragment and has the same width and height as the color buffer. The depth buffer is automatically created by the windowing system and stores its depth values as <code>16</code>, <code>24</code> or <code>32</code> bit floats. In most systems you'll see a depth buffer with a precision of <code>24</code> bits. -</p> - -<p> - When depth testing is enabled, OpenGL tests the depth value of a fragment against the content of the depth buffer. OpenGL performs a depth test and if this test passes, the fragment is rendered and the depth buffer is updated with the new depth value. If the depth test fails, the fragment is discarded. -</p> - -<p> - Depth testing is done in screen space after the fragment shader has run (and after the stencil test which we'll get to in the <a href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing" target="_blank">next</a> chapter). The screen space coordinates relate directly to the viewport defined by OpenGL's <fun><function id='22'>glViewport</function></fun> function and can be accessed via GLSL's built-in <var>gl_FragCoord</var> variable in the fragment shader. The x and y components of <var>gl_FragCoord</var> represent the fragment's screen-space coordinates (with (0,0) being the bottom-left corner). The <var>gl_FragCoord</var> variable also contains a z-component which contains the depth value of the fragment. This z value is the value that is compared to the depth buffer's content. -</p> - -<note> - Today most GPUs support a hardware feature called <def>early depth testing</def>. Early depth testing allows the depth test to run before the fragment shader runs. Whenever it is clear a fragment isn't going to be visible (it is behind other objects) we can prematurely discard the fragment. -<br/><br/> -Fragment shaders are usually quite expensive so wherever we can avoid running them we should. A restriction on the fragment shader for early depth testing is that you shouldn't write to the fragment's depth value. If a fragment shader would write to its depth value, early depth testing is impossible; OpenGL won't be able to figure out the depth value beforehand. -</note> - -<p> - Depth testing is disabled by default so to enable depth testing we need to enable it with the <var>GL_DEPTH_TEST</var> option: -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_DEPTH_TEST); -</code></pre> - -<p> - Once enabled, OpenGL automatically stores fragments their z-values in the depth buffer if they passed the depth test and discards fragments if they failed the depth test accordingly. If you have depth testing enabled you should also clear the depth buffer before each frame using <var>GL_DEPTH_BUFFER_BIT</var>; otherwise you're stuck with the depth values from last frame: -</p> - -<pre><code> -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -</code></pre> - -<p> - There are certain scenarios imaginable where you want to perform the depth test on all fragments and discard them accordingly, but <strong>not</strong> update the depth buffer. Basically, you're (temporarily) using a <def>read-only</def> depth buffer. OpenGL allows us to disable writing to the depth buffer by setting its depth mask to <code>GL_FALSE</code>: -</p> - -<pre><code> -<function id='65'>glDepthMask</function>(GL_FALSE); -</code></pre> - -<p> - Note that this only has effect if depth testing is enabled. -</p> - -<h2>Depth test function</h2> -<p> - OpenGL allows us to modify the comparison operators it uses for the depth test. This allows us to control when OpenGL should pass or discard fragments and when to update the depth buffer. We can set the comparison operator (or depth function) by calling <fun><function id='66'>glDepthFunc</function></fun>: -</p> - -<pre><code> -<function id='66'>glDepthFunc</function>(GL_LESS); -</code></pre> - -<p> - The function accepts several comparison operators that are listed in the table below: -</p> - -<table> - <tr> - <th>Function</th> - <th>Description</th> - </tr> - <tr> - <td><code>GL_ALWAYS</code></td> - <td>The depth test always passes.</td> - </tr> - <tr> - <td><code>GL_NEVER</code></td> - <td>The depth test never passes.</td> - </tr> - <tr> - <td><code>GL_LESS</code></td> - <td>Passes if the fragment's depth value is less than the stored depth value.</td> - </tr> - <tr> - <td><code>GL_EQUAL</code></td> - <td>Passes if the fragment's depth value is equal to the stored depth value.</td> - </tr><tr> - <td><code>GL_LEQUAL</code></td> - <td>Passes if the fragment's depth value is less than or equal to the stored depth value.</td> - </tr> - <tr> - <td><code>GL_GREATER</code></td> - <td>Passes if the fragment's depth value is greater than the stored depth value.</td> - </tr> - <tr> - <td><code>GL_NOTEQUAL</code></td> - <td>Passes if the fragment's depth value is not equal to the stored depth value.</td> - </tr> - <tr> - <td><code>GL_GEQUAL</code></td> - <td>Passes if the fragment's depth value is greater than or equal to the stored depth value.</td> - </tr> -</table> - -<p> - By default the depth function <var>GL_LESS</var> is used that discards all the fragments that have a depth value higher than or equal to the current depth buffer's value. -</p> - -<p> - Let's show the effect that changing the depth function has on the visual output. We'll use a fresh code setup that displays a basic scene with two textured cubes sitting on a textured floor with no lighting. You can find the source code <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/1.1.depth_testing/depth_testing.cpp" target="_blank">here</a>. -</p> - -<p> - Within the source code we changed the depth function to <var>GL_ALWAYS</var>: -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_DEPTH_TEST); -<function id='66'>glDepthFunc</function>(GL_ALWAYS); -</code></pre> - -<p> - This simulates the same behavior we'd get if we didn't enable depth testing. The depth test always passes so the fragments that are drawn last are rendered in front of the fragments that were drawn before, even though they should've been at the front. Since we've drawn the floor plane last, the plane's fragments overwrite each of the container's previously written fragments: -</p> - -<img src="/img/advanced/depth_testing_func_always.png" class="clean" alt="Depth testing in OpenGL with GL_ALWAYS as depth function"/> - -<p> - Setting it all back to <var>GL_LESS</var> gives us the type of scene we're used to: -</p> - -<img src="/img/advanced/depth_testing_func_less.png" class="clean" alt="Depth testing in OpenGL with GL_LESS as depth function"/> - -<h2>Depth value precision</h2> -<p> - The depth buffer contains depth values between <code>0.0</code> and <code>1.0</code> and it compares its content with the z-values of all the objects in the scene as seen from the viewer. These z-values in view space can be any value between the projection-frustum's <code>near</code> and <code>far</code> plane. We thus need some way to transform these view-space z-values to the range of <code>[0,1]</code> and one way is to linearly transform them. The following (linear) equation transforms the z-value to a depth value between <code>0.0</code> and <code>1.0</code>: -</p> - -\begin{equation} F_{depth} = \frac{z - near}{far - near} \end{equation} - -<p> - Here \(near\) and \(far\) are the <em>near</em> and <em>far</em> values we used to provide to the projection matrix to set the visible frustum (see <a href="https://learnopengl.com/Getting-started/Coordinate-Systems" target="_blank">coordinate Systems</a>). The equation takes a depth value \(z\) within the frustum and transforms it to the range <code>[0,1]</code>. The relation between the z-value and its corresponding depth value is presented in the following graph: -</p> - -<img src="/img/advanced/depth_linear_graph.png" class="clean" alt="Graph of depth values in OpenGL as a linear function"/> - -<note> - Note that all equations give a depth value close to <code>0.0</code> when the object is close by and a depth value close to <code>1.0</code> when the object is close to the far plane. -</note> - -<p> - In practice however, a <def>linear depth buffer</def> like this is almost never used. Because of projection properties a non-linear depth equation is used that is proportional to 1/z. The result is that we get enormous precision when z is small and much less precision when z is far away. -</p> - -<p> - Since the non-linear function is proportional to 1/z, z-values between <code>1.0</code> and <code>2.0</code> would result in depth values between <code>1.0</code> and <code>0.5</code> which is half of the [0,1] range, giving us enormous precision at small z-values. Z-values between <code>50.0</code> and <code>100.0</code> would account for only 2% of the [0,1] range. Such an equation, that also takes near and far distances into account, is given below: -</p> - -\begin{equation} F_{depth} = \frac{1/z - 1/near}{1/far - 1/near} \end{equation} - -<p> - Don't worry if you don't know exactly what is going on with this equation. The important thing to remember is that the values in the depth buffer are not linear in clip-space (they are linear in view-space before the projection matrix is applied). A value of <code>0.5</code> in the depth buffer does not mean the pixel's z-value is halfway in the frustum; the z-value of the vertex is actually quite close to the near plane! You can see the non-linear relation between the z-value and the resulting depth buffer's value in the following graph: -</p> - -<img src="/img/advanced/depth_non_linear_graph.png" class="clean" alt="Graph of depth values in OpenGL as a non-linear function"/> - -<p> - As you can see, the depth values are greatly determined by the small z-values giving us large depth precision to the objects close by. The equation to transform z-values (from the viewer's perspective) is embedded within the projection matrix so when we transform vertex coordinates from view to clip, and then to screen-space the non-linear equation is applied. -</p> - -<p> - The effect of this non-linear equation quickly becomes apparent when we try to visualize the depth buffer. -</p> - -<h2>Visualizing the depth buffer</h2> -<p> - We know that the z-value of the built-in <var>gl_FragCoord</var> vector in the fragment shader contains the depth value of that particular fragment. If we were to output this depth value of the fragment as a color we could display the depth values of all the fragments in the scene: -</p> - -<pre><code> -void main() -{ - FragColor = vec4(vec3(gl_FragCoord.z), 1.0); -} -</code></pre> - -<p> - If you'd then run the program you'll probably notice that everything is white, making it look like all of our depth values are the maximum depth value of <code>1.0</code>. So why aren't any of the depth values closer to <code>0.0</code> and thus darker? -</p> - -<p> - In the previous section we described that depth values in screen space are non-linear e.g. they have a very high precision for small z-values and a low precision for large z-values. The depth value of the fragment increases rapidly over distance so almost all the vertices have values close to <code>1.0</code>. If we were to carefully move really close to an object you may eventually see the colors getting darker, their z-values becoming smaller: -</p> - -<img src="/img/advanced/depth_testing_visible_depth.png" class="clean" alt="Depth buffer visualized in OpenGL and GLSL"/> - -<p> - This clearly shows the non-linearity of the depth value. Objects close by have a much larger effect on the depth value than objects far away. Only moving a few inches can result in the colors going from dark to completely white. -</p> - -<p> - We can however, transform the non-linear depth values of the fragment back to its linear sibling. To achieve this we basically need to reverse the process of projection for the depth values alone. This means we have to first re-transform the depth values from the range <code>[0,1]</code> to normalized device coordinates in the range <code>[-1,1]</code>. Then we want to reverse the non-linear equation (equation 2) as done in the projection matrix and apply this inversed equation to the resulting depth value. The result is then a linear depth value. -</p> - -<p> - First we transform the depth value to NDC which is not too difficult: -</p> - -<pre><code> -float ndc = depth * 2.0 - 1.0; -</code></pre> - -<p> - We then take the resulting <var>ndc</var> value and apply the inverse transformation to retrieve its linear depth value: -</p> - -<pre><code> -float linearDepth = (2.0 * near * far) / (far + near - ndc * (far - near)); -</code></pre> - -<p> - This equation is derived from the projection matrix for non-linearizing the depth values, returning depth values between <var>near</var> and <var>far</var>. This <a href="http://www.songho.ca/opengl/gl_projectionmatrix.html" target="_blank">math-heavy article</a> explains the projection matrix in enormous detail for the interested reader; it also shows where the equations come from. -</p> - -<p> - The complete fragment shader that transforms the non-linear depth in screen-space to a linear depth value is then as follows: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -float near = 0.1; -float far = 100.0; - -float LinearizeDepth(float depth) -{ - float z = depth * 2.0 - 1.0; // back to NDC - return (2.0 * near * far) / (far + near - z * (far - near)); -} - -void main() -{ - float depth = LinearizeDepth(gl_FragCoord.z) / far; // divide by far for demonstration - FragColor = vec4(vec3(depth), 1.0); -} -</code></pre> - -<p> - Because the linearized depth values range from <var>near</var> to <var>far</var> most of its values will be above <code>1.0</code> and displayed as completely white. By dividing the linear depth value by <var>far</var> in the <fun>main</fun> function we convert the linear depth value to the range [<code>0</code>, <code>1</code>]. This way we can gradually see the scene become brighter the closer the fragments are to the projection frustum's far plane, which works better for visualization purposes. -</p> - -<p> - If we'd now run the application we get depth values that are linear over distance. Try moving around the scene to see the depth values change in a linear fashion. -</p> - -<img src="/img/advanced/depth_testing_visible_linear.png" class="clean" alt="Depth buffer visualized in OpenGL and GLSL as linear values"/> - -<p> - The colors are mostly black because the depth values range linearly from the <code>near</code> plane (<code>0.1</code>) to the <code>far</code> plane (<code>100</code>) which is still quite far away from us. The result is that we're relatively close to the near plane and therefore get lower (darker) depth values. -</p> - -<h2>Z-fighting</h2> -<p> - A common visual artifact may occur when two planes or triangles are so closely aligned to each other that the depth buffer does not have enough precision to figure out which one of the two shapes is in front of the other. The result is that the two shapes continually seem to switch order which causes weird glitchy patterns. This is called <def>z-fighting</def>, because it looks like the shapes are fighting over who gets on top. -</p> - -<p> - In the scene we've been using so far there are a few spots where z-fighting can be noticed. The containers were placed at the exact height of the floor which means the bottom plane of the container is coplanar with the floor plane. The depth values of both planes are then the same so the resulting depth test has no way of figuring out which is the right one. -</p> - -<p> - If you move the camera inside one of the containers the effects are clearly visible, the bottom part of the container is constantly switching between the container's plane and the floor's plane in a zigzag pattern: -</p> - -<img src="/img/advanced/depth_testing_z_fighting.png" class="clean" alt="Demonstration of Z-fighting in OpenGL"/> - -<p> - Z-fighting is a common problem with depth buffers and it's generally more noticeable when objects are further away (because the depth buffer has less precision at larger z-values). Z-fighting can't be completely prevented, but there are a few tricks that will help to mitigate or completely prevent z-fighting in your scene. -</p> - -<h3>Prevent z-fighting</h3> -<p> - The first and most important trick is <em>never place objects too close to each other in a way that some of their triangles closely overlap</em>. By creating a small offset between two objects you can completely remove z-fighting between the two objects. In the case of the containers and the plane we could've easily moved the containers slightly upwards in the positive y direction. The small change of the container's positions would probably not be noticeable at all and would completely reduce the z-fighting. However, this requires manual intervention of each of the objects and thorough testing to make sure no objects in a scene produce z-fighting. -</p> - -<p> - A second trick is to <em>set the near plane as far as possible</em>. In one of the previous sections we've discussed that precision is extremely large when close to the <code>near</code> plane so if we move the <code>near</code> plane away from the viewer, we'll have significantly greater precision over the entire frustum range. However, setting the <code>near</code> plane too far could cause clipping of near objects so it is usually a matter of tweaking and experimentation to figure out the best <code>near</code> distance for your scene. -</p> - -<p> - Another great trick at the cost of some performance is to <em>use a higher precision depth buffer</em>. Most depth buffers have a precision of <code>24</code> bits, but most GPUs nowadays support <code>32</code> bit depth buffers, increasing the precision by a significant amount. So at the cost of some performance you'll get much more precision with depth testing, reducing z-fighting. -</p> - -<p> - The 3 techniques we've discussed are the most common and easy-to-implement anti z-fighting techniques. There are some other techniques out there that require a lot more work and still won't completely disable z-fighting. Z-fighting is a common issue, but if you use the proper combination of the listed techniques you probably won't need to deal with z-fighting that much. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-OpenGL/Face-culling.html b/translation/Advanced-OpenGL/Face-culling.html @@ -1,419 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Face culling</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Face culling</h1> -<h1 id="content-url" style='display:none;'>Advanced-OpenGL/Face-culling</h1> -<p> - Try mentally visualizing a 3D cube and count the maximum number of faces you'll be able to see from any direction. If your imagination is not too creative you probably ended up with a maximum number of 3. You can view a cube from any position and/or direction, but you would never be able to see more than 3 faces. So why would we waste the effort of drawing those other 3 faces that we can't even see. If we could discard those in some way we would save more than 50% of this cube's total fragment shader runs! -</p> - -<note> - We say <em>more than 50%</em> instead of 50%, because from certain angles only 2 or even 1 face could be visible. In that case we'd save <strong>more</strong> than 50%. -</note> - -<p> - This is a really great idea, but there's one problem we need to solve: how do we know if a face of an object is not visible from the viewer's point of view? If we imagine any closed shape, each of its faces has two sides. Each side would either <em>face</em> the user or show its back to the user. What if we could only render the faces that are <em>facing</em> the viewer? -</p> - -<p> - This is exactly what <def>face culling</def> does. OpenGL checks all the faces that are <def>front facing</def> towards the viewer and renders those while discarding all the faces that are <def>back facing</def>, saving us a lot of fragment shader calls. We do need to tell OpenGL which of the faces we use are actually the front faces and which faces are the back faces. OpenGL uses a clever trick for this by analyzing the <def>winding order</def> of the vertex data. -</p> - -<h2>Winding order</h2> -<p> - When we define a set of triangle vertices we're defining them in a certain winding order that is either <def>clockwise</def> or <def>counter-clockwise</def>. Each triangle consists of 3 vertices and we specify those 3 vertices in a winding order as seen from the center of the triangle. -</p> - -<img src="/img/advanced/faceculling_windingorder.png" class="clean" alt="Winding order of OpenGL triangles"/> - -<p> - As you can see in the image we first define vertex <code>1</code> and from there we can choose whether the next vertex is <code>2</code> or <code>3</code>. This choice defines the winding order of this triangle. The following code illustrates this: -</p> - -<pre><code> -float vertices[] = { - // clockwise - vertices[0], // vertex 1 - vertices[1], // vertex 2 - vertices[2], // vertex 3 - // counter-clockwise - vertices[0], // vertex 1 - vertices[2], // vertex 3 - vertices[1] // vertex 2 -}; -</code></pre> - -<p> - Each set of 3 vertices that form a triangle primitive thus contain a winding order. OpenGL uses this information when rendering your primitives to determine if a triangle is a <def>front-facing</def> or a <def>back-facing</def> triangle. By default, triangles defined with counter-clockwise vertices are processed as front-facing triangles. -</p> - -<p> - When defining your vertex order you visualize the corresponding triangle as if it was facing you, so each triangle that you're specifying should be counter-clockwise as if you're directly facing that triangle. The cool thing about specifying all your vertices like this is that the actual winding order is calculated at the rasterization stage, so when the vertex shader has already run. The vertices are then seen as from the <strong>viewer's point of view</strong>. -</p> - -<p> - All the triangle vertices that the viewer is then facing are indeed in the correct winding order as we specified them, but the vertices of the triangles at the other side of the cube are now rendered in such a way that their winding order becomes reversed. The result is that the triangles we're facing are seen as front-facing triangles and the triangles at the back are seen as back-facing triangles. The following image shows this effect: -</p> - -<img src="/img/advanced/faceculling_frontback.png" class="clean" alt="Image of viewer seeing front or back facing triangles"/> - -<p> - In the vertex data we defined both triangles in counter-clockwise order (the front and back triangle as 1, 2, 3). However, from the viewer's direction the back triangle is rendered clockwise if we draw it in the order of 1, 2 and 3 from the viewer's current point of view. Even though we specified the back triangle in counter-clockwise order, it is now rendered in a clockwise order. This is exactly what we want to <def>cull</def> (discard) non-visible faces! -</p> - -<h2>Face culling</h2> -<p> - At the start of the chapter we said that OpenGL is able to discard triangle primitives if they're rendered as back-facing triangles. Now that we know how to set the winding order of the vertices we can start using OpenGL's <def>face culling</def> option which is disabled by default. -</p> - -<p> - The cube vertex data we used in the previous chapters wasn't defined with the counter-clockwise winding order in mind, so I updated the vertex data to reflect a counter-clockwise winding order which you can copy from <a href="/code_viewer.php?code=advanced/faceculling_vertexdata" target="_blank">here</a>. It's a good practice to try and visualize that these vertices are indeed all defined in a counter-clockwise order for each triangle. -</p> - -<p> - To enable face culling we only have to enable OpenGL's <var>GL_CULL_FACE</var> option: -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_CULL_FACE); -</code></pre> - -<p> - From this point on, all the faces that are not front-faces are discarded (try flying inside the cube to see that all inner faces are indeed discarded). Currently we save over 50% of performance on rendering fragments if OpenGL decides to render the back faces first (otherwise depth testing would've discarded them already). Do note that this only really works with closed shapes like a cube. We do have to disable face culling again when we draw the grass leaves from the <a href="https://learnopengl.com/Advanced-OpenGL/Blending" target="_blank">previous</a> chapter, since their front <strong>and</strong> back face should be visible. -</p> - -<p> - OpenGL allows us to change the type of face we want to cull as well. What if we want to cull front faces and not the back faces? We can define this behavior with <fun><function id='74'>glCullFace</function></fun>: -</p> - -<pre><code> -<function id='74'>glCullFace</function>(GL_FRONT); -</code></pre> - -<p> - The <fun><function id='74'>glCullFace</function></fun> function has three possible options: -</p> - -<ul> - <li><var>GL_BACK</var>: Culls only the back faces.</li> - <li><var>GL_FRONT</var>: Culls only the front faces.</li> - <li><var>GL_FRONT_AND_BACK</var>: Culls both the front and back faces.</li> -</ul> - -<p> - The initial value of <fun><function id='74'>glCullFace</function></fun> is <var>GL_BACK</var>. We can also tell OpenGL we'd rather prefer clockwise faces as the front-faces instead of counter-clockwise faces via <fun><function id='75'>glFrontFace</function></fun>: -</p> - -<pre><code> -<function id='75'>glFrontFace</function>(GL_CCW); -</code></pre> - -<p> - The default value is <var>GL_CCW</var> that stands for counter-clockwise ordering with the other option being <var>GL_CW</var> which (obviously) stands for clockwise ordering. -</p> - -<p> - As a simple test we could reverse the winding order by telling OpenGL that the front-faces are now determined by a clockwise ordering instead of a counter-clockwise ordering: -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_CULL_FACE); -<function id='74'>glCullFace</function>(GL_BACK); -<function id='75'>glFrontFace</function>(GL_CW); -</code></pre> - -<p> - The result is that only the back faces are rendered: -</p> - -<img src="/img/advanced/faceculling_reverse.png" class="clean" alt="Image of faceculling with clockwise ordering, only culling the front faces"/> - -<p> - Note that you can create the same effect by culling front faces with the default counter-clockwise winding order: -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_CULL_FACE); -<function id='74'>glCullFace</function>(GL_FRONT); -</code></pre> - -<p> - As you can see, face culling is a great tool for increasing performance of your OpenGL applications with minimal effort; especially as all 3D applications export models with consistent winding orders (CCW by default). You do have to keep track of the objects that will actually benefit from face culling and which objects shouldn't be culled at all. -</p> - -<h2>Exercises</h2> -<ul> - <li>Can you re-define the vertex data by specifying each triangle in clockwise order and then render the scene with clockwise triangles set as the front faces: <a href="/code_viewer.php?code=advanced/faceculling-exercise1" target="_blank">solution</a></li> -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-OpenGL/Framebuffers.html b/translation/Advanced-OpenGL/Framebuffers.html @@ -1,850 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Framebuffers</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Framebuffers</h1> -<h1 id="content-url" style='display:none;'>Advanced-OpenGL/Framebuffers</h1> -<p> - 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. -</p> - -<p> - 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. -</p> - -<p> - 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. -</p> - -<h2>Creating a framebuffer</h2> -<p> - 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>: -</p> - -<pre class="cpp"><code> -unsigned int fbo; -<function id='76'>glGenFramebuffers</function>(1, &fbo); -</code></pre> - -<p> - 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>: -</p> - -<pre><code> -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, fbo); -</code></pre> - -<p> - 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>. -</p> - -<p> - 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: -</p> - -<ul> - <li>We have to attach at least one buffer (color, depth or stencil buffer).</li> - <li>There should be at least one color attachment.</li> - <li>All attachments should be complete as well (reserved memory).</li> - <li>Each buffer should have the same number of samples.</li> -</ul> - -<p> - 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. -</p> - -<p> - 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: -</p> - -<pre><code> -if(<function id='79'>glCheckFramebufferStatus</function>(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) - // execute victory dance -</code></pre> - -<p> - 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>: -</p> - -<pre class="cpp"><code> -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -</code></pre> - -<p> - When we're done with all framebuffer operations, do not forget to delete the framebuffer object: -</p> - -<pre class="cpp"><code> -<function id='80'>glDeleteFramebuffers</function>(1, &fbo); -</code></pre> - -<p> - 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. -</p> - -<h3>Texture attachments</h3> -<p> - 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. -</p> - -<p> - Creating a texture for a framebuffer is roughly the same as creating a normal texture: -</p> - -<pre class="cpp"><code> -unsigned int texture; -<function id='50'>glGenTextures</function>(1, &texture); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, texture); - -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); - -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -</code></pre> - -<p> - 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. -</p> - -<note> - 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. -</note> - -<p> - Now that we've created a texture, the last thing we need to do is actually attach it to the framebuffer: -</p> - -<pre class="cpp"><code> -<function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); -</code></pre> - -<p> - The <fun><function id='81'>glFrameBufferTexture2D</function></fun> function has the following parameters: -</p> - -<ul> - <li><code>target</code>: the framebuffer type we're targeting (draw, read or both).</li> - <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> - <li><code>textarget</code>: the type of the texture you want to attach.</li> - <li><code>texture</code>: the actual texture to attach.</li> - <li><code>level</code>: the mipmap level. We keep this at <code>0</code>.</li> -</ul> - -<p> - 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>. -</p> - -<p> - 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: -</p> - - -<pre class="cpp"><code> -<function id='52'>glTexImage2D</function>( - GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0, - GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL -); - -<function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0); -</code></pre> - -<h3>Renderbuffer object attachments</h3> -<p> - <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. -</p> - -<p> - 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. -</p> - -<p> - 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. -</p> - -<p> - Creating a renderbuffer object looks similar to the framebuffer's code: -</p> - -<pre class="cpp"><code> -unsigned int rbo; -<function id='82'>glGenRenderbuffers</function>(1, &rbo); -</code></pre> - -<p> - And similarly we want to bind the renderbuffer object so all subsequent renderbuffer operations affect the current <var>rbo</var>: -</p> - -<pre><code> -<function id='83'>glBindRenderbuffer</function>(GL_RENDERBUFFER, rbo); -</code></pre> - -<p> - 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. -</p> - -<p> - Creating a depth and stencil renderbuffer object is done by calling the <fun><function id='88'>glRenderbufferStorage</function></fun> function: -</p> - -<pre class="cpp"><code> -<function id='88'>glRenderbufferStorage</function>(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600); -</code></pre> - -<p> - 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. -</p> - -<p> - The last thing left to do is to actually attach the renderbuffer object: -</p> - -<pre><code> -<function id='89'>glFramebufferRenderbuffer</function>(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); -</code></pre> - -<p> - 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. -</p> - -<h2>Rendering to a texture</h2> -<p> - 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. -</p> - -<p> - First thing to do is to create an actual framebuffer object and bind it, this is all relatively straightforward: -</p> - -<pre class="cpp"><code> -unsigned int framebuffer; -<function id='76'>glGenFramebuffers</function>(1, &framebuffer); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, framebuffer); -</code></pre> - -<p> - 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: -</p> - -<pre><code> -// generate texture -unsigned int texColorBuffer; -<function id='50'>glGenTextures</function>(1, &texColorBuffer); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, texColorBuffer); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, 0); - -// attach it to currently bound framebuffer object -<function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0); -</code></pre> - -<p> - 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. -</p> - -<p> - 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: -</p> - -<pre class="cpp"><code> -unsigned int rbo; -<function id='82'>glGenRenderbuffers</function>(1, &rbo); -<function id='83'>glBindRenderbuffer</function>(GL_RENDERBUFFER, rbo); -<function id='88'>glRenderbufferStorage</function>(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600); -<function id='83'>glBindRenderbuffer</function>(GL_RENDERBUFFER, 0); -</code></pre> - -<p> - Once we've allocated enough memory for the renderbuffer object we can unbind the renderbuffer. -</p> - -<p> - 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: -</p> - -<pre><code> -<function id='89'>glFramebufferRenderbuffer</function>(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); -</code></pre> - -<p> - Then we want to check if the framebuffer is complete and if it's not, we print an error message. -</p> - -<pre class="cpp"><code> -if(<function id='79'>glCheckFramebufferStatus</function>(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl; -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -</code></pre> - -<p> - Be sure to unbind the framebuffer to make sure we're not accidentally rendering to the wrong framebuffer. -</p> - -<p> - 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. -</p> - -<p> - So, to draw the scene to a single texture we'll have to take the following steps: -</p> - -<ol> - <li>Render the scene as usual with the new framebuffer bound as the active framebuffer.</li> - <li>Bind to the default framebuffer.</li> - <li>Draw a quad that spans the entire screen with the new framebuffer's color buffer as its texture.</li> -</ol> - -<p> - 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. -</p> - -<p> - 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: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec2 aPos; -layout (location = 1) in vec2 aTexCoords; - -out vec2 TexCoords; - -void main() -{ - gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); - TexCoords = aTexCoords; -} -</code></pre> - -<p> - Nothing too fancy. The fragment shader is even more basic since the only thing we have to do is sample from a texture: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec2 TexCoords; - -uniform sampler2D screenTexture; - -void main() -{ - FragColor = texture(screenTexture, TexCoords); -} -</code></pre> - -<p> - 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: -</p> - -<pre><code> -// first pass -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, framebuffer); -<function id='13'><function id='10'>glClear</function>Color</function>(0.1f, 0.1f, 0.1f, 1.0f); -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // we're not using the stencil buffer now -<function id='60'>glEnable</function>(GL_DEPTH_TEST); -DrawScene(); - -// second pass -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); // back to default -<function id='13'><function id='10'>glClear</function>Color</function>(1.0f, 1.0f, 1.0f, 1.0f); -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT); - -screenShader.use(); -<function id='27'>glBindVertexArray</function>(quadVAO); -glDisable(GL_DEPTH_TEST); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, textureColorbuffer); -<function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); -</code></pre> - -<p> - 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. -</p> - -<p> - 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: -</p> - -<img src="/img/advanced/framebuffers_screen_texture.png" alt="An image of a 3D scene in OpenGL rendered to a texture via framebuffers"/> - -<p> - 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. -</p> - -<p> - 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>. -</p> - -<p> - 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. -</p> - -<h1>Post-processing</h1> -<p> - 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. -</p> - -<p> - Let's start with one of the simplest post-processing effects. -</p> - -<h3>Inversion</h3> -<p> - 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>: -</p> - -<pre><code> -void main() -{ - FragColor = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0); -} -</code></pre> - -<p> - While inversion is a relatively simple post-processing effect it already creates funky results: -</p> - -<img src="/img/advanced/framebuffers_inverse.png" class="clean" alt="Post-processing image of a 3D scene in OpenGL with inversed colors"/> - -<p> - The entire scene now has all its colors inversed with a single line of code in the fragment shader. Pretty cool huh? -</p> - -<h3>Grayscale</h3> -<p> - 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: -</p> - -<pre><code> -void main() -{ - FragColor = texture(screenTexture, TexCoords); - float average = (FragColor.r + FragColor.g + FragColor.b) / 3.0; - FragColor = vec4(average, average, average, 1.0); -} -</code></pre> - -<p> - 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: -</p> - -<pre><code> -void main() -{ - FragColor = texture(screenTexture, TexCoords); - float average = 0.2126 * FragColor.r + 0.7152 * FragColor.g + 0.0722 * FragColor.b; - FragColor = vec4(average, average, average, 1.0); -} -</code></pre> - -<img src="/img/advanced/framebuffers_grayscale.png" class="clean" alt="Post-processing image of a 3D scene in OpenGL with grayscale colors"/> - -<p> - You probably won't notice the difference right away, but with more complicated scenes, such a weighted grayscaling effect tends to be more realistic. -</p> - -<h2>Kernel effects</h2> -<p> - 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. -</p> - -<p> - 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: -</p> - -\[\begin{bmatrix}2 & 2 & 2 \\ 2 & -15 & 2 \\ 2 & 2 & 2 \end{bmatrix}\] - -<p> - 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. -</p> - -<note> - 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. -</note> - -<p> - 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): -</p> - -<pre><code> -const float offset = 1.0 / 300.0; - -void main() -{ - vec2 offsets[9] = vec2[]( - vec2(-offset, offset), // top-left - vec2( 0.0f, offset), // top-center - vec2( offset, offset), // top-right - vec2(-offset, 0.0f), // center-left - vec2( 0.0f, 0.0f), // center-center - vec2( offset, 0.0f), // center-right - vec2(-offset, -offset), // bottom-left - vec2( 0.0f, -offset), // bottom-center - vec2( offset, -offset) // bottom-right - ); - - float kernel[9] = float[]( - -1, -1, -1, - -1, 9, -1, - -1, -1, -1 - ); - - vec3 sampleTex[9]; - for(int i = 0; i < 9; i++) - { - sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i])); - } - vec3 col = vec3(0.0); - for(int i = 0; i < 9; i++) - col += sampleTex[i] * kernel[i]; - - FragColor = vec4(col, 1.0); -} -</code></pre> - -<p> - 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. -</p> - -<p> - This particular sharpen kernel looks like this: -</p> - -<img src="/img/advanced/framebuffers_sharpen.png" class="clean" alt="Post-processing image of a 3D scene in OpenGL with blurred colors"/> - -<p> - This could be the base of some interesting effects where your player may be on a narcotic adventure. -</p> - - -<h3>Blur</h3> -<p> - A kernel that creates a <def>blur</def> effect is defined as follows: -</p> - -\[\begin{bmatrix} 1 & 2 & 1 \\ 2 & 4 & 2 \\ 1 & 2 & 1 \end{bmatrix} / 16\] - -<p> - 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: -</p> - -<pre><code> -float kernel[9] = float[]( - 1.0 / 16, 2.0 / 16, 1.0 / 16, - 2.0 / 16, 4.0 / 16, 2.0 / 16, - 1.0 / 16, 2.0 / 16, 1.0 / 16 -); -</code></pre> - -<p> - By only changing the kernel array in the fragment shader we can completely change the post-processing effect. It now looks something like this: -</p> - -<img src="/img/advanced/framebuffers_blur.png" class="clean" alt="Post-processing image of a 3D scene in OpenGL with sharpened colors"/> - - -<p> - 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. -</p> - -<p> - 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. -</p> - -<h3>Edge detection</h3> -<p> - Below you can find an <def>edge-detection</def> kernel that is similar to the sharpen kernel: -</p> - -\[\begin{bmatrix} 1 & 1 & 1 \\ 1 & -8 & 1 \\ 1 & 1 & 1 \end{bmatrix}\] - -<p> - This kernel highlights all edges and darkens the rest, which is pretty useful when we only care about edges in an image. -</p> - -<img src="/img/advanced/framebuffers_edge_detection.png" class="clean" alt="Post-processing image of a 3D scene in OpenGL with edge detection filter"/> - -<p> - 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. -</p> - - -<h2>Exercises</h2> -<ul> - <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> - <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> -</ul> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-OpenGL/Geometry-Shader.html b/translation/Advanced-OpenGL/Geometry-Shader.html @@ -1,899 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Geometry Shader</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Geometry Shader</h1> -<h1 id="content-url" style='display:none;'>Advanced-OpenGL/Geometry-Shader</h1> -<p> - Between the vertex and the fragment shader there is an optional shader stage called the <def>geometry shader</def>. A geometry shader takes as input a set of vertices that form a single primitive e.g. a point or a triangle. The geometry shader can then transform these vertices as it sees fit before sending them to the next shader stage. What makes the geometry shader interesting is that it is able to convert the original primitive (set of vertices) to completely different primitives, possibly generating more vertices than were initially given. -</p> - -<p> - We're going to throw you right into the deep by showing you an example of a geometry shader: -</p> - -<pre><code> -#version 330 core -layout (points) in; -layout (line_strip, max_vertices = 2) out; - -void main() { - gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0); - EmitVertex(); - - gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0); - EmitVertex(); - - EndPrimitive(); -} -</code></pre> - -<p> - At the start of a geometry shader we need to declare the type of primitive input we're receiving from the vertex shader. We do this by declaring a layout specifier in front of the <fun>in</fun> keyword. This input layout qualifier can take any of the following primitive values: -</p> - -<ul> - <li><code>points</code>: when drawing <var>GL_POINTS</var> primitives (<code>1</code>).</li> - <li><code>lines</code>: when drawing <var>GL_LINES</var> or <var>GL_LINE_STRIP</var> (<code>2</code>).</li> - <li><code>lines_adjacency</code>: <var>GL_LINES_ADJACENCY</var> or <var>GL_LINE_STRIP_ADJACENCY</var> (<code>4</code>).</li> - <li><code>triangles</code>: <var>GL_TRIANGLES</var>, <var>GL_TRIANGLE_STRIP</var> or <var>GL_TRIANGLE_FAN</var> (<code>3</code>).</li> - <li><code>triangles_adjacency </code>: <var>GL_TRIANGLES_ADJACENCY</var> or <var>GL_TRIANGLE_STRIP_ADJACENCY </var> (<code>6</code>).</li> -</ul> - -<p> - These are almost all the rendering primitives we're able to give to rendering calls like <fun><function id='1'>glDrawArrays</function></fun>. If we'd chosen to draw vertices as <var>GL_TRIANGLES</var> we should set the input qualifier to <code>triangles</code>. The number within the parenthesis represents the minimal number of vertices a single primitive contains. -</p> - -<p> - We also need to specify a primitive type that the geometry shader will output and we do this via a layout specifier in front of the <fun>out</fun> keyword. Like the input layout qualifier, the output layout qualifier can take several primitive values: -</p> - -<ul> - <li><code>points</code></li> - <li><code>line_strip</code></li> - <li><code>triangle_strip</code></li> -</ul> - -<p> - With just these 3 output specifiers we can create almost any shape we want from the input primitives. To generate a single triangle for example we'd specify <code>triangle_strip</code> as the output and output 3 vertices. -</p> - -<p> - The geometry shader also expects us to set a maximum number of vertices it outputs (if you exceed this number, OpenGL won't draw the <em>extra</em> vertices) which we can also do within the layout qualifier of the <fun>out</fun> keyword. In this particular case we're going to output a <code>line_strip</code> with a maximum number of 2 vertices. -</p> - -<note> - In case you're wondering what a line strip is: a line strip binds together a set of points to form one continuous line between them with a minimum of 2 points. Each extra point results in a new line between the new point and the previous point as you can see in the following image with 5 point vertices: - -<img src="/img/advanced/geometry_shader_line_strip.png" class="clean" alt="Image of line_strip primitive in geometry shader"/> -</note> - -<p> - To generate meaningful results we need some way to retrieve the output from the previous shader stage. GLSL gives us a <def>built-in</def> variable called <fun>gl_in</fun> that internally (probably) looks something like this: -</p> - -<pre><code> -in gl_Vertex -{ - vec4 gl_Position; - float gl_PointSize; - float gl_ClipDistance[]; -} gl_in[]; -</code></pre> - -<p> - Here it is declared as an <def>interface block</def> (as discussed in the <a href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL" target="_blank">previous</a> chapter) that contains a few interesting variables of which the most interesting one is <var>gl_Position</var> that contains the vector we set as the vertex shader's output. -</p> - -<p> - Note that it is declared as an array, because most render primitives contain more than 1 vertex. The geometry shader receives <strong>all</strong> vertices of a primitive as its input. -</p> - -<p> - Using the vertex data from the vertex shader stage we can generate new data with 2 geometry shader functions called <fun>EmitVertex</fun> and <fun>EndPrimitive</fun>. The geometry shader expects you to generate/output at least one of the primitives you specified as output. In our case we want to at least generate one line strip primitive. -</p> - -<pre><code> -#version 330 core -layout (points) in; -layout (line_strip, max_vertices = 2) out; - -void main() { - gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0); - EmitVertex(); - - gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0); - EmitVertex(); - - EndPrimitive(); -} -</code></pre> - -<p> - Each time we call <fun>EmitVertex</fun>, the vector currently set to <var>gl_Position</var> is added to the output primitive. Whenever <fun>EndPrimitive</fun> is called, all emitted vertices for this primitive are combined into the specified output render primitive. By repeatedly calling <fun>EndPrimitive</fun>, after one or more <fun>EmitVertex</fun> calls, multiple primitives can be generated. This particular case emits two vertices that were translated by a small offset from the original vertex position and then calls <fun>EndPrimitive</fun>, combining the two vertices into a single line strip of 2 vertices. -</p> - -<p> - Now that you (sort of) know how geometry shaders work you can probably guess what this geometry shader does. This geometry shader takes a point primitive as its input and creates a horizontal line primitive with the input point at its center. If we were to render this it looks something like this: -</p> - -<img src="/img/advanced/geometry_shader_lines.png" class="clean" alt="Geometry shader drawing lines out of points in OpenGL"/> - -<p> - Not very impressive yet, but it's interesting to consider that this output was generated using just the following render call: -</p> - -<pre class="cpp"><code> -<function id='1'>glDrawArrays</function>(GL_POINTS, 0, 4); -</code></pre> - -<p> - While this is a relatively simple example, it does show you how we can use geometry shaders to (dynamically) generate new shapes on the fly. Later in this chapter we'll discuss a few interesting effects that we can create using geometry shaders, but for now we're going to start with a simple example. -</p> - -<h2>Using geometry shaders</h2> -<p> - To demonstrate the use of a geometry shader we're going to render a really simple scene where we draw 4 points on the z-plane in normalized device coordinates. The coordinates of the points are: -</p> - -<pre><code> -float points[] = { - -0.5f, 0.5f, // top-left - 0.5f, 0.5f, // top-right - 0.5f, -0.5f, // bottom-right - -0.5f, -0.5f // bottom-left -}; -</code></pre> - -<p> - The vertex shader needs to draw the points on the z-plane so we'll create a basic vertex shader: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec2 aPos; - -void main() -{ - gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); -} -</code></pre> - -<p> - And we'll output the color green for all points which we code directly in the fragment shader: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -void main() -{ - FragColor = vec4(0.0, 1.0, 0.0, 1.0); -} -</code></pre> - -<p> - Generate a VAO and a VBO for the points' vertex data and then draw them via <fun><function id='1'>glDrawArrays</function></fun>: -</p> - -<pre class="cpp"><code> -shader.use(); -<function id='27'>glBindVertexArray</function>(VAO); -<function id='1'>glDrawArrays</function>(GL_POINTS, 0, 4); -</code></pre> - -<p> - The result is a dark scene with 4 (difficult to see) green points: -</p> - -<img src="/img/advanced/geometry_shader_points.png" class="clean" alt="4 Points drawn using OpenGL"/> - -<p> - But didn't we already learn to do all this? Yes, and now we're going to spice this little scene up by adding geometry shader magic to the scene. -</p> - -<p> - For learning purposes we're first going to create what is called a <def>pass-through</def> geometry shader that takes a point primitive as its input and <em>passes</em> it to the next shader unmodified: -</p> - -<pre><code> -#version 330 core -layout (points) in; -layout (points, max_vertices = 1) out; - -void main() { - gl_Position = gl_in[0].gl_Position; - EmitVertex(); - EndPrimitive(); -} -</code></pre> - -<p> - By now this geometry shader should be fairly easy to understand. It simply emits the unmodified vertex position it received as input and generates a point primitive. -</p> - -<p> - A geometry shader needs to be compiled and linked to a program just like the vertex and fragment shader, but this time we'll create the shader using <var>GL_GEOMETRY_SHADER</var> as the shader type: -</p> - -<pre class="cpp"><code> -geometryShader = <function id='37'>glCreateShader</function>(GL_GEOMETRY_SHADER); -<function id='42'>glShaderSource</function>(geometryShader, 1, &gShaderCode, NULL); -<function id='38'>glCompileShader</function>(geometryShader); -[...] -<function id='34'>glAttachShader</function>(program, geometryShader); -<function id='35'>glLinkProgram</function>(program); -</code></pre> - -<p> - The shader compilation code is the same as the vertex and fragment shaders. Be sure to check for compile or linking errors! -</p> - -<p> - If you'd now compile and run you should be looking at a result that looks a bit like this: -</p> - -<img src="/img/advanced/geometry_shader_points.png" class="clean" alt="4 Points drawn using OpenGL (with geometry shader this time!)"/> - -<p> - It's exactly the same as without the geometry shader! It's a bit dull, I'll admit that, but the fact that we were still able to draw the points means that the geometry shader works, so now it's time for the more funky stuff! -</p> - -<h2>Let's build houses</h2> -<p> - Drawing points and lines isn't <strong>that</strong> interesting so we're going to get a little creative by using the geometry shader to draw a house for us at the location of each point. We can accomplish this by setting the output of the geometry shader to <def>triangle_strip</def> and draw a total of three triangles: two for the square house and one for the roof. -</p> - -<p> - A triangle strip in OpenGL is a more efficient way to draw triangles with fewer vertices. After the first triangle is drawn, each subsequent vertex generates another triangle next to the first triangle: every 3 adjacent vertices will form a triangle. If we have a total of 6 vertices that form a triangle strip we'd get the following triangles: (1,2,3), (2,3,4), (3,4,5) and (4,5,6); forming a total of 4 triangles. A triangle strip needs at least 3 vertices and will generate N-2 triangles; with 6 vertices we created 6-2 = 4 triangles. The following image illustrates this: -</p> - -<img src="/img/advanced/geometry_shader_triangle_strip.png" class="clean" alt="Image of a triangle strip with their index order in OpenGL"/> - -<p> - Using a triangle strip as the output of the geometry shader we can easily create the house shape we're after by generating 3 adjacent triangles in the correct order. The following image shows in what order we need to draw what vertices to get the triangles we need with the blue dot being the input point: -</p> - -<img src="/img/advanced/geometry_shader_house.png" class="clean" alt="How a house figure should be drawn from a single point using geometry shaders"/> - -<p> - This translates to the following geometry shader: -</p> - -<pre><code> -#version 330 core -layout (points) in; -layout (triangle_strip, max_vertices = 5) out; - -void build_house(vec4 position) -{ - gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0); // 1:bottom-left - EmitVertex(); - gl_Position = position + vec4( 0.2, -0.2, 0.0, 0.0); // 2:bottom-right - EmitVertex(); - gl_Position = position + vec4(-0.2, 0.2, 0.0, 0.0); // 3:top-left - EmitVertex(); - gl_Position = position + vec4( 0.2, 0.2, 0.0, 0.0); // 4:top-right - EmitVertex(); - gl_Position = position + vec4( 0.0, 0.4, 0.0, 0.0); // 5:top - EmitVertex(); - EndPrimitive(); -} - -void main() { - build_house(gl_in[0].gl_Position); -} -</code></pre> - -<p> - This geometry shader generates 5 vertices, with each vertex being the point's position plus an offset to form one large triangle strip. The resulting primitive is then rasterized and the fragment shader runs on the entire triangle strip, resulting in a green house for each point we've rendered: -</p> - -<img src="/img/advanced/geometry_shader_houses.png" class="clean" alt="Houses drawn with points using geometry shader in OpenGL"/> - -<p> - You can see that each house indeed consists of 3 triangles - all drawn using a single point in space. The green houses do look a bit boring though, so let's liven it up a bit by giving each house a unique color. To do this we're going to add an extra vertex attribute in the vertex shader with color information per vertex and direct it to the geometry shader that further forwards it to the fragment shader. -</p> - -<p> - The updated vertex data is given below: -</p> - -<pre><code> -float points[] = { - -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, // top-left - 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, // top-right - 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // bottom-right - -0.5f, -0.5f, 1.0f, 1.0f, 0.0f // bottom-left -}; -</code></pre> - -<p> - Then we update the vertex shader to forward the color attribute to the geometry shader using an interface block: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec2 aPos; -layout (location = 1) in vec3 aColor; - -out VS_OUT { - vec3 color; -} vs_out; - -void main() -{ - gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); - vs_out.color = aColor; -} -</code></pre> - -<p> - Then we also need to declare the same interface block (with a different interface name) in the geometry shader: -</p> - -<pre><code> -in VS_OUT { - vec3 color; -} gs_in[]; -</code></pre> - -<p> - Because the geometry shader acts on a set of vertices as its input, its input data from the vertex shader is always represented as arrays of vertex data even though we only have a single vertex right now. -</p> - -<note> - We don't necessarily have to use interface blocks to transfer data to the geometry shader. We could have also written it as: -<pre><code> -in vec3 outColor[]; -</code></pre> - This works if the vertex shader forwarded the color vector as <code>out</code> <code>vec3</code> <code>outColor</code>. However, interface blocks are easier to work with in shaders like the geometry shader. In practice, geometry shader inputs can get quite large and grouping them in one large interface block array makes a lot more sense. -</note> - -<p> - We should also declare an output color vector for the next fragment shader stage: -</p> - -<pre><code> -out vec3 fColor; -</code></pre> - -<p> - Because the fragment shader expects only a single (interpolated) color it doesn't make sense to forward multiple colors. The <var>fColor</var> vector is thus not an array, but a single vector. When emitting a vertex, that vertex will store the last stored value in <var>fColor</var> as that vertex's output value. For the houses, we can fill <var>fColor</var> once with the color from the vertex shader before the first vertex is emitted to color the entire house: -</p> - -<pre><code> -fColor = gs_in[0].color; // gs_in[0] since there's only one input vertex -gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0); // 1:bottom-left -EmitVertex(); -gl_Position = position + vec4( 0.2, -0.2, 0.0, 0.0); // 2:bottom-right -EmitVertex(); -gl_Position = position + vec4(-0.2, 0.2, 0.0, 0.0); // 3:top-left -EmitVertex(); -gl_Position = position + vec4( 0.2, 0.2, 0.0, 0.0); // 4:top-right -EmitVertex(); -gl_Position = position + vec4( 0.0, 0.4, 0.0, 0.0); // 5:top -EmitVertex(); -EndPrimitive(); -</code></pre> - -<p> - All the emitted vertices will have the last stored value in <var>fColor</var> embedded into their data, which is equal to the input vertex's color as we defined in its attributes. All the houses will now have a color of their own: -</p> - -<img src="/img/advanced/geometry_shader_houses_colored.png" class="clean" alt="Colored houses, generating using points with geometry shaders in OpenGL"/> - -<p> - Just for fun we could also pretend it's winter and give their roofs a little snow by giving the last vertex a color of its own: -</p> - -<pre><code> -fColor = gs_in[0].color; -gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0); // 1:bottom-left -EmitVertex(); -gl_Position = position + vec4( 0.2, -0.2, 0.0, 0.0); // 2:bottom-right -EmitVertex(); -gl_Position = position + vec4(-0.2, 0.2, 0.0, 0.0); // 3:top-left -EmitVertex(); -gl_Position = position + vec4( 0.2, 0.2, 0.0, 0.0); // 4:top-right -EmitVertex(); -gl_Position = position + vec4( 0.0, 0.4, 0.0, 0.0); // 5:top -fColor = vec3(1.0, 1.0, 1.0); -EmitVertex(); -EndPrimitive(); -</code></pre> - -<p> - The result now looks something like this: -</p> - -<img src="/img/advanced/geometry_shader_houses_snow.png" class="clean" alt="Snow-colored houses, generating using points with geometry shaders in OpenGL"/> - -<p> - You can compare your source code with the OpenGL code <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/9.1.geometry_shader_houses/geometry_shader_houses.cpp" target="_blank">here</a>. -</p> - -<p> - You can see that with geometry shaders you can get pretty creative, even with the simplest primitives. Because the shapes are generated dynamically on the ultra-fast hardware of your GPU this can be a lot more powerful than defining these shapes yourself within vertex buffers. Geometry shaders are a great tool for simple (often-repeating) shapes, like cubes in a voxel world or grass leaves on a large outdoor field. -</p> - -<h1>Exploding objects</h1> -<p> - While drawing houses is fun and all, it's not something we're going to use that much. That's why we're now going to take it up one notch and explode objects! That is something we're also probably not going to use that much either, but it's definitely fun to do! -</p> - -<p> - When we say <em>exploding</em> an object we're not actually going to blow up our precious bundled sets of vertices, but we're going to move each triangle along the direction of their normal vector over a small period of time. The effect is that the entire object's triangles seem to <em>explode</em>. The effect of exploding triangles on the backpack model looks a bit like this: -</p> - -<img src="/img/advanced/geometry_shader_explosion.png" class="clean" alt="Explosion effect with geometry shaders in OpenGL"/> - -<p> - The great thing about such a geometry shader effect is that it works on all objects, regardless of their complexity. -</p> - -<p> - Because we're going to translate each vertex into the direction of the triangle's normal vector we first need to calculate this normal vector. What we need to do is calculate a vector that is perpendicular to the surface of a triangle, using just the 3 vertices we have access to. You may remember from the <a href="https://learnopengl.com/Getting-started/Transformations" target="_blank">transformations</a> chapter that we can retrieve a vector perpendicular to two other vectors using the <def>cross product</def>. If we were to retrieve two vectors <var>a</var> and <var>b</var> that are parallel to the surface of a triangle we can retrieve its normal vector by doing a cross product on those vectors. The following geometry shader function does exactly this to retrieve the normal vector using 3 input vertex coordinates: -</p> - -<pre><code> -vec3 GetNormal() -{ - vec3 a = vec3(gl_in[0].gl_Position) - vec3(gl_in[1].gl_Position); - vec3 b = vec3(gl_in[2].gl_Position) - vec3(gl_in[1].gl_Position); - return normalize(cross(a, b)); -} -</code></pre> - -<p> - Here we retrieve two vectors <var>a</var> and <var>b</var> that are parallel to the surface of the triangle using vector subtraction. Subtracting two vectors from each other results in a vector that is the difference of the two vectors. Since all 3 points lie on the triangle plane, subtracting any of its vectors from each other results in a vector parallel to the plane. Do note that if we switched <var>a</var> and <var>b</var> in the <fun>cross</fun> function we'd get a normal vector that points in the opposite direction - order is important here! -</p> - -<p> - Now that we know how to calculate a normal vector we can create an <fun>explode</fun> function that takes this normal vector along with a vertex position vector. The function returns a new vector that translates the position vector along the direction of the normal vector: -</p> - -<pre><code> -vec4 explode(vec4 position, vec3 normal) -{ - float magnitude = 2.0; - vec3 direction = normal * ((sin(time) + 1.0) / 2.0) * magnitude; - return position + vec4(direction, 0.0); -} -</code></pre> - -<p> - The function itself shouldn't be too complicated. The <fun>sin</fun> function receives a <var>time</var> uniform variable as its argument that, based on the time, returns a value between <code>-1.0</code> and <code>1.0</code>. Because we don't want to <em>implode</em> the object we transform the sin value to the <code>[0,1]</code> range. The resulting value is then used to scale the <var>normal</var> vector and the resulting <var>direction</var> vector is added to the position vector. -</p> - -<p> - The complete geometry shader for the <def>explode</def> effect, while drawing a model loaded using our <a href="https://learnopengl.com/Model-Loading/Assimp" target="_blank">model loader</a>, looks a bit like this: -</p> - -<pre><code> -#version 330 core -layout (triangles) in; -layout (triangle_strip, max_vertices = 3) out; - -in VS_OUT { - vec2 texCoords; -} gs_in[]; - -out vec2 TexCoords; - -uniform float time; - -vec4 explode(vec4 position, vec3 normal) { ... } - -vec3 GetNormal() { ... } - -void main() { - vec3 normal = GetNormal(); - - gl_Position = explode(gl_in[0].gl_Position, normal); - TexCoords = gs_in[0].texCoords; - EmitVertex(); - gl_Position = explode(gl_in[1].gl_Position, normal); - TexCoords = gs_in[1].texCoords; - EmitVertex(); - gl_Position = explode(gl_in[2].gl_Position, normal); - TexCoords = gs_in[2].texCoords; - EmitVertex(); - EndPrimitive(); -} -</code></pre> - -<p> - Note that we're also outputting the appropriate texture coordinates before emitting a vertex. -</p> - -<p> - Also don't forget to actually set the <var>time</var> uniform in your OpenGL code: -</p> - -<pre><code> -shader.setFloat("time", <function id='47'>glfwGetTime</function>()); -</code></pre> - -<p> - The result is a 3D model that seems to continually explode its vertices over time after which it returns to normal again. Although not exactly super useful, it does show you a more advanced use of the geometry shader. You can compare your source code with the complete source code <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/9.2.geometry_shader_exploding/geometry_shader_exploding.cpp" target="_blank">here</a>. -</p> - -<h1>Visualizing normal vectors</h1> -<p> - To shake things up we're going to now discuss an example of using the geometry shader that is actually useful: visualizing the normal vectors of any object. When programming lighting shaders you will eventually run into weird visual outputs of which the cause is hard to determine. A common cause of lighting errors is incorrect normal vectors. Either caused by incorrectly loading vertex data, improperly specifying them as vertex attributes, or by incorrectly managing them in the shaders. What we want is some way to detect if the normal vectors we supplied are correct. A great way to determine if your normal vectors are correct is by visualizing them, and it just so happens that the geometry shader is an extremely useful tool for this purpose. -</p> - -<p> - The idea is as follows: we first draw the scene as normal without a geometry shader and then we draw the scene a second time, but this time only displaying normal vectors that we generate via a geometry shader. The geometry shader takes as input a triangle primitive and generates 3 lines from them in the directions of their normal - one normal vector for each vertex. In code it'll look something like this: -</p> - -<pre><code> -shader.use(); -DrawScene(); -normalDisplayShader.use(); -DrawScene(); -</code></pre> - -<p> - This time we're creating a geometry shader that uses the vertex normals supplied by the model instead of generating it ourself. To accommodate for scaling and rotations (due to the view and model matrix) we'll transform the normals with a normal matrix. The geometry shader receives its position vectors as view-space coordinates so we should also transform the normal vectors to the same space. This can all be done in the vertex shader: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec3 aNormal; - -out VS_OUT { - vec3 normal; -} vs_out; - -uniform mat4 view; -uniform mat4 model; - -void main() -{ - gl_Position = view * model * vec4(aPos, 1.0); - mat3 normalMatrix = mat3(transpose(inverse(view * model))); - vs_out.normal = normalize(vec3(vec4(normalMatrix * aNormal, 0.0))); -} -</code></pre> - -<p> - The transformed view-space normal vector is then passed to the next shader stage via an interface block. The geometry shader then takes each vertex (with a position and a normal vector) and draws a normal vector from each position vector: -</p> - -<pre><code> -#version 330 core -layout (triangles) in; -layout (line_strip, max_vertices = 6) out; - -in VS_OUT { - vec3 normal; -} gs_in[]; - -const float MAGNITUDE = 0.4; - -uniform mat4 projection; - -void GenerateLine(int index) -{ - gl_Position = projection * gl_in[index].gl_Position; - EmitVertex(); - gl_Position = projection * (gl_in[index].gl_Position + - vec4(gs_in[index].normal, 0.0) * MAGNITUDE); - EmitVertex(); - EndPrimitive(); -} - -void main() -{ - GenerateLine(0); // first vertex normal - GenerateLine(1); // second vertex normal - GenerateLine(2); // third vertex normal -} -</code></pre> - -<p> - The contents of geometry shaders like these should be self-explanatory by now. Note that we're multiplying the normal vector by a <var>MAGNITUDE</var> vector to restrain the size of the displayed normal vectors (otherwise they'd be a bit too large). -</p> - -<p> - Since visualizing normals are mostly used for debugging purposes we can just display them as mono-colored lines (or super-fancy lines if you feel like it) with the help of the fragment shader: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -void main() -{ - FragColor = vec4(1.0, 1.0, 0.0, 1.0); -} -</code></pre> - -<p> - Now rendering your model with normal shaders first and then with the special <em>normal-visualizing</em> shader you'll see something like this: -</p> - -<img src="/img/advanced/geometry_shader_normals.png" class="clean" alt="Image of geometry shader displaying normal vectors in OpenGL"/> - -<p> - Apart from the fact that our backpack now looks a bit hairy, it gives us a really useful method for determining if the normal vectors of a model are indeed correct. You can imagine that geometry shaders like this could also be used for adding <def>fur</def> to objects. -</p> - -<p> - You can find the OpenGL's source code <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/9.3.geometry_shader_normals/normal_visualization.cpp" target="_blank">here</a>. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-OpenGL/Instancing.html b/translation/Advanced-OpenGL/Instancing.html @@ -1,714 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Instancing</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Instancing</h1> -<h1 id="content-url" style='display:none;'>Advanced-OpenGL/Instancing</h1> -<p> - Say you have a scene where you're drawing a lot of models where most of these models contain the same set of vertex data, but with different world transformations. Think of a scene filled with grass leaves: each grass leave is a small model that consists of only a few triangles. You'll probably want to draw quite a few of them and your scene may end up with thousands or maybe tens of thousands of grass leaves that you need to render each frame. Because each leaf is only a few triangles, the leaf is rendered almost instantly. However, the thousands of render calls you'll have to make will drastically reduce performance. -</p> - -<p> - If we were to actually render such a large amount of objects it will look a bit like this in code: -</p> - -<pre><code> -for(unsigned int i = 0; i < amount_of_models_to_draw; i++) -{ - DoSomePreparations(); // bind VAO, bind textures, set uniforms etc. - <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, amount_of_vertices); -} -</code></pre> - -<p> - When drawing many <def>instances</def> of your model like this you'll quickly reach a performance bottleneck because of the many draw calls. Compared to rendering the actual vertices, telling the GPU to render your vertex data with functions like <fun><function id='1'>glDrawArrays</function></fun> or <fun><function id='2'>glDrawElements</function></fun> eats up quite some performance since OpenGL must make necessary preparations before it can draw your vertex data (like telling the GPU which buffer to read data from, where to find vertex attributes and all this over the relatively slow CPU to GPU bus). So even though rendering your vertices is super fast, giving your GPU the commands to render them isn't. -</p> - -<p> - It would be much more convenient if we could send data over to the GPU once, and then tell OpenGL to draw multiple objects using this data with a single drawing call. Enter <def>instancing</def>. -</p> - -<p> - Instancing is a technique where we draw many (equal mesh data) objects at once with a single render call, saving us all the CPU -> GPU communications each time we need to render an object. To render using instancing all we need to do is change the render calls <fun><function id='1'>glDrawArrays</function></fun> and <fun><function id='2'>glDrawElements</function></fun> to <fun><function id='98'><function id='1'>glDrawArrays</function>Instanced</function></fun> and <fun><function id='99'><function id='2'>glDrawElements</function>Instanced</function></fun> respectively. These <em>instanced</em> versions of the classic rendering functions take an extra parameter called the <def>instance count</def> that sets the number of instances we want to render. We sent all the required data to the GPU once, and then tell the GPU how it should draw all these instances with a single call. The GPU then renders all these instances without having to continually communicate with the CPU. -</p> - -<p> - By itself this function is a bit useless. Rendering the same object a thousand times is of no use to us since each of the rendered objects is rendered exactly the same and thus also at the same location; we would only see one object! For this reason GLSL added another built-in variable in the vertex shader called <var>gl_InstanceID</var>. -</p> - -<p> - When drawing with one of the instanced rendering calls, <var>gl_InstanceID</var> is incremented for each instance being rendered starting from <code>0</code>. If we were to render the 43th instance for example, <var>gl_InstanceID</var> would have the value <code>42</code> in the vertex shader. Having a unique value per instance means we could now for example index into a large array of position values to position each instance at a different location in the world. -</p> - -<p> - To get a feel for instanced drawing we're going to demonstrate a simple example that renders a hundred 2D quads in normalized device coordinates with just one render call. We accomplish this by uniquely positioning each instanced quad by indexing a uniform array of <code>100</code> offset vectors. The result is a neatly organized grid of quads that fill the entire window: -</p> - -<img src="/img/advanced/instancing_quads.png" class="clean" alt="100 Quads drawn via OpenGL instancing."/> - -<p> - Each quad consists of 2 triangles with a total of 6 vertices. Each vertex contains a 2D NDC position vector and a color vector. Below is the vertex data used for this example - the triangles are small enough to properly fit the screen when there's a 100 of them: -</p> - -<pre><code> -float quadVertices[] = { - // positions // colors - -0.05f, 0.05f, 1.0f, 0.0f, 0.0f, - 0.05f, -0.05f, 0.0f, 1.0f, 0.0f, - -0.05f, -0.05f, 0.0f, 0.0f, 1.0f, - - -0.05f, 0.05f, 1.0f, 0.0f, 0.0f, - 0.05f, -0.05f, 0.0f, 1.0f, 0.0f, - 0.05f, 0.05f, 0.0f, 1.0f, 1.0f -}; -</code></pre> - -<p> - The quads are colored in the fragment shader that receives a color vector from the vertex shader and sets it as its output: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec3 fColor; - -void main() -{ - FragColor = vec4(fColor, 1.0); -} -</code></pre> - -<p> - Nothing new so far, but at the vertex shader it's starting to get interesting: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec2 aPos; -layout (location = 1) in vec3 aColor; - -out vec3 fColor; - -uniform vec2 offsets[100]; - -void main() -{ - vec2 offset = offsets[gl_InstanceID]; - gl_Position = vec4(aPos + offset, 0.0, 1.0); - fColor = aColor; -} -</code></pre> - -<p> - Here we defined a uniform array called <var>offsets</var> that contain a total of <code>100</code> offset vectors. Within the vertex shader we retrieve an offset vector for each instance by indexing the <var>offsets</var> array using <var>gl_InstanceID</var>. If we now were to draw <code>100</code> quads with instanced drawing we'd get <code>100</code> quads located at different positions. -</p> - -<p> - We do need to actually set the offset positions that we calculate in a nested for-loop before we enter the render loop: -</p> - -<pre><code> -glm::vec2 translations[100]; -int index = 0; -float offset = 0.1f; -for(int y = -10; y < 10; y += 2) -{ - for(int x = -10; x < 10; x += 2) - { - glm::vec2 translation; - translation.x = (float)x / 10.0f + offset; - translation.y = (float)y / 10.0f + offset; - translations[index++] = translation; - } -} -</code></pre> - -<p> - Here we create a set of <code>100</code> translation vectors that contains an offset vector for all positions in a 10x10 grid. In addition to generating the <var>translations</var> array, we'd also need to transfer the data to the vertex shader's uniform array: -</p> - -<pre><code> -shader.use(); -for(unsigned int i = 0; i < 100; i++) -{ - shader.setVec2(("offsets[" + std::to_string(i) + "]")), translations[i]); -} -</code></pre> - -<p> - Within this snippet of code we transform the for-loop counter <var>i</var> to a <fun>string</fun> to dynamically create a location string for querying the uniform location. For each item in the <var>offsets</var> uniform array we then set the corresponding translation vector. -</p> - -<p> - Now that all the preparations are finished we can start rendering the quads. To draw via instanced rendering we call <fun><function id='98'><function id='1'>glDrawArrays</function>Instanced</function></fun> or <fun><function id='99'><function id='2'>glDrawElements</function>Instanced</function></fun>. Since we're not using an element index buffer we're going to call the <fun><function id='1'>glDrawArrays</function></fun> version: -</p> - -<pre class="cpp"><code> -<function id='27'>glBindVertexArray</function>(quadVAO); -<function id='98'><function id='1'>glDrawArrays</function>Instanced</function>(GL_TRIANGLES, 0, 6, 100); -</code></pre> - -<p> - The parameters of <fun><function id='98'><function id='1'>glDrawArrays</function>Instanced</function></fun> are exactly the same as <fun><function id='1'>glDrawArrays</function></fun> except the last parameter that sets the number of instances we want to draw. Since we want to display <code>100</code> quads in a 10x10 grid we set it equal to <code>100</code>. Running the code should now give you the familiar image of <code>100</code> colorful quads. -</p> - -<h2>Instanced arrays</h2> -<p> - While the previous implementation works fine for this specific use case, whenever we are rendering a lot more than <code>100</code> instances (which is quite common) we will eventually hit a <a href="https://www.khronos.org/opengl/wiki/GLSL_Uniform#Implementation_limits" target="_blank">limit</a> on the amount of uniform data we can send to the shaders. One alternative option is known as <def>instanced arrays</def>. Instanced arrays are defined as a vertex attribute (allowing us to store much more data) that are updated per instance instead of per vertex. -</p> - -<p> - With vertex attributes, at the start of each run of the vertex shader, the GPU will retrieve the next set of vertex attributes that belong to the current vertex. When defining a vertex attribute as an instanced array however, the vertex shader only updates the content of the vertex attribute per instance. This allows us to use the standard vertex attributes for data per vertex and use the instanced array for storing data that is unique per instance. -</p> - -<p> - To give you an example of an instanced array we're going to take the previous example and convert the offset uniform array to an instanced array. We'll have to update the vertex shader by adding another vertex attribute: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec2 aPos; -layout (location = 1) in vec3 aColor; -layout (location = 2) in vec2 aOffset; - -out vec3 fColor; - -void main() -{ - gl_Position = vec4(aPos + aOffset, 0.0, 1.0); - fColor = aColor; -} -</code></pre> - -<p> - We no longer use <var>gl_InstanceID</var> and can directly use the <var>offset</var> attribute without first indexing into a large uniform array. -</p> - -<p> - Because an instanced array is a vertex attribute, just like the <var>position</var> and <var>color</var> variables, we need to store its content in a vertex buffer object and configure its attribute pointer. We're first going to store the <var>translations</var> array (from the previous section) in a new buffer object: -</p> - -<pre><code> -unsigned int instanceVBO; -<function id='12'>glGenBuffers</function>(1, &instanceVBO); -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, instanceVBO); -<function id='31'>glBufferData</function>(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &translations[0], GL_STATIC_DRAW); -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, 0); -</code></pre> - -<p> - Then we also need to set its vertex attribute pointer and enable the vertex attribute: -</p> - -<pre><code> -<function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(2); -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, instanceVBO); -<function id='30'>glVertexAttribPointer</function>(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, 0); -<function id='100'>glVertexAttribDivisor</function>(2, 1); -</code></pre> - -<p> - What makes this code interesting is the last line where we call <fun><function id='100'>glVertexAttribDivisor</function></fun>. This function tells OpenGL <strong>when</strong> to update the content of a vertex attribute to the next element. Its first parameter is the vertex attribute in question and the second parameter the <def>attribute divisor</def>. By default, the attribute divisor is <code>0</code> which tells OpenGL to update the content of the vertex attribute each iteration of the vertex shader. By setting this attribute to <code>1</code> we're telling OpenGL that we want to update the content of the vertex attribute when we start to render a new instance. By setting it to <code>2</code> we'd update the content every 2 instances and so on. By setting the attribute divisor to <code>1</code> we're effectively telling OpenGL that the vertex attribute at attribute location <code>2</code> is an instanced array. -</p> - -<p> - If we now were to render the quads again with <fun><function id='98'><function id='1'>glDrawArrays</function>Instanced</function></fun> we'd get the following output: -</p> - -<img src="/img/advanced/instancing_quads.png" class="clean" alt="Same image of OpenGL instanced quads, but this time using instanced arrays."/> - -<p> - This is exactly the same as the previous example, but now with instanced arrays, which allows us to pass a lot more data (as much as memory allows us) to the vertex shader for instanced drawing. -</p> - -<p> - For fun we could slowly downscale each quad from top-right to bottom-left using <var>gl_InstanceID</var> again, because why not? -</p> - -<pre><code> -void main() -{ - vec2 pos = aPos * (gl_InstanceID / 100.0); - gl_Position = vec4(pos + aOffset, 0.0, 1.0); - fColor = aColor; -} -</code></pre> - -<p> - The result is that the first instances of the quads are drawn extremely small and the further we're in the process of drawing the instances, the closer <var>gl_InstanceID</var> gets to <code>100</code> and thus the more the quads regain their original size. It's perfectly legal to use instanced arrays together with <var>gl_InstanceID</var> like this. -</p> - -<img src="/img/advanced/instancing_quads_arrays.png" class="clean" alt="Image of instanced quads drawn in OpenGL using instanced arrays"/> - -<p> - If you're still a bit unsure about how instanced rendering works or want to see how everything fits together you can find the full source code of the application <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/10.1.instancing_quads/instancing_quads.cpp" target="_blank">here</a>. -</p> - -<p> - While fun and all, these examples aren't really good examples of instancing. Yes, they do give you an easy overview of how instancing works, but instancing gets most of its power when drawing an enormous amount of similar objects. For that reason we're going to venture into space. -</p> - -<h1>An asteroid field</h1> -<p> - Imagine a scene where we have one large planet that's at the center of a large asteroid ring. Such an asteroid ring could contain thousands or tens of thousands of rock formations and quickly becomes un-renderable on any decent graphics card. This scenario proves itself particularly useful for instanced rendering, since all the asteroids can be represented with a single model. Each single asteroid then gets its variation from a transformation matrix unique to each asteroid. -</p> - -<p> - To demonstrate the impact of instanced rendering we're first going to render a scene of asteroids hovering around a planet <em>without</em> instanced rendering. The scene will contain a large planet model that can be downloaded from <a href="/data/models/planet.zip" target="_blank">here</a> and a large set of asteroid rocks that we properly position around the planet. The asteroid rock model can be downloaded <a href="/data/models/rock.zip" target="_blank">here</a>. -</p> - -<p> - Within the code samples we load the models using the model loader we've previously defined in the <a href="https://learnopengl.com/Model-Loading/Assimp" target="_blank">model loading</a> chapters. -</p> - -<p> - To achieve the effect we're looking for we'll be generating a model transformation matrix for each asteroid. The transformation matrix first translates the rock somewhere in the asteroid ring - then we'll add a small random displacement value to the offset to make the ring look more natural. From there we also apply a random scale and a random rotation. The result is a transformation matrix that translates each asteroid somewhere around the planet while also giving it a more natural and unique look compared to the other asteroids. -</p> - -<pre><code> -unsigned int amount = 1000; -glm::mat4 *modelMatrices; -modelMatrices = new glm::mat4[amount]; -srand(<function id='47'>glfwGetTime</function>()); // initialize random seed -float radius = 50.0; -float offset = 2.5f; -for(unsigned int i = 0; i < amount; i++) -{ - glm::mat4 model = glm::mat4(1.0f); - // 1. translation: displace along circle with 'radius' in range [-offset, offset] - float angle = (float)i / (float)amount * 360.0f; - float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset; - float x = sin(angle) * radius + displacement; - displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset; - float y = displacement * 0.4f; // keep height of field smaller compared to width of x and z - displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset; - float z = cos(angle) * radius + displacement; - model = <function id='55'>glm::translate</function>(model, glm::vec3(x, y, z)); - - // 2. scale: scale between 0.05 and 0.25f - float scale = (rand() % 20) / 100.0f + 0.05; - model = <function id='56'>glm::scale</function>(model, glm::vec3(scale)); - - // 3. rotation: add random rotation around a (semi)randomly picked rotation axis vector - float rotAngle = (rand() % 360); - model = <function id='57'>glm::rotate</function>(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f)); - - // 4. now add to list of matrices - modelMatrices[i] = model; -} -</code></pre> - -<p> - This piece of code may look a little daunting, but we basically transform the x and z position of the asteroid along a circle with a radius defined by <var>radius</var> and randomly displace each asteroid a little around the circle by <var>-offset</var> and <var>offset</var>. We give the <code>y</code> displacement less of an impact to create a more flat asteroid ring. Then we apply scale and rotation transformations and store the resulting transformation matrix in <var>modelMatrices</var> that is of size <var>amount</var>. Here we generate <code>1000</code> model matrices, one per asteroid. -</p> - -<p> - After loading the planet and rock models and compiling a set of shaders, the rendering code then looks a bit like this: -</p> - -<pre><code> -// draw planet -shader.use(); -glm::mat4 model = glm::mat4(1.0f); -model = <function id='55'>glm::translate</function>(model, glm::vec3(0.0f, -3.0f, 0.0f)); -model = <function id='56'>glm::scale</function>(model, glm::vec3(4.0f, 4.0f, 4.0f)); -shader.setMat4("model", model); -planet.Draw(shader); - -// draw meteorites -for(unsigned int i = 0; i < amount; i++) -{ - shader.setMat4("model", modelMatrices[i]); - rock.Draw(shader); -} -</code></pre> - -<p> - First we draw the planet model, that we translate and scale a bit to accommodate the scene, and then we draw a number of rock models equal to the <var>amount</var> of transformations we generated previously. Before we draw each rock however, we first set the corresponding model transformation matrix within the shader. -</p> - -<p> - The result is then a space-like scene where we can see a natural-looking asteroid ring around a planet: -</p> - -<img src="/img/advanced/instancing_asteroids.png" class="clean" alt="Image of asteroid field drawn in OpenGL"/> - -<p> - This scene contains a total of <code>1001</code> rendering calls per frame of which <code>1000</code> are of the rock model. You can find the source code for this scene <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/10.2.asteroids/asteroids.cpp" target="_blank">here</a>. -</p> - -<p> - As soon as we start to increase this number we will quickly notice that the scene stops running smoothly and the number of frames we're able to render per second reduces drastically. As soon as we set <var>amount</var> to something close to <code>2000</code> the scene already becomes so slow on our GPU that it becomes difficult to move around. -</p> - -<p> - Let's now try to render the same scene, but this time with instanced rendering. We first need to adjust the vertex shader a little: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; -layout (location = 2) in vec2 aTexCoords; -layout (location = 3) in mat4 instanceMatrix; - -out vec2 TexCoords; - -uniform mat4 projection; -uniform mat4 view; - -void main() -{ - gl_Position = projection * view * instanceMatrix * vec4(aPos, 1.0); - TexCoords = aTexCoords; -} -</code></pre> - -<p> - We're no longer using a model uniform variable, but instead declare a <fun>mat4</fun> as a vertex attribute so we can store an instanced array of transformation matrices. However, when we declare a datatype as a vertex attribute that is greater than a <fun>vec4</fun> things work a bit differently. The maximum amount of data allowed for a vertex attribute is equal to a <fun>vec4</fun>. Because a <fun>mat4</fun> is basically 4 <fun>vec4</fun>s, we have to reserve 4 vertex attributes for this specific matrix. Because we assigned it a location of <code>3</code>, the columns of the matrix will have vertex attribute locations of <code>3</code>, <code>4</code>, <code>5</code>, and <code>6</code>. -</p> - -<p> - We then have to set each of the attribute pointers of those <code>4</code> vertex attributes and configure them as instanced arrays: -</p> - -<pre><code> -// vertex buffer object -unsigned int buffer; -<function id='12'>glGenBuffers</function>(1, &buffer); -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, buffer); -<function id='31'>glBufferData</function>(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW); - -for(unsigned int i = 0; i < rock.meshes.size(); i++) -{ - unsigned int VAO = rock.meshes[i].VAO; - <function id='27'>glBindVertexArray</function>(VAO); - // vertex attributes - std::size_t vec4Size = sizeof(glm::vec4); - <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(3); - <function id='30'>glVertexAttribPointer</function>(3, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)0); - <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(4); - <function id='30'>glVertexAttribPointer</function>(4, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(1 * vec4Size)); - <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(5); - <function id='30'>glVertexAttribPointer</function>(5, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(2 * vec4Size)); - <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(6); - <function id='30'>glVertexAttribPointer</function>(6, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(3 * vec4Size)); - - <function id='100'>glVertexAttribDivisor</function>(3, 1); - <function id='100'>glVertexAttribDivisor</function>(4, 1); - <function id='100'>glVertexAttribDivisor</function>(5, 1); - <function id='100'>glVertexAttribDivisor</function>(6, 1); - - <function id='27'>glBindVertexArray</function>(0); -} -</code></pre> - -<p> - Note that we cheated a little by declaring the <var>VAO</var> variable of the <fun>Mesh</fun> as a public variable instead of a private variable so we could access its vertex array object. This is not the cleanest solution, but just a simple modification to suit this example. Aside from the little hack, this code should be clear. We're basically declaring how OpenGL should interpret the buffer for each of the matrix's vertex attributes and that each of those vertex attributes is an instanced array. -</p> - -<p> - Next we take the <var>VAO</var> of the mesh(es) again and this time draw using <fun><function id='99'><function id='2'>glDrawElements</function>Instanced</function></fun>: -</p> - -<pre><code> -// draw meteorites -instanceShader.use(); -for(unsigned int i = 0; i < rock.meshes.size(); i++) -{ - <function id='27'>glBindVertexArray</function>(rock.meshes[i].VAO); - <function id='99'><function id='2'>glDrawElements</function>Instanced</function>( - GL_TRIANGLES, rock.meshes[i].indices.size(), GL_UNSIGNED_INT, 0, amount - ); -} -</code></pre> - -<p> - Here we draw the same <var>amount</var> of asteroids as the previous example, but this time with instanced rendering. The results should be exactly the same, but once we increase the <var>amount</var> you'll really start to see the power of instanced rendering. Without instanced rendering we were able to smoothly render around <code>1000</code> to <code>1500</code> asteroids. With instanced rendering we can now set this value to <code>100000</code>. This, with the rock model having <code>576</code> vertices, would equal around <code>57</code> million vertices drawn each frame without significant performance drops; and only 2 draw calls! -</p> - -<img src="/img/advanced/instancing_asteroids_quantity.png" class="clean" alt="Image of asteroid field in OpenGL drawn using instanced rendering"/> - -<p> - This image was rendered with <code>100000</code> asteroids with a radius of <code>150.0f</code> and an offset equal to <code>25.0f</code>. You can find the source code of the instanced rendering demo <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/10.3.asteroids_instanced/asteroids_instanced.cpp" target="_blank">here</a>. -</p> - -<note> - On different machines an asteroid count of <code>100000</code> may be a bit too high, so try tweaking the values till you reach an acceptable framerate. -</note> - -<p> - As you can see, with the right type of environments, instanced rendering can make an enormous difference to the rendering capabilities of your application. For this reason, instanced rendering is commonly used for grass, flora, particles, and scenes like this - basically any scene with many repeating shapes can benefit from instanced rendering. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Advanced-OpenGL/Stencil-testing.html b/translation/Advanced-OpenGL/Stencil-testing.html @@ -1,581 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Stencil testing</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Stencil testing</h1> -<h1 id="content-url" style='display:none;'>Advanced-OpenGL/Stencil-testing</h1> -<p> - Once the fragment shader has processed the fragment a so called <def>stencil test</def> is executed that, just like the depth test, has the option to discard fragments. After that the remaining fragments are passed to the depth test where OpenGL could possibly discard even more fragments. The stencil test is based on the content of yet another buffer called the <def>stencil buffer</def> that we're allowed to update during rendering to achieve interesting effects. -</p> - -<p> - A stencil buffer (usually) contains <code>8</code> bits per <def>stencil value</def> that amounts to a total of <code>256</code> different stencil values per pixel. We can set these stencil values to values of our liking and we can discard or keep fragments whenever a particular fragment has a certain stencil value. -</p> - -<note> - Each windowing library needs to set up a stencil buffer for you. GLFW does this automatically so we don't have to tell GLFW to create one, but other windowing libraries may not create a stencil buffer by default so be sure to check your library's documentation. -</note> - -<p> - A simple example of a stencil buffer is shown below (pixels not-to-scale): -</p> - -<img src="/img/advanced/stencil_buffer.png" class="clean" alt="A simple demonstration of a stencil buffer"/> - -<p> - The stencil buffer is first cleared with zeros and then an open rectangle of <code>1</code>s is stored in the stencil buffer. The fragments of the scene are then only rendered (the others are discarded) wherever the stencil value of that fragment contains a <code>1</code>. -</p> - -<p> - Stencil buffer operations allow us to set the stencil buffer at specific values wherever we're rendering fragments. By changing the content of the stencil buffer while we're rendering, we're <em>writing</em> to the stencil buffer. In the same (or following) frame(s) we can <em>read</em> these values to discard or pass certain fragments. When using stencil buffers you can get as crazy as you like, but the general outline is usually as follows: -</p> - -<ul> - <li>Enable writing to the stencil buffer.</li> - <li>Render objects, updating the content of the stencil buffer.</li> - <li>Disable writing to the stencil buffer.</li> - <li>Render (other) objects, this time discarding certain fragments based on the content of the stencil buffer.</li> -</ul> - -<p> - By using the stencil buffer we can thus discard certain fragments based on the fragments of other drawn objects in the scene. -</p> - -<p> - You can enable stencil testing by enabling <var>GL_STENCIL_TEST</var>. From that point on, all rendering calls will influence the stencil buffer in one way or another. -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_STENCIL_TEST); -</code></pre> - -<p> - Note that you also need to clear the stencil buffer each iteration just like the color and depth buffer: -</p> - -<pre><code> -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); -</code></pre> - -<p> - Also, just like the depth testing's <fun><function id='65'>glDepthMask</function></fun> function, there is an equivalent function for the stencil buffer. The function <fun><function id='67'>glStencilMask</function></fun> allows us to set a bitmask that is <code>AND</code>ed with the stencil value about to be written to the buffer. By default this is set to a bitmask of all <code>1</code>s not affecting the output, but if we were to set this to <code>0x00</code> all the stencil values written to the buffer end up as <code>0</code>s. This is equivalent to depth testing's <fun><function id='65'>glDepthMask</function>(GL_FALSE)</fun>: -</p> - -<pre><code> -<function id='67'>glStencilMask</function>(0xFF); // each bit is written to the stencil buffer as is -<function id='67'>glStencilMask</function>(0x00); // each bit ends up as 0 in the stencil buffer (disabling writes) -</code></pre> - -<p> - Most of the cases you'll only be using <code>0x00</code> or <code>0xFF</code> as the stencil mask, but it's good to know there are options to set custom bit-masks. -</p> - -<h2>Stencil functions</h2> -<p> - Similar to depth testing, we have a certain amount of control over when a stencil test should pass or fail and how it should affect the stencil buffer. There are a total of two functions we can use to configure stencil testing: <fun><function id='68'>glStencilFunc</function></fun> and <fun><function id='69'>glStencilOp</function></fun>. -</p> - -<p> - The <fun><function id='68'>glStencilFunc</function>(GLenum func, GLint ref, GLuint mask)</fun> has three parameters: -</p> - -<ul> - <li><code>func</code>: sets the stencil test function that determines whether a fragment passes or is discarded. This test function is applied to the stored stencil value and the <fun><function id='68'>glStencilFunc</function></fun>'s <code>ref</code> value. Possible options are: <var>GL_NEVER</var>, <var>GL_LESS</var>, <var>GL_LEQUAL</var>, <var>GL_GREATER</var>, <var>GL_GEQUAL</var>, <var>GL_EQUAL</var>, <var>GL_NOTEQUAL</var> and <var>GL_ALWAYS</var>. The semantic meaning of these is similar to the depth buffer's functions.</li> - <li><code>ref</code>: specifies the reference value for the stencil test. The stencil buffer's content is compared to this value.</li> - <li><code>mask</code>: specifies a mask that is <code>AND</code>ed with both the reference value and the stored stencil value before the test compares them. Initially set to all <code>1</code>s.</li> -</ul> - -<p> - So in the case of the simple stencil example we've shown at the start, the function would be set to: -</p> - -<pre class="cpp"><code> -<function id='68'>glStencilFunc</function>(GL_EQUAL, 1, 0xFF) -</code></pre> - -<p> - This tells OpenGL that whenever the stencil value of a fragment is equal (<var>GL_EQUAL</var>) to the reference value <code>1</code>, the fragment passes the test and is drawn, otherwise discarded. -</p> - -<p> - But <fun><function id='68'>glStencilFunc</function></fun> only describes whether OpenGL should pass or discard fragments based on the stencil buffer's content, not how we can actually update the buffer. That is where <fun><function id='69'>glStencilOp</function></fun> comes in. -</p> - - <p> - The <fun><function id='69'>glStencilOp</function>(GLenum sfail, GLenum dpfail, GLenum dppass)</fun> contains three options of which we can specify for each option what action to take: - </p> - - <ul> - <li><code>sfail</code>: action to take if the stencil test fails.</li> - <li><code>dpfail</code>: action to take if the stencil test passes, but the depth test fails.</li> - <li><code>dppass</code>: action to take if both the stencil and the depth test pass.</li> - </ul> - -<p> - Then for each of the options you can take any of the following actions: -</p> - -<table> - <tr> - <th>Action</th> - <th>Description</th> - </tr> - <tr> - <td><code>GL_KEEP</code></td> - <td>The currently stored stencil value is kept.</td> - </tr> - <tr> - <td><code>GL_ZERO</code></td> - <td>The stencil value is set to <code>0</code>.</td> - </tr> - <tr> - <td><code>GL_REPLACE</code></td> - <td>The stencil value is replaced with the reference value set with <fun><function id='68'>glStencilFunc</function></fun>.</td> - </tr> - <tr> - <td><code>GL_INCR</code></td> - <td>The stencil value is increased by <code>1</code> if it is lower than the maximum value. </td> - </tr><tr> - <td><code>GL_INCR_WRAP</code></td> - <td>Same as <var>GL_INCR</var>, but wraps it back to <code>0</code> as soon as the maximum value is exceeded.</td> - </tr> - <tr> - <td><code>GL_DECR</code></td> - <td>The stencil value is decreased by <code>1</code> if it is higher than the minimum value.</td> - </tr> - <tr> - <td><code>GL_DECR_WRAP</code></td> - <td>Same as <var>GL_DECR</var>, but wraps it to the maximum value if it ends up lower than <code>0</code>.</td> - </tr> - <tr> - <td><code>GL_INVERT</code></td> - <td>Bitwise inverts the current stencil buffer value.</td> - </tr> -</table> - -<p> - By default the <fun><function id='69'>glStencilOp</function></fun> function is set to <code>(GL_KEEP, GL_KEEP, GL_KEEP)</code> so whatever the outcome of any of the tests, the stencil buffer keeps its values. The default behavior does not update the stencil buffer, so if you want to write to the stencil buffer you need to specify at least one different action for any of the options. -</p> - -<p> - So using <fun><function id='68'>glStencilFunc</function></fun> and <fun><function id='69'>glStencilOp</function></fun> we can precisely specify when and how we want to update the stencil buffer and when to pass or discard fragments based on its content. -</p> - -<h1>Object outlining</h1> -<p> - It would be unlikely if you completely understood how stencil testing works from the previous sections alone so we're going to demonstrate a particular useful feature that can be implemented with stencil testing alone called <def>object outlining</def>. -</p> - -<img src="/img/advanced/stencil_object_outlining.png" class="clean" alt="An object outlined using stencil testing/buffer"/> - -<p> - Object outlining does exactly what it says it does. For each object (or only one) we're creating a small colored border around the (combined) objects. This is a particular useful effect when you want to select units in a strategy game for example and need to show the user which of the units were selected. The routine for outlining your objects is as follows: -</p> - -<ol> - <li>Enable stencil writing.</li> - <li>Set the stencil op to <var>GL_ALWAYS</var> before drawing the (to be outlined) objects, updating the stencil buffer with <code>1</code>s wherever the objects' fragments are rendered.</li> - <li>Render the objects.</li> - <li>Disable stencil writing and depth testing.</li> - <li>Scale each of the objects by a small amount.</li> - <li>Use a different fragment shader that outputs a single (border) color.</li> - <li>Draw the objects again, but only if their fragments' stencil values are not equal to <code>1</code>.</li> - <li>Enable depth testing again and restore stencil func to <var>GL_KEEP</var>.</li> -</ol> - -<p> - This process sets the content of the stencil buffer to <code>1</code>s for each of the object's fragments and when it's time to draw the borders, we draw scaled-up versions of the objects only where the stencil test passes. We're effectively discarding all the fragments of the scaled-up versions that are part of the original objects' fragments using the stencil buffer. -</p> - -<p> - So we're first going to create a very basic fragment shader that outputs a border color. We simply set a hardcoded color value and call the shader <var>shaderSingleColor</var>: -</p> - -<pre><code> -void main() -{ - FragColor = vec4(0.04, 0.28, 0.26, 1.0); -} -</code></pre> - -<p> - Using the scene from the <a href="https://learnopengl.com/Advanced-OpenGL/Depth-testing" target="_blank">previous</a> chapter we're going to add object outlining to the two containers, so we'll leave the floor out of it. We want to first draw the floor, then the two containers (while writing to the stencil buffer), and then draw the scaled-up containers (while discarding the fragments that write over the previously drawn container fragments). -</p> - -<p> - We first need to enable stencil testing: -</p> - -<pre class="cpp"><code> -<function id='60'>glEnable</function>(GL_STENCIL_TEST); -</code></pre> - -<p> - And then in each frame we want to specify the action to take whenever any of the stencil tests succeed or fail: -</p> - -<pre class="cpp"><code> -<function id='69'>glStencilOp</function>(GL_KEEP, GL_KEEP, GL_REPLACE); -</code></pre> - -<p> - If any of the tests fail we do nothing; we simply keep the currently stored value that is in the stencil buffer. If both the stencil test and the depth test succeed however, we want to replace the stored stencil value with the reference value set via <fun><function id='68'>glStencilFunc</function></fun> which we later set to <code>1</code>. -</p> - -<p> - We clear the stencil buffer to <code>0</code>s at the start of the frame and for the containers we update the stencil buffer to <code>1</code> for each fragment drawn: -</p> - -<pre><code> -<function id='69'>glStencilOp</function>(GL_KEEP, GL_KEEP, GL_REPLACE); -<function id='68'>glStencilFunc</function>(GL_ALWAYS, 1, 0xFF); // all fragments should pass the stencil test -<function id='67'>glStencilMask</function>(0xFF); // enable writing to the stencil buffer -normalShader.use(); -DrawTwoContainers(); -</code></pre> - -<p> - By using <var>GL_REPLACE</var> as the stencil op function we make sure that each of the containers' fragments update the stencil buffer with a stencil value of <code>1</code>. Because the fragments always pass the stencil test, the stencil buffer is updated with the reference value wherever we've drawn them. -</p> - -<p> - Now that the stencil buffer is updated with <code>1</code>s where the containers were drawn we're going to draw the upscaled containers, but this time with the appropriate test function and disabling writes to the stencil buffer: -</p> - -<pre><code> -<function id='68'>glStencilFunc</function>(GL_NOTEQUAL, 1, 0xFF); -<function id='67'>glStencilMask</function>(0x00); // disable writing to the stencil buffer -glDisable(GL_DEPTH_TEST); -shaderSingleColor.use(); -DrawTwoScaledUpContainers(); -</code></pre> - -<p> - We set the stencil function to <var>GL_NOTEQUAL</var> to make sure that we're only drawing parts of the containers that are not equal to <code>1</code>. This way we only draw the part of the containers that are outside the previously drawn containers. Note that we also disable depth testing so the scaled up containers (e.g. the borders) do not get overwritten by the floor. Make sure to enable the depth buffer again once you're done. -</p> - -<p> - The total object outlining routine for our scene looks something like this: -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_DEPTH_TEST); -<function id='69'>glStencilOp</function>(GL_KEEP, GL_KEEP, GL_REPLACE); - -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - -<function id='67'>glStencilMask</function>(0x00); // make sure we don't update the stencil buffer while drawing the floor -normalShader.use(); -DrawFloor() - -<function id='68'>glStencilFunc</function>(GL_ALWAYS, 1, 0xFF); -<function id='67'>glStencilMask</function>(0xFF); -DrawTwoContainers(); - -<function id='68'>glStencilFunc</function>(GL_NOTEQUAL, 1, 0xFF); -<function id='67'>glStencilMask</function>(0x00); -glDisable(GL_DEPTH_TEST); -shaderSingleColor.use(); -DrawTwoScaledUpContainers(); -<function id='67'>glStencilMask</function>(0xFF); -<function id='68'>glStencilFunc</function>(GL_ALWAYS, 1, 0xFF); -<function id='60'>glEnable</function>(GL_DEPTH_TEST); -</code></pre> - -<p> - As long as you understand the general idea behind stencil testing this shouldn't be too hard to understand. Otherwise try to carefully read the previous sections again and try to completely understand what each of the functions does now that you've seen an example of it can be used. -</p> - -<p> - The result of the outlining algorithm then looks like this: -</p> - - -<img src="/img/advanced/stencil_scene_outlined.png" class="clean" alt="3D scene with object outlining using a stencil buffer"/> - -<p> - Check the source code <a href="/code_viewer_gh.php?code=src/4.advanced_opengl/2.stencil_testing/stencil_testing.cpp" target="_blank">here</a> to see the complete code of the object outlining algorithm. -</p> - -<note> - You can see that the borders overlap between both containers which is usually the effect that we want (think of strategy games where we want to select 10 units; merging borders is generally preferred). If you want a complete border per object you'd have to clear the stencil buffer per object and get a little creative with the depth buffer. -</note> - -<p> - The object outlining algorithm you've seen is commonly used in games to visualize selected objects (think of strategy games) and an algorithm like this can easily be implemented within a model class. You could set a boolean flag within the model class to draw either with borders or without. If you want to be creative you could even give the borders a more natural look with the help of post-processing filters like Gaussian Blur. -</p> - -<p> - Stencil testing has many more purposes (beside outlining objects) like drawing textures inside a rear-view mirror so it neatly fits into the mirror shape, or rendering real-time shadows with a stencil buffer technique called <def>shadow volumes</def>. Stencil buffers give us with yet another nice tool in our already extensive OpenGL toolkit. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Code-repository.html b/translation/Code-repository.html @@ -1,292 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Code repository</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Code repository</h1> -<h1 id="content-url" style='display:none;'>Code-repository</h1> -<p> - You can find all relevant code samples online in each chapter, but if you want to quickly run the chapter demos yourself or compare your code with working examples you can find an online code repository <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank">here</a> hosted on Github. -</p> - -<p> - At the moment the <code>CMakeLists.txt</code> file can properly generate visual studio project files, make-files and works on both Windows and Linux. It hasn't been extensively tested on Apple's OS X and not on all IDEs so feel free to leave a comment or update the <code>CMakeLists.txt</code> via a pull request if you can get it working on different systems. -</p> - -<p> - I'd like to thank Zwookie for being an enormous help on the Linux side of the CMake script. Thanks to Zwookie's updates on the CMakeLists it now succesfully generates project files on both Windows and Linux. -</p> - -<p> - Also, check out <a href="https://github.com/Polytonic/Glitter" target="_blank">Glitter</a> by Polytonic, a dead-simple boilerplate project for these chapters that comes pre-configured with the relevant dependencies. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Guest-Articles/2020/OIT/Introduction.html b/translation/Guest-Articles/2020/OIT/Introduction.html @@ -1,437 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Introduction</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Introduction</h1> -<h1 id="content-url" style='display:none;'>Guest-Articles/2020/OIT/Introduction</h1> -<p> - In the <a href="https://learnopengl.com/Advanced-OpenGL/Blending" target="_blank">Blending</a> chapter, the subject of color blending was introduced. Blending is the way of implementing transparent surfaces in a 3D scene. In short, transparency delves into the subject of drawing semi-solid or fully see-through objects like glasses in computer graphics. The idea is explained up to a suitable point in that chapter, so if you're unfamiliar with the topic, better read Blending first. - </p> - - <p> - In this article, we are scratching the surface of this topic a bit further, since there are so many techniques involved in implementing such an effect in a 3D environment. - </p> - - <p> - To begin with, we are going to discuss about the limitations of the graphics library/hardware and the hardships they entail, and the reason that why transparency is such a tricky subject. Later on, we will introduce and briefly review some of the more well-known transparency techniques that have been invented and used for the past twenty years associated with the current hardware. Ultimately, we are going to focus on explaining and implementing one of them, which will be the subject of the following part of this article. - </p> - - <p> - Note that the goal of this article is to introduce techniques which have significantly better performance than the technique that was used in the Blending chapter. Otherwise, there isn't a genuinely compelling reason to expand on that matter. - </p> - - <h2>Graphics library/hardware limitations</h2> - - <p> - The reason that this article exists, and you're reading it, is that there is no direct way to draw transparent surfaces with the current technology. Many people wish, that it was as simple as turning on a flag in their graphics API, but that's a fairy tale. Whether, this is a limitation of the graphics libraries or video cards, that's debatable. - </p> - - <p> - As explained in the Blending chapter, the source of this problem arises from combining depth testing and color blending. At the fragment stage, there is no buffer like the depth buffer for transparent pixels that would tell the graphics library, which pixels are fully visible or semi-visible. One of the reasons could be, that there is no efficient way of storing the information of transparent pixels in such a buffer that can hold an infinite number of pixels for each coordinate on the screen. Since each transparent pixel could expose its underlying pixels, therefore there needs to be a way to store different layers of all pixels for all screen coordinates. - </p> - - <p> - This limitation leaves us to think for a way to overcome such an issue and since neither the graphics library nor the hardware gives us a hand, this all has to be done by the developer with the tools at hand. We will examine two methods which are prominent in this subject. One being, <def>ordered transparency</def> and the other <def>order-independent transparency</def>. - </p> - - <h2>Ordered transparency</h2> - - <p> - The most convenient solution to overcome this issue, is to sort your transparent objects, so they're either drawn from the furthest to the nearest, or from the nearest to the furthest in relation to the camera's position. This way, the depth testing wouldn't affect the outcome of those pixels that have been drawn after/before but over/under a further/closer object. However major the expenditure this method entails for the CPU, it was used in many early games that probably most of us have played. - </p> - - <p> - For example, the sample image below shows the importance of blending order. The top part of the image produces an incorrect result with unordered alpha blending, while the bottom correctly sorts the geometry. Note lower visibility of the skeletal structure without correct depth ordering. - This image is from <a href="http://gpuopen.com/archive/gpu-demos/radeon-hd-5000-series-graphics-real-time-demos/" target="_blank">ATI Mecha Demo</a>: - </p> - -<img src="/img/guest/2020/oit/ATI_Mecha_Demo_Screenshot.jpg" width="287" alt="Importance of ordering from ATI Mecha Demo"> - - <p> - So far, we have understood that in order to overcome the limitation of current technology to draw transparent objects, we need order for our transparent objects to be displayed properly on the screen. Ordering takes away performance from your application, and since most of 3D applications are running in real-time, this will be so much more evident as you perform sorting at every frame. - </p> - - <p> - Therefore, we will be looking into the world of order-independent transparency techniques and to find one which better suits our purpose and furthermore our pipeline, so we don't have to sort the objects before drawing. - </p> - - <h2>Order-independent transparency</h2> - - <p> - Order-independent transparency or for short <def>OIT</def>, is a technique which doesn't require us to draw our transparent objects in an orderly fashion. At first glance, this will give us back the CPU cycles that we were taking for sorting the objects, but at the same time OIT techniques have their pros and cons. - </p> - - <p> - The goal of OIT techniques is to eliminate the need of sorting transparent objects at draw time. Depending on the technique, some of them must sort fragments for an accurate result, but only at a later stage when all the draw calls have been made, and some of them don't require sorting, but results are approximated. - </p> - - <h3>History</h3> - - <p> - Some of the more advanced techniques that have been invented to overcome the limitation of rendering transparent surfaces, explicitly use a buffer (e.g. a linked list or a 3D array such as [x][y][z]) that can hold multiple layers of pixels' information and can sort pixels on the GPU, normally because of its parallel processing power, as opposed to CPU. - </p> - - <note> - The <a href="https://en.wikipedia.org/wiki/A-buffer" target="_blank">A-buffer</a> is a computer graphics technique introduced in 1984 which stores per-pixel lists of fragment data (including <a href="https://en.wikipedia.org/wiki/Micropolygon" target="_blank">micro-polygon</a> information) in a software rasterizer, <a href="https://en.wikipedia.org/wiki/Reyes_rendering" target="_blank">REYES</a>, originally designed for anti-aliasing but also supporting transparency. - </note> - - <p> - At the same time, there has been hardware capable of facilitating this task by performing on-hardware calculations which is the most convenient way for a developer to have access to transparency out of the box. - </p> - - <note> - <a href="https://en.wikipedia.org/wiki/Dreamcast#Hardware" target="_blank">SEGA Dreamcast</a> was one of the few consoles that had automatic per-pixel translucency sorting, implemented in its hardware. - </note> - - <p> - Commonly, OIT techniques are separated into two categories which are <def>exact</def> and <def>approximate</def>. Respectively, exact will result in better images with an accurate transparency which suits every scenario, while approximate although resulting in good-looking images, lacks accuracy in complex scenes. - </p> - - <h3>Exact OIT</h3> - - <p> - These techniques accurately compute the final color, for which all fragments must be sorted. For high depth complexity scenes, sorting becomes the bottleneck. - </p> - - <p> - One issue with the sorting stage is <def>local memory limited occupancy</def>, in this case a <a href="https://en.wikipedia.org/wiki/Single_Instruction_Multiple_Threads" target="_blank">single instruction, multiple threads</a> attribute relating to the throughput and operation latency hiding of GPUs. - Although, <a href="http://diglib.eg.org/handle/10.2312/PE.PG.PG2013short.059-064" target="_blank">BMA</a> (backwards memory allocation) can group pixels by their depth complexity and sort them in batches to improve the occupancy and hence performance of low depth complexity pixels in the context of a potentially high depth complexity scene. Up to a 3× overall OIT performance increase is reported. - </p> - - <note> - The sorting stage requires relatively large amounts of temporary memory in shaders that is usually conservatively allocated at a maximum, which impacts memory occupancy and performance. - </note> - - <p> - Sorting is typically performed in a local array, however performance can be improved further by making use of the GPU's memory hierarchy and sorting in registers, similarly to an <a href="https://en.wikipedia.org/wiki/External_sorting#External_merge_sort" target="_blank">external merge sort</a>, especially in conjunction with BMA. - </p> - - <h3>Approximate OIT</h3> - - <p> - Approximate OIT techniques relax the constraint of exact rendering to provide faster results. Higher performance can be gained from not having to store all fragments or only partially sorting the geometry. A number of techniques also compress, or reduce, the fragment data. These include: - </p> - - <ul> - <li>Stochastic Transparency: draw in a higher resolution in full opacity but discard some fragments. Down-sampling will then yield transparency.</li> - <li>Adaptive Transparency: a two-pass technique where the first constructs a visibility function which compresses on the fly (this compression avoids having to fully sort the fragments) and the second uses this data to composite unordered fragments. Intel's pixel synchronization avoids the need to store all fragments, removing the unbounded memory requirement of many other OIT techniques.</li> - </ul> - - <h3>Techniques</h3> - - <p> - Some of the OIT techniques that have been commonly used in the industry are as follows: - </p> - - <ul> - <li><a href="https://en.wikipedia.org/wiki/Depth_peeling" target="_blank">Depth peeling</a>: Introduced in 2001, described a hardware accelerated OIT technique which utilizes the depth buffer to peel a layer of pixels at each pass. With limitations in graphics hardware the scene's geometry had to be rendered many times.</li> - <li><a href="http://developer.download.nvidia.com/SDK/10/opengl/src/dual_depth_peeling/doc/DualDepthPeeling.pdf" target="_blank">Dual depth peeling</a>: Introduced in 2008, improves on the performance of depth peeling, still with many-pass rendering limitation.</li> - <li><a href="http://jcgt.org/published/0002/02/09/" target="_blank">Weighted, blended</a>: Published in 2013, utilizes a weighting function and two buffers for pixel color and pixel reveal threshold for the final composition pass. Results in an approximated image with a decent quality in complex scenes.</li> - </ul> - - <h2>Implementation</h2> - - <p> - The usual way of performing OIT in 3D applications is to do it in multiple passes. There are at least three passes required for an OIT technique to be performed, so in order to do this, you'll have to have a perfect understanding of how <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">Framebuffers</a> work in OpenGL. Once you're comfortable with Framebuffers, it all boils down to the implementation complexity of the technique you are trying to implement. - </p> - - <p> - Briefly explained, the three passes involved are as follows: - </p> - - <ol> - <li>First pass, is where you draw all of your solid objects, this means any object that does not let the light travel through its geometry.</li> - <li>Second pass, is where you draw all of your translucent objects. Objects that need alpha discarding, can be rendered in the first pass.</li> - <li>Third pass, is where you composite the images that resulted from two previous passes and draw that image onto your backbuffer.</li> - </ol> - - <p> - This routine is almost identical in implementing OIT techniques across all different pipelines. - </p> - - <p> - In the next part of this article, we are going to implement weighted, blended OIT which is one of the easiest and high performance OIT techniques that has been used in the video game industry for the past ten years. - </p> - - <h2>Further reading</h2> - <ul> - <li><a href="https://en.wikipedia.org/wiki/Dreamcast#Hardware" target="_blank">SEGA Dreamcast Hardware</a>: Dreamcast was one of the few consoles that had hardware implemented order-independent transparency.</li> - <li><a href="https://en.wikipedia.org/wiki/Order-independent_transparency" target="_blank">Order-independent transparency</a>: A series of techniques that have a great performance and produce nice results even with the approximated methods.</li> - <li><a href="http://casual-effects.blogspot.com/2014/03/weighted-blended-order-independent.html" target="_blank">Weighted, blended order-independent transparency</a>: One of the easiest OIT techniques in terms of implementation while producing highly acceptable images for complex scenes.</li> - </ul> - -<author> - <strong>Article by: </strong>Mahan Heshmati Moghaddam<br/> - <strong>Contact: </strong><a href="mailto:mahangm@gmail.com" target="_blank">e-mail</a> -</author> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Guest-Articles/2020/OIT/Weighted-Blended.html b/translation/Guest-Articles/2020/OIT/Weighted-Blended.html @@ -1,821 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Weighted Blended</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Weighted Blended</h1> -<h1 id="content-url" style='display:none;'>Guest-Articles/2020/OIT/Weighted-Blended</h1> - <p> - Weighted, Blended is an approximate order-independent transparency technique which was published in the <a href="http://jcgt.org/published/0002/02/09/">journal of computer graphics techniques</a> in 2013 by Morgan McGuire and Louis Bavoil at NVIDIA to address the transparency problem on a broad class of then gaming platforms. - </p> - - <p> - Their approach to avoid the cost of storing and sorting primitives or fragments is to alter the compositing operator so that it is order independent, thus allowing a pure streaming approach. - </p> - - <p> - Most games have ad-hoc and scene-dependent ways of working around transparent surface rendering limitations. These include limited sorting, additive-only blending, and hard-coded render and composite ordering. Most of these methods also break at some point during gameplay and create visual artifacts. One not-viable alternative is <a href="http://developer.download.nvidia.com/SDK/10/opengl/screenshots/samples/dual_depth_peeling.html" target="_blank">depth peeling</a>, which produces good images, but is too slow for scenes with many layers both in theory and practice. - </p> - - <p> - There are many <a href="https://en.wikipedia.org/wiki/Asymptotic_analysis" target="_blank">asymptotically</a> fast solutions for transparency rendering, such as bounded A-buffer approximations using programmable blending (e.g., <a href="http://software.intel.com/en-us/articles/multi-layer-alpha-blending">Marco Salvi's work</a>), stochastic transparency (as <a href="https://www.computer.org/csdl/journal/tg/2011/08/ttg2011081036/13rRUxBa55X" target="_blank">explained by Eric Enderton and others</a>), and ray tracing. One or more of these will probably dominate at some point, but all were impractical on the game platforms of five or six years ago, including PC DX11/GL4 GPUs, mobile devices with OpenGL ES 3.0 GPUs, and last-generation consoles like PlayStation 4. - </p> - - <note> - In mathematical analysis, asymptotic analysis, also known as asymptotics, is a method of describing limiting behavior. - </note> - - <p> - The below image is a transparent CAD view of a car engine rendered by this technique. - </p> - -<img src="/img/guest/2020/oit/cad_view_of_an_engine.png" width="560" alt="A transparent CAD view of a car engine rendered by this technique."> - - <h2>Theory</h2> - - <p> - This technique renders non-refractive, monochrome transmission through surfaces that themselves have color, without requiring sorting or new hardware features. In fact, it can be implemented with a simple shader for any GPU that supports blending to render targets with more than 8 bits per channel. - </p> - - <p> - It works best on GPUs with multiple render targets and floating-point texture, where it is faster than sorted transparency and avoids sorting artifacts and popping for particle systems. It also consumes less bandwidth than even a 4-deep RGBA8 K-buffer and allows mixing low-resolution particles with full-resolution surfaces such as glass. - </p> - - <p> - For the mixed resolution case, the peak memory cost remains that of the higher resolution render target but bandwidth cost falls based on the proportional of low-resolution surfaces. - </p> - - <p> - The basic idea of Weighted, Blended method is to compute the coverage of the background by transparent surfaces exactly, but to only approximate the light scattered towards the camera by the transparent surfaces themselves. The algorithm imposes a heuristic on inter-occlusion factor among transparent surfaces that increases with distance from the camera. - </p> - - <note> - A heuristic technique, or a heuristic, is any approach to problem solving or self-discovery that employs a practical method that is not guaranteed to be optimal, perfect, or rational, but is nevertheless sufficient for reaching an immediate, short-term goal or approximation. In our case, the heuristic is the weighting function. - </note> - - <p> - After all transparent surfaces have been rendered, it then performs a full-screen normalization and compositing pass to reduce errors where the heuristic was a poor approximation of the true inter-occlusion. - </p> - - <p> - The below image is a glass chess set rendered with this technique. Note that the glass pieces are not refracting any light. - </p> - - <img src="/img/guest/2020/oit/a_glass_chess_set.png" width="560" alt="A glass chess set rendered with this technique."> - - <p> - For a better understanding and a more detailed explanation of the weight function, please refer to page 5, 6 and 7 of the original paper as the Blended OIT has been implemented and improved by different methods along the years. Link to the paper is provided at the end of this article. - </p> - - <h2>Limitation</h2> - - <p> - The primary limitation of the technique is that the weighting heuristic must be tuned for the anticipated depth range and opacity of transparent surfaces. - </p> - - <p> - The technique was implemented in OpenGL for the <a href="http://g3d.sf.net/">G3D Innovation Engine</a> and DirectX for the <a href="http://www.unrealengine.com/">Unreal Engine</a> to produce the results live and in the paper. Dan Bagnell and Patrick Cozzi <a href="http://bagnell.github.io/cesium/Apps/Sandcastle/gallery/OIT.html">implemented it in WebGL</a> for their open-source Cesium engine (see also their <a href="http://cesiumjs.org/2014/03/14/Weighted-Blended-Order-Independent-Transparency/">blog post</a> discussing it). - </p> - - <p> - From those implementations, a good set of weighting functions were found, which are reported in the journal paper. In the paper, they also discuss how to spot artifacts from a poorly-tuned weighting function and fix them. - </p> - - <p> - Also, I haven't been able to find a proper way to implement this technique in a deferred renderer. Since pixels override each other in a deferred renderer, we lose information about the previous layers so we cannot correctly accumulate the color values for the lighting stage. - </p> - - <p> - One feasible solution is to apply this technique as you would ordinarily do in a forward renderer. This is basically borrowing the transparency pass of a forward renderer and incorporate it in a deferred one. - </p> - - <h2>Implementation</h2> - - <p> - This technique is fairly straight forward to implement and the shader modifications are very simple. If you're familiar with how Framebuffers work in OpenGL, you're almost halfway there. - </p> - - <p> - The only caveat is we need to write our code in OpenGL ^4.0 to be able to use blending to multiple render targets (e.g. utilizing <fun><function id='70'>glBlendFunc</function>i</fun>). In the paper, different ways of implementation have been discussed for libraries that do not support rendering or blending to multiple targets. - </p> - - <warning>Don't forget to change your OpenGL version when initializng GLFW and also your GLSL version in your shaders.</warning> - - <h3>Overview</h3> - - <p> - During the transparent surface rendering, shade surfaces as usual, but output to two render targets. The first render target (<def>accum</def>) must have at least <var>RGBA16F</var> precision and the second (<def>revealage</def>) must have at least <var>R8</var> precision. Clear the first render target to <var>vec4(0)</var> and the second render target to 1 (using a pixel shader or <fun><function id='10'>glClear</function>Buffer</fun> + <fun><function id='10'>glClear</function></fun>). - </p> - - <p> - Then, render the surfaces in any order to these render targets, adding the following to the bottom of the pixel shader and using the specified blending modes: - </p> - -<pre><code> -// your first render target which is used to accumulate pre-multiplied color values -layout (location = 0) out vec4 accum; - -// your second render target which is used to store pixel revealage -layout (location = 1) out float reveal; - -... - -// output linear (not gamma encoded!), unmultiplied color from the rest of the shader -vec4 color = ... // regular shading code - -// insert your favorite weighting function here. the color-based factor -// avoids color pollution from the edges of wispy clouds. the z-based -// factor gives precedence to nearer surfaces -float weight = - max(min(1.0, max(max(color.r, color.g), color.b) * color.a), color.a) * - clamp(0.03 / (1e-5 + pow(z / 200, 4.0)), 1e-2, 3e3); - -// blend func: GL_ONE, GL_ONE -// switch to pre-multiplied alpha and weight -accum = vec4(color.rgb * color.a, color.a) * weight; - -// blend func: GL_ZERO, GL_ONE_MINUS_SRC_ALPHA -reveal = color.a; -</code></pre> - - <p> - Finally, after all surfaces have been rendered, composite the result onto the screen using a full-screen pass: - </p> - -<pre><code> -// bind your accum render target to this texture unit -layout (binding = 0) uniform sampler2D rt0; - -// bind your reveal render target to this texture unit -layout (binding = 1) uniform sampler2D rt1; - -// shader output -out vec4 color; - -// fetch pixel information -vec4 accum = texelFetch(rt0, int2(gl_FragCoord.xy), 0); -float reveal = texelFetch(rt1, int2(gl_FragCoord.xy), 0).r; - -// blend func: GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA -color = vec4(accum.rgb / max(accum.a, 1e-5), reveal); -</code></pre> - - <p> - Use this table as a reference for your render targets: - </p> - - <table border="1"> - <tbody> - <tr><td>Render Target</td><td>Format</td><td>Clear</td><td>Src Blend</td><td>Dst Blend</td><td>Write ("Src")</td></tr> - <tr><td>accum</td><td>RGBA16F</td><td>(0,0,0,0)</td><td>ONE</td><td>ONE</td><td><code>(r*a, g*a, b*a, a) * w</code></td></tr> - <tr><td>revealage</td><td>R8</td><td>(1,0,0,0)</td><td>ZERO</td><td>ONE_MINUS_SRC_COLOR</td><td><code>a</code></td></tr> - </tbody> - </table> - - <p> - A total of three rendering passes are needed to accomplish the finished result which is down below: - </p> - - <img src="/img/guest/2020/oit/weighted_blended_result.png" width="640" alt="Weighted, Blended result."> - - <h3>Details</h3> - - <p> - To get started, we would have to setup a quad for our solid and transparent surfaces. The red quad will be the solid one, and the green and blue will be the transparent one. Since we're using the same quad for our screen quad as well, here we define UV values for texture mapping purposes at the screen pass. - </p> - -<pre><code> -float quadVertices[] = { - // positions // uv - -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, - 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, - - 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, - -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 0.0f, 0.0f, 0.0f -}; - -// quad VAO -unsigned int quadVAO, quadVBO; -<function id='33'>glGenVertexArrays</function>(1, &quadVAO); -<function id='12'>glGenBuffers</function>(1, &quadVBO); -<function id='27'>glBindVertexArray</function>(quadVAO); -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, quadVBO); -<function id='31'>glBufferData</function>(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW); -<function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(0); -<function id='30'>glVertexAttribPointer</function>(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); -<function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(1); -<function id='30'>glVertexAttribPointer</function>(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); -<function id='27'>glBindVertexArray</function>(0); -</code></pre> - - <p> - Next, we will create two framebuffers for our solid and transparent passes. Our solid pass needs a color buffer and a depth buffer to store color and depth information. Our transparent pass needs two color buffers to store color accumulation and pixel revealage threshold. We will also attach the opaque framebuffer's depth texture to our transparent framebuffer, to utilize it for depth testing when rendering our transparent surfaces. - </p> - -<pre><code> -// set up framebuffers -unsigned int opaqueFBO, transparentFBO; -<function id='76'>glGenFramebuffers</function>(1, &opaqueFBO); -<function id='76'>glGenFramebuffers</function>(1, &transparentFBO); - -// set up attachments for opaque framebuffer -unsigned int opaqueTexture; -<function id='50'>glGenTextures</function>(1, &opaqueTexture); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, opaqueTexture); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_HALF_FLOAT, NULL); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, 0); - -unsigned int depthTexture; -<function id='50'>glGenTextures</function>(1, &depthTexture); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, depthTexture); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SCR_WIDTH, SCR_HEIGHT, - 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, 0); - -... - -// set up attachments for transparent framebuffer -unsigned int accumTexture; -<function id='50'>glGenTextures</function>(1, &accumTexture); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, accumTexture); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_HALF_FLOAT, NULL); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, 0); - -unsigned int revealTexture; -<function id='50'>glGenTextures</function>(1, &revealTexture); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, revealTexture); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_R8, SCR_WIDTH, SCR_HEIGHT, 0, GL_RED, GL_FLOAT, NULL); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, 0); - -... - -// don't forget to explicitly tell OpenGL that your transparent framebuffer has two draw buffers -const GLenum transparentDrawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; -glDrawBuffers(2, transparentDrawBuffers); -</code></pre> - - <note> - For the sake of this article, we are creating two separate framebuffers, so it would be easier to understand how the technique unfolds. We could omit the opaque framebuffer and use backbuffer for our solid pass or just create a single framebuffer with four attachments all together (opaque, accumulation, revealage, depth) and render to different render targets at each pass. - </note> - - <p> - Before rendering, setup some model matrices for your quads. You can set the Z axis however you want since this is an order-independent technique and objects closer or further to the camera would not impose any problem. - </p> - -<pre><code>glm::mat4 redModelMat = calculate_model_matrix(glm::vec3(0.0f, 0.0f, 0.0f)); -glm::mat4 greenModelMat = calculate_model_matrix(glm::vec3(0.0f, 0.0f, 1.0f)); -glm::mat4 blueModelMat = calculate_model_matrix(glm::vec3(0.0f, 0.0f, 2.0f)); -</code></pre> - - <p> - At this point, we have to perform our solid pass, so configure the render states and bind the opaque framebuffer. - </p> - -<pre><code> -// configure render states -<function id='60'>glEnable</function>(GL_DEPTH_TEST); -<function id='66'>glDepthFunc</function>(GL_LESS); -<function id='65'>glDepthMask</function>(GL_TRUE); -glDisable(GL_BLEND); -<function id='13'><function id='10'>glClear</function>Color</function>(0.0f, 0.0f, 0.0f, 0.0f); - -// bind opaque framebuffer to render solid objects -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, opaqueFBO); -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -</code></pre> - - <p> - We have to reset our depth function and depth mask for the solid pass at every frame since pipeline changes these states further down the line. - </p> - - <p> - Now, draw the solid objects using the solid shader. You can draw alpha cutout objects both at this stage and the next stage as well. The solid shader is just a simple shader that transforms the vertices and draws the mesh with the supplied color. - </p> - -<pre><code> -// use solid shader -solidShader.use(); - -// draw red quad -solidShader.setMat4("mvp", vp * redModelMat); -solidShader.setVec3("color", glm::vec3(1.0f, 0.0f, 0.0f)); -<function id='27'>glBindVertexArray</function>(quadVAO); -<function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); -</code></pre> - - <p> - So far so good. For our transparent pass, like in the solid pass, configure the render states to blend to these render targets as below, then bind the transparent framebuffer and clear its two color buffers to <var>vec4(0.0f)</var> and <var>vec4(1.0)</var>. - </p> - -<pre><code> -// configure render states -// disable depth writes so transparent objects wouldn't interfere with solid pass depth values -<function id='65'>glDepthMask</function>(GL_FALSE); -<function id='60'>glEnable</function>(GL_BLEND); -<function id='70'>glBlendFunc</function>i(0, GL_ONE, GL_ONE); // accumulation blend target -<function id='70'>glBlendFunc</function>i(1, GL_ZERO, GL_ONE_MINUS_SRC_COLOR); // revealge blend target -<function id='72'>glBlendEquation</function>(GL_FUNC_ADD); - -// bind transparent framebuffer to render transparent objects -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, transparentFBO); -// use a four component float array or a glm::vec4(0.0) -<function id='10'>glClear</function>Bufferfv(GL_COLOR, 0, &zeroFillerVec[0]); - // use a four component float array or a glm::vec4(1.0) -<function id='10'>glClear</function>Bufferfv(GL_COLOR, 1, &oneFillerVec[0]); -</code></pre> - - <p> - Then, draw the transparent surfaces with your preferred alpha values. - </p> - -<pre><code> -// use transparent shader -transparentShader.use(); - -// draw green quad -transparentShader.setMat4("mvp", vp * greenModelMat); -transparentShader.setVec4("color", glm::vec4(0.0f, 1.0f, 0.0f, 0.5f)); -<function id='27'>glBindVertexArray</function>(quadVAO); -<function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); - -// draw blue quad -transparentShader.setMat4("mvp", vp * blueModelMat); -transparentShader.setVec4("color", glm::vec4(0.0f, 0.0f, 1.0f, 0.5f)); -<function id='27'>glBindVertexArray</function>(quadVAO); -<function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); -</code></pre> - - <p> - The transparent shader is where half the work is done. It's primarily a shader that collects pixel information for our composite pass: - </p> - -<pre><code> -// shader outputs -layout (location = 0) out vec4 accum; -layout (location = 1) out float reveal; - -// material color -uniform vec4 color; - -void main() -{ - // weight function - float weight = clamp(pow(min(1.0, color.a * 10.0) + 0.01, 3.0) * 1e8 * - pow(1.0 - gl_FragCoord.z * 0.9, 3.0), 1e-2, 3e3); - - // store pixel color accumulation - accum = vec4(color.rgb * color.a, color.a) * weight; - - // store pixel revealage threshold - reveal = color.a; -} -</code></pre> - - <p> - Note that, we are directly using the color passed to the shader as our final fragment color. Normally, if you are in a lighting shader, you want to use the final result of the lighting to store in accumulation and revealage render targets. - </p> - - <p> - Now that everything has been rendered, we have to <def>composite</def> these two images so we can have the finished result. - </p> - - <note> - Compositing is a common method in many techniques that use a post-processing quad drawn all over the screen. Think of it as merging two layers in a photo editing software like Photoshop or Gimp. - </note> - - <p> - In OpenGL, we can achieve this by color blending feature: - </p> - -<pre><code> -// set render states -<function id='66'>glDepthFunc</function>(GL_ALWAYS); -<function id='60'>glEnable</function>(GL_BLEND); -<function id='70'>glBlendFunc</function>(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - -// bind opaque framebuffer -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, opaqueFBO); - -// use composite shader -compositeShader.use(); - -// draw screen quad -<function id='49'>glActiveTexture</function>(GL_TEXTURE0); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, accumTexture); -<function id='49'>glActiveTexture</function>(GL_TEXTURE1); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, revealTexture); -<function id='27'>glBindVertexArray</function>(quadVAO); -<function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); -</code></pre> - - <p> - Composite shader is where the other half of the work is done. We're basically merging two layers, one being the solid objects image and the other being the transparent objects image. Accumulation buffer tells us about the color and revealage buffer determines the visibility of the the underlying pixel: - </p> - -<pre><code> -// shader outputs -layout (location = 0) out vec4 frag; - -// color accumulation buffer -layout (binding = 0) uniform sampler2D accum; - -// revealage threshold buffer -layout (binding = 1) uniform sampler2D reveal; - -// epsilon number -const float EPSILON = 0.00001f; - -// calculate floating point numbers equality accurately -bool isApproximatelyEqual(float a, float b) -{ - return abs(a - b) <= (abs(a) < abs(b) ? abs(b) : abs(a)) * EPSILON; -} - -// get the max value between three values -float max3(vec3 v) -{ - return max(max(v.x, v.y), v.z); -} - -void main() -{ - // fragment coordination - ivec2 coords = ivec2(gl_FragCoord.xy); - - // fragment revealage - float revealage = texelFetch(reveal, coords, 0).r; - - // save the blending and color texture fetch cost if there is not a transparent fragment - if (isApproximatelyEqual(revealage, 1.0f)) - discard; - - // fragment color - vec4 accumulation = texelFetch(accum, coords, 0); - - // suppress overflow - if (isinf(max3(abs(accumulation.rgb)))) - accumulation.rgb = vec3(accumulation.a); - - // prevent floating point precision bug - vec3 average_color = accumulation.rgb / max(accumulation.a, EPSILON); - - // blend pixels - frag = vec4(average_color, 1.0f - revealage); -} -</code></pre> - - <p> - Note that, we are using some helper functions like <fun>isApproximatelyEqual</fun> or <fun>max3</fun> to help us with the accurate calculation of floating-point numbers. Due to inaccuracy of floating-point numbers calculation in current generation processors, we need to compare our values with an extremely small amount called an <def>epsilon</def> to avoid underflows or overflows. - </p> - - <p> - Also, we don't need an intermediate framebuffer to do compositing. We can use our opaque framebuffer as the base framebuffer and paint over it since it already has the opaque pass information. Plus, we're stating that all depth tests should pass since we want to paint over the opaque image. - </p> - - <p> - Finally, draw your composited image (which is the opaque texture attachment since you rendered your transparent image over it in the last pass) onto the backbuffer and observe the result. - </p> - -<pre><code> -// set render states -glDisable(GL_DEPTH); -<function id='65'>glDepthMask</function>(GL_TRUE); // enable depth writes so <function id='10'>glClear</function> won't ignore clearing the depth buffer -glDisable(GL_BLEND); - -// bind backbuffer -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -<function id='13'><function id='10'>glClear</function>Color</function>(0.0f, 0.0f, 0.0f, 0.0f); -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - -// use screen shader -screenShader.use(); - -// draw final screen quad -<function id='49'>glActiveTexture</function>(GL_TEXTURE0); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, opaqueTexture); -<function id='27'>glBindVertexArray</function>(quadVAO); -<function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); -</code></pre> - - <p> - Screen shader is just a simple post-processing shader which draws a full-screen quad. - </p> - - <p> - In a regular pipeline, you would also apply gamma-correction, tone-mapping, etc. in an intermediate post-processing framebuffer before you render to backbuffer, but ensure you are not applying them while rendering your solid and transparent surfaces and also not before composition since this transparency technique needs raw color values for calculating transparent pixels. - </p> - - <p> - Now, the interesting part is to play with the Z axis of your objects to see order-independence in action. Try to place your transparent objects behind the solid object or mess up the orders entirely. - </p> - - <img src="/img/guest/2020/oit/weighted_blended_reordered.png" width="640" alt="Weighted, Blended reordered."> - - <p> - In the image above, the green quad is rendered after the red quad, but behind it, and if you move the camera around to see the green quad from behind, you won't see any artifacts. - </p> - - <p> - As stated earlier, one limitation that this technique imposes is that for scenes with higher depth/alpha complexity we need to tune the weighting function to achieve the correct result. Luckily, a number of tested weighting functions are provided in the paper which you can refer and investigate them for your environment. - </p> - - <p> - Be sure to also check the colored transmission transparency which is the improved version of this technique in the links below. - </p> - - <p> - You can find the source code for this demo <a href="/code_viewer_gh.php?code=src/8.guest/2020/oit/weighted_blended.cpp" target="_blank">here</a>. - </p> - - <h2>Further reading</h2> - - <ul> - <li><a href="http://jcgt.org/published/0002/02/09" href="_blank">Weighted, Blended paper</a>: The original paper published in the journal of computer graphics. A brief history of the transparency and the emergence of the technique itself is provided. This is a must for the dedicated readers.</li> - <li><a href="http://casual-effects.blogspot.com/2014/03/weighted-blended-order-independent.html" href="_blank">Weighted, Blended introduction</a>: Casual Effects is Morgan McGuire's personal blog. This post is the introduction of their technique which goes into further details and is definitely worth to read. Plus, there are videos of their implementation live in action that you would not want to miss.</li> - <li><a href="http://casual-effects.blogspot.com/2015/03/implemented-weighted-blended-order.html" href="_blank">Weighted, Blended for implementors</a>: And also another blog post by him on implementing the technique for implementors.</li> - <li><a href="http://casual-effects.blogspot.com/2015/03/colored-blended-order-independent.html" href="_blank">Weighted, Blended and colored transmission</a>: And another blog post on colored transmission for transparent surfaces.</li> - <li><a href="http://bagnell.github.io/cesium/Apps/Sandcastle/gallery/OIT.html" href="_blank">A live implementation of the technique</a>: This is a live WebGL visualization from Cesium engine which accepts weighting functions for you to test in your browser!</li> - </ul> - -<author> - <strong>Article by: </strong>Mahan Heshmati Moghaddam<br/> - <strong>Contact: </strong><a href="mailto:mahangm@gmail.com" target="_blank">e-mail</a> -</author> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Guest-Articles/2020/Skeletal-Animation.html b/translation/Guest-Articles/2020/Skeletal-Animation.html @@ -1,1158 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Skeletal Animation</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Skeletal Animation</h1> -<h1 id="content-url" style='display:none;'>Guest-Articles/2020/Skeletal-Animation</h1> -<p>3D Animations can bring our games to life. Objects in 3D world like humans and & - animals feel more organic when they move their limbs to do certain things like walking, running & attacking. - This tutorial is about Skeletal animation which you all have been waiting for. We will first understand the concept thoroughly and then understand the data - we need to animate a 3D model using Assimp. I'd recommend you to finish the <a href="https://learnopengl.com/Model-Loading/Assimp">Model Loading</a> chapter of this saga as this tutorial code continues from there. You can still understand the concept and implement it in your way. So let's get started.</p> - - - <h3>Interpolation</h3> -<p>To understand how animation works at basic level we need to understand the concept of Interpolation. - Interpolation can be defined as something happening over time. Like an enemy moving from point A to point B in time T i.e Translation happening over time . - A gun turret smoothly rotates to face the target i.e Rotation happening over time and a tree is scaling up from size A to size B in time T i.e Scaling happening over time.</p> -<p>A simple interpolation equation used for Translation and Scale looks like this..</p> -<p style="text-align: center;"><strong>a = a * (1 - t) + b * t </strong></p> -<p>It is known as as Linear Interpolation equation or Lerp. For Rotation we cannot use Vector. - The reason for that is if we went ahead and tried to use the linear interpolation equation on a vector of X(Pitch),Y(Yaw) & Z(Roll), the interpolation won't be linear. - You will encounter - weird issues like The - Gimbal Lock(See references section below to learn about it). To avoid this issue we use Quaternion for rotations. - Quaternion provides something called The Spherical Interpolation or - Slerp equation which gives the same result as Lerp but for two rotations A & B. - I won't be able to explain how the equation works because its out of the scope for now. You can surely checkout references section below to - understand The Quaternion. -</p> -<h3>Components of An Animated Model : Skin, Bones and Keyframes</h3> -<p>The whole process of an animation starts with the addition of the first component which is The Skin in a software like blender or Maya. - Skin is nothing but meshes which add visual aspect to the model to tell the viewer how it looks like. - But If you want to move any mesh then just like the real world, you need to add Bones. You can see the images below to understand how it looks in software like blender....</p> -<p> </p> -<img src="/img/guest/2020/skeletal_animation/skin.png" alt="skin" width="300" height="300" class="clean"> <img src="/img/guest/2020/skeletal_animation/bones.png" alt="bones" width="300" height="300"class="clean"> <img src="/img/guest/2020/skeletal_animation/merged.png" alt="skin and bones" width="300" height="300"class="clean"> -<p>These bones are usually added in hierarchical fashion for characters like humans & animals and the reason is pretty obvious. We want parent-child relationship among limbs. - For example, If we move our right shoulder then our right bicep, forearm, hand and fingers should move as well. This is how the hierarchy looks like....</p> -<p> </p> - - <p> - <img src="/img/guest/2020/skeletal_animation/parent_child.png" alt="" width="853" height="425"/></p> - -<p>In the above diagram if you grab the hip bone and move it, all limbs will be affected by its movement.</p> -<p>At this point, we are ready to create KeyFrames for an animation. Keyframes are poses at different point of time in an animation. We will interpolate between - these Keyframes to go from one pose to another pose smoothly in our code. Below you can see how poses are created for a simple 4 frame jump animation...</p> - <p><img src="/img/guest/2020/skeletal_animation/poses.gif" width="300px" class="clean" alt=""/> <img src="/img/guest/2020/skeletal_animation/interpolating.gif" class="clean" width="300px" alt="Interpolation bw frames" hspace="20"/></p> -<p> </p> -<h3>How Assimp holds animation data</h3> -<p>We are almost there to the code part but first we need to understand how assimp holds imported animation data. Look at the diagram below..</p> - <p><img src="/img/guest/2020/skeletal_animation/assimp1.jpeg" alt="" width="710" height="800"/></p> -<p>Just like in the <a href="https://learnopengl.com/Model-Loading/Assimp">Model Loading</a> chapter, we will start with the <code>aiScene</code> pointer - which holds a pointer to the root node and look what do we have here, an array of Animations. - This array of <code>aiAnimation</code> contains the general information like duration of an animation represented here as - <code>mDuration</code> and then we have a <code>mTicksPerSecond</code> variable, which controls how fast - we should interpolate between frames. If you remember from the last section that an animation has keyframes. - Similary, an <code>aiAnimation</code> contains an <code>aiNodeAnim</code> array called Channels. - This array of contains all bones and their keyframes which are going to be engaged in an animation. - - An <code>aiNodeAnim</code> contains name of the bone and you - will find 3 types of keys to interpolate between here, Translation,Rotation & Scale.</p> - -<p>Alright, there's one last thing we need to understand and we are good to go for writing some code.</p> - -<p> </p> -<h3>Influence of multiple bones on vertices</h3> -<p>When we curl our forearm and we see our biceps muscle pop up. We can also say that forearm bone transformation is affecting vertices on our biceps. - Similary, there could be multiple bones affecting a single vertex in a mesh. - For characters like solid metal robots all forearm vertices will only be affected by forearm bone but for characters like humans, animals etc, there could be - upto 4 bones which can affect a vertex. Let's see how assimp stores that information...</p> -<p> </p> - <p><img src="/img/guest/2020/skeletal_animation/assimp2.jpeg" alt="" width="760" height="860"/></p> -<p> </p> -<p>We start with the <code>aiScene</code> pointer again which contains an array of all aiMeshes. - Each <code>aiMesh</code> object has an array of <code>aiBone</code> which contains the information like - how much influence this <code>aiBone</code> will have on set of vertices on the mesh. - aiBone contains the name of the bone, an array of <code>aiVertexWeight</code> which basically - tells us how much influence this <code>aiBone</code> will have on what vertices on the mesh. - Now we have one more member of <code>aiBone</code> which is offsetMatrix. It's a 4x4 matrix - used to transform vertices from model space to their bone space. - You can see this in action in images below....</p> - - <img src="/img/guest/2020/skeletal_animation/mesh_space.png" class="clean" alt="Mesh Space" style="width:50%"> - <img src="/img/guest/2020/skeletal_animation/bone_space.png" class="clean" alt="Bone Space" style="width:50%"> - <p> - When vertices are in bone space they will be transformed relative to their bone - as they are supposed to. You will soon see this in action - in code. - </p> - -<h3>Finally! Let's code.</h3> -<p>Thank you for making it this far. We will start with directly looking at the end result which is our final vertex - shader code. This will give us good sense what we need at the end.. </p> - -<pre><code>#version 430 core - -layout(location = 0) in vec3 pos; -layout(location = 1) in vec3 norm; -layout(location = 2) in vec2 tex; -layout(location = 3) in ivec4 boneIds; -layout(location = 4) in vec4 weights; - -uniform mat4 projection; -uniform mat4 view; -uniform mat4 model; - -const int MAX_BONES = 100; -const int MAX_BONE_INFLUENCE = 4; -uniform mat4 finalBonesMatrices[MAX_BONES]; - -out vec2 TexCoords; - -void main() -{ - vec4 totalPosition = vec4(0.0f); - for(int i = 0 ; i < MAX_BONE_INFLUENCE ; i++) - { - if(boneIds[i] == -1) - continue; - if(boneIds[i] >= MAX_JOINTS) - { - totalPosition = vec4(pos,1.0f); - break; - } - vec4 localPosition = finalBoneMatrices[boneIds[i]] * vec4(pos,1.0f); - totalPosition += localPosition * weights[i]; - vec3 localNormal = mat3(finalBoneMatrices[boneIds[i]]) * norm; - } - - mat4 viewModel = view * model; - gl_Position = projection * viewModel * totalPosition; - TexCoords = tex; -} -</code></pre> - -<p> - Fragment shader remains the same from the <a href="https://learnopengl.com/Model-Loading/Model">model loading</a> chapter. - Starting from the top you see two new attributes layout declaration. - First <code>boneIds</code> and second is <code>weights</code>. we also have - a uniform array <code>finalBonesMatrices</code> which stores transformations of all bones. - <code>boneIds</code> contains indices which are used to read the <code>finalBonesMatrices</code> - array and apply those transformation to <code>pos</code> vertex with their respective weights - stored in <code> weights </code> array. This happens inside <code> for </code> loop above. - Now let's add support in our <code>Mesh</code> class for bone weights first.. -</p> - -<pre><code>#define MAX_BONE_INFLUENCE 4 - -struct Vertex { - // position - glm::vec3 Position; - // normal - glm::vec3 Normal; - // texCoords - glm::vec2 TexCoords; - - //bone indexes which will influence this vertex - int m_BoneIDs[MAX_BONE_INFLUENCE]; - - //weights from each bone - float m_Weights[MAX_BONE_INFLUENCE]; -}; -</code></pre> - -<p> - We have added two new attributes for the <code>Vertex</code>, just like we saw in our vertex shader. - Now's let's load them in GPU buffers just like other attributes in our <code>Mesh::setupMesh </code> function... - </p> - -<pre><code>class Mesh -{ - ... - - void setupMesh() - { - ... - - // ids - <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(3); - glVertexAttribIPointer(3, 4, GL_INT, sizeof(Vertex), - (void*)offsetof(Vertex, m_BoneIDs)); - - // weights - <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(4); - <function id='30'>glVertexAttribPointer</function>(4, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), - (void*)offsetof(Vertex, m_Weights)); - - ... - } - ... -} -</code></pre> -<p>Just like before, except now we have added 3 and 4 layout location ids for <code>boneIds</code> and <code>weights</code>. One imporant thing to notice here is how we are passing data for <code>boneIds</code>. We are using <code>glVertexAttribIPointer</code> and we passed GL_INT as third parameter. </p> -<p>Now we can extract the bone-weight information from the assimp data structure. Let's make some changes in Model class...</p> - -<pre><code>struct BoneInfo -{ - /*id is index in finalBoneMatrices*/ - int id; - - /*offset matrix transforms vertex from model space to bone space*/ - glm::mat4 offset; - -}; -</code></pre> - -<p> This <code> BoneInfo </code> will store our offset matrix and also a unique id which will - be used as an index to store it in <code>finalBoneMatrices</code> array we saw earlier in our shader. -Now we will add bone weight extraction support in <code>Model</code>... </p> - -<pre><code>class Model -{ -private: - ... - std::map<string, BoneInfo> m_BoneInfoMap; // - int m_BoneCounter = 0; - - ... - void SetVertexBoneDataToDefault(Vertex& vertex) - { - for (int i = 0; i < MAX_BONE_WEIGHTS; i++) - { - vertex.m_BoneIDs[i] = -1; - vertex.m_Weights[i] = 0.0f; - } - } - - Mesh processMesh(aiMesh* mesh, const aiScene* scene) - { - vector vertices; - vector indices; - vector textures; - - for (unsigned int i = 0; i < mesh->mNumVertices; i++) - { - Vertex vertex; - - SetVertexBoneDataToDefault(vertex); - - vertex.Position = AssimpGLMHelpers::GetGLMVec(mesh->mVertices[i]); - vertex.Normal = AssimpGLMHelpers::GetGLMVec(mesh->mNormals[i]); - - if (mesh->mTextureCoords[0]) - { - glm::vec2 vec; - vec.x = mesh->mTextureCoords[0][i].x; - vec.y = mesh->mTextureCoords[0][i].y; - vertex.TexCoords = vec; - } - else - vertex.TexCoords = glm::vec2(0.0f, 0.0f); - - vertices.push_back(vertex); - } - ... - ExtractBoneWeightForVertices(vertices,mesh,scene); - - return Mesh(vertices, indices, textures); - } - - void SetVertexBoneData(Vertex& vertex, int boneID, float weight) - { - for (int i = 0; i < MAX_BONE_WEIGHTS; ++i) - { - if (vertex.m_BoneIDs[i] < 0) - { - vertex.m_Weights[i] = weight; - vertex.m_BoneIDs[i] = boneID; - break; - } - } - } - - void ExtractBoneWeightForVertices(std::vector& vertices, aiMesh* mesh, - const aiScene* scene) - { - for (int boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex) - { - int boneID = -1; - std::string boneName = mesh->mBones[boneIndex]->mName.C_Str(); - if (m_BoneInfoMap.find(boneName) == m_BoneInfoMap.end()) - { - BoneInfo newBoneInfo; - newBoneInfo.id = m_BoneCounter; - newBoneInfo.offset = AssimpGLMHelpers:: - ConvertMatrixToGLMFormat(mesh->mBones[boneIndex]->mOffsetMatrix); - m_BoneInfoMap[boneName] = newBoneInfo; - boneID = m_BoneCounter; - m_BoneCounter++; - } - else - { - boneID = m_BoneInfoMap[boneName].id; - } - assert(boneID != -1); - auto weights = mesh->mBones[boneIndex]->mWeights; - int numWeights = mesh->mBones[boneIndex]->mNumWeights; - - for (int weightIndex = 0; weightIndex < numWeights; ++weightIndex) - { - int vertexId = weights[weightIndex].mVertexId; - float weight = weights[weightIndex].mWeight; - assert(vertexId <= vertices.size()); - SetVertexBoneData(vertices[vertexId], boneID, weight); - } - } - } - ....... -}; -</code></pre> - -<p>We start by declaring a map <code>m_BoneInfoMap</code> and a counter <code>m_BoneCounter</code> - which will be incremented as soon as we read a new bone. - we saw in the diagram earlier that each <code>aiMesh</code> contains all - aiBones which are associated with the <code>aiMesh</code>. - The whole process of the bone-weight extraction starts from the - <code> processMesh </code> - function. For each loop iteration we are setting <code>m_BoneIDs</code> and <code>m_Weights</code> to - their default values - by calling function <code>SetVertexBoneDataToDefault</code>. - Just before the <code>processMesh</code> function ends, we call the - <code>ExtractBoneWeightData</code>. In the <code>ExtractBoneWeightData</code> we run - a for loop for each <code>aiBone</code> and check if this bone already exists in the <code>m_BoneInfoMap</code>. - If we couldn't find it then it's considered a new bone and we create new <code>BoneInfo</code> - with an id and store its associated <code>mOffsetMatrix</code> to it. Then we store this new <code>BoneInfo</code> - in <code>m_BoneInfoMap</code> and then we increment the <code>m_BoneCounter</code> counter to create - an id for next bone. In case we find the bone name in <code>m_BoneInfoMap</code> then - that means this bone affects vertices of mesh out of - its scope. So we take it's Id and proceed further to know which vertices it affects. </p> - - <p> One thing to notice that we are calling <code>AssimpGLMHelpers::ConvertMatrixToGLMFormat</code>. - Assimp store its matrix data in different format than GLM so this function just gives us our matrix in GLM format. - </p> - <p>We have extracted the offsetMatrix for the bone and now we will simply iterate its <code>aiVertexWeight</code>array - and extract all vertices indices which will be influenced by this bone along with their - respective weights and call <code>SetVertexBoneData</code> to fill up <code>Vertex.boneIds</code> and <code>Vertex.weights</code> with extracted information. </p> - - <p>Phew! You deserve a coffee break at this point. </p> - -<h3>Bone,Animation & Animator classes</h3> -<p>Here's high level view of classes..</p> - - <p><img src="/img/guest/2020/skeletal_animation/bird_eye_view.png" class="clean" alt="" width="700"/></p> - -<p> Let us remind ourselves what we are trying to achieve. For each rendering frame we want to interpolate all bones in heirarchy smoothly and get their final transformations matrices which will be supplied to shader - uniform <code>finalBonesMatrices</code>. - Here's what each class does... - - <p><b>Bone</b> : A single bone which reads all keyframes data from <code>aiNodeAnim</code>. It will also interpolate between its keys i.e Translation,Scale & Rotation based on the current animation time. </p> - <p><b>AssimpNodeData</b> : This struct will help us to isolate our <code><b>Animation</b> from Assimp. </code> </p> - <p><b>Animation</b> : An asset which reads data from aiAnimation and create a heirarchical record of <code><b>Bone</b></code>s </p> - <p><b>Animator</b> : This will read the heirarchy of <code>AssimpNodeData</code>, - Interpolate all bones in a recursive manner and then prepare final bone transformation matrices for us that we need. - -</p> - -<p> - Here's the code for <code>Bone</code>... - - <pre><code>struct KeyPosition -{ - glm::vec3 position; - float timeStamp; -}; - -struct KeyRotation -{ - glm::quat orientation; - float timeStamp; -}; - -struct KeyScale -{ - glm::vec3 scale; - float timeStamp; -}; - -class Bone -{ -private: - std::vector<KeyPosition> m_Positions; - std::vector<KeyRotation> m_Rotations; - std::vector<KeyScale> m_Scales; - int m_NumPositions; - int m_NumRotations; - int m_NumScalings; - - glm::mat4 m_LocalTransform; - std::string m_Name; - int m_ID; -public: - /*reads keyframes from aiNodeAnim*/ - Bone(const std::string& name, int ID, const aiNodeAnim* channel) - : - m_Name(name), - m_ID(ID), - m_LocalTransform(1.0f) - { - m_NumPositions = channel->mNumPositionKeys; - - for (int positionIndex = 0; positionIndex < m_NumPositions; ++positionIndex) - { - aiVector3D aiPosition = channel->mPositionKeys[positionIndex].mValue; - float timeStamp = channel->mPositionKeys[positionIndex].mTime; - KeyPosition data; - data.position = AssimpGLMHelpers::GetGLMVec(aiPosition); - data.timeStamp = timeStamp; - m_Positions.push_back(data); - } - - m_NumRotations = channel->mNumRotationKeys; - for (int rotationIndex = 0; rotationIndex < m_NumRotations; ++rotationIndex) - { - aiQuaternion aiOrientation = channel->mRotationKeys[rotationIndex].mValue; - float timeStamp = channel->mRotationKeys[rotationIndex].mTime; - KeyRotation data; - data.orientation = AssimpGLMHelpers::GetGLMQuat(aiOrientation); - data.timeStamp = timeStamp; - m_Rotations.push_back(data); - } - - m_NumScalings = channel->mNumScalingKeys; - for (int keyIndex = 0; keyIndex < m_NumScalings; ++keyIndex) - { - aiVector3D scale = channel->mScalingKeys[keyIndex].mValue; - float timeStamp = channel->mScalingKeys[keyIndex].mTime; - KeyScale data; - data.scale = AssimpGLMHelpers::GetGLMVec(scale); - data.timeStamp = timeStamp; - m_Scales.push_back(data); - } - } - - /* Interpolates b/w positions,rotations & scaling keys based on the curren time of the - animation and prepares the local transformation matrix by combining all keys tranformations */ - void Update(float animationTime) - { - glm::mat4 translation = InterpolatePosition(animationTime); - glm::mat4 rotation = InterpolateRotation(animationTime); - glm::mat4 scale = InterpolateScaling(animationTime); - m_LocalTransform = translation * rotation * scale; - } - - glm::mat4 GetLocalTransform() { return m_LocalTransform; } - std::string GetBoneName() const { return m_Name; } - int GetBoneID() { return m_ID; } - - /* Gets the current index on mKeyPositions to interpolate to based on the current - animation time */ - int GetPositionIndex(float animationTime) - { - for (int index = 0; index < m_NumPositions - 1; ++index) - { - if (animationTime < m_Positions[index + 1].timeStamp) - return index; - } - assert(0); - } - - /* Gets the current index on mKeyRotations to interpolate to based on the current - animation time */ - int GetRotationIndex(float animationTime) - { - for (int index = 0; index < m_NumRotations - 1; ++index) - { - if (animationTime < m_Rotations[index + 1].timeStamp) - return index; - } - assert(0); - } - - /* Gets the current index on mKeyScalings to interpolate to based on the current - animation time */ - int GetScaleIndex(float animationTime) - { - for (int index = 0; index < m_NumScalings - 1; ++index) - { - if (animationTime < m_Scales[index + 1].timeStamp) - return index; - } - assert(0); - } -private: - - /* Gets normalized value for Lerp & Slerp*/ - float GetScaleFactor(float lastTimeStamp, float nextTimeStamp, float animationTime) - { - float scaleFactor = 0.0f; - float midWayLength = animationTime - lastTimeStamp; - float framesDiff = nextTimeStamp - lastTimeStamp; - scaleFactor = midWayLength / framesDiff; - return scaleFactor; - } - - /* figures out which position keys to interpolate b/w and performs the interpolation - and returns the translation matrix */ - glm::mat4 InterpolatePosition(float animationTime) - { - if (1 == m_NumPositions) - return <function id='55'>glm::translate</function>(glm::mat4(1.0f), m_Positions[0].position); - - int p0Index = GetPositionIndex(animationTime); - int p1Index = p0Index + 1; - float scaleFactor = GetScaleFactor(m_Positions[p0Index].timeStamp, - m_Positions[p1Index].timeStamp, animationTime); - glm::vec3 finalPosition = glm::mix(m_Positions[p0Index].position, - m_Positions[p1Index].position - , scaleFactor); - return <function id='55'>glm::translate</function>(glm::mat4(1.0f), finalPosition); - } - - /* figures out which rotations keys to interpolate b/w and performs the interpolation - and returns the rotation matrix */ - glm::mat4 InterpolateRotation(float animationTime) - { - if (1 == m_NumRotations) - { - auto rotation = glm::normalize(m_Rotations[0].orientation); - return glm::toMat4(rotation); - } - - int p0Index = GetRotationIndex(animationTime); - int p1Index = p0Index + 1; - float scaleFactor = GetScaleFactor(m_Rotations[p0Index].timeStamp, - m_Rotations[p1Index].timeStamp, animationTime); - glm::quat finalRotation = glm::slerp(m_Rotations[p0Index].orientation, - m_Rotations[p1Index].orientation, scaleFactor); - finalRotation = glm::normalize(finalRotation); - return glm::toMat4(finalRotation); - } - - /* figures out which scaling keys to interpolate b/w and performs the interpolation - and returns the scale matrix */ - glm::mat4 Bone::InterpolateScaling(float animationTime) - { - if (1 == m_NumScalings) - return <function id='56'>glm::scale</function>(glm::mat4(1.0f), m_Scales[0].scale); - - int p0Index = GetScaleIndex(animationTime); - int p1Index = p0Index + 1; - float scaleFactor = GetScaleFactor(m_Scales[p0Index].timeStamp, - m_Scales[p1Index].timeStamp, animationTime); - glm::vec3 finalScale = glm::mix(m_Scales[p0Index].scale, - m_Scales[p1Index].scale, scaleFactor); - return <function id='56'>glm::scale</function>(glm::mat4(1.0f), finalScale); - } -}; -</code></pre> - - <p> - - We start by creating 3 structs for our key types. Each struct holds a value and a time stamp. Timestamp tells us at what point of an animation we need to interpolate to its value. - <code>Bone</code> has a constructor which reads from <code>aiNodeAnim</code> and stores keys and their timestamps to <code>mPositionKeys, mRotationKeys & mScalingKeys </code>. The main interpolation process - starts from <code>Update(float animationTime)</code> which gets called every frame. This function calls respective interpolation functions for all key types and combines all final interpolation results - and store it to a 4x4 Matrix <code>m_LocalTransform</code>. The interpolations functions for translation & scale keys are similar but for rotation we are using <code>Slerp</code> to interpolate between quaternions. - Both <code>Lerp</code> & <code>Slerp</code> takes 3 arguments. First argument takes last key, second argument takes next key and third argument takes value of range 0-1,we call it scale factor here. - Let's see how we calculate this scale factor in function <code>GetScaleFactor</code>... - - <p><img src="/img/guest/2020/skeletal_animation/scale_factor.png" alt="skin"/></p> - - <p>In code...</p> - - <p><b> float midWayLength = animationTime - lastTimeStamp; </b></p> - <p><b> float framesDiff = nextTimeStamp - lastTimeStamp;</b></p> - <p><b> scaleFactor = midWayLength / framesDiff; </b></p> - <p></p> - </p> - - Let's move on to <code><b>Animation</b></code> class now... - -<pre><code>struct AssimpNodeData -{ - glm::mat4 transformation; - std::string name; - int childrenCount; - std::vector<AssimpNodeData> children; -}; - -class Animation -{ -public: - Animation() = default; - - Animation(const std::string& animationPath, Model* model) - { - Assimp::Importer importer; - const aiScene* scene = importer.ReadFile(animationPath, aiProcess_Triangulate); - assert(scene && scene->mRootNode); - auto animation = scene->mAnimations[0]; - m_Duration = animation->mDuration; - m_TicksPerSecond = animation->mTicksPerSecond; - ReadHeirarchyData(m_RootNode, scene->mRootNode); - ReadMissingBones(animation, *model); - } - - ~Animation() - { - } - - Bone* FindBone(const std::string& name) - { - auto iter = std::find_if(m_Bones.begin(), m_Bones.end(), - [&](const Bone& Bone) - { - return Bone.GetBoneName() == name; - } - ); - if (iter == m_Bones.end()) return nullptr; - else return &(*iter); - } - - - inline float GetTicksPerSecond() { return m_TicksPerSecond; } - - inline float GetDuration() { return m_Duration;} - - inline const AssimpNodeData& GetRootNode() { return m_RootNode; } - - inline const std::map<std::string,BoneInfo>& GetBoneIDMap() - { - return m_BoneInfoMap; - } - -private: - void ReadMissingBones(const aiAnimation* animation, Model& model) - { - int size = animation->mNumChannels; - - auto& boneInfoMap = model.GetBoneInfoMap();//getting m_BoneInfoMap from Model class - int& boneCount = model.GetBoneCount(); //getting the m_BoneCounter from Model class - - //reading channels(bones engaged in an animation and their keyframes) - for (int i = 0; i < size; i++) - { - auto channel = animation->mChannels[i]; - std::string boneName = channel->mNodeName.data; - - if (boneInfoMap.find(boneName) == boneInfoMap.end()) - { - boneInfoMap[boneName].id = boneCount; - boneCount++; - } - m_Bones.push_back(Bone(channel->mNodeName.data, - boneInfoMap[channel->mNodeName.data].id, channel)); - } - - m_BoneInfoMap = boneInfoMap; - } - - void ReadHeirarchyData(AssimpNodeData& dest, const aiNode* src) - { - assert(src); - - dest.name = src->mName.data; - dest.transformation = AssimpGLMHelpers::ConvertMatrixToGLMFormat(src->mTransformation); - dest.childrenCount = src->mNumChildren; - - for (int i = 0; i < src->mNumChildren; i++) - { - AssimpNodeData newData; - ReadHeirarchyData(newData, src->mChildren[i]); - dest.children.push_back(newData); - } - } - float m_Duration; - int m_TicksPerSecond; - std::vector<Bone> m_Bones; - AssimpNodeData m_RootNode; - std::map<std::string, BoneInfo> m_BoneInfoMap; -}; -</code></pre> - - <p> Here, creation of an <code>Animation</code> object starts with a constructor. It takes two arguments. First, path to the animation file & second parameter is the <code>Model</code> for this animation. -You will see later ahead why we need this <code>Model</code> reference here. We then create an <code>Assimp::Importer</code> to read the animation file, followed by an <code>assert</code> check which will throw -an error if animation could not be found. Then we read general animation data like how long is this animation which is <code>mDuration</code> and the animation speed represented by <code>mTicksPerSecond</code>. -We then call <code>ReadHeirarchyData</code> which replicates <code>aiNode</code> heirarchy of Assimp and creates heirarchy of <code>AssimpNodeData</code>. -</p> - -<p> Then we call a function called <code>ReadMissingBones</code>. I had to write this function because sometimes when I loaded FBX model separately, it had some bones missing and I found those missing bones in -the animation file. This function reads the missing bones information and stores their information in <code>m_BoneInfoMap</code> of <code>Model</code> and saves a reference of <code>m_BoneInfoMap</code> locally in -the m_BoneInfoMap.</p> - -<p>And we have our animation ready. Now let's look at our final stage, The Animator class...</p> - -<pre><code>class Animator -{ -public: - Animator::Animator(Animation* Animation) - { - m_CurrentTime = 0.0; - m_CurrentAnimation = currentAnimation; - - m_FinalBoneMatrices.reserve(100); - - for (int i = 0; i < 100; i++) - m_FinalBoneMatrices.push_back(glm::mat4(1.0f)); - } - - void Animator::UpdateAnimation(float dt) - { - m_DeltaTime = dt; - if (m_CurrentAnimation) - { - m_CurrentTime += m_CurrentAnimation->GetTicksPerSecond() * dt; - m_CurrentTime = fmod(m_CurrentTime, m_CurrentAnimation->GetDuration()); - CalculateBoneTransform(&m_CurrentAnimation->GetRootNode(), glm::mat4(1.0f)); - } - } - - void Animator::PlayAnimation(Animation* pAnimation) - { - m_CurrentAnimation = pAnimation; - m_CurrentTime = 0.0f; - } - - void Animator::CalculateBoneTransform(const AssimpNodeData* node, glm::mat4 parentTransform) - { - std::string nodeName = node->name; - glm::mat4 nodeTransform = node->transformation; - - Bone* Bone = m_CurrentAnimation->FindBone(nodeName); - - if (Bone) - { - Bone->Update(m_CurrentTime); - nodeTransform = Bone->GetLocalTransform(); - } - - glm::mat4 globalTransformation = parentTransform * nodeTransform; - - auto boneInfoMap = m_CurrentAnimation->GetBoneIDMap(); - if (boneInfoMap.find(nodeName) != boneInfoMap.end()) - { - int index = boneInfoMap[nodeName].id; - glm::mat4 offset = boneInfoMap[nodeName].offset; - m_FinalBoneMatrices[index] = globalTransformation * offset; - } - - for (int i = 0; i < node->childrenCount; i++) - CalculateBoneTransform(&node->children[i], globalTransformation); - } - - std::vector<glm::mat4> GetFinalBoneMatrices() - { - return m_FinalBoneMatrices; - } - -private: - std::vector<glm::mat4> m_FinalBoneMatrices; - Animation* m_CurrentAnimation; - float m_CurrentTime; - float m_DeltaTime; -}; -</code></pre> - -<p> - - <code>Animator</code> constructor takes an animation to play and - then it proceeds to reset the animation time <code>m_CurrentTime</code> to 0. - It also initializes <code>m_FinalBoneMatrices</code> - which is a <code>std::vector<glm::mat4></code>. - The main point of attention here is <code>UpdateAnimation(float deltaTime)</code> function. - It advances the <code>m_CurrentTime</code> with rate of - <code>m_TicksPerSecond</code> and then calls the <code>CalculateBoneTransform</code> function. - We will pass two arguments in the start, first is the <code>m_RootNode</code> of <code>m_CurrentAnimation</code> - and second is an identity matrix passed as <code>parentTransform</code> This function then check if <code>m_RootNode</code>s bone is engaged in this animation by finding it in <code>m_Bones</code> array of <code>Animation</code>. - If bone is found then it calls <code>Bone.Update()</code> function which interpolates all bones and return local bone transform matrix to - <code>nodeTransform</code>. - But this is local space matrix and will move bone around origin if passed in shaders. So we multiply this <code>nodeTransform</code> with <code>parentTransform</code> and - we store the result in <code>globalTransformation</code>. This would be enough but vertices are still in default model space. - we find offset matrix in <code>m_BoneInfoMap</code> and then multiply it - with <code>globalTransfromMatrix</code>. - We will also get the id index which will be used to write final transformation of this bone to m_FinalBoneMatrices. - </p> - - <p> - Finally! we call <code>CalculateBoneTransform</code> for each child nodes of this node and pass <code>globalTransformation</code> as <code>parentTransform</code>. - We break this recursive loop when there will no children - left to process further. - </p> - -</p> -</p> - -<h3> Let's Animate</h3> - -<p> -Fruit of our hardwork is finally here! Here's how we will play the animation in <code>main.cpp</code> ... -</p> - -<pre><code>int main() -{ - ... - - Model ourModel(FileSystem::getPath("resources/objects/vampire/dancing_vampire.dae")); - Animation danceAnimation(FileSystem::getPath( - "resources/objects/vampire/dancing_vampire.dae"), &ourModel); - Animator animator(&danceAnimation); - - // draw in wireframe - //<function id='43'>glPolygonMode</function>(GL_FRONT_AND_BACK, GL_LINE); - - // render loop - // ----------- - while (!<function id='14'>glfwWindowShouldClose</function>(window)) - { - // per-frame time logic - // -------------------- - float currentFrame = <function id='47'>glfwGetTime</function>(); - deltaTime = currentFrame - lastFrame; - lastFrame = currentFrame; - - // input - // ----- - processInput(window); - animator.UpdateAnimation(deltaTime); - - // render - // ------ - <function id='13'><function id='10'>glClear</function>Color</function>(0.05f, 0.05f, 0.05f, 1.0f); - <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - // don't forget to enable shader before setting uniforms - ourShader.use(); - - // view/projection transformations - glm::mat4 projection = <function id='58'>glm::perspective</function>(<function id='63'>glm::radians</function>(camera.Zoom), - (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); - glm::mat4 view = camera.GetViewMatrix(); - ourShader.setMat4("projection", projection); - ourShader.setMat4("view", view); - - auto transforms = animator.GetFinalBoneMatrices(); - for (int i = 0; i < transforms.size(); ++i) - ourShader.setMat4("finalBonesTransformations[" + std::to_string(i) + "]", - transforms[i]); - - // render the loaded model - glm::mat4 model = glm::mat4(1.0f); - model = <function id='55'>glm::translate</function>(model, glm::vec3(0.0f, -0.4f, 0.0f)); - model = <function id='56'>glm::scale</function>(model, glm::vec3(.5f, .5f, .5f)); - ourShader.setMat4("model", model); - ourModel.Draw(ourShader); - - // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) - // ------------------------------------------------------------------------------- - <function id='24'>glfwSwapBuffers</function>(window); - <function id='23'>glfwPollEvents</function>(); - } - - // glfw: terminate, clearing all previously allocated GLFW resources. - // ------------------------------------------------------------------ - <function id='25'>glfwTerminate</function>(); - return 0; -} -</code></pre> - -<p> - - We start with loading our <code>Model</code> which will setup bone weight data for the shader and then create an <code>Animation</code> by giving it the path. - Then we create our <code>Animator</code> object by passing it the created <code>Animation</code>. In render loop we then update our <code>Animator</code>, take the - final bone transformations and give it to shaders. Here's the output we all have been waiting for... - -</p> - -<img src="/img/guest/2020/skeletal_animation/output.gif" alt="output"> - - <p> Download the model used <a href="/data/models/vampire.zip">here.</a> Note that animations -and meshes are baked in single DAE(collada) file. You can find the full source code <a href="/code_viewer_gh.php?code=src/8.guest/2020/skeletal_animation/skeletal_animation.cpp" target="_blank">here</a>. - -<h3>Further reading</h3> - <ul> - <li><a href="http://www.songho.ca/math/quaternion/quaternion.html" target="_blank"> - Quaternions</a>: An article by songho to understand quaternions in depth.</li> - <li><a href="http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html" target="_blank"> - Skeletal Animation with Assimp</a>: An article by OGL Dev.</li> - <li><a href="https://youtu.be/f3Cr8Yx3GGA" target="_blank"> - Skeletal Animation with Java</a>: A fantastic youtube playlist by Thin Matrix.</li> - <li><a href="https://www.gamasutra.com/view/feature/131686/rotating_objects_using_quaternions.php" target="_blank"> - Why Quaternions should be used for Rotation</a>: An awesome gamasutra article.</li> - - </ul> - - - -<author> - <strong>Article by: </strong>Ankit Singh Kushwah<br/> - <strong>Contact: </strong><a href="mailto:eklavyagames@gmail.com" target="_blank">e-mail</a> -</author> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Guest-Articles/2021/CSM.html b/translation/Guest-Articles/2021/CSM.html @@ -1,738 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - CSM</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">CSM</h1> -<h1 id="content-url" style='display:none;'>Guest-Articles/2021/CSM</h1> -<p> - <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping" target="_blank">Shadow mapping</a> as described on LearnOpenGL is a powerful, and relatively simple technique. However, if implemented as-is from the above referred tutorial, the avid OpenGL student will notice a few shortcomings. - </p> - <ul> - <li>The shadow map is always created around the origin, and not on the area the camera is actually looking at. It would be best of course if we could shadow map the whole scene, with sufficient resolution, but on current hardware this is not feasible. In reality we want the shadow maps to be created on objects that are in view, saving our precious GPU memory for things that matter. </li> - <li>The shadow map orthographic projection matrix is not properly fitted onto the view frustum. To achieve the best possible resolution for our shadow maps, the ortho matrix needs to be as tightly fit to the camera frustum as possible, because again: if it’s larger that means that less detail is spent on the objects that are actually visible.</li> - <li>The shadow maps (even with advanced PCF functions) are blurry if we want the shadow rendering distance to be large, as we would in a game with a first-person camera. We can increase the resolution of the shadow maps to mitigate this, but GPU memory is a resource we should be conservative of.</li> - </ul> - <p> - Cascaded shadow mapping is a direct answer to the third point, however while implementing it we will solve the first two points, too. The core insight in cascaded shadow mapping is, that we don’t need the same amount of shadow detail for things that are far from us. We want crisp shadows for stuff that’s near to the near plane, and we are absolutely fine with blurriness for objects that are hundreds of units away: it’s not going to be noticeable at all. How can we achieve this? The answer is beautiful in its simplicity: just render different shadow maps for things that are close and for those that are far away, and sample from them according to the depth of the fragment in the fragment shader. The high-level algorithm is as follows: - </p> - <ul> - <li>Divide our ordinary view frustum into n subfrusta, where the far plane of the <code>i</code>-th frustum is the near plane of the <code>(i+1)</code>-th frustum</li> - <li>Compute the tightly fitting ortho matrix for each frustum</li> - <li>For each frustum render a shadow map as if seen from our directional light</li> - <li>Send all shadow maps to our fragment shader</li> - <li>Render the scene, and according to the fragment’s depth value sample from the correct shadow map</li> - </ul> - <p> - Sounds simple right? Well, it is, but as it often is when it comes to our friend OpenGL: the devil is in the details. - </p> - <img src="/img/guest/2021/CSM/cs_go.png" width="800px"> - <p> - In the above image we can see the edges of shadow cascades in Counter-Strike: Global Offensive. The image was captured on low graphics settings. - </p> - - <h2>World coordinates of the view frustum</h2> - <p> - Before getting our hands dirty with shadows, we need to tackle a more abstract problem: making our projection matrix fit nicely onto a generic frustum. To be able to do this, we need to know the world space coordinates of the frustum in question. While this might sound daunting at first, we already have all the tools necessary in our repertoire. If we think back on the excellent <a href="https://learnopengl.com/Getting-started/Coordinate-Systems" target="_blank"> coordinate systems</a> tutorial, the beheaded pyramid of the frustum only looks that way in world coordinates, and our view and projection matrices do the job of transforming this unusual shape into the NDC cube. And we know the coordinates of the corners of the NDC cube: the coordinates are in the range <code>[-1,1]</code> on the three axes. Because matrix multiplication is a reversible process, we can apply the inverse of the view and projection matrices on the corner points of the NDC cube to get the frustum corners in world space. - </p> - <math> - $$v_{NDC} = M_{proj} M_{view} v_{world}$$ - $$v_{world} = (M_{proj} M_{view})^{-1} v_{NDC}$$ - </math> - -<pre><code> -std::vector<glm::vec4> getFrustumCornersWorldSpace(const glm::mat4& proj, const glm::mat4& view) -{ - const auto inv = glm::inverse(proj * view); - - std::vector<glm::vec4> frustumCorners; - for (unsigned int x = 0; x < 2; ++x) - { - for (unsigned int y = 0; y < 2; ++y) - { - for (unsigned int z = 0; z < 2; ++z) - { - const glm::vec4 pt = - inv * glm::vec4( - 2.0f * x - 1.0f, - 2.0f * y - 1.0f, - 2.0f * z - 1.0f, - 1.0f); - frustumCorners.push_back(pt / pt.w); - } - } - } - - return frustumCorners; -} -</code></pre> - - <p> - The projection matrix described here is a perspective matrix, using the camera’s aspect ratio and fov, and using the near and far plane of the current frustum being analyzed. The view matrix is the view matrix of our camera. - </p> - -<pre><code> -const auto proj = <function id='58'>glm::perspective</function>( - <function id='63'>glm::radians</function>(camera.Zoom), - (float)SCR_WIDTH / (float)SCR_HEIGHT, - nearPlane, - farPlane -); -</code></pre> - - <img src="/img/guest/2021/CSM/frustum_fitting.png"> - <br> - - <h2>The light view-projection matrix</h2> - <p> - This matrix - as in ordinary shadow mapping – is the product of the view and projection matrix that transforms the scene as if it were seen by the light. Calculating the view matrix is simple, we know the direction our light is coming from, and we know a point in world space that it definitely is looking at: the center of the frustum. The position of the frustum center can be obtained by averaging the coordinates of the frustum corners. (This is so because averaging the coordinates of the near and far plane gives us the center of those rectangles, and taking the midpoint of these two points gives us the center of the frustum.) - </p> - <pre><code> -glm::vec3 center = glm::vec3(0, 0, 0); -for (const auto& v : corners) -{ - center += glm::vec3(v); -} -center /= corners.size(); - -const auto lightView = <function id='62'>glm::lookAt</function>( - center + lightDir, - center, - glm::vec3(0.0f, 1.0f, 0.0f) -); - </code></pre> - <p> - The projection matrix is bit more complex. Because the light in question is a directional light, the matrix needs to be an orthographic projection matrix, this much is clear. To understand how we determine the left, right, top etc. parameters of the matrix imagine the scene you are drawing from the perspective of the light. From this viewpoint the shadow map we are trying to render is going to be an axis aligned rectangle, and this rectangle – as we established before – needs to fit on the frustum tightly. So we need to obtain the coordinates of the frustum in this space, and take the maximum and minimum of the coordinates along the coordinate axes. While this might sound daunting at first, this perspective is exactly what our light view matrix transformation gives us. We need to transform the frustum corner points in the light view space, and find the maximum and minimum coordinates. - </p> - - <pre><code> -float minX = std::numeric_limits<float>::max(); -float maxX = std::numeric_limits<float>::min(); -float minY = std::numeric_limits<float>::max(); -float maxY = std::numeric_limits<float>::min(); -float minZ = std::numeric_limits<float>::max(); -float maxZ = std::numeric_limits<float>::min(); -for (const auto& v : corners) -{ - const auto trf = lightView * v; - minX = std::min(minX, trf.x); - maxX = std::max(maxX, trf.x); - minY = std::min(minY, trf.y); - maxY = std::max(maxY, trf.y); - minZ = std::min(minZ, trf.z); - maxZ = std::max(maxZ, trf.z); -} - </code></pre> - - <p> - We are going to create our projection matrix from the product of two matrices. First, we are going to create an ortho projection matrix, with the left, right, top, bottom parameters set to <code>-1</code> or <code>1</code>, and the z values set to <var>minZ</var> and <var>maxZ</var>. This creates a 3D rectangle sitting on the origin with width and height of <code>2</code>, and depth of (<var>maxZ</var> – <var>minZ</var>). In the code we increase the amount of space covered by <var>minZ</var> and <var>maxZ</var> by multiplying or dividing them with a <var>zMult</var>. This is because we want to include geometry which is behind or in front of our frustum in camera space. Think about it: not only geometry which is in the frustum can cast shadows on a surface in the frustum! - </p> - -<pre><code> -// Tune this parameter according to the scene -constexpr float zMult = 10.0f; -if (minZ < 0) -{ - minZ *= zMult; -} -else -{ - minZ /= zMult; -} -if (maxZ < 0) -{ - maxZ /= zMult; -} -else -{ - maxZ *= zMult; -} - -const glm::mat4 lpMatrix = <function id='59'>glm::ortho</function>(-1.0f, 1.0f, -1.0f, 1.0f, minZ, maxZ); -</code></pre> - <p> - The other part of our projection matrix is going to be a crop matrix, which moves the rectangle onto the frustum in light view space. Starting from a unit matrix, we need to set the x and y scaling components, and the x and y offset components of the matrix. - </p> - - <math> - $$S_x = {2 \over M_x - m_x}$$ - $$S_y = {2 \over M_y - m_y}$$ - $$O_x = -0.5(M_x + m_x)S_x$$ - $$O_y = -0.5(M_y + m_y)S_y$$ - $$C = \begin{bmatrix}S_x & 0 & 0 & O_x \\0 & S_y & 0 & O_y \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1\end{bmatrix}$$ - </math> - - <p> - Let’s look at S<sub>x</sub>. It needs to shrink down the frustum bounding rectangle to a unit size, this is achieved by dividing by the width of the rectangle (M<sub>x</sub> – m<sub>x</sub>). However, we need to scale by <code>2</code>, because our 3D rectangle sitting on the origin has a width of <code>2</code>. S<sub>y</sub> is analogous to this. - </p> - <img src="/img/guest/2021/CSM/frustum_cropping.png" width="800px"> - <p> - For the x offset, we need to multiply by the negative halfway point between M<sub>x</sub> and m<sub>x</sub>, and scale by S<sub>x</sub>. And the y offset is analogous. - </p> - -<pre><code> -const float scaleX = 2.0f / (maxX - minX); -const float scaleY = 2.0f / (maxY - minY); -const float offsetX = -0.5f * (minX + maxX) * scaleX; -const float offsetY = -0.5f * (minY + maxY) * scaleY; - -glm::mat4 cropMatrix(1.0f); -cropMatrix[0][0] = scaleX; -cropMatrix[1][1] = scaleY; -cropMatrix[3][0] = offsetX; -cropMatrix[3][1] = offsetY; - -return cropMatrix * lpMatrix * lightView; -</code></pre> - - <p> - Multiplying the view, projection and crop matrices together, we get the view-projection matrix of the light for the given frustum. We need to do this procedure for every frustum in our cascade. - </p> - - <h2>2D array textures</h2> - <p> - While we let our stomachs digest what we learned about frustum fitting we should figure out how to store our shadow maps. In principle there is no limit on how many cascades we can do, so hardcoding an arbitrary value doesn’t seem like a wise idea. Also, it quickly becomes tiresome typing out and binding sampler2Ds for our shaders. OpenGL has a good solution to this problem in the form of <def>2D array textures</def>. This object is an array of textures, which have the same dimensions. To use them in shaders declare them like this: - </p> - -<pre><code> -uniform sampler2DArray shadowMap; -</code></pre> - - <p> - To sample from them we can use the regular texture function with a vec3 parameter for texture coordinates, the third dimension specifying which layer to sample from, starting from <code>0</code>. - </p> - -<pre><code> -texture(depthMap, vec3(TexCoords, currentLayer)) -</code></pre> - - <p> - Creating our array texture is slightly different than creating a regular old texture2D. Instead of <function id='52'>glTexImage2D</function> we use glTexImage3D to allocate memory, and when binding the texture to the framebuffer we use glFramebufferTexture instead of <function id='81'>glFramebufferTexture2D</function>. The parameters of these functions are somewhat self-explanatory if we know the old versions. When calling the OpenGL functions, we need to pass <var>GL_TEXTURE_2D_ARRAY</var> instead of <var>GL_TEXTURE_2D</var> as the target, to tell OpenGL what kind of texture we are dealing with. - </p> - -<pre><code> -<function id='76'>glGenFramebuffers</function>(1, &lightFBO); - -<function id='50'>glGenTextures</function>(1, &lightDepthMaps); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D_ARRAY, lightDepthMaps); -glTexImage3D( - GL_TEXTURE_2D_ARRAY, - 0, - GL_DEPTH_COMPONENT32F, - depthMapResolution, - depthMapResolution, - int(shadowCascadeLevels.size()) + 1, - 0, - GL_DEPTH_COMPONENT, - GL_FLOAT, - nullptr); - -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - -constexpr float bordercolor[] = { 1.0f, 1.0f, 1.0f, 1.0f }; -<function id='15'>glTexParameter</function>fv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, bordercolor); - -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, lightFBO); -glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, lightDepthMaps, 0); -glDrawBuffer(GL_NONE); -glReadBuffer(GL_NONE); - -int status = <function id='79'>glCheckFramebufferStatus</function>(GL_FRAMEBUFFER); -if (status != GL_FRAMEBUFFER_COMPLETE) -{ - std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!"; - throw 0; -} - -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -</code></pre> - - <p> - Take care when binding this texture to a sampler. Again: we need to use <var>GL_TEXTURE_2D_ARRAY</var> as the target parameter. - </p> - -<pre><code> -<function id='49'>glActiveTexture</function>(GL_TEXTURE1); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D_ARRAY, lightDepthMaps); -</code></pre> - - <p> - So far so good, now we know the semantics of using a texture array. It all seems straightforward, but OpenGL has one more curveball to throw at us: we can’t render into this texture the ordinary way, we need to do something called <def>layered rendering</def>. This process is very similar to what we did with <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows" target="_blank">cubemaps and pointlights</a> , we coax the <a href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader" target="_blank">geometry shader</a> into generating multiple layers of geometry for us at the same time. If we recall our depthmap shader is very simple: transform the vertices to light space in the vertex stage, and let the hardware do the rest with an empty fragment shader. In our new depthmap shader we are going to only do the world space transformation in the vertex shader. - </p> - -<pre><code> -#version 460 core -layout (location = 0) in vec3 aPos; - -uniform mat4 model; - -void main() -{ - gl_Position = model * vec4(aPos, 1.0); -} -</code></pre> - - <p> - The newly inserted geometry shader will look something like this: - </p> - -<pre><code> -#version 460 core - -layout(triangles, invocations = 5) in; -layout(triangle_strip, max_vertices = 3) out; - -layout (std140, binding = 0) uniform LightSpaceMatrices -{ - mat4 lightSpaceMatrices[16]; -}; - -void main() -{ - for (int i = 0; i < 3; ++i) - { - gl_Position = - lightSpaceMatrices[gl_InvocationID] * gl_in[i].gl_Position; - gl_Layer = gl_InvocationID; - EmitVertex(); - } - EndPrimitive(); -} -</code></pre> - - <p> - The input declaration has a new member, specifying the <def>invocation count</def>. This number means, that this shader will be instanced, these instances running in parallel, and we can refer the current instance by the inbuilt variable <var>gl_InvocationID</var>. We will use this number in the shader code to reference which layer of the array texture we are rendering to, and which shadow matrix we are going to use to do the light space transform. We are iterating over all triangles, and setting <var>gl_Layer</var> and <var>gl_Position</var> accordingly. - </p> - - <note> - I strongly suggest modifying your Shader class in your engine to enable the possibility of inserting variables into the shader code before shader compilation, so that you can make the <i>invocations</i> parameter dynamic. This way if you modify the number of cascades in the C++ code you dont have to modify the shader itself, removing one cog from the complex machine that is your engine. I didn't include this in the tutorial for the sake of simplicity. - </note> - - <p> - The fragment shader remains the same empty, passthrough shader. - </p> - -<pre><code> -#version 460 core - -void main() -{ -} -</code></pre> - - <p> - Our draw call invoking the shader looks something like this: - </p> - -<pre><code> -simpleDepthShader.use(); - -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, lightFBO); -glFramebufferTexture(GL_FRAMEBUFFER, GL_TEXTURE_2D_ARRAY, lightDepthMaps, 0); -<function id='22'>glViewport</function>(0, 0, depthMapResolution, depthMapResolution); -<function id='10'>glClear</function>(GL_DEPTH_BUFFER_BIT); -<function id='74'>glCullFace</function>(GL_FRONT); // peter panning -renderScene(simpleDepthShader); -<function id='74'>glCullFace</function>(GL_BACK); -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -</code></pre> - - <img src="/img/guest/2021/CSM/cascades.png" width="800px"> - - <h2>Scene rendering</h2> - <p> - Now the only thing remaining is doing the actual shadow rendering. In our ordinary phong/deferred fragment shader where we calculate whether the current fragment is occluded or not, we need to insert some logic to decide which light space matrix to use, and which texture to sample from. - </p> - -<pre><code> -// select cascade layer -vec4 fragPosViewSpace = view * vec4(fragPosWorldSpace, 1.0); -float depthValue = abs(fragPosViewSpace.z); - -int layer = -1; -for (int i = 0; i < cascadeCount; ++i) -{ - if (depthValue < cascadePlaneDistances[i]) - { - layer = i; - break; - } -} -if (layer == -1) -{ - layer = cascadeCount; -} - -vec4 fragPosLightSpace = lightSpaceMatrices[layer] * vec4(fragPosWorldSpace, 1.0); -</code></pre> - - <p> - If you remember to prevent shadow acne we applied a depth bias to our image. We need to do the same here, but keep in mind that we are dealing with multiple shadow maps, and on each of them the pixels cover a widely different amount of space, and a unit increase in pixel value means different depth increase in all of them. Because of this we need to apply a different bias depending on which shadow map we sample from. In my experience scaling the bias inversely proportionally with the far plane works nicely. - </p> - -<pre><code> -// perform perspective divide -vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; -// transform to [0,1] range -projCoords = projCoords * 0.5 + 0.5; - -// get depth of current fragment from light's perspective -float currentDepth = projCoords.z; -if (currentDepth > 1.0) -{ - return 0.0; -} -// calculate bias (based on depth map resolution and slope) -vec3 normal = normalize(fs_in.Normal); -float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005); -if (layer == cascadeCount) -{ - bias *= 1 / (farPlane * 0.5f); -} -else -{ - bias *= 1 / (cascadePlaneDistances[layer] * 0.5f); -} -</code></pre> - - <note> - Please note that there are different strategies for applying bias when dealing with shadow maps. I will link to a few sources detailing these in the citations section. - </note> - - <p> - The rest of the function is the same as before, the only difference is that we are sampling from a 2D array texture, hence we need to add a third parameter to the <fun>texture</fun> and the <fun>textureSize</fun> functions. - </p> - -<pre><code> -// PCF -float shadow = 0.0; -vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0)); -for(int x = -1; x <= 1; ++x) -{ - for(int y = -1; y <= 1; ++y) - { - float pcfDepth = texture( - shadowMap, - vec3(projCoords.xy + vec2(x, y) * texelSize, - layer) - ).r; - shadow += (currentDepth - bias) > pcfDepth ? 1.0 : 0.0; - } -} -shadow /= 9.0; - -// keep the shadow at 0.0 when outside the far_plane region of the light's frustum. -if(projCoords.z > 1.0) -{ - shadow = 0.0; -} - -return shadow; -</code></pre> - - <p> - And that's it! If we did everything correctly we should see that the renderer switches between shadow maps based on the distance. Try setting some unreasonable cascade plane distances (for example only one, which is a few units from the camera) to see if the code really does work. You should see a noticable degradation in shadow quality between the two sides of the plane. If you see moire artifacts on the screen try changing around bias parameters a bit. - </p> - - <img src="/img/guest/2021/CSM/demoscene.png" width="800px"> - - <p> - You can find the full source code for the cascaded shadow mapping demo <a href="/code_viewer_gh.php?code=src/8.guest/2021/2.csm/shadow_mapping.cpp" target="_blank">here</a>. - </p> - - <h2>Closing thoughts</h2> - <p> - In the sample project provided you can toggle depthmap visualization by pressing <kbd>F</kbd>. When in depthmap visualization mode you can press the <kbd>+</kbd> key to swap between the different layers. - </p> - <p> - When browsing through the code you might wonder why is the UBO array length <code>16</code>. This is just an arbitrary choice, to me it seemed unlikely that anyone would use more than <code>16</code> shadow cascades, so this seemed like a nice number to allocate. - </p> - - <h2>Additional Resources</h2> - <ul> - <li><a href="https://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascaded_shadow_maps.pdf" target="_blank">NVIDIA paper on the subject:</a> incomprehensible in my opinion but has to be mentioned</li> - <li><a href="https://www.gamedev.net/forums/topic/672664-fitting-directional-light-in-view-frustum/?page=1" target="_blank">A series of incredibly helpful and useful forum posts</a></li> - <li><a href="https://ogldev.org/www/tutorial49/tutorial49.html" target="_blank">Another interesting tutorial from OGLDev</a></li> - <li><a href="https://docs.microsoft.com/en-us/windows/win32/dxtecharts/cascaded-shadow-maps" target="_blank">An article from Microsoft:</a> nice pictures illustrating some issues with CSM</li> - <li><a href="https://digitalrune.github.io/DigitalRune-Documentation/html/3f4d959e-9c98-4a97-8d85-7a73c26145d7.htm" target="_blank">An article about shadow bias</a></li> - <li><a href="http://c0de517e.blogspot.com/2011/05/shadowmap-bias-notes.html" target="_blank">Some informative drawings about shadow bias strategies</a></li> - </ul> - <br> - -<author> - <strong>Article by: </strong>Márton Árbócz<br/> - <!--<strong>Contact: </strong><a href="mailto:eklavyagames@gmail.com" target="_blank">e-mail</a>--> -</author> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Guest-Articles/2021/Scene/Frustum-Culling.html b/translation/Guest-Articles/2021/Scene/Frustum-Culling.html @@ -1,789 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Frustum Culling</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Frustum Culling</h1> -<h1 id="content-url" style='display:none;'>Guest-Articles/2021/Scene/Frustum-Culling</h1> -<p> - Now we know how to create a Scene graph and organize your object in a scene, we are going to see how to limit your GPU usage thanks to a technical name's the frustum culling. - This technique is simple to understand. - Instead of sending all information to your GPU, you will sort visible and invisible elements and render only visible elements. - Thanks to this technique, you will earn GPU compute time. - You need to know that when information travels toward another unit in your computer, it takes a long time. - For example, information from your GPU to your ram takes time. - It's the same if you want to send information from your CPU to your GPU like a model matrice. - It's for this reason that the "draw instance" is so powerful. - You send a large block to your GPU instead of sending elements one by one. - But this technique isn’t free. - To sort your element, you need to create a physical scene to compute some stuff with math. - This chapter will start with an introduction to the mathematical concept that will allow us to understand how frustum culling works. - Next, we are going to implement it. - Finally, we are going to study possible optimizations and talk about the balance of the technical. - </p> - - <video width="850" controls> - <source src="/img\guest\2021\Frustum_culling\frustumExample.mp4" type="video/mp4"> - Your browser does not support the video tag. - </video> - - <p> - In this video illustrating frustum culling in a forest, the yellow and red shape on the left side is the bounding volume that contains the mesh. - Red color means that the mesh is not visible and not sent to the GPU. - Yellow means that the mesh is rendered. - As you can see lots of things are rendered and few are visible for the player. - </p> - - <h2>Mathematical concept</h2> - - <p> - Let's start the mathematical parts from top to bottom. - Firstly, what is a frustum? - As we can see in <a href="https://en.wikipedia.org/wiki/Frustum" target="_blank"> Wikipedia</a>, frustum is a portion of a solid like a cone or pyramid. - The frustum is usually used in game engine to speak about the camera frustum. - Camera frustum represents the zone of vision of a camera. - Without limit, we have a pyramid but with near and far we have a frustum. - </p> - - <img src="/img/guest/2021/Frustum_culling/VisualCameraFrustum.png" alt="Camera frustum shape"/> - - <p> - How to mathematically represent a frustum? - Thanks to 6 plans: near, far, right, left top and bottom plans. - So, an object is visible if it is forward or on the 6 plans. - Mathematically a plan is represented with a normal vector and distance to the origin. - A plan doesn't have any size or limit as a quad. - </p> - - <img src="/img/guest/2021/Frustum_culling/plan.png" width="600" alt="Plan representation"/> - - <p> - So, create a struct to represent a plan: - </p> - <pre><code> -struct Plan -{ - // unit vector - glm::vec3 normal = { 0.f, 1.f, 0.f }; - - // distance from origin to the nearest point in the plan - float distance = 0.f; - - [...] -}; - </code></pre> - - <p> - We can now create <fun>Frustum</fun> structure: - </p> - - <pre><code> -struct Frustum -{ - Plan topFace; - Plan bottomFace; - - Plan rightFace; - Plan leftFace; - - Plan farFace; - Plan nearFace; -}; - </code></pre> - - <p> - Reminder: a plan can be built with a point and a normal. - For the near, the normal is the front vector of the camera. - For the far plan, it's the opposite. - The normal of the right face we will need to do a cross product. - The cross product is the second wonderful tool for the programmer who likes vectors. - It allows you to get a perpendicular vector to a plan created with two vectors. - To go forward, we need to do the cross product of the right axis per up. - We will use it like that: - </p> - - <img src="/img/guest/2021/Frustum_culling/RightNormal.png" alt="Plan representation"/> - - <p> - But to know the direction of each vector from the camera to the far plan we will know the side length of the far quad: - </p> - <img src="/img/guest/2021/Frustum_culling/hAndVSide.png" alt="Plan representation"/> - - <p> - hSide and vSide are the far quad limited by the other plans of the camera frustum. - To compute its edge, we will need of trigonometry. - As you can see in the image above, we have two rectangle triangles and we can apply the trigonometric functions. - So, we would like to obtain vSide which is the opposite side and we have zFar that is the adjacent side of the camera. - Tan of fovY is equal to the opposite side (vSide) divided by the adjacent side (zFar). - In conclusion, if I move the adjacent side on the left on our equation, tan of fovY multiplied by the zFar is equal to the vSide. - We now need to compute hSide. - Thanks to the aspect that is a ratio of the width by the height, we can easily obtain it. - So, hSide is equal to the vSide multiplied by the aspect as you can see on the right side of the image above. - We can now implement our function: - </p> - - <pre><code> -Frustum createFrustumFromCamera(const Camera& cam, float aspect, float fovY, - float zNear, float zFar) -{ - Frustum frustum; - const float halfVSide = zFar * tanf(fovY * .5f); - const float halfHSide = halfVSide * aspect; - const glm::vec3 frontMultFar = zFar * cam.Front; - - frustum.nearFace = { cam.Position + zNear * cam.Front, cam.Front }; - frustum.farFace = { cam.Position + frontMultFar, -cam.Front }; - frustum.rightFace = { cam.Position, - <function id='61'>glm::cross</function>(cam.Up,frontMultFar + cam.Right * halfHSide) }; - frustum.leftFace = { cam.Position, - <function id='61'>glm::cross</function>(frontMultFar - cam.Right * halfHSide, cam.Up) }; - frustum.topFace = { cam.Position, - <function id='61'>glm::cross</function>(cam.Right, frontMultFar - cam.Up * halfVSide) }; - frustum.bottomFace = { cam.Position, - <function id='61'>glm::cross</function>(frontMultFar + cam.Up * halfVSide, cam.Right) }; - - return frustum; -} -</code></pre> -<note> - In this example, the camera doesn't know the near, aspect but I encourage you to include this variable inside your Camera class. -</note> - - <h3>Bounding volume</h3> - - <p> - Let's take a minute to imagine an algorithm that can detect collisions with your mesh (with all types of polygons in general) and a plan. - You will start to say that image is an algorithm that checks if a triangle is on or outside the plane. - This algorithm looks pretty and fast! But now imagine that you have hundreds of mesh with thousands of triangles each one. - Your algorithm will sign the death of your frame rate fastly. - Another method is to wrap your objects in another geometrical object with simplest properties such as a sphere, a box, a capsule... - Now our algorithm looks possible without creating a framerate black hole. - Its shape is called bounding volume and allows us to create a simpler shape than our mesh to simplify the process. - All shapes have their own properties and can correspond plus or minus to our mesh. - </p> - <img src="/img/guest/2021/Frustum_culling/boundingVolumeQuality.png" alt="Bounding volume quality vs computation speed"/> - - <p> - All shapes also have their own compute complexity. - The <a href="https://en.wikipedia.org/wiki/Bounding_volume" target="_blank">article</a> on Wikipedia is very nice and describes some bounding volumes with their balance and application. - In this article, we are going to see 2 bounding volumes: the sphere and the AABB. - Let's create a simple abstract struct Volume that represent all our bounding volumes: - </p> - - <pre><code> -struct Volume -{ - virtual bool isOnFrustum(const Frustum& camFrustum, - const Transform& modelTransform) const = 0; -}; - </code></pre> - - <h4>Sphere</h4> - - <img src="/img/guest/2021/Frustum_culling/boundingSphere.png" alt="Bounding sphere example"/> - - <p> - The bounding sphere is the simplest shape to represent a bounding volume. - It is represented by center and radius. - A sphere is ideal to encapsulate mesh with any rotation. - It must be adjusted with the scale and position of the object. - We can create struct Sphere that inheritance from volume struct: - </p> - - <pre><code> -struct Sphere : public Volume -{ - glm::vec3 center{ 0.f, 0.f, 0.f }; - float radius{ 0.f }; - - [...] -} - </code></pre> - - <p> - This struct doesn't compile because we haven't defined the function isOnFrustum. - Let's make it. - Remember that our bounding volume is processed thanks to our meshes. - That assumes that we will need to apply a transform to our bounding volume to apply it. - As we have seen in the previous chapter, we will apply the transformation to a scene graph. - </p> - - <pre><code> -bool isOnFrustum(const Frustum& camFrustum, const Transform& transform) const final -{ - //Get global scale is computed by doing the magnitude of - //X, Y and Z model matrix's column. - const glm::vec3 globalScale = transform.getGlobalScale(); - - //Get our global center with process it with the global model matrix of our transform - const glm::vec3 globalCenter{ transform.getModelMatrix() * glm::vec4(center, 1.f) }; - - //To wrap correctly our shape, we need the maximum scale scalar. - const float maxScale = std::max(std::max(globalScale.x, globalScale.y), globalScale.z); - - //Max scale is assuming for the diameter. So, we need the half to apply it to our radius - Sphere globalSphere(globalCenter, radius * (maxScale * 0.5f)); - - //Check Firstly the result that have the most chance - //to faillure to avoid to call all functions. - return (globalSphere.isOnOrForwardPlan(camFrustum.leftFace) && - globalSphere.isOnOrForwardPlan(camFrustum.rightFace) && - globalSphere.isOnOrForwardPlan(camFrustum.farFace) && - globalSphere.isOnOrForwardPlan(camFrustum.nearFace) && - globalSphere.isOnOrForwardPlan(camFrustum.topFace) && - globalSphere.isOnOrForwardPlan(camFrustum.bottomFace)); -}; - </code></pre> - <note> - To compute the globalCenter we can’t only add the current center with the global position because we need to apply translation caused by rotation and scale. - This is the reason why we use the model matrix. - </note> - - <p> - As you can see, we used a function undefined for now called <fun>isOnOrForwardPlan</fun>. - This implementation method is called top/down programming and consists to create a high-level function to determine which kind of function need to be implemented. - It avoids to implement too many unused functions that can be the case in "bottom/up". - So to understand how this function works, let's make a drawing : - </p> - - <img src="/img/guest/2021/Frustum_culling/SpherePlanDetection.png" width="400" height="400" alt="Sphere plan collision shema"/> - - <p> - We can see 3 possible cases: Sphere is inside the plan, back or forward. - To detect when a sphere is colliding with a plan we need to compute the nearest distance from the center of the sphere to the plan. - When we have this distance, we need to compare this distance with radius. - </p> - - <pre><code> -bool isOnOrForwardPlan(const Plan& plan) const -{ - return plan.getSignedDistanceToPlan(center) > -radius; -} - </code></pre> - - <note> - We can see the problem in the other way and create a function called <fun>isOnBackwardPlan</fun>. - To use it we simply need to check if bounding volume IS NOT on the backward plan - </note> - - <p> - Now we need to create the function <fun>getSignedDistanceToPlan</fun> in the </un>Plan</fun> structure. - Let me realize my most beautiful paint for you : - </p> - -<img src="/img/guest/2021/Frustum_culling/SignedDistanceDraw.png" width="400" height="400" alt="Signed distance to plan shema"/> - - <p> - Signed distance is a positive distance from a point if this point is forward the plan. - Otherwise this distance will be negative. - To obtain it, we will need to call a friend: The dot product. - Dot product allows us to obtain the projection from a vector to another. - The result of the dot product is a scale and this scalar is a distance. - If both vectors go oppositely, the dot product will be negative. - Thanks to it, we will obtain the horizontal scale component of a vector in the same direction as the normal of the plan. - Next, we will need to subtract this dot product by the nearest distance from the plan to the origin. - Hereafter you will find the implementation of this function : - </p> - - <pre><code> -float getSignedDistanceToPlan(const glm::vec3& point) const -{ - return glm::dot(normal, point) - distance; -} - </code></pre> - - <h4>AABB</h4> -<img src="/img/guest/2021/Frustum_culling/boundingAABB.png" alt="Bounding AABB example"/> - - <p> - AABB is the acronym of Axis aligned bounding box. - It means that this volume has the same orientation as the world. - It can be constructed as different can be we generally create it with its center and its half extension. - The half extension is a distance from center to the edge in the direction of an axis. - The half extension can be called Ii, Ij, Ik. In this chapter, we will call it Ix, Iy, Iz. - </p> - -<img src="/img/guest/2021/Frustum_culling/AABBRepresentation.png" width="400" alt="AABB representation"/> - - <p> - Let's make the base of this structure with few constructors to made its creation the simplest - </p> - - <pre><code> -struct AABB : public BoundingVolume -{ - glm::vec3 center{ 0.f, 0.f, 0.f }; - glm::vec3 extents{ 0.f, 0.f, 0.f }; - - AABB(const glm::vec3& min, const glm::vec3& max) - : BoundingVolume{}, - center{ (max + min) * 0.5f }, - extents{ max.x - center.x, max.y - center.y, max.z - center.z } - {} - - AABB(const glm::vec3& inCenter, float iI, float iJ, float iK) - : BoundingVolume{}, center{ inCenter }, extents{ iI, iJ, iK } - {} - - [...] -}; - </code></pre> - - <p> - We now need to add the function <fun>isOnFrustum</fun> and <fun>isOnOrForwardPlan</fun>. - The problem is not easy as a bounding sphere because if I rotate my mesh, the AABB will need to be adjusted. - An image talks much than a text : - </p> - -<img src="/img/guest/2021/Frustum_culling/AABBProblem.png" alt="AABB rotation probleme"/> - - <p> - To solve this problem lets draw it : - </p> - -<img src="/img/guest/2021/Frustum_culling/AABB orientation.png" alt="AABB orientation problem explication"/> - - <p> - Crazy guys want to rotate our beautiful Eiffel tower but we can see that after its rotation, the AABB is not the same. - To make the Shema more readable, assume that referential is not a unit and represented the half extension with the orientation of the mesh. - To adjust it, we can see in the third picture that the new extension is the sum of the dot product with the world axis and the scaled referential of our mesh. - The problem is seen in 2D but in 3D it's the same thing. Let's implement the function to do it. - </p> - - <pre><code> -bool isOnFrustum(const Frustum& camFrustum, const Transform& transform) const final -{ - //Get global scale thanks to our transform - const glm::vec3 globalCenter{ transform.getModelMatrix() * glm::vec4(center, 1.f) }; - - // Scaled orientation - const glm::vec3 right = transform.getRight() * extents.x; - const glm::vec3 up = transform.getUp() * extents.y; - const glm::vec3 forward = transform.getForward() * extents.z; - - const float newIi = std::abs(glm::dot(glm::vec3{ 1.f, 0.f, 0.f }, right)) + - std::abs(glm::dot(glm::vec3{ 1.f, 0.f, 0.f }, up)) + - std::abs(glm::dot(glm::vec3{ 1.f, 0.f, 0.f }, forward)); - - const float newIj = std::abs(glm::dot(glm::vec3{ 0.f, 1.f, 0.f }, right)) + - std::abs(glm::dot(glm::vec3{ 0.f, 1.f, 0.f }, up)) + - std::abs(glm::dot(glm::vec3{ 0.f, 1.f, 0.f }, forward)); - - const float newIk = std::abs(glm::dot(glm::vec3{ 0.f, 0.f, 1.f }, right)) + - std::abs(glm::dot(glm::vec3{ 0.f, 0.f, 1.f }, up)) + - std::abs(glm::dot(glm::vec3{ 0.f, 0.f, 1.f }, forward)); - - //We not need to divise scale because it's based on the half extention of the AABB - const AABB globalAABB(globalCenter, newIi, newIj, newIk); - - return (globalAABB.isOnOrForwardPlan(camFrustum.leftFace) && - globalAABB.isOnOrForwardPlan(camFrustum.rightFace) && - globalAABB.isOnOrForwardPlan(camFrustum.topFace) && - globalAABB.isOnOrForwardPlan(camFrustum.bottomFace) && - globalAABB.isOnOrForwardPlan(camFrustum.nearFace) && - globalAABB.isOnOrForwardPlan(camFrustum.farFace)); -}; - </code></pre> - - <p> - For the function <fun>isOnOrForwardPlan</fun>, I have taken an algorithm that I found in a wonderful <a href="https://gdbooks.gitbooks.io/3dcollisions/content/Chapter2/static_aabb_plan.html" target="_blank">article</a>. - I invite you to have a look at it if you want to understand how it works. - I just modify the result of its algorithm to check if the AABB is on or forward my plan. - </p> - - <pre><code> -bool isOnOrForwardPlan(const Plan& plan) const -{ - // Compute the projection interval radius of b onto L(t) = b.c + t * p.n - const float r = extents.x * std::abs(plan.normal.x) + - extents.y * std::abs(plan.normal.y) + extents.z * std::abs(plan.normal.z); - - return -r <= plan.getSignedDistanceToPlan(center); -} - </code></pre> - - <p> - To check if our algorithm works, we need to check that every object disappeared in front of our camera when we moved. - Then, we can add a counter that is incremented if an object is displayed and another for the total displayed in our console. - </p> - - <pre><code> -// in main.cpp main lopp -unsigned int total = 0, display = 0; -ourEntity.drawSelfAndChild(camFrustum, ourShader, display, total); -std::cout << "Total process in CPU : " << total; -std::cout << " / Total send to GPU : " << display << std::endl; - -// In the drawSelfAndChild function of entity -void drawSelfAndChild(const Frustum& frustum, Shader& ourShader, - unsigned int& display, unsigned int& total) -{ - if (boundingVolume->isOnFrustum(frustum, transform)) - { - ourShader.setMat4("model", transform.getModelMatrix()); - pModel->Draw(ourShader); - display++; - } - total++; - - for (auto&& child : children) - { - child->drawSelfAndChild(frustum, ourShader, display, total); - } -} - </code></pre> - -<img src="/img/guest/2021/Frustum_culling/result.png" alt="Result"/> - - <p> - Ta-dah ! The average of objects sent to our GPUrepresents now about 15% of the total and is only divided by 6. - A wonderful result if your GPU process is the bottleneck because of your shader or number of polygons. - You can find the code <a href="https://learnopengl.com/code_viewer_gh.php?code=src/8.guest/2021/1.scene/2.frustum_culling/frustum_culling.cpp" target="_blank">here</a>. - </p> - - <h2>Optimization</h2> - <p> - Now you know how to make your frustum culling. - Frustum culling can be useful to avoid computation of things that are not visible. - You can use it to not compute the animation state of your entity, simplify its AI... - For this reason, I advise you to add a IsInFrustum flag in your entity and do a frustum culling pass that fills this variable. - </p> - <h3>Space partitionning</h3> - <p> - In our example, frustum culling is a good balance with a small number of entities in the CPU. - If you want to optimize your detection, you now will need to partition your space. - To do it, a lot of algorithms exist and each has interesting properties which depend on your usage : - - BSH (Bounding sphere hierarchy or tree) : - Different kinds exist. The simplest implementation is to wrap both nearest objects in a sphere. - Wrap this sphere with another group or objetc etc... - <img src="/img/guest/2021/Frustum_culling/BSH.png" width="400" height="400" alt="BSH example"/> - </p> - <note> - In this example, only 2 checks allow us to know that 3 objects are in frustum instead of 6 because if the bounding sphere is totally inside the frustum all its content is also inside. - If the bounding sphere is not inside when needed to inter and check its content. - </note> - <p> - - <a href="https://en.wikipedia.org/wiki/Quadtree" target="_blank">Quadtree</a> : - The main idea is that you will split space into 4 zones that can be split into four zones etc... until an object wasn't wrapped alone. - Your object will be the leaf of this diagram. - The quadtree is very nice to partition 2D spaces but also if you don't need to partition height. It can be very useful in strategy games like 4x (like age of empire, war selection...) because you don't need height partitioning. - <img src="/img/guest/2021/Frustum_culling/quadtree.png" width="400" height="400" alt="Quatree example"/> - - <a href="https://en.wikipedia.org/wiki/Octree" target="_blank">Octree</a> : - It's like a quadtree but with 8 nodes. It's nice if you have a 3D game with elements in different height levels. - <img src="/img/guest/2021/Frustum_culling/octree.png" width="500" alt="Octree example"/> - - <a href="https://en.wikipedia.org/wiki/Binary_space_partitioning" target="_blank">BSP (binary space partitioning)</a> : - It's a very fast algorithm that allows you to split space with segments. You will define a segment and the algorithm will sort if an object is in front of this segment or behind. - It's very useful with a map, city, dungeon... The segments can be created at the same time if you generate a map and can be fast forward. - <img src="/img/guest/2021/Frustum_culling/BSP.png" alt="BSP example"/> - - Lot of other methods exist, be curious. - I don't implement each of these methods, I just learn it to know that they exist if one day I need specific space partitioning. - Some algorithm is great to parallelize like octree of quadtree if you use multithread and must also balance on your decision. - </p> - - <h3>Compute shader</h3> - <p> - Compute shader allows you to process computation on shader. - This technique must be used only if you have a high parallelized task like check collision with a simple list of bounds. - I never implemented this technique for the frustum culling but it can be used in this case to avoid updating space partitioning if you have a lot of objects that move. - </p> - - <h2>Additional resources</h2> - <ul> - <li><a href="http://www.cs.otago.ac.nz/postgrads/alexis/planExtraction.pdf" target="_blank"> - Article about camera frustum extraction</a>: Fast Extraction of Viewing Frustum Plans from the WorldView-Projection Matrix by Gil Gribb and Klaus Hartmann</li> - - <li><a href="https://gdbooks.gitbooks.io/3dcollisions/content/Chapter1/aabb.html" target="_blank"> - Article about collisions detection</a>: A wonderful resource for collision detection and another approach about volume, culling and mathematic concept</li> - - <li><a href="https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/frustum-culling-r4613/" target="_blank"> - Article to go further</a>: A good article to go further on GPU culling process, multithreading and OBB</li> - </ul> - - - <author> - <strong>Article by: </strong>Six Jonathan<br> - <strong>Contact: </strong><a href="Six-Jonathan@orange.fr" target="_blank">e-mail</a><br> - <strong>Date: </strong> 09/2021<br> - <div> - <a href="https://github.com/Renardjojo"> - <svg height="32" aria-hidden="true" viewBox="0 0 16 16" version="1.1" width="32" data-view-component="true" class="octicon octicon-mark-github v-align-middle"> - <path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path> - </svg> - </a> - <a href="https://www.linkedin.com/in/jonathan-six-4553611a9/"> - <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 34 34" class="global-nav__logo"> - <path d="M34,2.5v29A2.5,2.5,0,0,1,31.5,34H2.5A2.5,2.5,0,0,1,0,31.5V2.5A2.5,2.5,0,0,1,2.5,0h29A2.5,2.5,0,0,1,34,2.5ZM10,13H5V29h5Zm.45-5.5A2.88,2.88,0,0,0,7.59,4.6H7.5a2.9,2.9,0,0,0,0,5.8h0a2.88,2.88,0,0,0,2.95-2.81ZM29,19.28c0-4.81-3.06-6.68-6.1-6.68a5.7,5.7,0,0,0-5.06,2.58H17.7V13H13V29h5V20.49a3.32,3.32,0,0,1,3-3.58h.19c1.59,0,2.77,1,2.77,3.52V29h5Z" fill="currentColor"></path> - </svg> - </a> - </div> - </author> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Guest-Articles/2021/Scene/Scene-Graph.html b/translation/Guest-Articles/2021/Scene/Scene-Graph.html @@ -1,725 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Scene Graph</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Scene Graph</h1> -<h1 id="content-url" style='display:none;'>Guest-Articles/2021/Scene/Scene-Graph</h1> -<p> - In this article, we will talk about the <def>scene graph</def>. - A scene graph is not a class or an object, it's more like a pattern that allows you to create inheritance. - This pattern is used a lot in game engines. - For example, it is used in animation to manage bones. - If I move my arm, my hand needs to move too. - To obtain this result, I need a hierarchy with parents and children. - Thanks to it, my hand will be a child of my arm that is also a child of my body. - This hierarchy has the consequence of representing objects in global and local space. - Global space is based on position/rotation and scale based on the world and local space is based on its parent. - So, if I only move my hand, its local and global position will change but not the local and global position of my arm. - However, if I move my arm, its local and global position will change but also the global position of my hand. - Scene graphs can take a lot of forms: register or not the parent, list or contiguous buffer to store the children, flags... - Firstly, we will see a quick example of it. - In a second time, we will see simple optimizations to improve it. - So, talk less, code more ! ;D - </p> - - <div class="box"> - <img src="/img/guest/2021/scene_graph/graph.png" width="400" style="display: inline" alt="Graph example"/> - <img src="/img/guest/2021/scene_graph/sceneGraph.png" width="400" style="display: inline" alt="Scene graph example in editor"/> - </div> - - <p> - First of all, let’s create a transform. - Transform will contain local and global space information about our entities - </p> - - <pre><code> -struct Transform -{ - /*SPACE INFORMATION*/ - //Local space information - glm::vec3 pos = { 0.0f, 0.0f, 0.0f }; - glm::vec3 eulerRot = { 0.0f, 0.0f, 0.0f }; - glm::vec3 scale = { 1.0f, 1.0f, 1.0f }; - - //Global space information concatenate in matrix - glm::mat4 modelMatrix = glm::mat4(1.0f); -}; - </code></pre> - - <note> - In this example, we use the Euler angle system. - The Euler system is easy to use and understand but it contains a lot of undesirable effects: <a href="https://en.wikipedia.org/wiki/Gimbal_lock" target="_blank">Gimbal lock</a>, shortest angle between to angle thanks to lerp is wrong, cost and size... - To avoid it, we need to use <strong>quaternion</strong> but it needs to be hidden because it's hard to represent a rotation thanks to it. - To have a better understanding of this tutorial we will use the Euler angle but I invite you to do some research on quaternion. - </note> - - <p> - Now we can represent local and global space information about an object. - We will make create a class called <funct>Entity</funct>. - This class will simply wrap the space information with a model for the visual demonstration. - </p> - - <pre><code> -class Entity : public Model -{ -public: -[...] - -Transform transform; - - // constructor, expects a filepath to a 3D model. - Entity(string const& path, bool gamma = false) : Model(path, gamma) - {} -}; - </code></pre> - <p> - The scene graph is still missing. So, let's add it into the Entity class. - </p> - <pre><code> -/*SCENE GRAPH*/ -std::list<std::unique_ptr<Entity>> children; - Entity* parent = nullptr; - </code></pre> - <p> - As we said, scene graphs can take a lot of forms: use std::vector instead of std::list, don't register its parent to simplify the size of the class... - In our example we used std::list. - We don't want our entity address to change at all. - My parents’ address always needs to be valid. - Then, I used std::unique_ptr because it's the cleanest approach to avoid a leak. - Memory leak appears happens if you allocate memory thanks to new or malloc and forget to call delete or free when datas disappears. - Information is still in memory but you cannot have access to it. - If you want more information about it, many tutorials talk about it more clearly. - </p> - - <p> - <dl> - <dt> - We need now to create a function to add a child to our entity. - This function will be easy: - </dt> - <dd> - 1: Add entity - </dd> - <dd> - 2: Register parent of this new entity as the current entity - </dd> - </dl> - </p> - - - <pre><code> -template<typename... TArgs> -void addChild(const TArgs&... args) -{ - children.emplace_back(std::make_unique<Entity>(args...)); - children.back()->parent = this; -} - </code></pre> - - <note> - A variadic template allows us to create an entity with any constructor that you want to use without overloading the addChild function. - I used it in this example to awake your curiosity and show you a simple use of it. - </note> - - <p> - Ok, perfect! It's almost done, courage! - Now we have scene graphs and space information but something is still missing. - When we want to send space information to our shader, we need a model matrix. - However, we don't see how to compute it thanks to our parents and our local information. - First, we need a function to create a local model matrix that represents an object in space based on its parent. - This function will be added to the transform class. - </p> - - <pre><code> -glm::mat4 getLocalModelMatrix() -{ - const glm::mat4 transformX = <function id='57'>glm::rotate</function>(glm::mat4(1.0f), - <function id='63'>glm::radians</function>(eulerRot.x), - glm::vec3(1.0f, 0.0f, 0.0f)); - const glm::mat4 transformY = <function id='57'>glm::rotate</function>(glm::mat4(1.0f), - <function id='63'>glm::radians</function>(eulerRot.y), - glm::vec3(0.0f, 1.0f, 0.0f)); - const glm::mat4 transformZ = <function id='57'>glm::rotate</function>(glm::mat4(1.0f), - <function id='63'>glm::radians</function>(eulerRot.z), - glm::vec3(0.0f, 0.0f, 1.0f)); - - // Y * X * Z - const glm::mat4 roationMatrix = transformY * transformX * transformZ; - - // translation * rotation * scale (also know as TRS matrix) - return <function id='55'>glm::translate</function>(glm::mat4(1.0f), pos) * - roationMatrix * - <function id='56'>glm::scale</function>(glm::mat4(1.0f), scale); -} - </code></pre> - - <p> - To combine multiple model matrices, we need to multiply them. - So if I multiply the local model matrix of my hand with the model matrix of the world with arm and arm with hand, I will obtain the global matrix of my hand! - So, let's implement a function in entity class that will do it for us: - </p> - - <pre><code> -void updateSelfAndChild() -{ - if (parent) - modelMatrix = parent->modelMatrix * getLocalModelMatrix(); - else - modelMatrix = getLocalModelMatrix(); - - for (auto&& child : children) - { - child->updateSelfAndChild(); - } -} - </code></pre> - - <p> - Now, if I update the world, all of my entities contained will also be updated and our global model matrix will be computed. - Finally, we just need to add some code in the main function to add multiple moons with the same local distance and scale and code in the main loop to move the first entity. - </p> - - <pre><code> -/*BEFOR THE MAIN LOOP*/ - -// load entities -// ----------- -const char* pathStr = "resources/objects/planet/planet.obj"; -Entity ourEntity(FileSystem::getPath(pathStr)); -ourEntity.transform.pos.x = 10; -const float scale = 0.75; -ourEntity.transform.scale = { scale, scale, scale }; - -{ - Entity* lastEntity = &ourEntity; - - for (unsigned int i = 0; i < 10; ++i) - { - lastEntity->addChild(FileSystem::getPath(pathStr)); - lastEntity = lastEntity->children.back().get(); - - //Set tranform values - lastEntity->transform.pos.x = 10; - lastEntity->transform.scale = { scale, scale, scale }; - } -} -ourEntity.updateSelfAndChild(); - -/*IN THE MAIN LOOP*/ - -// draw our scene graph -Entity* lastEntity = &ourEntity; -while (lastEntity->children.size()) -{ - ourShader.setMat4("model", lastEntity->transform.modelMatrix); - lastEntity->Draw(ourShader); - lastEntity = lastEntity->children.back().get(); -} - -ourEntity.transform.eulerRot.y += 20 * deltaTime; -ourEntity.updateSelfAndChild(); - </code></pre> - - <p> - We can see this result thanks to this code. - </p> - - <img src="/img/guest/2021/scene_graph/result.png" alt="Graph example"/> - - <h2>Optimization</h2> - - <p> - Being pragmatic is essential to implement a new feature but now let's see how to optimize it. - Firstly, in the main loop, we always update the model matrix even if it doesn't move. - In programming, a pattern called a dirty flag can help us. - A dirty flag is a simple boolean called "isDirty" that allows us to know if an entity was moved during the previous frame. - So, if the user scales, rotates or translates it, we need to set this flag on. - Don't forget, the model matrix is based on the parent model matrix. - So if I change it, I also need to compute all its children's matrix. - This flag will be set off in the update function when the model matrix was recomputed. - </p> - - <warning> - This pattern is very powerful but can be integrated easily only if you encapsulate your class correctly. - I haven’t done it to give you an example of rigid code without the possibility to evolve and change easily. - This code requires the programmer to code its functionality perfectly and is difficult to change, improve or optimize. - But in production, time is a precious resource and you sometimes need to implement the feature without optimizing. - One day, if your feature becomes the performance bottleneck, you need to change it easily. - Encapsulation, private/protected, getter/setter is C++ advantage that allows you to do it. - Encapsulation has its downsides too. - It can increase the size of your class and reduce its visibility. - It can also be laborious if you create a getter/setter for all your members. - An illarouse <a href="https://www.youtube.com/watch?v=-AQfQFcXac8" target="_blank">video</a> show the c++ limit but keep in mind this example and make do the balance in function of your situation. - </warning> - - <p> - Let's remake do again transform function with encapsulation and dirty flag - </p> - - <pre><code> -class Transform -{ -protected: - //Local space information - glm::vec3 m_pos = { 0.0f, 0.0f, 0.0f }; - glm::vec3 m_eulerRot = { 0.0f, 0.0f, 0.0f }; //In degrees - glm::vec3 m_scale = { 1.0f, 1.0f, 1.0f }; - - //Global space information concatenate in matrix - glm::mat4 m_modelMatrix = glm::mat4(1.0f); - - //Dirty flag - bool m_isDirty = true; - -protected: - glm::mat4 getLocalModelMatrix() - { - const glm::mat4 transformX = <function id='57'>glm::rotate</function>(glm::mat4(1.0f), - <function id='63'>glm::radians</function>(m_eulerRot.x), - glm::vec3(1.0f, 0.0f, 0.0f)); - const glm::mat4 transformY = <function id='57'>glm::rotate</function>(glm::mat4(1.0f), - <function id='63'>glm::radians</function>(m_eulerRot.y), - glm::vec3(0.0f, 1.0f, 0.0f)); - const glm::mat4 transformZ = <function id='57'>glm::rotate</function>(glm::mat4(1.0f), - <function id='63'>glm::radians</function>(m_eulerRot.z), - glm::vec3(0.0f, 0.0f, 1.0f)); - - // Y * X * Z - const glm::mat4 roationMatrix = transformY * transformX * transformZ; - - // translation * rotation * scale (also know as TRS matrix) - return <function id='55'>glm::translate</function>(glm::mat4(1.0f), m_pos) * - roationMatrix * - <function id='56'>glm::scale</function>(glm::mat4(1.0f), m_scale); - } -public: - - void computeModelMatrix() - { - m_modelMatrix = getLocalModelMatrix(); - } - - void computeModelMatrix(const glm::mat4& parentGlobalModelMatrix) - { - m_modelMatrix = parentGlobalModelMatrix * getLocalModelMatrix(); - } - - void setLocalPosition(const glm::vec3& newPosition) - { - m_pos = newPosition; - m_isDirty = true; - } - - [...] - - const glm::vec3& getLocalPosition() - { - return m_pos; - } - - [...] - - const glm::mat4& getModelMatrix() - { - return m_modelMatrix; - } - - bool isDirty() - { - return m_isDirty; - } -}; - - -class Entity : public Model -{ -public: - //Scene graph - std::list<std::unique_ptr<Entity>> children; - Entity* parent = nullptr; - - //Space information - Transform transform; - - // constructor, expects a filepath to a 3D model. - Entity(string const& path, bool gamma = false) : Model(path, gamma) - {} - - //Add child. Argument input is argument of any constructor that you create. - //By default you can use the default constructor and don't put argument input. - template<typename... TArgs> - void addChild(const TArgs&... args) - { - children.emplace_back(std::make_unique<Entity>(args...)); - children.back()->parent = this; - } - - //Update transform if it was changed - void updateSelfAndChild() - { - if (!transform.isDirty()) - return; - - forceUpdateSelfAndChild(); - } - - //Force update of transform even if local space don't change - void forceUpdateSelfAndChild() - { - if (parent) - transform.computeModelMatrix(parent->transform.getModelMatrix()); - else - transform.computeModelMatrix(); - - for (auto&& child : children) - { - child->forceUpdateSelfAndChild(); - } - } -}; - </code></pre> - - <p> - Perfect! Another solution to improve performance can be memorizing the local Model matrix. - Thanks to it, we avoid recomputing all child local model matrices if the parent is moved. - This solution improves performance but transforms become heavier. - This size is really important for your hardware. - We will see why in the limitation subchapter. - You can find the code <a href="https://learnopengl.com/code_viewer_gh.php?code=src/8.guest/2021/1.scene/1.scene_graph/scene_graph.cpp" target="_blank">here</a>. - </p> - - <h2>Limitation</h2> - - <p> - Do you ever hear about <def>data oriented design</def> ? No ? Let's talk brevely about it ! - </p> - <p> - This design is based on how your hardware works. - In your computer, data is aligned into your memory. - When you use data like a variable, this data is sent to the cache. - The cache is a very fast memory but without a lot of space. - This memory is localized into your CPU. - For example, if you want to make a cake, you need to go to a supermarket (hard disk) to buy ingredients. - A supermarket is very big but is also very far from your home. - When you buy your ingredients, you store them in your fridge (RAM). - Your fridge contains ingredients that you need to live but I hope for you that you don't live only with cake ^^ - It is also small but near to your kitchen worktop. - And finally, your kitchen worktop (the cache) is small and can't contain all your ingredients but is very near to your preparation. - It's the same for the PC! - When you need to process data, your system will copy your data but also all datas after in cache (depending on your cache line size). - So, if all your datas are not contiguous in your memory, you will be as slow as if you need to take eggs one by one in your fridge to make your cake. - std::list is a noncontiguous array. - std::map will probably make the job better but inheritance cannot be aligned into your memory because it's a tree. - So, if you want to make RTS for example, with an independent unit, you probably don't need or don't want a scene graph to manage your entities. - </p> - - <note> - std::map is tree and is aligned in memory but scene graph will jump into this memory and will be slower than don't use inheritance. - </note> - - <p> - You know now what a Scene graph is and how to use it with its limitations. - In the next chapter, we will talk about frustum culling with a scene graph. - </p> - - <h2>Additional resources</h2> - - <ul> - <li><a href="https://walterkuppens.com/post/wtf-is-a-scene-graph/" target="_blank"> - walterkuppens article about scene graph</a>: An article by Walter Kuppens with another approach of the scene graph.</li> - - <li><a href="https://webglfundamentals.org/webgl/lessons/webgl-scene-graph.html" target="_blank"> - webglfundamentals article and demonstration about scene graph</a>: An article with animated webGL code and animated demonstration</li> - </ul> - - <author> - <strong>Article by: </strong>Six Jonathan<br> - <strong>Contact: </strong><a href="Six-Jonathan@orange.fr" target="_blank">e-mail</a><br> - <strong>Date: </strong> 09/2021<br> - <div> - <a href="https://github.com/Renardjojo"> - <svg height="32" aria-hidden="true" viewBox="0 0 16 16" version="1.1" width="32" data-view-component="true" class="octicon octicon-mark-github v-align-middle"> - <path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path> - </svg> - </a> - <a href="https://www.linkedin.com/in/jonathan-six-4553611a9/"> - <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 34 34" class="global-nav__logo"> - <path d="M34,2.5v29A2.5,2.5,0,0,1,31.5,34H2.5A2.5,2.5,0,0,1,0,31.5V2.5A2.5,2.5,0,0,1,2.5,0h29A2.5,2.5,0,0,1,34,2.5ZM10,13H5V29h5Zm.45-5.5A2.88,2.88,0,0,0,7.59,4.6H7.5a2.9,2.9,0,0,0,0,5.8h0a2.88,2.88,0,0,0,2.95-2.81ZM29,19.28c0-4.81-3.06-6.68-6.1-6.68a5.7,5.7,0,0,0-5.06,2.58H17.7V13H13V29h5V20.49a3.32,3.32,0,0,1,3-3.58h.19c1.59,0,2.77,1,2.77,3.52V29h5Z" fill="currentColor"></path> - </svg> - </a> - </div> - </author> - - - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Guest-Articles/How-to-publish.html b/translation/Guest-Articles/How-to-publish.html @@ -1,338 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - How to publish</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">How to publish</h1> -<h1 id="content-url" style='display:none;'>Guest-Articles/How-to-publish</h1> -<p> -If you'd like to write your own article and have it published on LearnOpenGL for everyone to read you can send over your article to me by <a href="mailto:joey.d.vries@gmail.com">e-mail</a>. By having interested readers publish their own articles on LearnOpenGL I hope this website will be more than just an online book, but a more centralized knowledge platform for everything OpenGL. -</p> - -<p> -If you'd like to publish an article you'll want to follow the following guidelines: -</p> - -<ul> -<li>Article should be OpenGL related. Articles may be technical e.g. <em>How to use <function id='1'>glDrawArrays</function>Indirect and compute shaders to efficiently render large scenes</em>, or sementical e.g. <em>How I learned OpenGL in 30 days</em>.</li> -<li>Article needs to have a positive impact on LearnOpenGL e.g. be educational, informative, inspirational.</li> -<li>Article should be written in a style similar to LearnOpenGL: a more step-by-step approach to writing tutorials.</li> -<li>When using shaders and/or a camera, make sure to use LearnOpenGL's shader and camera includes for consistency with all the other articles. Similarly, use GLFW and GLAD.</li> -<li>Article should use HTML tags to organize its layout. There is a source page you can find <a href="https://learnopengl.com/demo/example_page.txt" target="_blank">here</a> as an example (replace <code>.txt</code> extension with <code>.html</code> to view as HTML). Additionally, copy this <a href="https://learnopengl.com/demo/layout.css" target="_blank">example CSS</a> file in the same folder to test with similar styling/layout.</li> - <li>Add <code>target="_blank"</code> to your URLs/links (see example page) so pages open in a new tab by default.</li> - <li>When using the <code><</code> or <code>></code> symbol it may break the page as these are used for HTML tags. Use <b>&lt;</b> and <b>&gt;</b> instead. </li> -<li>Images/videos can be referenced by URL. If you'd like to use locally hosted images/videos, send them alongside your article and reference them as `/img/guest/<year>/<your_article_name>/<image.png>` with `<your_article_name>` in lowercase and seperated by underlines.</li> - <li>Images should not be wider than 800px, height can be anything.</li> -<li>When displaying code in your article, make sure to use spaces over tabs (4 spaces per tab).</li> -<li>If the article is technical, try to keep the code as self-contained (and as few files) as possible. And make the full source code available.</li> -<li>It's perfectly fine to have your article be a series of articles e.g. <em>Terrain rendering tutorial part X/5</em>.</li> - <li>When referencing original LearnOpenGL chapters, try to link to them and use the word <em>Chapter</em> over article/tutorial to keep that online 'book' feeling.</li> -<li>Article should of course be technically and mathematically sound.</li> -<li>Preferably keep the article name short so it fits in the sidebar :)</li> -<li>Check with me first before you start as someone else may already be working on the same topic or the topic doesn't match well.</li> -</ul> - -<p> -As I intend to keep the quality-bar high, I'll be thorough in enforcing these restrictions. This means there's a good chance I'll reject your article, or return it with a significant amount of feedback. Nevertheless, if you followed the guidelines and generally make for an interesting article that adds something on top of the already existing content it's likely your article ends up on LearnOpenGL. -</p> - -<p> -When you submit an article, please mention your full name, and a link to your website/blog if you'd like the additional exposure. -</p> - -<h2>Topic suggestions</h2> -<p> - If you'd like to contribute but don't know what to write about, the following topics are a selection of highly requested topics: -</p> - -<ul> - <li>Casaded Shadow Mapping</li> - <li>Depth of Field</li> - <li>Motion Blur</li> - <li>Temporal AA</li> - <li>Compute Shaders</li> - <li>OpenGL 4+ features</li> - <li>Tesselation Shaders</li> - <li>Reflection Probes (and in-between blending)</li> - <li>Heightmaps (with dynamic LOD-ing using quadtree or octree, and layers for grass/mud/sand/rock textures)</li> - <li>Lens flares</li> - <li>Decals</li> - <li>GPU particle effects</li> - <li>Global Illumination</li> - <li>Light Adaptation</li> - <li>HBAO+</li> - <li>Water Simulation</li> - <li>Forward+ Rendering</li> - <li>GPU programming, creating optimized shaders</li> - <li>Procedural clouds/sky</li> -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/2D-Game/Audio.html b/translation/In-Practice/2D-Game/Audio.html @@ -1,406 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Audio</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Audio</h1> -<h1 id="content-url" style='display:none;'>In-Practice/2D-Game/Audio</h1> -<p> - The game's making great progress, but it still feels a bit empty as there's no audio whatsoever. In this chapter we're going to fix that. -</p> - -<p> - OpenGL doesn't offer us any support for audio capabilities (like many other aspects of game development). We have to manually load audio files into a collection of bytes, process and convert them to an audio stream, and manage multiple audio streams appropriately for use in our game. This can get complicated pretty quick and requires some low-level knowledge of audio engineering. -</p> - -<p> - If it is your cup of tea then feel free to manually load audio streams from one or more audio file extensions. We are, however, going to make use of a library for audio management called <strong>irrKlang</strong>. -</p> - -<h2>Irrklang</h2> - -<img src="/img/in-practice/breakout/irrklang.png" class="right" alt="Irrklang logo"/> -<p> - IrrKlang is a high level 2D and 3D cross platform (Windows, Mac OS X, Linux) sound engine and audio library that plays WAV, MP3, OGG, and FLAC files to name a few. It also features several audio effects like reverb, delay, and distortion that can be extensively tweaked. -</p> - -<note> - 3D audio means that an audio source can have a 3D position that will attenuate its volume based on the camera's distance to the audio source, making it feel natural in a 3D world (think of gunfire in a 3D world; most often you'll be able to hear where it came from just by the direction/location of the sound). -</note> - -<p> - IrrKlang is an easy-to-use audio library that can play most audio files with just a few lines of code, making it a perfect candidate for our Breakout game. Note that irrKlang has a slightly restrictive license: you are allowed to use irrKlang as you see fit for non-commercial purposes, but you have to pay for their pro version whenever you want to use irrKlang commercially. -</p> - -<p> - You can download irrKlang from their <a href="http://www.ambiera.com/irrklang/downloads.html" target="_blank">download</a> page; we're using version 1.5 for this chapter. Because irrKlang is closed-source, we cannot compile the library ourselves so we'll have to do with whatever irrKlang provided for us. Luckily they have plenty of precompiled library files. -</p> - -<p> - Once you include the header files of irrKlang, add their (64-bit) library (<code>irrKlang.lib</code>) to the linker settings, and copy the dll file(s) to the appropriate locations (usually the same location where the <code>.exe</code> resides) we're set to go. Note that if you want to load MP3 files, you'll also have to include the <code>ikpMP3.dll</code> file. -</p> - -<h3>Adding music</h3> -<p> - Specifically for this game I created a small little audio track so the game feels a bit more alive. You can find the audio track <a href="/audio/in-practice/breakout/breakout.mp3" target="_blank">here</a> that we'll use as the game's background music. This track is what we'll play whenever the game starts and that continuously loops until the player closes the game. Feel free to replace it with your own tracks or use it in any way you like. -</p> - -<audio controls> - <source src="/audio/in-practice/breakout/breakout.mp3" type="audio/mpeg"> - Your browser does not support the audio element. -</audio> - -<p> - Adding this to the Breakout game is extremely easy with the irrKlang library. We include the irrKlang header file, create an <code>irrKlang::ISoundEngine</code>, initialize it with <fun>createIrrKlangDevice</fun>, and then use the engine to load and play audio files: -</p> - -<pre><code> -#include <irrklang/irrKlang.h> -using namespace irrklang; - -ISoundEngine *SoundEngine = createIrrKlangDevice(); - -void Game::Init() -{ - [...] - SoundEngine->play2D("audio/breakout.mp3", true); -} -</code></pre> - - -<p> - Here we created a <var>SoundEngine</var> that we use for all audio-related code. Once we've initialized the sound engine, all we need to do to play audio is simply call its <fun>play2D</fun> function. Its first parameter is the filename, and the second parameter whether we want the file to loop (play again once it's finished). -</p> - -<p> - And that is all there is to it! Running the game should now cause your speakers (or headset) to violently blast out sound waves. -</p> - -<h3>Adding sounds</h3> -<p> - We're not there yet, since music by itself is not enough to make the game as great as it could be. We want to play sounds whenever something interesting happens in the game, as extra feedback to the player. Like when we hit a brick, or when we activate a powerup. Below you can find all the sounds we're going to use (courtesy of freesound.org): -</p> - -<p> - <a href="/audio/in-practice/breakout/bleep.mp3" target="_blank"><strong>bleep.mp3</strong></a>: the sound for when the ball hit a non-solid block. -</p> - -<audio controls> - <source src="/audio/in-practice/breakout/bleep.mp3" type="audio/mpeg"> - Your browser does not support the audio element. -</audio> - -<p> - <a href="/audio/in-practice/breakout/solid.wav" target="_blank"><strong>solid.wav</strong></a>: the sound for when the ball hit a solid block. -</p> - -<audio controls> - <source src="/audio/in-practice/breakout/solid.wav" type="audio/mpeg"> - Your browser does not support the audio element. -</audio> - - <p> - <a href="/audio/in-practice/breakout/powerup.wav" target="_blank"><strong>powerup.wav</strong></a>: the sound for when we the player paddle collided with a powerup block. -</p> - -<audio controls> - <source src="/audio/in-practice/breakout/powerup.wav" type="audio/mpeg"> - Your browser does not support the audio element. -</audio> - -<p> - <a href="/audio/in-practice/breakout/bleep.wav" target="_blank"><strong>bleep.wav</strong></a>: the sound for when we the ball bounces of the player paddle. -</p> - -<audio controls> - <source src="/audio/in-practice/breakout/bleep.wav" type="audio/mpeg"> - Your browser does not support the audio element. -</audio> - -<p> - Wherever a collision occurs, we play the corresponding sound. I won't walk through each of the lines of code where this is supposed to happen, but simply list the updated game code <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/9.game.cpp" target="_blank">here</a>. You should easily be able to add the sound effects at their appropriate locations. -</p> - -<p> - Putting it all together gives us a game that feels a lot more complete. All together it looks (and sounds) like this: -</p> - -<div class="video"> - <video width="600" height="450" controls> - <source src="/video/in-practice/breakout/audio.mp4" type="video/mp4" /> - </video> -</div> - -<p> - IrrKlang allows for much more fine-grained control of audio controls like advanced memory management, audio effects, or sound event callbacks. Check out their simple C++ <a href="http://www.ambiera.com/irrklang/tutorials.html" target="_blank">tutorials</a> and try to experiment with its features. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/2D-Game/Breakout.html b/translation/In-Practice/2D-Game/Breakout.html @@ -1,343 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Breakout</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Breakout</h1> -<h1 id="content-url" style='display:none;'>In-Practice/2D-Game/Breakout</h1> -<p> - Over these chapters we learned a fair share about OpenGL's inner workings and how we can use them to create fancy graphics. However, aside from a lot of tech demos, we haven't really created a practical application with OpenGL. This is the introduction of a larger series about creating a relatively simple 2D game using OpenGL. The next chapters will demonstrate how we can use OpenGL in a larger, more complicated, setting. Note that the series does not necessarily introduce new OpenGL concepts but more or less show how we can apply these concepts to a larger whole. -</p> - -<p> - Because we rather keep things simple, we're going to base our 2D game on an already existing 2D arcade game. Introducing <def>Breakout</def>, a classic 2D game released in 1976 on the Atari 2600 console. Breakout requires the player, who controls a small horizontal paddle, to destroy all the bricks by bouncing a small ball against each brick without allowing the ball to reach the bottom edge. Once the player destroys all bricks, he completes the game. -</p> - -<p> - Below we can see how Breakout originally looked on the Atari 2600: -</p> - -<img src="/img/in-practice/breakout/breakout2600.png" class="medium" alt="Image of Atari 2600's Breakout"/> - -<p> - The game has the following mechanics: -</p> - -<ul> - <li>A small paddle is controlled by the player and can only move horizontally within the bounds of the screen.</li> - <li>The ball travels across the screen and each collision results in the ball changing its direction based on where it hit; this applies to the screen bounds, the bricks, and the paddle.</li> - <li>If the ball reaches the bottom edge of the screen, the player is either game over or loses a life.</li> - <li>As soon as a brick touches the ball, the brick is destroyed.</li> - <li>The player wins as soon as all bricks are destroyed.</li> - <li>The direction of the ball can be manipulated by how far the ball bounces from the paddle's center.</li> -</ul> - -<p> - Because from time to time the ball may find a small gap reaching the area above the brick wall, it will continue to bounce up and forth between the top edge of the level and the top edge of the brick layer. The ball keeps this up, until it eventually finds a gap again. This is logically where the game obtained its name from, since the ball has to <em>break out</em>. -</p> - -<h1>OpenGL Breakout</h1> -<p> - We're going to take this classic arcade game as the basis of a 2D game that we'll completely implement with OpenGL. This version of Breakout will render its graphics on the GPU which gives us the ability to enhance the classical Breakout game with some nice extra features. -</p> - -<p> - Other than the classic mechanics, our version of Breakout will feature: -</p> - -<ul> - <li>Amazing graphics!</li> - <li>Particles</li> - <li>Text rendering</li> - <li>Power-ups</li> - <li>Postprocessing effects</li> - <li>Multiple (customizable) levels</li> -</ul> - -<p> - To get you excited you can see what the game will look like after you've finished these chapters: -</p> - -<img src="/img/in-practice/breakout/cover.png" alt="OpenGL version of Breakout"/> - -<p> - These chapters will combine a large number of concepts from previous chapters and demonstrate how they can work together as a whole. Therefore, it is important to have at least finished the <a href="https://learnopengl.com/Getting-started/OpenGL" target="_blank">Getting started</a> chapters before working your way through these series. -</p> - -<p> - Also, several chapters will require concepts from other chapters (<def>Framebuffers</def> for example from the <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">Advanced OpenGL</a> section) so where necessary, the required chapters are listed. -</p> - -<p> - If you believe you're ready to get your hands dirty then move on to the <a href="https://learnopengl.com/In-Practice/2D-Game/Setting-up" target="_blank">next</a> chapter. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/2D-Game/Collisions/Ball.html b/translation/In-Practice/2D-Game/Collisions/Ball.html @@ -1,457 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Ball</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Ball</h1> -<h1 id="content-url" style='display:none;'>In-Practice/2D-Game/Collisions/Ball</h1> -<p> - At this point we have a level full of bricks and a movable player paddle. The only thing missing from the classic Breakout recipe is the ball. The objective is to let the ball collide with all the bricks until each of the destroyable bricks are destroyed, but this all within the condition that the ball is not allowed to reach the bottom edge of the screen. -</p> - -<p> - In addition to the general game object components, a ball has a radius, and an extra boolean value indicating whether the ball is <def>stuck</def> on the player paddle or it's allowed free movement. When the game starts, the ball is initially stuck on the player paddle until the player starts the game by pressing some arbitrary key. -</p> - -<p> - Because the ball is effectively a <fun>GameObject</fun> with a few extra properties it makes sense to create a <fun>BallObject</fun> class as a subclass of <fun>GameObject</fun>: -</p> - -<pre><code> -class BallObject : public GameObject -{ - public: - // ball state - float Radius; - bool Stuck; - - - BallObject(); - BallObject(glm::vec2 pos, float radius, glm::vec2 velocity, Texture2D sprite); - - glm::vec2 Move(float dt, unsigned int window_width); - void Reset(glm::vec2 position, glm::vec2 velocity); -}; -</code></pre> - -<p> - The constructor of <fun>BallObject</fun> initializes its own values, but also initializes the underlying <fun>GameObject</fun>. The <fun>BallObject</fun> class hosts a <fun>Move</fun> function that moves the ball based on its velocity. It also checks if it reaches any of the scene's edges and if so, reverses the ball's velocity: -</p> - -<pre><code> -glm::vec2 BallObject::Move(float dt, unsigned int window_width) -{ - // if not stuck to player board - if (!this->Stuck) - { - // move the ball - this->Position += this->Velocity * dt; - // check if outside window bounds; if so, reverse velocity and restore at correct position - if (this->Position.x <= 0.0f) - { - this->Velocity.x = -this->Velocity.x; - this->Position.x = 0.0f; - } - else if (this->Position.x + this->Size.x >= window_width) - { - this->Velocity.x = -this->Velocity.x; - this->Position.x = window_width - this->Size.x; - } - if (this->Position.y <= 0.0f) - { - this->Velocity.y = -this->Velocity.y; - this->Position.y = 0.0f; - } - - } - return this->Position; -} -</code></pre> - -<p> - In addition to reversing the ball's velocity, we also want relocate the ball back along the edge; the ball is only able to move if it isn't stuck. -</p> - -<note> - Because the player is game over (or loses a life) if the ball reaches the bottom edge, there is no code to let the ball bounce of the bottom edge. We do need to later implement this logic somewhere in the game code though. -</note> - -<p> - You can find the code for the ball object below: -</p> - -<ul> - <li><strong>BallObject</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/5.1.ball_object_collisions.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/5.1.ball_object_collisions.cpp" target="_blank">code</a></li> -</ul> - -<p> - First, let's add the ball to the game. Just like the player paddle, we create a <fun>BallObject</fun> and define two constants that we use to initialize the ball. As for the texture of the ball, we're going to use an image that makes perfect sense in a LearnOpenGL Breakout game: <a href="/img/textures/awesomeface.png" target="_blank">ball texture</a>. -</p> - -<pre><code> -// Initial velocity of the Ball -const glm::vec2 INITIAL_BALL_VELOCITY(100.0f, -350.0f); -// Radius of the ball object -const float BALL_RADIUS = 12.5f; - -BallObject *Ball; - -void Game::Init() -{ - [...] - glm::vec2 ballPos = playerPos + glm::vec2(PLAYER_SIZE.x / 2.0f - BALL_RADIUS, - -BALL_RADIUS * 2.0f); - Ball = new BallObject(ballPos, BALL_RADIUS, INITIAL_BALL_VELOCITY, - ResourceManager::GetTexture("face")); -} -</code></pre> - -<p> - Then we have to update the position of the ball each frame by calling its <fun>Move</fun> function within the game code's <fun>Update</fun> function: -</p> - -<pre><code> -void Game::Update(float dt) -{ - Ball->Move(dt, this->Width); -} -</code></pre> - -<p> - Furthermore, because the ball is initially stuck to the paddle, we have to give the player the ability to remove it from its stuck position. We select the space key for freeing the ball from the paddle. This means we have to change the <fun>processInput</fun> function a little: -</p> - -<pre><code> -void Game::ProcessInput(float dt) -{ - if (this->State == GAME_ACTIVE) - { - float velocity = PLAYER_VELOCITY * dt; - // move playerboard - if (this->Keys[GLFW_KEY_A]) - { - if (Player->Position.x >= 0.0f) - { - Player->Position.x -= velocity; - if (Ball->Stuck) - Ball->Position.x -= velocity; - } - } - if (this->Keys[GLFW_KEY_D]) - { - if (Player->Position.x <= this->Width - Player->Size.x) - { - Player->Position.x += velocity; - if (Ball->Stuck) - Ball->Position.x += velocity; - } - } - if (this->Keys[GLFW_KEY_SPACE]) - Ball->Stuck = false; - } -} -</code></pre> - -<p> - Here, if the user presses the space bar, the ball's <var>Stuck</var> variable is set to <code>false</code>. Note that we also move the position of the ball alongside the paddle's position whenever the ball is stuck. -</p> - -<p> - Last, we need to render the ball which by now should be fairly obvious: -</p> - -<pre><code> -void Game::Render() -{ - if (this->State == GAME_ACTIVE) - { - [...] - Ball->Draw(*Renderer); - } -} -</code></pre> - -<p> - The result is a ball that follows the paddle and roams freely whenever we press the spacebar. The ball also properly bounces of the left, right, and top edge, but it doesn't yet seem to collide with any of the bricks as we can see: -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/in-practice/breakout/no_collisions.mp4" type="video/mp4" /> - <img src="/img/in-practice/breakout/no_collisions.png" class="clean"/> - </video> -</div> - -<p> - What we want is to create one or several function(s) that check if the ball object is colliding with any of the bricks in the level and if so, destroy the brick. These so called <def>collision detection</def> functions is what we'll focus on in the <a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection" target="_blank">next</a> chapter. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/2D-Game/Collisions/Collision-detection.html b/translation/In-Practice/2D-Game/Collisions/Collision-detection.html @@ -1,494 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Collision detection</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Collision detection</h1> -<h1 id="content-url" style='display:none;'>In-Practice/2D-Game/Collisions/Collision-detection</h1> -<p> - When trying to determine if a collision occurs between two objects, we generally do not use the vertex data of the objects themselves since these objects often have complicated shapes; this in turn makes the collision detection complicated. For this reason, it is a common practice to use more simple shapes (that usually have a nice mathematical definition) for collision detection that we <em>overlay</em> on top of the original object. We then check for collisions based on these simple shapes; this makes the code easier and saves a lot of performance. A few examples of such <def>collision shapes</def> are circles, spheres, rectangles, and boxes; these are a lot simpler to work with compared to arbitrary meshes with hundreds of triangles. -</p> - -<p> - While the simple shapes do give us easier and more efficient collision detection algorithms, they share a common disadvantage in that these shapes usually do not fully surround the object. The effect is that a collision may be detected that didn't really collide with the actual object; one should always keep in mind that these shapes are just approximations of the real shapes. -</p> - -<h2>AABB - AABB collisions</h2> -<p> - AABB stands for <def>axis-aligned bounding box</def>, a rectangular collision shape aligned to the base axes of the scene, which in 2D aligns to the x and y axis. Being axis-aligned means the rectangular box has no rotation and its edges are parallel to the base axes of the scene (e.g. left and right edge are parallel to the y axis). The fact that these boxes are always aligned to the axes of the scene makes calculations easier. Here we surround the ball object with an AABB: -</p> - -<img src="/img/in-practice/breakout/collisions_ball_aabb.png" alt="AABB on top of ball in OpenGL"/> - - - -<p> - Almost all the objects in Breakout are rectangular based objects, so it makes perfect sense to use axis aligned bounding boxes for detecting collisions. This is exactly what we're going to do. -</p> - -<p> - Axis aligned bounding boxes can be defined in several ways. One of them is to define an AABB by a top-left and a bottom-right position. The <fun>GameObject</fun> class that we defined already contains a top-left position (its <var>Position</var> vector), and we can easily calculate its bottom-right position by adding its size to the top-left position vector (<var>Position</var><code> + </code><var>Size</var>). Effectively, each <fun>GameObject</fun> contains an AABB that we can use for collisions. -</p> - -<p> - So how do we check for collisions? A collision occurs when two collision shapes enter each other's regions e.g. the shape that determines the first object is in some way inside the shape of the second object. For AABBs this is quite easy to determine due to the fact that they're aligned to the scene's axes: we check for each axis if the two object' edges on that axis overlap. So we check if the horizontal edges overlap, and if the vertical edges overlap of both objects. If both the horizontal <strong>and</strong> vertical edges overlap we have a collision. -</p> - -<img src="/img/in-practice/breakout/collisions_overlap.png" class="clean" alt="Image of overlapping edges of AABB"/> - -<p> - Translating this concept to code is relatively straightforward. We check for overlap on both axes and if so, return a collision: -</p> - -<pre><code> -bool CheckCollision(GameObject &one, GameObject &two) // AABB - AABB collision -{ - // collision x-axis? - bool collisionX = one.Position.x + one.Size.x >= two.Position.x && - two.Position.x + two.Size.x >= one.Position.x; - // collision y-axis? - bool collisionY = one.Position.y + one.Size.y >= two.Position.y && - two.Position.y + two.Size.y >= one.Position.y; - // collision only if on both axes - return collisionX && collisionY; -} -</code></pre> - -<p> - We check if the right side of the first object is greater than the left side of the second object <strong>and</strong> if the second object's right side is greater than the first object's left side; similarly for the vertical axis. If you have trouble visualizing this, try to draw the edges/rectangles on paper and determine this for yourself. -</p> - -<p> - To keep the collision code a bit more organized we add an extra function to the <fun>Game</fun> class: -</p> - -<pre><code> -class Game -{ - public: - [...] - void DoCollisions(); -}; -</code></pre> - -<p> - Within <fun>DoCollisions</fun>, we check for collisions between the ball object and each brick of the level. If we detect a collision, we set the brick's <var>Destroyed</var> property to <code>true</code>, which instantly stops the level from rendering this brick: -</p> - -<pre><code> -void Game::DoCollisions() -{ - for (GameObject &box : this->Levels[this->Level].Bricks) - { - if (!box.Destroyed) - { - if (CheckCollision(*Ball, box)) - { - if (!box.IsSolid) - box.Destroyed = true; - } - } - } -} -</code></pre> - -<p> - Then we also need to update the game's <fun>Update</fun> function: -</p> - -<pre><code> -void Game::Update(float dt) -{ - // update objects - Ball->Move(dt, this->Width); - // check for collisions - this->DoCollisions(); -} -</code></pre> - - -<p> - If we run the code now, the ball should detect collisions with each of the bricks and if the brick is not solid, the brick is destroyed. If you run the game now it'll look something like this: -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/in-practice/breakout/collisions.mp4" type="video/mp4" /> - <img src="/img/in-practice/breakout/collisions.png" class="clean"/> - </video> -</div> - -<p> - While the collision detection does work, it's not very precise since the ball's rectangular collision shape collides with most of the bricks without the ball directly touching them. Let's see if we can figure out a more precise collision detection technique. -</p> - -<h2>AABB - Circle collision detection</h2> -<p> - Because the ball is a circle-like object, an AABB is probably not the best choice for the ball's collision shape. The collision code thinks the ball is a rectangular box, so the ball often collides with a brick even though the ball sprite itself isn't yet touching the brick. -</p> - -<img src="/img/in-practice/breakout/collisions_ball_aabb_touch.png" alt="Ball colliding with brick as an AABB"/> - -<p> - It makes much more sense to represent the ball with a circle collision shape instead of an AABB. For this reason we included a <var>Radius</var> variable within the ball object. To define a circle collision shape, all we need is a position vector and a radius. -</p> - - <img src="/img/in-practice/breakout/collisions_circle.png" alt="Ball circular collision shape"/> - -<p> - This does mean we have to update the detection algorithm since it currently only works between two AABBs. Detecting collisions between a circle and a rectangle is a bit more complicated, but the trick is as follows: we find the point on the AABB that is closest to the circle, and if the distance from the circle to this point is less than its radius, we have a collision. -</p> - - <p> - The difficult part is getting this closest point \(\color{red}{\bar{P}}\) on the AABB. The following image shows how we can calculate this point for any arbitrary AABB and circle: - </p> - - <img src="/img/in-practice/breakout/collisions_aabb_circle.png" class="clean" alt="AABB - Circle collision detection"/> - -<p> - We first need to get the difference vector between the ball's center \(\color{blue}{\bar{C}}\) and the AABB's center \(\color{green}{\bar{B}}\) to obtain \(\color{purple}{\bar{D}}\). What we then need to do is <def>clamp</def> this vector \(\color{purple}{\bar{D}}\) to the AABB's half-extents \(\color{orange}{{w}}\) and \(\color{teal}{\bar{h}}\) and add it to \(\color{green}{\bar{B}}\). The half-extents of a rectangle are the distances between the rectangle's center and its edges: its size divided by two. This returns a position vector that is always located somewhere at the edge of the AABB (unless the circle's center is inside the AABB). -</p> - -<note> - A clamp operation <strong>clamps</strong> a value to a value within a given range. This is often expressed as: - -<pre><code> -float clamp(float value, float min, float max) { - return std::max(min, std::min(max, value)); -} -</code></pre> - - For example, a value of <code>42.0f</code> is clamped to <code>6.0f</code> with a range of <code>3.0f</code> to <code>6.0f</code>, and a value of <code>4.20f</code> would be clamped to <code>4.20f</code>. <br/> - - Clamping a 2D vector means we clamp both its <code>x</code> and its <code>y</code> component within the given range. -</note> - -<p> - This clamped vector \(\color{red}{\bar{P}}\) is then the closest point from the AABB to the circle. What we then need to do is calculate a new difference vector \(\color{purple}{\bar{D'}}\) that is the difference between the circle's center \(\color{blue}{\bar{C}}\) and the vector \(\color{red}{\bar{P}}\). -</p> - - <img src="/img/in-practice/breakout/collisions_aabb_circle_radius_compare.png" class="clean" alt="Calculating difference vector D' to get distance between circle and closest point AABB"/> - -<p> - Now that we have the vector \(\color{purple}{\bar{D'}}\), we can compare its length to the radius of the circle. If the length of \(\color{purple}{\bar{D'}}\) is less than the circle's radius, we have a collision. -</p> - -<p> - This is all expressed in code as follows: -</p> - -<pre><code> -bool CheckCollision(BallObject &one, GameObject &two) // AABB - Circle collision -{ - // get center point circle first - glm::vec2 center(one.Position + one.Radius); - // calculate AABB info (center, half-extents) - glm::vec2 aabb_half_extents(two.Size.x / 2.0f, two.Size.y / 2.0f); - glm::vec2 aabb_center( - two.Position.x + aabb_half_extents.x, - two.Position.y + aabb_half_extents.y - ); - // get difference vector between both centers - glm::vec2 difference = center - aabb_center; - glm::vec2 clamped = glm::clamp(difference, -aabb_half_extents, aabb_half_extents); - // add clamped value to AABB_center and we get the value of box closest to circle - glm::vec2 closest = aabb_center + clamped; - // retrieve vector between center circle and closest point AABB and check if length <= radius - difference = closest - center; - return glm::length(difference) < one.Radius; -} -</code></pre> - -<p> - We create an overloaded function for <fun>CheckCollision</fun> that specifically deals with the case between a <fun>BallObject</fun> and a <fun>GameObject</fun>. Because we did not store the collision shape information in the objects themselves we have to calculate them: first the center of the ball is calculated, then the AABB's half-extents and its center. -</p> - -<p> - Using these collision shape attributes we calculate vector \(\color{purple}{\bar{D}}\) as <var>difference</var> that we clamp to <var>clamped</var> and add to the AABB's center to get point \(\color{red}{\bar{P}}\) as <var>closest</var>. Then we calculate the difference vector \(\color{purple}{\bar{D'}}\) between <var>center</var> and <var>closest</var> and return whether the two shapes collided or not. -</p> - -<p> - Since we previously called <fun>CheckCollision</fun> with the ball object as its first argument, we do not have to change any code since the overloaded version of <fun>CheckCollision</fun> now automatically applies. The result is now a much more precise collision detection algorithm: -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/in-practice/breakout/collisions_circle.mp4" type="video/mp4" /> - <img src="/img/in-practice/breakout/collisions_precise.png" class="clean"/> - </video> -</div> - -<p> - It seems to work, but still, something is off. We properly do all the collision detection, but the ball does not <strong>react</strong> in any way to the collisions. We need to update the ball's position and/or velocity whenever a collision occurs. This is the topic of the <a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution" target="_blank">next</a> chapter. -</p> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/2D-Game/Collisions/Collision-resolution.html b/translation/In-Practice/2D-Game/Collisions/Collision-resolution.html @@ -1,570 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Collision resolution</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Collision resolution</h1> -<h1 id="content-url" style='display:none;'>In-Practice/2D-Game/Collisions/Collision-resolution</h1> -<p> - At the end of the last chapter we had a working collision detection system. However, the ball does not react in any way to the detected collisions; it moves straight through all the bricks. We want the ball to <em>bounce</em> of the collided bricks. This chapter discusses how we can accomplish this so called <def>collision resolution</def> within the AABB - circle collision detection logic. -</p> - -<p> - Whenever a collision occurs we want two things to happen: we want to reposition the ball so it is no longer inside the other object and second, we want to change the direction of the ball's velocity so it looks like it's bouncing of the object. -</p> - -<h3>Collision repositioning</h3> -<p> - To position the ball object outside the collided AABB we have to figure out the distance the ball penetrated the bounding box. For this we'll revisit the diagram from the previous chapter: -</p> - -<img src="/img/in-practice/breakout/collisions_aabb_circle_resolution.png" class="clean" alt="Collision resolution between circle and AABB"/> - -<p> - Here the ball moved slightly into the AABB and a collision was detected. We now want to move the ball out of the shape so that it merely touches the AABB as if no collision occurred. To figure out how much we need to move the ball out of the AABB we need to retrieve the vector \(\color{brown}{\bar{R}}\), which is the level of penetration into the AABB. To get this vector \(\color{brown}{\bar{R}}\), we subtract \(\color{green}{\bar{V}}\) from the ball's radius. Vector \(\color{green}{\bar{V}}\) is the difference between closest point \(\color{red}{\bar{P}}\) and the ball's center \(\color{blue}{\bar{C}}\). -</p> - -<p> - Knowing \(\color{brown}{\bar{R}}\), we offset the ball's position by \(\color{brown}{\bar{R}}\) positioning it directly against the AABB; the ball is now properly positioned. -</p> - -<h3>Collision direction</h3> -<p> - Next we need to figure out how to update the ball's velocity after a collision. For Breakout we use the following rules to change the ball's velocity: -</p> - - <ol> - <li>If the ball collides with the right or left side of an AABB, its horizontal velocity (<code>x</code>) is reversed.</li> - <li>If the ball collides with the bottom or top side of an AABB, its vertical velocity (<code>y</code>) is reversed.</li> - </ol> - -<p> - But how do we figure out the direction the ball hit the AABB? There are several approaches to this problem. One of them is that, instead of 1 AABB, we use 4 AABBs for each brick that we each position at one of its edges. This way we can determine which AABB and thus which edge was hit. However, a simpler approach exists with the help of the dot product. -</p> - -<p> - You probably still remember from the <a href="https://learnopengl.com/Getting-started/Transformations" target="_blank">transformations</a> chapter that the dot product gives us the angle between two normalized vectors. What if we were to define four vectors pointing north, south, west, and east, and calculate the dot product between them and a given vector? The resulting dot product between these four direction vectors and the given vector that is highest (dot product's maximum value is <code>1.0f</code> which represents a <code>0</code> degree angle) is then the direction of the vector. -</p> - -<p> - This procedure looks as follows in code: -</p> - -<pre><code> -Direction VectorDirection(glm::vec2 target) -{ - glm::vec2 compass[] = { - glm::vec2(0.0f, 1.0f), // up - glm::vec2(1.0f, 0.0f), // right - glm::vec2(0.0f, -1.0f), // down - glm::vec2(-1.0f, 0.0f) // left - }; - float max = 0.0f; - unsigned int best_match = -1; - for (unsigned int i = 0; i < 4; i++) - { - float dot_product = glm::dot(glm::normalize(target), compass[i]); - if (dot_product > max) - { - max = dot_product; - best_match = i; - } - } - return (Direction)best_match; -} -</code></pre> - -<p> - The function compares <var>target</var> to each of the direction vectors in the <var>compass</var> array. The compass vector <var>target</var> is closest to in angle, is the direction returned to the function caller. Here <var>Direction</var> is part of an enum defined in the game class's header file: -</p> - -<pre><code> -enum Direction { - UP, - RIGHT, - DOWN, - LEFT -}; -</code></pre> - -<p> - Now that we know how to get vector \(\color{brown}{\bar{R}}\) and how to determine the direction the ball hit the AABB, we can start writing the collision resolution code. -</p> - -<h3>AABB - Circle collision resolution</h3> -<p> - To calculate the required values for collision resolution we need a bit more information from the collision function(s) than just a <code>true</code> or <code>false</code>. We're now going to return a <def>tuple</def> of information that tells us if a collision occurred, what direction it occurred, and the difference vector \(\color{brown}{\bar{R}}\). You can find the <code>tuple</code> container in the <code><tuple></code> header. -</p> - -<p> - To keep the code slightly more organized we'll typedef the collision relevant data as <fun>Collision</fun>: -</p> - -<pre><code> -typedef std::tuple<bool, Direction, glm::vec2> Collision; -</code></pre> - -<p> - Then we change the code of the <fun>CheckCollision</fun> function to not only return <code>true</code> or <code>false</code>, but also the direction and difference vector: -</p> - -<pre><code> -Collision CheckCollision(BallObject &one, GameObject &two) // AABB - AABB collision -{ - [...] - if (glm::length(difference) <= one.Radius) - return std::make_tuple(true, VectorDirection(difference), difference); - else - return std::make_tuple(false, UP, glm::vec2(0.0f, 0.0f)); -} -</code></pre> - -<p> - The game's <fun>DoCollision</fun> function now doesn't just check if a collision occurred, but also acts appropriately whenever a collision did occur. The function now calculates the level of penetration (as shown in the diagram at the start of this tutorial) and adds or subtracts it from the ball's position based on the direction of the collision. -</p> - -<pre><code> -void Game::DoCollisions() -{ - for (GameObject &box : this->Levels[this->Level].Bricks) - { - if (!box.Destroyed) - { - Collision collision = CheckCollision(*Ball, box); - if (std::get<0>(collision)) // if collision is true - { - // destroy block if not solid - if (!box.IsSolid) - box.Destroyed = true; - // collision resolution - Direction dir = std::get<1>(collision); - glm::vec2 diff_vector = std::get<2>(collision); - if (dir == LEFT || dir == RIGHT) // horizontal collision - { - Ball->Velocity.x = -Ball->Velocity.x; // reverse horizontal velocity - // relocate - float penetration = Ball->Radius - std::abs(diff_vector.x); - if (dir == LEFT) - Ball->Position.x += penetration; // move ball to right - else - Ball->Position.x -= penetration; // move ball to left; - } - else // vertical collision - { - Ball->Velocity.y = -Ball->Velocity.y; // reverse vertical velocity - // relocate - float penetration = Ball->Radius - std::abs(diff_vector.y); - if (dir == UP) - Ball->Position.y -= penetration; // move ball back up - else - Ball->Position.y += penetration; // move ball back down - } - } - } - } -} -</code></pre> - -<p> - Don't get too scared by the function's complexity since it is basically a direct translation of the concepts introduced so far. First we check for a collision and if so, we destroy the block if it is non-solid. Then we obtain the collision direction <var>dir</var> and the vector \(\color{green}{\bar{V}}\) as <var>diff_vector</var> from the tuple and finally do the collision resolution. -</p> - -<p> - We first check if the collision direction is either horizontal or vertical and then reverse the velocity accordingly. If horizontal, we calculate the penetration value \(\color{brown}R\) from the <var>diff_vector</var>'s x component and either add or subtract this from the ball's position. The same applies to the vertical collisions, but this time we operate on the <code>y</code> component of all the vectors. -</p> - -<p> - Running your application should now give you working collision resolution, but it's probably difficult to really see its effect since the ball will bounce towards the bottom edge as soon as you hit a single block and be lost forever. We can fix this by also handling player paddle collisions. -</p> - -<h2>Player - ball collisions</h2> -<p> - Collisions between the ball and the player is handled slightly different from what we've previously discussed, since this time the ball's horizontal velocity should be updated based on how far it hit the paddle from its center. The further the ball hits the paddle from its center, the stronger its horizontal velocity change should be. -</p> - -<pre><code> -void Game::DoCollisions() -{ - [...] - Collision result = CheckCollision(*Ball, *Player); - if (!Ball->Stuck && std::get<0>(result)) - { - // check where it hit the board, and change velocity based on where it hit the board - float centerBoard = Player->Position.x + Player->Size.x / 2.0f; - float distance = (Ball->Position.x + Ball->Radius) - centerBoard; - float percentage = distance / (Player->Size.x / 2.0f); - // then move accordingly - float strength = 2.0f; - glm::vec2 oldVelocity = Ball->Velocity; - Ball->Velocity.x = INITIAL_BALL_VELOCITY.x * percentage * strength; - Ball->Velocity.y = -Ball->Velocity.y; - Ball->Velocity = glm::normalize(Ball->Velocity) * glm::length(oldVelocity); - } -} - </code></pre> - -<p> - After we checked collisions between the ball and each brick, we'll check if the ball collided with the player paddle. If so (and the ball is not stuck to the paddle) we calculate the percentage of how far the ball's center is moved from the paddle's center compared to the half-extent of the paddle. The horizontal velocity of the ball is then updated based on the distance it hit the paddle from its center. In addition to updating the horizontal velocity, we also have to reverse the y velocity. -</p> - -<p> - Note that the old velocity is stored as <var>oldVelocity</var>. The reason for storing the old velocity is that we update the horizontal velocity of the ball's velocity vector while keeping its <code>y</code> velocity constant. This would mean that the length of the vector constantly changes, which has the effect that the ball's velocity vector is much larger (and thus stronger) if the ball hit the edge of the paddle compared to if the ball would hit the center of the paddle. For this reason, the new velocity vector is normalized and multiplied by the length of the old velocity vector. This way, the velocity of the ball is always consistent, regardless of where it hits the paddle. -</p> - -<h3>Sticky paddle</h3> -<p> - You may or may not have noticed it when you ran the code, but there is still a large issue with the player and ball collision resolution. The following shows what may happen: -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/in-practice/breakout/collisions_sticky_paddle.mp4" type="video/mp4" /> - <img src="/img/in-practice/breakout/collisions_sticky_paddle.png" class="clean"/> - </video> -</div> - -<p> - This issue is called the <def>sticky paddle</def> issue. This happens, because the player paddle moves with a high velocity towards the ball with the ball's center ending up inside the player paddle. Since we did not account for the case where the ball's center is inside an AABB, the game tries to continuously react to all the collisions. Once it finally breaks free, it will have reversed its <code>y</code> velocity so much that it's unsure whether to go up or down after breaking free. -</p> - -<p> - We can easily fix this behavior by introducing a small hack made possible by the fact that the we can always assume we have a collision at the top of the paddle. Instead of reversing the <code>y</code> velocity, we simply always return a positive <code>y</code> direction so whenever it does get stuck, it will immediately break free. -</p> - -<pre><code> - //Ball->Velocity.y = -Ball->Velocity.y; -Ball->Velocity.y = -1.0f * abs(Ball->Velocity.y); -</code></pre> - -<p> - If you try hard enough the effect is still noticeable, but I personally find it an acceptable trade-off. -</p> - -<h3>The bottom edge</h3> -<p> - The only thing that is still missing from the classic Breakout recipe is some loss condition that resets the level and the player. Within the game class's <fun>Update</fun> function we want to check if the ball reached the bottom edge, and if so, reset the game. -</p> - -<pre><code> -void Game::Update(float dt) -{ - [...] - if (Ball->Position.y >= this->Height) // did ball reach bottom edge? - { - this->ResetLevel(); - this->ResetPlayer(); - } -} -</code></pre> - -<p> - The <fun>ResetLevel</fun> and <fun>ResetPlayer</fun> functions re-load the level and reset the objects' values to their original starting values. The game should now look a bit like this: -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/in-practice/breakout/collisions_complete.mp4" type="video/mp4" /> - </video> -</div> - -<p> - And there you have it, we just finished creating a clone of the classical Breakout game with similar mechanics. You can find the game class' source code here: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/5.game.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/5.game.cpp" target="_blank">code</a>. - </p> - -<h2>A few notes</h2> -<p> - Collision detection is a difficult topic of video game development and possibly its most challenging. Most collision detection and resolution schemes are combined with physics engines as found in most modern-day games. The collision scheme we used for the Breakout game is a very simple scheme and one specialized specifically for this type of game. - </p> - -<p> - It should be stressed that this type of collision detection and resolution is not perfect. It calculates possible collisions only per frame and only for the positions exactly as they are at that timestep; this means that if an object would have such a velocity that it would pass over another object within a single frame, it would look like it never collided with this object. So if there are framedrops, or you reach high enough velocities, this collision detection scheme will not hold. -</p> - -<p> - Several of the issues that can still occur: -</p> - -<ul> - <li>If the ball goes too fast, it may skip over an object entirely within a single frame, not detecting any collisions.</li> - <li>If the ball hits more than one object within a single frame, it will have detected two collisions and reversed its velocity twice; not affecting its original velocity.</li> - <li>Hitting a corner of a brick could reverse the ball's velocity in the wrong direction since the distance it travels in a single frame could decide the difference between <fun>VectorDirection</fun> returning a vertical or horizontal direction.</li> -</ul> - -<p> - These chapters are however aimed to teach the readers the basics of several aspects of graphics and game-development. For this reason, this collision scheme serves its purpose; its understandable and works quite well in normal scenarios. Just keep in mind that there exist better (more complicated) collision schemes that work well in almost all scenarios (including movable objects) like the <def>separating axis theorem</def>. -</p> - -<p> - Thankfully, there exist large, practical, and often quite efficient physics engines (with timestep-independent collision schemes) for use in your own games. If you wish to delve further into such systems or need more advanced physics and have trouble figuring out the mathematics, <a href="http://box2d.org/" target="_blank">Box2D</a> is a perfect 2D physics library for implementing physics and collision detection in your applications. -</p> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/2D-Game/Final-thoughts.html b/translation/In-Practice/2D-Game/Final-thoughts.html @@ -1,310 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Final thoughts</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Final thoughts</h1> -<h1 id="content-url" style='display:none;'>In-Practice/2D-Game/Final-thoughts</h1> -<p> - These last chapter gave a glimpse of what it's like to create something more than just a tech demo in OpenGL. We created a complete 2D game from scratch and learned how to abstract from certain low-level graphics concepts, use basic collision detection techniques, create particles, and we've shown a practical scenario for an orthographic projection matrix. All this using concepts we've discussed in all previous chapters. We didn't really learn new and exciting graphics techniques using OpenGL, but more as to how to combine all the knowledge so far into a larger whole. -</p> - -<p> - Creating a simple game like Breakout can be accomplished in thousands of different ways, of which this approach is just one of many. The larger a game becomes, the more you start applying abstractions and design patterns. For further reading you can find a lot more on these abstractions and design patterns in the wonderful <a href="http://gameprogrammingpatterns.com/" target="_blank">game programming patterns</a> website. -</p> - -<p> - Keep in mind that it is a difficult feat to create a game with extremely clean and well-thought out code (often close to impossible). Simply make your game in whatever way you think feels right at the time. The more you practice video-game development, the more you learn new and better approaches to solve problems. Don't let the struggle to want to create perfect code demotivate you; keep on coding! -</p> - -<h2>Optimizations</h2> -<p> - The content of these chapters and the finished game code were all focused on explaining concepts as simple as possible, without delving too much in optimization details. Therefore, many performance considerations were left out of the chapters. We'll list some of the more common improvements you'll find in modern 2D OpenGL games to boost performance for when your framerate starts to drop: -</p> - -<ul> - <li><strong>Sprite sheet / Texture atlas</strong>: instead of rendering a sprite with a single texture at a time, we combine all required textures into a single large texture (like bitmap fonts) and select the appropriate sprite texture with a targeted set of texture coordinates. Switching texture states can be expensive so a sprite sheet makes sure we rarely have to switch between textures; this also allows the GPU to more efficiently cache the texture in memory for faster lookups.</li> - <li><strong>Instanced rendering</strong>: instead of rendering a quad at a time, we could've also <def>batched</def> all the quads we want to render and then, with an <a href="https://learnopengl.com/Advanced-OpenGL/Instancing" target="_blank">instanced renderer</a>, render all the batched sprites with just a single draw call. This is relatively easy to do since each sprite is composed of the same vertices, but differs in only a model matrix; something that we can easily include in an instanced array. This allows OpenGL to render a lot more sprites per frame. Instanced rendering can also be used to render particles and/or characters glyphs. </li> - <li><strong>Triangle strips</strong>: instead of rendering each quad as two triangles, we could've rendered them with OpenGL's <var>TRIANGLE_STRIP</var> render primitive that takes only <code>4</code> vertices instead of <code>6</code>. This saves a third of the data sent to the GPU.</li> - <li><strong>Space partitioning algorithms</strong>: when checking for collisions, we compare the ball object to <strong>each</strong> of the bricks in the active level. This is a bit of a waste of CPU resources since we can easily tell that most of the bricks won't even come close to the ball within this frame. Using <def>space partitioning algorithms</def> like BSP, Octrees, or k-d trees, we partition the visible space into several smaller regions and first determine in which region(s) the ball is in. We then only check collisions between other bricks in whatever region(s) the ball is in, saving us a significant amount of collision checks. For a simple game like Breakout this will likely be overkill, but for more complicated games with more complicated collision detection algorithms this will significantly increase performance.</li> - <li><strong>Minimize state changes</strong>: state changes (like binding textures or switching shaders) are generally quite expensive in OpenGL, so you want to avoid doing a large amount of state changes. One approach to minimize state changes is to create your own state manager that stores the current value of an OpenGL state (like which texture is bound) and only switch if this value needs to change; this prevents unnecessary state changes. Another approach is to sort all the renderable objects by state change: first render all the objects with shader one, then all objects with shader two, and so on; this can of course be extended to blend state changes, texture binds, framebuffer switches etc.</li> -</ul> - -<p> - These should give you some hints as to what kind of advanced tricks we can apply to further boost the performance of a 2D game. This also gives you a glimpse of the power of OpenGL: by doing most rendering by hand we have full control over the entire process and thus also complete power over how to optimize the process. If you're not satisfied with Breakout's performance then feel free to take any of these as an exercise. -</p> - -<h2>Get creative</h2> -<p> - Now that you've seen how to create a simple game in OpenGL it is up to you to create your own rendering/game applications. Many of the techniques that we've discussed so far can be used in most 2D (and even 3D) games like sprite rendering, collision detection, postprocessing, text rendering, and particles. It is now up to you to take these techniques and combine/modify them in whichever way you think is right and develop your own handcrafted game. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/2D-Game/Levels.html b/translation/In-Practice/2D-Game/Levels.html @@ -1,622 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Levels</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Levels</h1> -<h1 id="content-url" style='display:none;'>In-Practice/2D-Game/Levels</h1> -<p> - Breakout is unfortunately not just about a single happy green face, but contains complete levels with a lot of playfully colored bricks. We want these levels to be configurable such that they can support any number of rows and/or columns, we want the levels to have solid bricks (that cannot be destroyed), we want the levels to support multiple brick colors, and we want them to be stored externally in (text) files. -</p> - -<p> - In this chapter we'll briefly walk through the code of a game level object that is used to manage a large amount of bricks. We first have to define what an actual <def>brick</def> is though. -</p> - -<p> - We create a component called a <def>game object</def> that acts as the base representation of an object inside the game. Such a game object holds state data like its position, size, and velocity. It holds a color, a rotation component, whether it is solid and/or destroyed, and it also stores a <fun>Texture2D</fun> variable as its sprite. -</p> - -<p> - Each object in the game is represented as a <fun>GameObject</fun> or a derivative of this class. You can find the code of the <fun>GameObject</fun> class below: -</p> - -<ul> - <li><strong>GameObject</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/game_object.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/game_object.cpp" target="_blank">code</a> </li> -</ul> - -<p> - A level in Breakout consists entirely of bricks so we can represent a level by exactly that: a collection of bricks. Because a brick requires the same state as a game object, we're going to represent each brick of the level as a <fun>GameObject</fun>. The declaration of the <fun>GameLevel</fun> class then looks as follows: -</p> - -<pre><code> -class GameLevel -{ -public: - // level state - std::vector<GameObject> Bricks; - // constructor - GameLevel() { } - // loads level from file - void Load(const char *file, unsigned int levelWidth, unsigned int levelHeight); - // render level - void Draw(SpriteRenderer &renderer); - // check if the level is completed (all non-solid tiles are destroyed) - bool IsCompleted(); -private: - // initialize level from tile data - void init(std::vector<std::vector<unsigned int>> tileData, - unsigned int levelWidth, unsigned int levelHeight); -}; -</code></pre> - -<p> - Since a level is loaded from an external (text) file, we need to propose some kind of level structure. Here is an example of what a game level may look like in a text file: -</p> - -<pre><code> -1 1 1 1 1 1 -2 2 0 0 2 2 -3 3 4 4 3 3 -</code></pre> - -<p> - A level is stored in a matrix-like structure where each number represents a type of brick, each one separated by a space. Within the level code we can then assign what each number represents. We have chosen the following representation: -</p> - -<ul> - <li>A number of 0: no brick, an empty space within the level.</li> - <li>A number of 1: a solid brick, a brick that cannot be destroyed.</li> - <li>A number higher than 1: a destroyable brick; each subsequent number only differs in color.</li> -</ul> - -<p> - The example level listed above would, after being processed by <fun>GameLevel</fun>, look like this: -</p> - -<img src="/img/in-practice/breakout/levels-example.png" class="clean" alt="Example of a level using the Breakout GameLevel class"/> - -<p> - The <fun>GameLevel</fun> class uses two functions to generate a level from file. It first loads all the numbers in a two-dimensional vector within its <fun>Load</fun> function that then processes these numbers (to create all game objects) in its <fun>init</fun> function. -</p> - - -<pre><code> -void GameLevel::Load(const char *file, unsigned int levelWidth, unsigned int levelHeight) -{ - // clear old data - this->Bricks.clear(); - // load from file - unsigned int tileCode; - GameLevel level; - std::string line; - std::ifstream fstream(file); - std::vector<std::vector<unsigned int>> tileData; - if (fstream) - { - while (std::getline(fstream, line)) // read each line from level file - { - std::istringstream sstream(line); - std::vector<unsigned int> row; - while (sstream >> tileCode) // read each word separated by spaces - row.push_back(tileCode); - tileData.push_back(row); - } - if (tileData.size() > 0) - this->init(tileData, levelWidth, levelHeight); - } -} -</code></pre> - -<p> - The loaded <var>tileData</var> is then passed to the game level's <fun>init</fun> function: -</p> - -<pre><code> -void GameLevel::init(std::vector<std::vector<unsigned int>> tileData, - unsigned int lvlWidth, unsigned int lvlHeight) -{ - // calculate dimensions - unsigned int height = tileData.size(); - unsigned int width = tileData[0].size(); - float unit_width = lvlWidth / static_cast<float>(width); - float unit_height = lvlHeight / height; - // initialize level tiles based on tileData - for (unsigned int y = 0; y < height; ++y) - { - for (unsigned int x = 0; x < width; ++x) - { - // check block type from level data (2D level array) - if (tileData[y][x] == 1) // solid - { - glm::vec2 pos(unit_width * x, unit_height * y); - glm::vec2 size(unit_width, unit_height); - GameObject obj(pos, size, - ResourceManager::GetTexture("block_solid"), - glm::vec3(0.8f, 0.8f, 0.7f) - ); - obj.IsSolid = true; - this->Bricks.push_back(obj); - } - else if (tileData[y][x] > 1) - { - glm::vec3 color = glm::vec3(1.0f); // original: white - if (tileData[y][x] == 2) - color = glm::vec3(0.2f, 0.6f, 1.0f); - else if (tileData[y][x] == 3) - color = glm::vec3(0.0f, 0.7f, 0.0f); - else if (tileData[y][x] == 4) - color = glm::vec3(0.8f, 0.8f, 0.4f); - else if (tileData[y][x] == 5) - color = glm::vec3(1.0f, 0.5f, 0.0f); - - glm::vec2 pos(unit_width * x, unit_height * y); - glm::vec2 size(unit_width, unit_height); - this->Bricks.push_back( - GameObject(pos, size, ResourceManager::GetTexture("block"), color) - ); - } - } - } -} -</code></pre> - -<p> - The <fun>init</fun> function iterates through each of the loaded numbers and adds a <fun>GameObject</fun> to the level's <var>Bricks</var> vector based on the processed number. The size of each brick is automatically calculated (<var>unit_width</var> and <var>unit_height</var>) based on the total number of bricks so that each brick perfectly fits within the screen bounds. -</p> - -<p> - Here we load the game objects with two new textures, a <a href="/img/in-practice/breakout/textures/block.png" target="_blank">block</a> texture and a <a href="/img/in-practice/breakout/textures/block_solid.png" target="_blank">solid block</a> texture. -</p> - -<img src="/img/in-practice/breakout/block-textures.png" alt="Image of two types of block textures"/> - -<p> - A nice little trick here is that these textures are completely in gray-scale. The effect is that we can neatly manipulate their colors within the game-code by multiplying their grayscale colors with a defined color vector; exactly as we did within the <fun>SpriteRenderer</fun>. This way, customizing the appearance of their colors doesn't look too weird or unbalanced. -</p> - -<p> - The <fun>GameLevel</fun> class also houses a few other functions, like rendering all non-destroyed bricks, or validating if all non-solid bricks are destroyed. You can find the source code of the <fun>GameLevel</fun> class below: -</p> - -<ul> - <li><strong>GameLevel</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/game_level.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/game_level.cpp" target="_blank">code</a> </li> -</ul> - -<p> - The game level class gives us a lot of flexibility since any amount of rows and columns are supported and a user could easily create his/her own levels by modifying the level files. -</p> - -<h2>Within the game</h2> -<p> - We would like to support multiple levels in the Breakout game so we'll have to extend the game class a little by adding a vector that holds variables of type <fun>GameLevel</fun>. We'll also store the currently active level while we're at it: -</p> - -<pre><code> -class Game -{ - [...] - std::vector<GameLevel> Levels; - unsigned int Level; - [...] -}; -</code></pre> - -<p> - This series' version of the Breakout game features a total of 4 levels: -</p> - -<ul> - <li><a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/levels/one.lvl" target="_blank">Standard</a></li> - <li><a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/levels/two.lvl" target="_blank">A few small gaps</a></li> - <li><a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/levels/three.lvl" target="_blank">Space invader</a></li> - <li><a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/levels/four.lvl" target="_blank">Bounce galore</a></li> -</ul> - -<p> - Each of the textures and levels are then initialized within the game class's <fun>Init</fun> function: -</p> - -<pre><code> -void Game::Init() -{ - [...] - // load textures - ResourceManager::LoadTexture("textures/background.jpg", false, "background"); - ResourceManager::LoadTexture("textures/awesomeface.png", true, "face"); - ResourceManager::LoadTexture("textures/block.png", false, "block"); - ResourceManager::LoadTexture("textures/block_solid.png", false, "block_solid"); - // load levels - GameLevel one; one.Load("levels/one.lvl", this->Width, this->Height / 2); - GameLevel two; two.Load("levels/two.lvl", this->Width, this->Height / 2); - GameLevel three; three.Load("levels/three.lvl", this->Width, this->Height / 2); - GameLevel four; four.Load("levels/four.lvl", this->Width, this->Height / 2); - this->Levels.push_back(one); - this->Levels.push_back(two); - this->Levels.push_back(three); - this->Levels.push_back(four); - this->Level = 0; -} -</code></pre> - -<p> - Now all that is left to do, is actually render the level. We accomplish this by calling the currently active level's <fun>Draw</fun> function that in turn calls each <fun>GameObject</fun>'s <fun>Draw</fun> function using the given sprite renderer. Next to the level, we'll also render the scene with a nice <a href="/img/in-practice/breakout/textures/background.jpg" target="_blank">background image</a> (courtesy of Tenha): -</p> - -<pre><code> -void Game::Render() -{ - if(this->State == GAME_ACTIVE) - { - // draw background - Renderer->DrawSprite(ResourceManager::GetTexture("background"), - glm::vec2(0.0f, 0.0f), glm::vec2(this->Width, this->Height), 0.0f - ); - // draw level - this->Levels[this->Level].Draw(*Renderer); - } -} -</code></pre> - -<p> - The result is then a nicely rendered level that really starts to make the game feel more alive: -</p> - -<img src="/img/in-practice/breakout/levels.png" class="clean" alt="Level in OpenGL breakout"/> - -<h3>The player paddle</h3> -<p> - While we're at it, we may just as well introduce a paddle at the bottom of the scene that is controlled by the player. The paddle only allows for horizontal movement and whenever it touches any of the scene's edges, its movement should halt. For the player paddle we're going to use the <a href="/img/in-practice/breakout/textures/paddle.png" target="_blank">following</a> texture: -</p> - -<img src="/img/in-practice/breakout/textures/paddle.png" class="clean" style="width:256px;height:auto;" alt="Texture image if a paddle in OpenGL breakout"/> - -<p> - A paddle object will have a position, a size, and a sprite texture, so it makes sense to define the paddle as a <fun>GameObject</fun> as well: -</p> - -<pre><code> -// Initial size of the player paddle -const glm::vec2 PLAYER_SIZE(100.0f, 20.0f); -// Initial velocity of the player paddle -const float PLAYER_VELOCITY(500.0f); - -GameObject *Player; - -void Game::Init() -{ - [...] - ResourceManager::LoadTexture("textures/paddle.png", true, "paddle"); - [...] - glm::vec2 playerPos = glm::vec2( - this->Width / 2.0f - PLAYER_SIZE.x / 2.0f, - this->Height - PLAYER_SIZE.y - ); - Player = new GameObject(playerPos, PLAYER_SIZE, ResourceManager::GetTexture("paddle")); -} -</code></pre> - -<p> - Here we defined several constant values that define the paddle's size and speed. Within the Game's <fun>Init</fun> function we calculate the starting position of the paddle within the scene. We make sure the player paddle's center is aligned with the horizontal center of the scene. -</p> - -<p> - With the player paddle initialized, we also need to add a statement to the Game's <fun>Render</fun> function: -</p> - -<pre><code> -Player->Draw(*Renderer); -</code></pre> - -<p> - If you'd start the game now, you would not only see the level, but also a fancy player paddle aligned to the bottom edge of the scene. As of now, it doesn't really do anything so we're going to delve into the Game's <fun>ProcessInput</fun> function to horizontally move the paddle whenever the user presses the <var>A</var> or <var>D</var> key: -</p> - -<pre><code> -void Game::ProcessInput(float dt) -{ - if (this->State == GAME_ACTIVE) - { - float velocity = PLAYER_VELOCITY * dt; - // move playerboard - if (this->Keys[GLFW_KEY_A]) - { - if (Player->Position.x >= 0.0f) - Player->Position.x -= velocity; - } - if (this->Keys[GLFW_KEY_D]) - { - if (Player->Position.x <= this->Width - Player->Size.x) - Player->Position.x += velocity; - } - } -} -</code></pre> - -<p> - Here we move the player paddle either in the left or right direction based on which key the user pressed (note how we multiply the velocity with the <def>deltatime</def> variable). If the paddle's <code>x</code> value would be less than <code>0</code> it would've moved outside the left edge, so we only move the paddle to the left if the paddle's <code>x</code> value is higher than the left edge's <code>x</code> position (<code>0.0</code>). We do the same for when the paddle breaches the right edge, but we have to compare the right edge's position with the right edge of the paddle (subtract the paddle's width from the right edge's <code>x</code> position). -</p> - -<p> - Now running the game gives us a player paddle that we can move all across the bottom edge: -</p> - -<img src="/img/in-practice/breakout/levels-player.png" class="clean" alt="Image of OpenGL breakout now with player paddle"/> - -<p> - You can find the updated code of the Game class here: -</p> - -<ul> - <li><strong>Game</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/4.game.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/4.game.cpp" target="_blank">code</a> </li> -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/2D-Game/Particles.html b/translation/In-Practice/2D-Game/Particles.html @@ -1,565 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Particles</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Particles</h1> -<h1 id="content-url" style='display:none;'>In-Practice/2D-Game/Particles</h1> -<p> - A <def>particle</def>, as seen from OpenGL's perspective, is a tiny 2D quad that is always facing the camera (billboarding) and (usually) contains a texture with large parts of the sprite being transparent. A particle by itself is effectively just a sprite as we've been using extensively so far. However, when you put together hundreds or even thousands of these particles together you can create amazing effects. -</p> - -<p> - When working with particles, there is usually an object called a <def>particle emitter</def> or <def>particle generator</def> that, from its location, continuously <def>spawns</def> new particles that decay over time. If such a particle emitter would for example spawn tiny particles with a smoke-like texture, color them less bright the larger the distance from the emitter, and give them a glowy appearance, you'd get a fire-like effect: -</p> - -<img src="/img/in-practice/breakout/particles_example.jpg" alt="Example of particles as a fire"/> - -<p> - A single particle often has a life variable that slowly decays once it's spawned. Once its life is less than a certain threshold (usually <code>0</code>), we <def>kill</def> the particle so it can be replaced with a new particle when the next particle spawns. A particle emitter controls all its spawned particles and changes their behavior based on their attributes. A particle generally has the following attributes: -</p> - -<pre><code> -struct Particle { - glm::vec2 Position, Velocity; - glm::vec4 Color; - float Life; - - Particle() - : Position(0.0f), Velocity(0.0f), Color(1.0f), Life(0.0f) { } -}; -</code></pre> - -<p> - Looking at the fire example, the particle emitter probably spawns each particle with a position close to the emitter and with an upwards velocity. It seems to have 3 different regions, so it probably gives some particles a higher velocity than others. We can also see that the higher the <code>y</code> position of the particle, the less <em>yellow</em> or <em>bright</em> its color becomes. After the particles have reached a certain height, their life is depleted and the particles are killed; never reaching the stars. -</p> - -<p> - You can imagine that with systems like these we can create interesting effects like fire, smoke, fog, magic effects, gunfire residue etc. In Breakout, we're going to add a simple particle generator that follows the ball to make it all look just a bit more interesting. It'll look something like this: -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/in-practice/breakout/particles.mp4" type="video/mp4" /> - <img src="/img/in-practice/breakout/particles.png" class="clean"/> - </video> -</div> - -<p> - Here, the particle generator spawns each particle at the ball's position, gives it a velocity equal to a fraction of the ball's velocity, and changes the color of the particle based on how long it lived. -</p> - -<p> - For rendering the particles we'll be using a different set of shaders: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec4 vertex; // <vec2 position, vec2 texCoords> - -out vec2 TexCoords; -out vec4 ParticleColor; - -uniform mat4 projection; -uniform vec2 offset; -uniform vec4 color; - -void main() -{ - float scale = 10.0f; - TexCoords = vertex.zw; - ParticleColor = color; - gl_Position = projection * vec4((vertex.xy * scale) + offset, 0.0, 1.0); -} -</code></pre> - -<p> - And the fragment shader: -</p> - -<pre><code> -#version 330 core -in vec2 TexCoords; -in vec4 ParticleColor; -out vec4 color; - -uniform sampler2D sprite; - -void main() -{ - color = (texture(sprite, TexCoords) * ParticleColor); -} -</code></pre> - -<p> - We take the standard position and texture attributes per particle and also accept an <var>offset</var> and a <var>color</var> uniform for changing the outcome per particle. Note that in the vertex shader we scale the particle quad by <code>10.0f</code>; you can also set the scale as a uniform and control this individually per particle. -</p> - -<p> - First, we need a list of particles that we instantiate with default <fun>Particle</fun> structs: -</p> - -<pre><code> -unsigned int nr_particles = 500; -std::vector<Particle> particles; - -for (unsigned int i = 0; i < nr_particles; ++i) - particles.push_back(Particle()); -</code></pre> - -<p> - Then in each frame, we spawn several new particles with starting values. For each particle that is (still) alive we also update their values: -</p> - -<pre><code> -unsigned int nr_new_particles = 2; -// add new particles -for (unsigned int i = 0; i < nr_new_particles; ++i) -{ - int unusedParticle = FirstUnusedParticle(); - RespawnParticle(particles[unusedParticle], object, offset); -} -// update all particles -for (unsigned int i = 0; i < nr_particles; ++i) -{ - Particle &p = particles[i]; - p.Life -= dt; // reduce life - if (p.Life > 0.0f) - { // particle is alive, thus update - p.Position -= p.Velocity * dt; - p.Color.a -= dt * 2.5f; - } -} -</code></pre> - -<p> - The first loop may look a little daunting. As particles die over time we want to spawn <var>nr_new_particles</var> particles each frame, but since we don't want to infinitely keep spawning new particles (we'll quickly run out of memory this way) we only spawn up to a max of <var>nr_particles</var>. If were to push all new particles to the end of the list we'll quickly get a list filled with thousands of particles. This isn't really efficient considering only a small portion of that list has particles that are alive. -</p> - -<p> - What we want is to find the first particle that is dead (life < <code>0.0f</code>) and update that particle as a new respawned particle. -</p> - -<p> - The function <fun>FirstUnusedParticle</fun> tries to find the first particle that is dead and returns its index to the caller. - </p> - -<pre><code> -unsigned int lastUsedParticle = 0; -unsigned int FirstUnusedParticle() -{ - // search from last used particle, this will usually return almost instantly - for (unsigned int i = lastUsedParticle; i < nr_particles; ++i) { - if (particles[i].Life <= 0.0f){ - lastUsedParticle = i; - return i; - } - } - // otherwise, do a linear search - for (unsigned int i = 0; i < lastUsedParticle; ++i) { - if (particles[i].Life <= 0.0f){ - lastUsedParticle = i; - return i; - } - } - // override first particle if all others are alive - lastUsedParticle = 0; - return 0; -} -</code></pre> - -<p> - The function stores the index of the last dead particle it found. Since the next dead particle will most likely be right after the last particle index, we first search from this stored index. If we found no dead particles this way, we simply do a slower linear search. If no particles are dead, it will return index <code>0</code> which results in the first particle being overwritten. Note that if it reaches this last case, it means your particles are alive for too long; you'd need to spawn less particles per frame and/or reserve a larger number of particles. -</p> - -<p> - Then, once the first dead particle in the list is found, we update its values by calling <fun>RespawnParticle</fun> that takes the particle, a <fun>GameObject</fun>, and an offset vector: -</p> - -<pre><code> -void RespawnParticle(Particle &particle, GameObject &object, glm::vec2 offset) -{ - float random = ((rand() % 100) - 50) / 10.0f; - float rColor = 0.5f + ((rand() % 100) / 100.0f); - particle.Position = object.Position + random + offset; - particle.Color = glm::vec4(rColor, rColor, rColor, 1.0f); - particle.Life = 1.0f; - particle.Velocity = object.Velocity * 0.1f; -} -</code></pre> - -<p> - This function simply resets the particle's life to <code>1.0f</code>, randomly gives it a brightness (via the color vector) starting from <code>0.5</code>, and assigns a (slightly random) position and velocity based on the game object's data. -</p> - -<p> - The second particle loop within the update function loops over all particles and for each particle reduces their life by the delta time variable; this way, each particle's life corresponds to exactly the second(s) it's allowed to live multiplied by some scalar. Then we check if the particle is alive and if so, update its position and color attributes. We also slowly reduce the alpha component of each particle so it looks like they're slowly disappearing over time. -</p> - -<p> - Then what's left to do is render the particles: -</p> - -<pre><code> -<function id='70'>glBlendFunc</function>(GL_SRC_ALPHA, GL_ONE); -particleShader.Use(); -for (Particle particle : particles) -{ - if (particle.Life > 0.0f) - { - particleShader.SetVector2f("offset", particle.Position); - particleShader.SetVector4f("color", particle.Color); - particleTexture.Bind(); - <function id='27'>glBindVertexArray</function>(particleVAO); - <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); - <function id='27'>glBindVertexArray</function>(0); - } -} -<function id='70'>glBlendFunc</function>(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -</code></pre> - -<p> - Here, for each particle, we set their offset and color uniform values, bind the texture, and render the 2D quad. What's interesting to note here are the two calls to <fun><function id='70'>glBlendFunc</function></fun>. When rendering the particles, instead of the default destination blend mode of <code>GL_ONE_MINUS_SRC_ALPHA</code>, we use the <code>GL_ONE</code> (additive) blend mode that gives the particles a very neat <def>glow effect</def> when stacked onto each other. This is also likely the blend mode used when rendering the fire at the top of the chapter, since the fire is more 'glowy' at the center where most of the particles are. -</p> - -<p> - Because we (like most other parts of the Breakout chapters) like to keep things organized, we create another class called <fun>ParticleGenerator</fun> that hosts all the functionality we just described. You can find the source code below: -</p> - -<ul> - <li><a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/particle_generator.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/particle_generator.cpp" target="_blank">code</a></li> -</ul> - -<p> - Within the game code, we create a particle generator and initialize it with <a href="/img/in-practice/breakout/textures/particle.png" target="_blank">this</a> texture. -</p> - -<pre><code> -ParticleGenerator *Particles; - -void Game::Init() -{ - [...] - ResourceManager::LoadShader("shaders/particle.vs", "shaders/particle.frag", nullptr, "particle"); - [...] - ResourceManager::LoadTexture("textures/particle.png", true, "particle"); - [...] - Particles = new ParticleGenerator( - ResourceManager::GetShader("particle"), - ResourceManager::GetTexture("particle"), - 500 - ); -} -</code></pre> - -<p> - Then we change the game class's <fun>Update</fun> function by adding an update statement for the particle generator: -</p> - -<pre><code> -void Game::Update(float dt) -{ - [...] - // update particles - Particles->Update(dt, *Ball, 2, glm::vec2(Ball->Radius / 2.0f)); - [...] -} -</code></pre> - -<p> - Each of the particles will use the game object properties from the ball object, spawn 2 particles each frame, and their positions will be offset towards the center of the ball. Last up is rendering the particles: -</p> - -<pre><code> -void Game::Render() -{ - if (this->State == GAME_ACTIVE) - { - [...] - // draw player - Player->Draw(*Renderer); - // draw particles - Particles->Draw(); - // draw ball - Ball->Draw(*Renderer); - } -} -</code></pre> - -<p> - Note that we render the particles before we render the ball. This way, the particles end up rendered in front of all other objects, but behind the ball. You can find the updated game class code <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/6.game.cpp" target="_blank">here</a>. -</p> - -<p> - If you'd now compile and run your application you should see a trail of particles following the ball, just like at the beginning of the chapter, giving the game a more modern look. The system can also easily be extended to host more advanced effects, so feel free to experiment with the particle generation and see if you can come up with your own creative effects. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/2D-Game/Postprocessing.html b/translation/In-Practice/2D-Game/Postprocessing.html @@ -1,544 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Postprocessing</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Postprocessing</h1> -<h1 id="content-url" style='display:none;'>In-Practice/2D-Game/Postprocessing</h1> -<p> - Wouldn't it be fun if we could completely spice up the visuals of the Breakout game with just a few postprocessing effects? We could create a blurry shake effect, inverse all the colors of the scene, do crazy vertex movement, and/or make use of other interesting effects with relative ease thanks to OpenGL's framebuffers. -</p> - -<note> - This chapters makes extensive use of concepts from the <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">framebuffers</a> and <a href="https://learnopengl.com!Advanced-OpenGL/Anti-Aliasing" target="_blank">anti-aliasing</a> chapters. -</note> - -<p> - In the framebuffers chapter we demonstrated how we could use postprocessing to achieve interesting effects using just a single texture. In Breakout we're going to do something similar: we're going to create a framebuffer object with a multisampled renderbuffer object attached as its color attachment. All the game's render code should render to this multisampled framebuffer that then blits its content to a different framebuffer with a texture attachment as its color buffer. This texture contains the rendered anti-aliased image of the game that we'll render to a full-screen 2D quad with zero or more postprocessing effects applied. -</p> - -<p> - So to summarize, the rendering steps are: -</p> - -<ol> - <li>Bind to multisampled framebuffer.</li> - <li>Render game as normal.</li> - <li>Blit multisampled framebuffer to normal framebuffer with texture attachment.</li> - <li>Unbind framebuffer (use default framebuffer).</li> - <li>Use color buffer texture from normal framebuffer in postprocessing shader.</li> - <li>Render quad of screen-size as output of postprocessing shader.</li> -</ol> - -<p> - The postprocessing shader allows for three type of effects: shake, confuse, and chaos. -</p> - -<ul> - <li><strong>shake</strong>: slightly shakes the scene with a small blur.</li> - <li><strong>confuse</strong>: inverses the colors of the scene, but also the <code>x</code> and <code>y</code> axis.</li> - <li><strong>chaos</strong>: makes use of an edge detection kernel to create interesting visuals and also moves the textured image in a circular fashion for an interesting <em>chaotic</em> effect.</li> -</ul> - - -<p> - Below is a glimpse of what these effects are going to look like: -</p> - -<img src="/img/in-practice/breakout/postprocessing_effects.png" alt="Postprocessing effects in OpenGL Breakout game"/> - -<p> - Operating on a 2D quad, the vertex shader looks as follows: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec4 vertex; // <vec2 position, vec2 texCoords> - -out vec2 TexCoords; - -uniform bool chaos; -uniform bool confuse; -uniform bool shake; -uniform float time; - -void main() -{ - gl_Position = vec4(vertex.xy, 0.0f, 1.0f); - vec2 texture = vertex.zw; - if (chaos) - { - float strength = 0.3; - vec2 pos = vec2(texture.x + sin(time) * strength, texture.y + cos(time) * strength); - TexCoords = pos; - } - else if (confuse) - { - TexCoords = vec2(1.0 - texture.x, 1.0 - texture.y); - } - else - { - TexCoords = texture; - } - if (shake) - { - float strength = 0.01; - gl_Position.x += cos(time * 10) * strength; - gl_Position.y += cos(time * 15) * strength; - } -} -</code></pre> - -<p> - Based on whatever uniform is set to <code>true</code>, the vertex shader takes different paths. If either <var>chaos</var> or <var>confuse</var> is set to <code>true</code>, the vertex shader will manipulate the texture coordinates to move the scene around (either translate texture coordinates in a circle-like fashion, or inverse them). Because we set the texture wrapping methods to <code>GL_REPEAT</code>, the chaos effect will cause the scene to repeat itself at various parts of the quad. Additionally if <var>shake</var> is set to <code>true</code>, it will move the vertex positions around by a small amount, as if the screen shakes. Note that <var>chaos</var> and <var>confuse</var> shouldn't be <code>true</code> at the same time while <var>shake</var> is able to work with any of the other effects on. -</p> - -<p> - In addition to offsetting the vertex positions or texture coordinates, we'd also like to create some visual change as soon as any of the effects are active. We can accomplish this within the fragment shader: -</p> - -<pre><code> -#version 330 core -in vec2 TexCoords; -out vec4 color; - -uniform sampler2D scene; -uniform vec2 offsets[9]; -uniform int edge_kernel[9]; -uniform float blur_kernel[9]; - -uniform bool chaos; -uniform bool confuse; -uniform bool shake; - -void main() -{ - color = vec4(0.0f); - vec3 sample[9]; - // sample from texture offsets if using convolution matrix - if(chaos || shake) - for(int i = 0; i < 9; i++) - sample[i] = vec3(texture(scene, TexCoords.st + offsets[i])); - - // process effects - if (chaos) - { - for(int i = 0; i < 9; i++) - color += vec4(sample[i] * edge_kernel[i], 0.0f); - color.a = 1.0f; - } - else if (confuse) - { - color = vec4(1.0 - texture(scene, TexCoords).rgb, 1.0); - } - else if (shake) - { - for(int i = 0; i < 9; i++) - color += vec4(sample[i] * blur_kernel[i], 0.0f); - color.a = 1.0f; - } - else - { - color = texture(scene, TexCoords); - } -} - -</code></pre> - -<p> - This long shader almost directly builds upon the fragment shader from the framebuffers chapter and processes several postprocessing effects based on the effect type activated. This time though, the offset matrix and convolution kernels are defined as a uniform that we set from the OpenGL code. The advantage is that we only have to set this once, instead of recalculating these matrices each fragment shader run. For example, the <var>offsets</var> matrix is configured as follows: -</p> - -<pre><code> -float offset = 1.0f / 300.0f; -float offsets[9][2] = { - { -offset, offset }, // top-left - { 0.0f, offset }, // top-center - { offset, offset }, // top-right - { -offset, 0.0f }, // center-left - { 0.0f, 0.0f }, // center-center - { offset, 0.0f }, // center - right - { -offset, -offset }, // bottom-left - { 0.0f, -offset }, // bottom-center - { offset, -offset } // bottom-right -}; -<function id='44'>glUniform</function>2fv(<function id='45'>glGetUniformLocation</function>(shader.ID, "offsets"), 9, (float*)offsets); -</code></pre> - -<p> - Since all of the concepts of managing (multisampled) framebuffers were already extensively discussed in earlier chapters, I won't delve into the details this time. Below you'll find the code of a <fun>PostProcessor</fun> class that manages initialization, writing/reading the framebuffers, and rendering a screen quad. You should be able to understand the code if you understood the framebuffers and anti-aliasing chapter: -</p> - -<ul> - <li><strong>PostProcessor</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/post_processor.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/post_processor.cpp" target="_blank">code</a>.</li> -</ul> - -<p> - What is interesting to note here are the <fun>BeginRender</fun> and <fun>EndRender</fun> functions. Since we have to render the entire game scene into the framebuffer we can conventiently call <fun>BeginRender()</fun> and <fun>EndRender()</fun> before and after the scene's rendering code respectively. The class will then handle the behind-the-scenes framebuffer operations. For example, using the <fun>PostProcessor</fun> class will look like this within the game's <fun>Render</fun> function: -</p> - -<pre><code> -PostProcessor *Effects; - -void Game::Render() -{ - if (this->State == GAME_ACTIVE) - { - Effects->BeginRender(); - // draw background - // draw level - // draw player - // draw particles - // draw ball - Effects->EndRender(); - Effects->Render(<function id='47'>glfwGetTime</function>()); - } -} -</code></pre> - -<p> - Wherever we want, we can now conveniently set the required effect property of the postprocessing class to <code>true</code> and its effect will be immediately active. -</p> - -<h3>Shake it</h3> -<p> - As a (practical) demonstration of these effects we'll emulate the visual impact of the ball when it hits a solid concrete block. By enabling the shake effect for a short period of time wherever a solid collision occurs, it'll look like the collision had a stronger impact. -</p> - -<p> - We want to enable the screen shake effect only over a small period of time. We can get this to work by creating a variable called <var>ShakeTime</var> that manages the duration the shake effect is supposed to be active. Wherever a solid collision occurs, we reset this variable to a specific duration: -</p> - -<pre><code> -float ShakeTime = 0.0f; - -void Game::DoCollisions() -{ - for (GameObject &box : this->Levels[this->Level].Bricks) - { - if (!box.Destroyed) - { - Collision collision = CheckCollision(*Ball, box); - if (std::get<0>(collision)) // if collision is true - { - // destroy block if not solid - if (!box.IsSolid) - box.Destroyed = true; - else - { // if block is solid, enable shake effect - ShakeTime = 0.05f; - Effects->Shake = true; - } - [...] - } - } - } - [...] -} -</code></pre> - -<p> - Then within the game's <fun>Update</fun> function, we decrease the <var>ShakeTime</var> variable until it's <code>0.0</code> after which we disable the shake effect: -</p> - -<pre><code> -void Game::Update(float dt) -{ - [...] - if (ShakeTime > 0.0f) - { - ShakeTime -= dt; - if (ShakeTime <= 0.0f) - Effects->Shake = false; - } -} -</code></pre> - -<p> - Then each time we hit a solid block, the screen briefly starts to shake and blur, giving the player some visual feedback the ball collided with a solid object. -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/in-practice/breakout/postprocessing_shake.mp4" type="video/mp4" /> - <img src="/img/in-practice/breakout/postprocessing_shake.png" class="clean"/> - </video> -</div> - -<p> - You can find the updated source code of the game class <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/7.game.cpp" target="_blank">here</a>. -</p> - -<p> - In the <a href="https://learnopengl.com/In-Practice/2D-Game/Powerups" target="_blank">next</a> chapter about powerups we'll bring the other two postprocessing effects to good use. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/2D-Game/Powerups.html b/translation/In-Practice/2D-Game/Powerups.html @@ -1,669 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Powerups</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Powerups</h1> -<h1 id="content-url" style='display:none;'>In-Practice/2D-Game/Powerups</h1> -<p> - Breakout is close to finished, but it would be cool to add at least one more gameplay mechanic so it's not your average standard Breakout clone; what about powerups? -</p> - -<p> - The idea is that whenever a brick is destroyed, the brick has a small chance of spawning a powerup block. Such a block will slowly fall downwards and if it collides with the player paddle, an interesting effect occurs based on the type of powerup. For example, one powerup makes the paddle larger, and another powerup allows the ball to pass through objects. We also include several negative powerups that affect the player in a negative way. -</p> - -<p> - We can model a powerup as a <fun>GameObject</fun> with a few extra properties. That's why we define a class <fun>PowerUp</fun> that inherits from <fun>GameObject</fun>: -</p> - -<pre><code> -const glm::vec2 SIZE(60.0f, 20.0f); -const glm::vec2 VELOCITY(0.0f, 150.0f); - -class PowerUp : public GameObject -{ -public: - // powerup state - std::string Type; - float Duration; - bool Activated; - // constructor - PowerUp(std::string type, glm::vec3 color, float duration, - glm::vec2 position, Texture2D texture) - : GameObject(position, SIZE, texture, color, VELOCITY), - Type(type), Duration(duration), Activated() - { } -}; -</code></pre> - -<p> - A <fun>PowerUp</fun> is just a <fun>GameObject</fun> with extra state, so we can simply define it in a single header file which you can find <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/power_up.h" target="_blank">here</a>. -</p> - -<p> - Each powerup defines its type as a string, a duration for how long it is active, and whether it is currently activated. Within Breakout we're going to feature a total of 4 positive powerups and 2 negative powerups: -</p> - -<img src="/img/in-practice/breakout/powerups.png" class="clean" alt="PowerUps used in OpenGL Breakoout"/> - -<ul> - <li><strong>Speed</strong>: increases the velocity of the ball by 20%. </li> - <li><strong>Sticky</strong>: when the ball collides with the paddle, the ball remains stuck to the paddle unless the spacebar is pressed again. This allows the player to better position the ball before releasing it. </li> - <li><strong>Pass-Through</strong>: collision resolution is disabled for non-solid blocks, allowing the ball to pass through multiple blocks.</li> - <li><strong>Pad-Size-Increase</strong>: increases the width of the paddle by 50 pixels.</li> - <li><strong>Confuse</strong>: activates the confuse postprocessing effect for a short period of time, confusing the user. </li> - <li><strong>Chaos</strong>: activates the chaos postprocessing effect for a short period of time, heavily disorienting the user.</li> -</ul> - -<p> - You can find the textures here: -</p> - -<ul> - <li><strong>Textures</strong>: <a href="/img/in-practice/breakout/textures/powerup_speed.png" target="_blank">Speed</a>, <a href="/img/in-practice/breakout/textures/powerup_sticky.png" target="_blank">Sticky</a>, <a href="/img/in-practice/breakout/textures/powerup_passthrough.png" target="_blank">Pass-Through</a>, <a href="/img/in-practice/breakout/textures/powerup_increase.png" target="_blank">Pad-Size-Increase</a>, <a href="/img/in-practice/breakout/textures/powerup_confuse.png" target="_blank">Confuse</a>, <a href="/img/in-practice/breakout/textures/powerup_chaos.png" target="_blank">Chaos</a>. -</ul> - -<p> - Similar to the level block textures, each of the powerup textures is completely grayscale. This makes sure the color of the powerups remain balanced whenever we multiply them with a color vector. -</p> - -<p> - Because powerups have state, a duration, and certain effects associated with them, we would like to keep track of all the powerups currently active in the game; we store them in a vector: -</p> - -<pre><code> -class Game { - public: - [...] - std::vector<PowerUp> PowerUps; - [...] - void SpawnPowerUps(GameObject &block); - void UpdatePowerUps(float dt); -}; -</code></pre> - -<p> - We've also defined two functions for managing powerups. <fun>SpawnPowerUps</fun> spawns a powerups at the location of a given block and <fun>UpdatePowerUps</fun> manages all powerups currently active within the game. -</p> - -<h3>Spawning PowerUps</h3> -<p> - Each time a block is destroyed we would like to, given a small chance, spawn a powerup. This functionality is found inside the game's <fun>SpawnPowerUps</fun> function: -</p> - -<pre><code> -bool ShouldSpawn(unsigned int chance) -{ - unsigned int random = rand() % chance; - return random == 0; -} -void Game::SpawnPowerUps(GameObject &block) -{ - if (ShouldSpawn(75)) // 1 in 75 chance - this->PowerUps.push_back( - PowerUp("speed", glm::vec3(0.5f, 0.5f, 1.0f), 0.0f, block.Position, tex_speed - )); - if (ShouldSpawn(75)) - this->PowerUps.push_back( - PowerUp("sticky", glm::vec3(1.0f, 0.5f, 1.0f), 20.0f, block.Position, tex_sticky - ); - if (ShouldSpawn(75)) - this->PowerUps.push_back( - PowerUp("pass-through", glm::vec3(0.5f, 1.0f, 0.5f), 10.0f, block.Position, tex_pass - )); - if (ShouldSpawn(75)) - this->PowerUps.push_back( - PowerUp("pad-size-increase", glm::vec3(1.0f, 0.6f, 0.4), 0.0f, block.Position, tex_size - )); - if (ShouldSpawn(15)) // negative powerups should spawn more often - this->PowerUps.push_back( - PowerUp("confuse", glm::vec3(1.0f, 0.3f, 0.3f), 15.0f, block.Position, tex_confuse - )); - if (ShouldSpawn(15)) - this->PowerUps.push_back( - PowerUp("chaos", glm::vec3(0.9f, 0.25f, 0.25f), 15.0f, block.Position, tex_chaos - )); -} -</code></pre> - -<p> - The <fun>SpawnPowerUps</fun> function creates a new <fun>PowerUp</fun> object based on a given chance (1 in 75 for normal powerups and 1 in 15 for negative powerups) and sets their properties. Each powerup is given a specific color to make them more recognizable for the user and a duration in seconds based on its type; here a duration of <code>0.0f</code> means its duration is infinite. Additionally, each powerup is given the position of the destroyed block and one of the textures from the beginning of this chapter. -</p> - -<h3>Activating PowerUps</h3> -<p> - We then have to update the game's <fun>DoCollisions</fun> function to not only check for brick and paddle collisions, but also collisions between the paddle and each non-destroyed PowerUp. Note that we call <fun>SpawnPowerUps</fun> directly after a block is destroyed. -</p> - -<pre><code> -void Game::DoCollisions() -{ - for (GameObject &box : this->Levels[this->Level].Bricks) - { - if (!box.Destroyed) - { - Collision collision = CheckCollision(*Ball, box); - if (std::get<0>(collision)) // if collision is true - { - // destroy block if not solid - if (!box.IsSolid) - { - box.Destroyed = true; - this->SpawnPowerUps(box); - } - [...] - } - } - } - [...] - for (PowerUp &powerUp : this->PowerUps) - { - if (!powerUp.Destroyed) - { - if (powerUp.Position.y >= this->Height) - powerUp.Destroyed = true; - if (CheckCollision(*Player, powerUp)) - { // collided with player, now activate powerup - ActivatePowerUp(powerUp); - powerUp.Destroyed = true; - powerUp.Activated = true; - } - } - } -} -</code></pre> - -<p> - For all powerups not yet destroyed, we check if the powerup either reached the bottom edge of the screen or collided with the paddle. In both cases the powerup is destroyed, but when collided with the paddle, it is also activated. -</p> - -<p> - Activating a powerup is accomplished by settings its <var>Activated</var> property to <code>true</code> and enabling the powerup's effect by giving it to the <fun>ActivatePowerUp</fun> function: -</p> - -<pre><code> -void ActivatePowerUp(PowerUp &powerUp) -{ - if (powerUp.Type == "speed") - { - Ball->Velocity *= 1.2; - } - else if (powerUp.Type == "sticky") - { - Ball->Sticky = true; - Player->Color = glm::vec3(1.0f, 0.5f, 1.0f); - } - else if (powerUp.Type == "pass-through") - { - Ball->PassThrough = true; - Ball->Color = glm::vec3(1.0f, 0.5f, 0.5f); - } - else if (powerUp.Type == "pad-size-increase") - { - Player->Size.x += 50; - } - else if (powerUp.Type == "confuse") - { - if (!Effects->Chaos) - Effects->Confuse = true; // only activate if chaos wasn't already active - } - else if (powerUp.Type == "chaos") - { - if (!Effects->Confuse) - Effects->Chaos = true; - } -} -</code></pre> - -<p> - The purpose of <fun>ActivatePowerUp</fun> is exactly as it sounds: it activates the effect of a powerup as we've described at the start of this chapter. We check the type of the powerup and change the game state accordingly. For the <code>"sticky"</code> and <code>"pass-through"</code> effect, we also change the color of the paddle and the ball respectively to give the user some feedback as to which effect is currently active. -</p> - -<p> - Because the sticky and pass-through effects somewhat change the game logic we store their effect as a property of the ball object; this way we can change the game logic based on whatever effect on the ball is currently active. The only thing we've changed in the <fun>BallObject</fun> header is the addition of these two properties, but for completeness' sake its updated code is listed below: -</p> - -<ul> - <li><strong>BallObject</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/ball_object.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/ball_object.cpp" target="_blank">code</a>.</li> -</ul> - -<p> - We can then easily implement the sticky effect by slightly updating the <fun>DoCollisions</fun> function at the collision code between the ball and the paddle: -</p> - -<pre><code> -if (!Ball->Stuck && std::get<0>(result)) -{ - [...] - Ball->Stuck = Ball->Sticky; -} -</code></pre> - -<p> - Here we set the ball's <var>Stuck</var> property equal to the ball's <var>Sticky</var> property. If the sticky effect is activated, the ball will end up stuck to the player paddle whenever it collides; the user then has to press the spacebar again to release the ball. -</p> - -<p> - A similar small change is made for the pass-through effect within the same <fun>DoCollisions</fun> function. When the ball's <var>PassThrough</var> property is set to <code>true</code> we do not perform any collision resolution on the non-solid bricks. -</p> - -<pre><code> -Direction dir = std::get<1>(collision); -glm::vec2 diff_vector = std::get<2>(collision); -if (!(Ball->PassThrough && !box.IsSolid)) -{ - if (dir == LEFT || dir == RIGHT) // horizontal collision - { - [...] - } - else - { - [...] - } -} -</code></pre> - -<p> - The other effects are activated by simply modifying the game's state like the ball's velocity, the paddle's size, or an effect of the <fun>PostProcesser</fun> object. -</p> - -<h3>Updating PowerUps</h3> -<p> - Now all that is left to do is make sure that powerups are able to move once they've spawned and that they're deactivated as soon as their duration runs out; otherwise powerups will stay active forever. -</p> - -<p> - Within the game's <fun>UpdatePowerUps</fun> function we move the powerups based on their velocity and decrease the active powerups their duration. Whenever a powerup's duration is decreased to <code>0.0f</code>, its effect is deactivated and the relevant variables are reset to their original state: -</p> - -<pre><code> -void Game::UpdatePowerUps(float dt) -{ - for (PowerUp &powerUp : this->PowerUps) - { - powerUp.Position += powerUp.Velocity * dt; - if (powerUp.Activated) - { - powerUp.Duration -= dt; - - if (powerUp.Duration <= 0.0f) - { - // remove powerup from list (will later be removed) - powerUp.Activated = false; - // deactivate effects - if (powerUp.Type == "sticky") - { - if (!isOtherPowerUpActive(this->PowerUps, "sticky")) - { // only reset if no other PowerUp of type sticky is active - Ball->Sticky = false; - Player->Color = glm::vec3(1.0f); - } - } - else if (powerUp.Type == "pass-through") - { - if (!isOtherPowerUpActive(this->PowerUps, "pass-through")) - { // only reset if no other PowerUp of type pass-through is active - Ball->PassThrough = false; - Ball->Color = glm::vec3(1.0f); - } - } - else if (powerUp.Type == "confuse") - { - if (!isOtherPowerUpActive(this->PowerUps, "confuse")) - { // only reset if no other PowerUp of type confuse is active - Effects->Confuse = false; - } - } - else if (powerUp.Type == "chaos") - { - if (!isOtherPowerUpActive(this->PowerUps, "chaos")) - { // only reset if no other PowerUp of type chaos is active - Effects->Chaos = false; - } - } - } - } - } - this->PowerUps.erase(std::remove_if(this->PowerUps.begin(), this->PowerUps.end(), - [](const PowerUp &powerUp) { return powerUp.Destroyed && !powerUp.Activated; } - ), this->PowerUps.end()); -} -</code></pre> - -<p> - You can see that for each effect we disable it by resetting the relevant items to their original state. We also set the powerup's <var>Activated</var> property to <code>false</code>. At the end of <fun>UpdatePowerUps</fun> we then loop through the <var>PowerUps</var> vector and erase each powerup if they are destroyed <strong>and</strong> deactivated. We use the <fun>remove_if</fun> function from the <fun>algorithm</fun> header to erase these items given a lambda predicate. -</p> - -<note> - The <fun>remove_if</fun> function moves all elements for which the lambda predicate is true to the end of the container object and returns an iterator to the start of this <em>removed elements</em> range. The container's <fun>erase</fun> function then takes this iterator and the vector's end iterator to remove all the elements between these two iterators. -</note> - -<p> - It may happen that while one of the powerup effects is active, another powerup of the same type collides with the player paddle. In that case we have more than 1 powerup of that type currently active within the game's <var>PowerUps</var> vector. Whenever one of these powerups gets deactivated, we don't want to disable its effects yet since another powerup of the same type may still be active. For this reason we use the <fun>IsOtherPowerUpActive</fun> function to check if there is still another powerup active of the same type. Only if this function returns <code>false</code> we deactivate the powerup. This way, the powerup's duration of a given type is extended to the duration of its last activated powerup: -</p> - -<pre><code> -bool IsOtherPowerUpActive(std::vector<PowerUp> &powerUps, std::string type) -{ - for (const PowerUp &powerUp : powerUps) - { - if (powerUp.Activated) - if (powerUp.Type == type) - return true; - } - return false; -} -</code></pre> - -<p> - The function checks for all activated powerups if there is still a powerup active of the same type and if so, returns <code>true</code>. -</p> - -<p> - The last thing left to do is render the powerups: -</p> - -<pre><code> -void Game::Render() -{ - if (this->State == GAME_ACTIVE) - { - [...] - for (PowerUp &powerUp : this->PowerUps) - if (!powerUp.Destroyed) - powerUp.Draw(*Renderer); - [...] - } -} -</code></pre> - -<p> - Combine all this functionality and we have a working powerup system that not only makes the game more fun, but also a lot more challenging. It'll look a bit like this: -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/in-practice/breakout/powerups.mp4" type="video/mp4" /> - <img src="/img/in-practice/breakout/powerups_video.png" class="clean"/> - </video> -</div> - -<p> - You can find the updated game code here (there we also reset all powerup effects whenever the level is reset): -</p> - -<ul> - <li><strong>Game</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/8.game.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/8.game.cpp" target="_blank">code</a>.</li> -</ul> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/2D-Game/Render-text.html b/translation/In-Practice/2D-Game/Render-text.html @@ -1,683 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Render text</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Render text</h1> -<h1 id="content-url" style='display:none;'>In-Practice/2D-Game/Render-text</h1> -<p> - In this chapter we'll be adding the final enhancements to the game by adding a life system, a win condition, and feedback in the form of rendered text. This chapter heavily builds upon the earlier introduced <a href="https://learnopengl.com/In-Practice/Text-Rendering" target="_blank">Text Rendering</a> chapter so it is highly advised to first work your way through that chapter if you haven't already. -</p> - -<p> - In Breakout all text rendering code is encapsulated within a class called <fun>TextRenderer</fun> that features the initialization of the FreeType library, render configuration, and the actual render code. You can find the code of the <fun>TextRenderer</fun> class here: -</p> - -<ul> - <li><strong>TextRenderer</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/text_renderer.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/text_renderer.cpp" target="_blank">code</a>.</li> - <li><strong>Text shaders</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/shaders/text.vs" target="_blank">vertex</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/shaders/text.frag" target="_blank">fragment</a>.</li> -</ul> - -<p> - The content of the text renderer's functions is almost exactly the same as the code from the text rendering chapter. However, the code for rendering glyphs onto the screen is slightly different: -</p> - -<pre><code> -void TextRenderer::RenderText(std::string text, float x, float y, float scale, glm::vec3 color) -{ - [...] - for (c = text.begin(); c != text.end(); c++) - { - float xpos = x + ch.Bearing.x * scale; - float ypos = y + (this->Characters['H'].Bearing.y - ch.Bearing.y) * scale; - - float w = ch.Size.x * scale; - float h = ch.Size.y * scale; - // update VBO for each character - float vertices[6][4] = { - { xpos, ypos + h, 0.0f, 1.0f }, - { xpos + w, ypos, 1.0f, 0.0f }, - { xpos, ypos, 0.0f, 0.0f }, - - { xpos, ypos + h, 0.0f, 1.0f }, - { xpos + w, ypos + h, 1.0f, 1.0f }, - { xpos + w, ypos, 1.0f, 0.0f } - }; - [...] - } -} -</code></pre> - -<p> - The reason for it being slightly different is that we use a different orthographic projection matrix from the one we've used in the text rendering chapter. In the text rendering chapter all <code>y</code> values ranged from bottom to top, while in the Breakout game all <code>y</code> values range from top to bottom with a <code>y</code> coordinate of <code>0.0</code> corresponding to the top edge of the screen. This means we have to slightly modify how we calculate the vertical offset. -</p> - -<p> - Since we now render downwards from <fun>RenderText</fun>'s <var>y</var> parameter, we calculate the vertical offset as the distance a glyph is pushed downwards from the top of the glyph space. Looking back at the glyph metrics image from FreeType, this is indicated by the red arrow: -</p> - -<img src="/img/in-practice/breakout/glyph_offset.png" alt="Vertical offset of a FreeType glyph from the top of its glyph space for vertically inversed orthographic projection matrix in OpenGL"/> - -<p> - To calculate this vertical offset we need to get the top of the glyph space (the length of the black vertical arrow from the origin). Unfortunately, FreeType has no such metric for us. What we do know is that that some glyphs always touch this top edge; characters like 'H', 'T' or 'X'. So what if we calculate the length of this red vector by subtracting <code>bearingY</code> from any of these <em>top-reaching</em> glyphs by <code>bearingY</code> of the glyph in question. This way, we push the glyph down based on how far its top point differs from the top edge. -</p> - -<pre><code> -float ypos = y + (this->Characters['H'].Bearing.y - ch.Bearing.y) * scale; -</code></pre> - -<p> - In addition to updating the <code>ypos</code> calculation, we also switched the order of the vertices a bit to make sure all the vertices are still front facing when multiplied with the current orthographic projection matrix (as discussed in the <a href="https://learnopengl.com/Advanced-OpenGL/Face-culling" target="_blank">face culling</a> chapter). -</p> - -<p> - Adding the <fun>TextRenderer</fun> to the game is easy: -</p> - -<pre><code> -TextRenderer *Text; - -void Game::Init() -{ - [...] - Text = new TextRenderer(this->Width, this->Height); - Text->Load("fonts/ocraext.TTF", 24); -} -</code></pre> - -<p> - The text renderer is initialized with a font called OCR A Extended that you can download from <a href="http://fontzone.net/font-details/ocr-a-extended" target="_blank">here</a>. If the font is not to your liking, feel free to use a different font. -</p> - -<p> - Now that we have a text renderer, let's finish the gameplay mechanics. -</p> - -<h2>Player lives</h2> -<p> - Instead of immediately resetting the game as soon as the ball reaches the bottom edge, we'd like to give the player a few extra chances. We do this in the form of player lives, where the player begins with an initial number of lives (say <code>3</code>) and each time the ball touches the bottom edge, the player's life total is decreased by 1. Only when the player's life total becomes <code>0</code> we reset the game. This makes it easier for the player to finish a level while also building tension. -</p> - -<p> - We keep count of the lives of a player by adding it to the game class (initialized within the constructor to a value of <code>3</code>): -</p> - -<pre><code> -class Game -{ - [...] - public: - unsigned int Lives; -} -</code></pre> - -<p> - We then modify the game's <fun>Update</fun> function to, instead of resetting the game, decrease the player's life total, and only reset the game once the life total reaches <code>0</code>: -</p> - -<pre><code> -void Game::Update(float dt) -{ - [...] - if (Ball->Position.y >= this->Height) // did ball reach bottom edge? - { - --this->Lives; - // did the player lose all his lives? : Game over - if (this->Lives == 0) - { - this->ResetLevel(); - this->State = GAME_MENU; - } - this->ResetPlayer(); - } -} -</code></pre> - -<p> - As soon as the player is game over (<var>lives</var> equals <code>0</code>), we reset the level and change the game state to <var>GAME_MENU</var> which we'll get to later. -</p> - -<p> - Don't forget to reset the player's life total as soon as we reset the game/level: -</p> - -<pre><code> -void Game::ResetLevel() -{ - [...] - this->Lives = 3; -} -</code></pre> - -<p> - The player now has a working life total, but has no way of seeing how many lives he currently has while playing the game. That's where the text renderer comes in: -</p> - -<pre><code> -void Game::Render() -{ - if (this->State == GAME_ACTIVE) - { - [...] - std::stringstream ss; ss << this->Lives; - Text->RenderText("Lives:" + ss.str(), 5.0f, 5.0f, 1.0f); - } -} -</code></pre> - -<p> - Here we convert the number of lives to a string, and display it at the top-left of the screen. It'll now look a bit like this: -</p> - - <img src="/img/in-practice/breakout/render_text_lives.png" class="clean" alt="Rendered text with FreeType in OpenGL displaying the life total of the player"/> - -<p> - As soon as the ball touches the bottom edge, the player's life total is decreased which is instantly visible at the top-left of the screen. -</p> - -<h2>Level selection</h2> -<p> - Whenever the user is in the game state <var>GAME_MENU</var>, we'd like to give the player the control to select the level he'd like to play in. With either the 'w' or 's' key the player should be able to scroll through any of the levels we loaded. Whenever the player feels like the chosen level is indeed the level he'd like to play in, he can press the enter key to switch from the game's <var>GAME_MENU</var> state to the <var>GAME_ACTIVE</var> state. -</p> - -<p> - Allowing the player to choose a level is not too difficult. All we have to do is increase or decrease the game class's <var>Level</var> variable based on whether he pressed 'w' or 's' respectively: -</p> - -<pre><code> -if (this->State == GAME_MENU) -{ - if (this->Keys[GLFW_KEY_ENTER]) - this->State = GAME_ACTIVE; - if (this->Keys[GLFW_KEY_W]) - this->Level = (this->Level + 1) % 4; - if (this->Keys[GLFW_KEY_S]) - { - if (this->Level > 0) - --this->Level; - else - this->Level = 3; - } -} -</code></pre> - -<p> - We use the modulus operator (<code>%</code>) to make sure the <var>Level</var> variable remains within the acceptable level range (between <code>0</code> and <code>3</code>). -</p> - -<p> - We also want to define what we want to render when we're in the menu state. We'd like to give the player some instructions in the form of text and also display the selected level in the background. -</p> - -<pre><code> -void Game::Render() -{ - if (this->State == GAME_ACTIVE || this->State == GAME_MENU) - { - [...] // Game state's rendering code - } - if (this->State == GAME_MENU) - { - Text->RenderText("Press ENTER to start", 250.0f, Height / 2, 1.0f); - Text->RenderText("Press W or S to select level", 245.0f, Height / 2 + 20.0f, 0.75f); - } -} -</code></pre> - -<p> - Here we render the game whenever we're in either the <var>GAME_ACTIVE</var> state or the <var>GAME_MENU</var> state, and whenever we're in the <var>GAME_MENU</var> state we also render two lines of text to inform the player to select a level and/or accept his choice. Note that for this to work when launching the game you do have to set the game's state as <var>GAME_MENU</var> by default. -</p> - -<img src="/img/in-practice/breakout/render_text_select.png" class="clean" alt="Selecting levels with FreeType rendered text in OpenGL"/> - -<p> - It looks great, but once you try to run the code you'll probably notice that as soon as you press either the 'w' or the 's' key, the game rapidly scrolls through the levels making it difficult to select the level you want to play in. This happens because the game records the key press over frames until we release the key. This causes the <fun>ProcessInput</fun> function to process the pressed key more than once. -</p> - -<p> - We can solve this issue with a little trick commonly found within GUI systems. The trick is to, not only record the keys currently pressed, but also store the keys that have been processed once, until released again. We then check (before processing) whether the key has not yet been processed, and if so, process this key after which we store this key as being processed. Once we want to process the same key again without the key having been released, we do not process the key. This probably sounds somewhat confusing, but as soon as you see it in practice it (probably) starts to make sense. -</p> - -<p> - First we have to create another array of bool values to indicate which keys have been processed. We define this within the game class: -</p> - -<pre><code> -class Game -{ - [...] - public: - bool KeysProcessed[1024]; -} -</code></pre> - -<p> - We then set the relevant key(s) to <code>true</code> as soon as they're processed and make sure to only process the key if it wasn't processed before (until released): -</p> - -<pre><code> -void Game::ProcessInput(float dt) -{ - if (this->State == GAME_MENU) - { - if (this->Keys[GLFW_KEY_ENTER] && !this->KeysProcessed[GLFW_KEY_ENTER]) - { - this->State = GAME_ACTIVE; - this->KeysProcessed[GLFW_KEY_ENTER] = true; - } - if (this->Keys[GLFW_KEY_W] && !this->KeysProcessed[GLFW_KEY_W]) - { - this->Level = (this->Level + 1) % 4; - this->KeysProcessed[GLFW_KEY_W] = true; - } - if (this->Keys[GLFW_KEY_S] && !this->KeysProcessed[GLFW_KEY_S]) - { - if (this->Level > 0) - --this->Level; - else - this->Level = 3; - this->KeysProcessed[GLFW_KEY_S] = true; - } - } - [...] -} -</code></pre> - -<p> - Now as soon as the key's value in the <var>KeysProcessed</var> array has not yet been set, we process the key and set its value to <code>true</code>. Next time we reach the <code>if</code> condition of the same key, it will have been processed so we'll pretend we never pressed the button until it's released again. -</p> - -<p> - Within GLFW's key callback function we then need to reset the key's processed value as soon as it's released so we can process it again the next time it's pressed: -</p> - -<pre><code> -void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) -{ - [...] - if (key >= 0 && key < 1024) - { - if (action == GLFW_PRESS) - Breakout.Keys[key] = true; - else if (action == GLFW_RELEASE) - { - Breakout.Keys[key] = false; - Breakout.KeysProcessed[key] = false; - } - } -} -</code></pre> - -<p> - Launching the game gives us a neat level select screen that now precisely selects a single level per key press, no matter how long we press he key. -</p> - -<h2>Winning</h2> -<p> - Currently the player is able to select levels, play the game, and fail in doing so to lose. It is kind of unfortunate if the player finds out after destroying all the bricks he cannot in any way win the game. So let's fix that. -</p> - -<p> - The player wins when all of the non-solid blocks have been destroyed. We already created a function to check for this condition in the <fun>GameLevel</fun> class: -</p> - -<pre><code> -bool GameLevel::IsCompleted() -{ - for (GameObject &tile : this->Bricks) - if (!tile.IsSolid && !tile.Destroyed) - return false; - return true; -} -</code></pre> - -<p> - We check all bricks in the game level and if a single non-solid brick isn't yet destroyed we return <code>false</code>. All we have to do is check for this condition in the game's <fun>Update</fun> function and as soon as it returns <code>true</code> we change the game state to <var>GAME_WIN</var>: -</p> - -<pre><code> -void Game::Update(float dt) -{ - [...] - if (this->State == GAME_ACTIVE && this->Levels[this->Level].IsCompleted()) - { - this->ResetLevel(); - this->ResetPlayer(); - Effects->Chaos = true; - this->State = GAME_WIN; - } -} -</code></pre> - -<p> - Whenever the level is completed while the game is active, we reset the game and display a small victory message in the <var>GAME_WIN</var> state. For fun we'll also enable the chaos effect while in the <var>GAME_WIN</var> screen. In the <fun>Render</fun> function we'll congratulate the player and ask him to either restart or quit the game: -</p> - -<pre><code> -void Game::Render() -{ - [...] - if (this->State == GAME_WIN) - { - Text->RenderText( - "You WON!!!", 320.0, Height / 2 - 20.0, 1.0, glm::vec3(0.0, 1.0, 0.0) - ); - Text->RenderText( - "Press ENTER to retry or ESC to quit", 130.0, Height / 2, 1.0, glm::vec3(1.0, 1.0, 0.0) - ); - } -} -</code></pre> - -<p> - Then we of course have to actually catch the mentioned keys: -</p> - -<pre><code> -void Game::ProcessInput(float dt) -{ - [...] - if (this->State == GAME_WIN) - { - if (this->Keys[GLFW_KEY_ENTER]) - { - this->KeysProcessed[GLFW_KEY_ENTER] = true; - Effects->Chaos = false; - this->State = GAME_MENU; - } - } -} -</code></pre> - -<p> - If you're then good enough to actually win the game, you'd get the following image: -</p> - - <img src="/img/in-practice/breakout/render_text_win.png" class="clean" alt="Image of winning in OpenGL Breakout with FreeType rendered text"/> - -<p> - And that is it! The final piece of the puzzle of the Breakout game we've been actively working on. Try it out, customize it to your liking, and show it to all your family and friends! -</p> - -<p> - You can find the final version of the game's code below: -</p> - -<ul> - <li><strong>Game</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/game.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/game.cpp" target="_blank">code</a>.</li> -</ul> - - <h2>Further reading</h2> -<ul> - <li><a href="https://www.websiteplanet.com/blog/best-free-fonts/" target="_blank">70+ Best Free Fonts for Designers</a>: summarized list of a large group of fonts to use in your project for personal or commercial use.</li> -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/2D-Game/Rendering-Sprites.html b/translation/In-Practice/2D-Game/Rendering-Sprites.html @@ -1,539 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Rendering Sprites</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Rendering Sprites</h1> -<h1 id="content-url" style='display:none;'>In-Practice/2D-Game/Rendering-Sprites</h1> -<p> - To bring some life to the currently black abyss of our game world, we will render sprites to fill the void. A <def>sprite</def> has many definitions, but it's effectively not much more than a 2D image used together with some data to position it in a larger world (e.g. position, rotation, and size). Basically, sprites are the render-able image/texture objects we use in a 2D game. -</p> - -<p> - We can, just like we did in previous chapters, create a 2D shape out of vertex data, pass all data to the GPU, and transform it all by hand. However, in a larger application like this we rather have some abstractions on rendering 2D shapes. If we were to manually define these shapes and transformations for each object, it'll quickly get messy. -</p> - -<p> - In this chapter we'll define a rendering class that allows us to render a large amount of unique sprites with a minimal amount of code. This way, we're abstracting the gameplay code from the gritty OpenGL rendering code as is commonly done in larger projects. First, we have to set up a proper projection matrix though. -</p> - -<h2>2D projection matrix</h2> -<p> - We know from the <a href="https://learnopengl.com/Getting-started/Coordinate-Systems" target="_blank">coordinate systems</a> chapter that a projection matrix converts all view-space coordinates to clip-space (and then to normalized device) coordinates. By generating the appropriate projection matrix we can work with different coordinates that are easier to work with, compared to directly specifying all coordinates as normalized device coordinates. -</p> - -<p> - We don't need any perspective applied to the coordinates, since the game is entirely in 2D, so an orthographic projection matrix would suit the rendering quite well. Because an orthographic projection matrix directly transforms all coordinates to normalized device coordinates, we can choose to specify the world coordinates as screen coordinates by defining the projection matrix as follows: -</p> - -<pre><code> -glm::mat4 projection = <function id='59'>glm::ortho</function>(0.0f, 800.0f, 600.0f, 0.0f, -1.0f, 1.0f); -</code></pre> - -<p> - The first four arguments specify in order the left, right, bottom, and top part of the projection frustum. This projection matrix transforms all <code>x</code> coordinates between <code>0</code> and <code>800</code> to <code>-1</code> and <code>1</code>, and all <code>y</code> coordinates between <code>0</code> and <code>600</code> to <code>-1</code> and <code>1</code>. Here we specified that the top of the frustum has a <code>y</code> coordinate of <code>0</code>, while the bottom has a <code>y</code> coordinate of <code>600</code>. The result is that the top-left coordinate of the scene will be at (<code>0,0</code>) and the bottom-right part of the screen is at coordinate (<code>800,600</code>), just like screen coordinates; the world-space coordinates directly correspond to the resulting pixel coordinates. -</p> - -<img src="/img/in-practice/breakout/projection.png" class="clean" alt="Orthographic projection in OpenGL"/> - -<p> - This allows us to specify all vertex coordinates equal to the pixel coordinates they end up in on the screen, which is rather intuitive for 2D games. -</p> - -<h2>Rendering sprites</h2> -<p> - Rendering an actual sprite shouldn't be too complicated. We create a textured quad that we can transform with a model matrix, after which we project it using the previously defined orthographic projection matrix. -</p> - -<note> - Since Breakout is a single-scene game, there is no need for a view/camera matrix. Using the projection matrix we can directly transform the world-space coordinates to normalized device coordinates. -</note> - -<p> - To transform a sprite, we use the following vertex shader: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec4 vertex; // <vec2 position, vec2 texCoords> - -out vec2 TexCoords; - -uniform mat4 model; -uniform mat4 projection; - -void main() -{ - TexCoords = vertex.zw; - gl_Position = projection * model * vec4(vertex.xy, 0.0, 1.0); -} -</code></pre> - -<p> - Note that we store both the position and texture-coordinate data in a single <fun>vec4</fun> variable. Because both the position and texture coordinates contain two floats, we can combine them in a single vertex attribute. -</p> - -<p> - The fragment shader is relatively straightforward as well. We take a texture and a color vector that both affect the final color of the fragment. By having a uniform color vector, we can easily change the color of sprites from the game-code: -</p> - -<pre><code> -#version 330 core -in vec2 TexCoords; -out vec4 color; - -uniform sampler2D image; -uniform vec3 spriteColor; - -void main() -{ - color = vec4(spriteColor, 1.0) * texture(image, TexCoords); -} -</code></pre> - -<p> - To make the rendering of sprites more organized, we define a <fun>SpriteRenderer</fun> class that is able to render a sprite with just a single function. Its definition is as follows: -</p> - -<pre><code> -class SpriteRenderer -{ - public: - SpriteRenderer(Shader &shader); - ~SpriteRenderer(); - - void DrawSprite(Texture2D &texture, glm::vec2 position, - glm::vec2 size = glm::vec2(10.0f, 10.0f), float rotate = 0.0f, - glm::vec3 color = glm::vec3(1.0f)); - private: - Shader shader; - unsigned int quadVAO; - - void initRenderData(); -}; -</code></pre> - -<p> - The <def>SpriteRenderer</def> class hosts a shader object, a single vertex array object, and a render and initialization function. Its constructor takes a shader object that it uses for all future rendering. -</p> - -<h3>Initialization</h3> -<p> - First, let's delve into the <fun>initRenderData</fun> function that configures the <var>quadVAO</var>: -</p> - -<pre><code> -void SpriteRenderer::initRenderData() -{ - // configure VAO/VBO - unsigned int VBO; - float vertices[] = { - // pos // tex - 0.0f, 1.0f, 0.0f, 1.0f, - 1.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 0.0f, - - 0.0f, 1.0f, 0.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 0.0f, 1.0f, 0.0f - }; - - <function id='33'>glGenVertexArrays</function>(1, &this->quadVAO); - <function id='12'>glGenBuffers</function>(1, &VBO); - - <function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, VBO); - <function id='31'>glBufferData</function>(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - <function id='27'>glBindVertexArray</function>(this->quadVAO); - <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(0); - <function id='30'>glVertexAttribPointer</function>(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0); - <function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, 0); - <function id='27'>glBindVertexArray</function>(0); -} -</code></pre> - -<p> - Here we first define a set of vertices with (<code>0,0</code>) being the top-left corner of the quad. This means that when we apply translation or scaling transformations on the quad, they're transformed from the top-left position of the quad. This is commonly accepted in 2D graphics and/or GUI systems where elements' positions correspond to the top-left corner of the elements. -</p> - -<p> - Next we simply sent the vertices to the GPU and configure the vertex attributes, which in this case is a single vertex attribute. We only have to define a single VAO for the sprite renderer since all sprites share the same vertex data. -</p> - -<h3>Rendering</h3> -<p> - Rendering sprites is not too difficult; we use the sprite renderer's shader, configure a model matrix, and set the relevant uniforms. What is important here is the order of transformations: -</p> - -<pre><code> -void SpriteRenderer::DrawSprite(Texture2D &texture, glm::vec2 position, - glm::vec2 size, float rotate, glm::vec3 color) -{ - // prepare transformations - this->shader.Use(); - glm::mat4 model = glm::mat4(1.0f); - model = <function id='55'>glm::translate</function>(model, glm::vec3(position, 0.0f)); - - model = <function id='55'>glm::translate</function>(model, glm::vec3(0.5f * size.x, 0.5f * size.y, 0.0f)); - model = <function id='57'>glm::rotate</function>(model, <function id='63'>glm::radians</function>(rotate), glm::vec3(0.0f, 0.0f, 1.0f)); - model = <function id='55'>glm::translate</function>(model, glm::vec3(-0.5f * size.x, -0.5f * size.y, 0.0f)); - - model = <function id='56'>glm::scale</function>(model, glm::vec3(size, 1.0f)); - - this->shader.SetMatrix4("model", model); - this->shader.SetVector3f("spriteColor", color); - - <function id='49'>glActiveTexture</function>(GL_TEXTURE0); - texture.Bind(); - - <function id='27'>glBindVertexArray</function>(this->quadVAO); - <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); - <function id='27'>glBindVertexArray</function>(0); -} -</code></pre> - -<p> - When trying to position objects somewhere in a scene with rotation and scaling transformations, it is advised to first scale, then rotate, and finally translate the object. Because multiplying matrices occurs from right to left, we transform the matrix in reverse order: translate, rotate, and then scale. -</p> - -<p> - The rotation transformation may still seem a bit daunting. We know from the <a href="https://learnopengl.com/Getting-started/Transformations" target="_blank">transformations</a> chapter that rotations always revolve around the origin (<code>0,0</code>). Because we specified the quad's vertices with (<code>0,0</code>) as the top-left coordinate, all rotations will rotate around this point of (<code>0,0</code>). The <def>origin of rotation</def> is at the top-left of the quad, which produces undesirable results. What we want to do is move the origin of rotation to the center of the quad so the quad neatly rotates around this origin, instead of rotating around the top-left of the quad. We solve this by translating the quad by half its size first, so its center is at coordinate (<code>0,0</code>) before rotating. -</p> - -<img src="/img/in-practice/breakout/rotation-origin.png" class="clean" alt="Properly rotating at the center of origin of the quad"/> - -<p> - Since we first scale the quad, we have to take the size of the sprite into account when translating to the sprite's center, which is why we multiply with the sprite's <var>size</var> vector. Once the rotation transformation is applied, we reverse the previous translation. -</p> - -<p> - Combining all these transformations, we can position, scale, and rotate each sprite in any way we like. Below you can find the complete source code of the sprite renderer: -</p> - -<ul> - <li><strong>SpriteRenderer</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/sprite_renderer.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/sprite_renderer.cpp" target="_blank">code</a> </li> -</ul> - -<h2>Hello sprite</h2> -<p> - With the <fun>SpriteRenderer</fun> class we finally have the ability to render actual images to the screen! Let's initialize one within the game code and load our favorite <a href="/img/textures/awesomeface.png" target="_blank">texture</a> while we're at it: -</p> - -<pre><code> -SpriteRenderer *Renderer; - -void Game::Init() -{ - // load shaders - ResourceManager::LoadShader("shaders/sprite.vs", "shaders/sprite.frag", nullptr, "sprite"); - // configure shaders - glm::mat4 projection = <function id='59'>glm::ortho</function>(0.0f, static_cast<float>(this->Width), - static_cast<float>(this->Height), 0.0f, -1.0f, 1.0f); - ResourceManager::GetShader("sprite").Use().SetInteger("image", 0); - ResourceManager::GetShader("sprite").SetMatrix4("projection", projection); - // set render-specific controls - Renderer = new SpriteRenderer(ResourceManager::GetShader("sprite")); - // load textures - ResourceManager::LoadTexture("textures/awesomeface.png", true, "face"); -} -</code></pre> - -<p> - Then within the render function we can render our beloved mascot to see if everything works as it should: -</p> - -<pre><code> -void Game::Render() -{ - Renderer->DrawSprite(ResourceManager::GetTexture("face"), - glm::vec2(200.0f, 200.0f), glm::vec2(300.0f, 400.0f), 45.0f, glm::vec3(0.0f, 1.0f, 0.0f)); -} -</code></pre> - -<p> - Here we position the sprite somewhat close to the center of the screen with its height being slightly larger than its width. We also rotate it by 45 degrees and give it a green color. Note that the position we give the sprite equals the top-left vertex of the sprite's quad. -</p> - -<p> - If you did everything right you should get the following output: -</p> - -<img src="/img/in-practice/breakout/rendering-sprites.png" class="clean" alt="Image of a rendered sprite using our custom-made OpenGL's SpriteRenderer class"/> - -<p> - You can find the updated game class's source code <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/3.game.cpp" target="_blank">here</a>. -</p> - -<p> - Now that we got the render systems working, we can put it to good use in the <a href="https://learnopengl.com/In-Practice/2D-Game/Levels" target="_blank">next</a> chapter where we'll work on building the game's levels. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/2D-Game/Setting-up.html b/translation/In-Practice/2D-Game/Setting-up.html @@ -1,420 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Setting up</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Setting up</h1> -<h1 id="content-url" style='display:none;'>In-Practice/2D-Game/Setting-up</h1> -<p> - Before we get started with the game mechanics, we first need to set up a simple framework for the game to reside in. The game will use several third party libraries of which most have been introduced in earlier chapters. Wherever a new library is required, it will be properly introduced. -</p> - -<p> - First, we define a so called <def>uber</def> game class that contains all relevant render and gameplay code. The idea of such a game class is that it (sort of) organizes your game code, while also decoupling all windowing code from the game. This way, you could use the same class in a completely different windowing library (like SDL or SFML for example) without much effort. -</p> - -<note> - There are thousands of ways of trying to abstract and generalize game/graphics code into classes and objects. What you will see in these chapters is just one (relatively simple) approach to solve this issue. If you feel there is a better approach, try to come up with your own improvement of the implementation. -</note> - -<p> - The game class hosts an initialization function, an update function, a function to process input, and a render function: -</p> - -<pre><code> -class Game -{ - public: - // game state - GameState State; - bool Keys[1024]; - unsigned int Width, Height; - // constructor/destructor - Game(unsigned int width, unsigned int height); - ~Game(); - // initialize game state (load all shaders/textures/levels) - void Init(); - // game loop - void ProcessInput(float dt); - void Update(float dt); - void Render(); -}; -</code></pre> - -<p> - The class hosts what you may expect from a game class. We initialize the game with a width and height (the resolution you want to play the game in) and use the <fun>Init</fun> function to load shaders, textures, and initialize all gameplay state. We can process input as stored within the <var>Keys</var> array by calling <fun>ProcessInput</fun>, and update all gameplay events (like player/ball movement) in the <fun>Update</fun> function. Last, we can render the game by calling <fun>Render</fun>. Note that we split the movement logic from the render logic. -</p> - -<p> - The <fun>Game</fun> class also hosts a variable called <var>State</var> which is of type <def>GameState</def> as defined below: -</p> - -<pre><code> -// Represents the current state of the game -enum GameState { - GAME_ACTIVE, - GAME_MENU, - GAME_WIN -}; -</code></pre> - -<p> - This allows us to keep track of what state the game is currently in. This way, we can decide to adjust rendering and/or processing based on the current state of the game (we probably render and process different items when we're in the game's menu for example). -</p> - -<p> - As of now, the functions of the game class are completely empty since we have yet to write the actual game code, but here are the Game class's <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/2.game.h" target="_blank">header</a> - and <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/2.game.cpp" target="_blank">code</a> file. -</p> - -<h2>Utility</h2> -<p> - Since we're creating a large application we'll frequently have to re-use several OpenGL concepts, like textures and shaders. It thus makes sense to create a more easy-to-use interface for these two items as similarly done in one of the earlier chapters where we created a shader class. -</p> - -<p> - We define a shader class that generates a compiled shader (or generates error messages if it fails) from two or three strings (if a geometry shader is present). The shader class also contains a lot of useful utility functions to quickly set uniform values. We also define a texture class that generates a 2D texture image (based on its properties) from a byte array and a given width and height. Again, the texture class also hosts utility functions. -</p> - -<p> - We won't delve into the details of the classes since by now you should easily understand how they work. For this reason you can find the header and code files, fully commented, below: -</p> - -<ul> - <li><strong>Shader</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/shader.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/shader.cpp" target="_blank">code</a>.</li> - <li><strong>Texture</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/texture.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/texture.cpp" target="_blank">code</a>.</li> -</ul> - -<p> - Note that the current texture class is solely designed for 2D textures only, but could easily be extended for alternative texture types. -</p> - -<h2>Resource management</h2> -<p> - While the shader and texture classes function great by themselves, they do require either a byte array or a list of strings for initialization. We could easily embed file loading code within the classes themselves, but this slightly violates the <def>single responsibility principle</def>. We'd prefer these classes to only focus on either textures or shaders respectively, and not necessarily their file-loading mechanics. -</p> - -<p> - For this reason it is often considered a more organized approach to create a single entity designed for loading game-related resources called a <def>resource manager</def>. There are several approaches to creating a resource manager; for this chapter we chose to use a singleton static resource manager that is (due to its static nature) always available throughout the project, hosting all loaded resources and their relevant loading functionality. -</p> - -<p> - Using a singleton class with static functionality has several advantages and disadvantages, with its disadvantages mostly being the loss of several OOP properties and less control over construction/destruction. However, for relatively small projects like this it is easy to work with. -</p> - -<p> - Like the other class files, the resource manager is listed below: -</p> - -<ul> - <li><strong>Resource Manager</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/resource_manager.h" target="_blank">header</a>, <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/resource_manager.cpp" target="_blank">code</a>.</li> -</ul> - -<p> - Using the resource manager, we can easily load shaders into the program like: -</p> - -<pre><code> -Shader shader = ResourceManager::LoadShader("vertex.vs", "fragment.vs", nullptr, "test"); -// then use it -shader.Use(); -// or -ResourceManager::GetShader("test").Use(); -</code></pre> - -<p> - The defined <fun>Game</fun> class, together with the resource manager and the easily manageable <fun>Shader</fun> and <fun>Texture2D</fun> classes, form the basis for the next chapters as we'll be extensively using these classes to implement the Breakout game. -</p> - -<h2>Program</h2> -<p> - We still need a window for the game and set some initial OpenGL state as we make use of OpenGL's <a href="https://learnopengl.com/Advanced-OpenGL/Blending" target="_blank">blending</a> functionality. We do not enable depth testing, since the game is entirely in 2D. All vertices are defined with the same z-values so enabling depth testing would be of no use and likely cause z-fighting. -</p> - -<p> - The startup code of the Breakout game is relatively simple: we create a window with GLFW, register a few callback functions, create the <fun>Game</fun> object, and propagate all relevant functionality to the game class. The code is given below: -</p> - -<ul> - <li><strong>Program</strong>: <a href="/code_viewer_gh.php?code=src/7.in_practice/3.2d_game/0.full_source/progress/2.program.cpp" target="_blank">code</a>.</li> -</ul> - -<p> - Running the code should give you the following output: -</p> - -<img src="/img/in-practice/breakout/setting-up.png" class="clean" alt="Blank image of start of Breakout game in OpenGL"/> - -<p> - By now we have a solid framework for the upcoming chapters; we'll be continuously extending the game class to host new functionality. Hop over to the <a href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites" target="_blank">next</a> chapter once you're ready. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/Debugging.html b/translation/In-Practice/Debugging.html @@ -1,823 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Debugging</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Debugging</h1> -<h1 id="content-url" style='display:none;'>In-Practice/Debugging</h1> -<p> - Graphics programming can be a lot of fun, but it can also be a large source of frustration whenever something isn't rendering just right, or perhaps not even rendering at all! Seeing as most of what we do involves manipulating pixels, it can be difficult to figure out the cause of error whenever something doesn't work the way it's supposed to. Debugging these kinds of <em>visual</em> errors is different than what you're used to when debugging errors on the CPU. We have no console to output text to, no breakpoints to set on GLSL code, and no way of easily checking the state of GPU execution. -</p> - -<p> - In this chapter we'll look into several techniques and tricks of debugging your OpenGL program. Debugging in OpenGL is not too difficult to do and getting a grasp of its techniques definitely pays out in the long run. -</p> - -<h2>glGetError()</h2> -<p> - The moment you incorrectly use OpenGL (like configuring a buffer without first binding any) it will take notice and generate one or more user error flags behind the scenes. We can query these error flags using a function named <fun>glGetError</fun> that checks the error flag(s) set and returns an error value if OpenGL got misused: -</p> - -<pre><code> -GLenum glGetError(); -</code></pre> - -<p> - - The moment <fun>glGetError</fun> is called, it returns either an error flag or no error at all. The error codes that <fun>glGetError</fun> can return are listed below: -</p> - -<table> - <tr> - <th>Flag</th> - <th>Code</th> - <th>Description</th> - </tr> - <tr> - <td><var>GL_NO_ERROR</var></td> - <td>0</td> - <td>No user error reported since the last call to <fun>glGetError</fun>.</td> - </tr> - <tr> - <td><var>GL_INVALID_ENUM</var></td> - <td>1280</td> - <td>Set when an enumeration parameter is not legal.</td> - </tr> - <tr> - <td><var>GL_INVALID_VALUE</var></td> - <td>1281</td> - <td>Set when a value parameter is not legal.</td> - </tr> - <tr> - <td><var>GL_INVALID_OPERATION</var></td> - <td>1282</td> - <td>Set when the state for a command is not legal for its given parameters.</td> - </tr> - <tr> - <td><var>GL_STACK_OVERFLOW</var></td> - <td>1283</td> - <td>Set when a stack pushing operation causes a stack overflow.</td> - </tr> - <tr> - <td><var>GL_STACK_UNDERFLOW</var></td> - <td>1284</td> - <td>Set when a stack popping operation occurs while the stack is at its lowest point.</td> - </tr> - <tr> - <td><var>GL_OUT_OF_MEMORY</var></td> - <td>1285</td> - <td>Set when a memory allocation operation cannot allocate (enough) memory.</td> - </tr> - <tr> - <td><var>GL_INVALID_FRAMEBUFFER_OPERATION</var></td> - <td>1286</td> - <td>Set when reading or writing to a framebuffer that is not complete.</td> - </tr> -</table> - -<p> - Within OpenGL's function documentation you can always find the error codes a function generates the moment it is incorrectly used. For instance, if you take a look at the documentation of <a href="http://docs.gl/gl3/glBindTextur%65" target="_blank"><function id='48'>glBindTexture</function></a> function, you can find all the user error codes it could generate under the <em>Errors</em> section. -</p> - -<p> - The moment an error flag is set, no other error flags will be reported. Furthermore, the moment <fun>glGetError</fun> is called it clears all error flags (or only one if on a distributed system, see note below). This means that if you call <fun>glGetError</fun> once at the end of each frame and it returns an error, you can't conclude this was the only error, and the source of the error could've been anywhere in the frame. -</p> - -<note> - Note that when OpenGL runs distributedly like frequently found on X11 systems, other user error codes can still be generated as long as they have different error codes. Calling <fun>glGetError</fun> then only resets one of the error code flags instead of all of them. Because of this, it is recommended to call <fun>glGetError</fun> inside a loop. -</note> - -<pre><code> -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, tex); -std::cout << glGetError() << std::endl; // returns 0 (no error) - -<function id='52'>glTexImage2D</function>(GL_TEXTURE_3D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_BYTE, data); -std::cout << glGetError() << std::endl; // returns 1280 (invalid enum) - -<function id='50'>glGenTextures</function>(-5, textures); -std::cout << glGetError() << std::endl; // returns 1281 (invalid value) - -std::cout << glGetError() << std::endl; // returns 0 (no error) -</code></pre> - -<p> - The great thing about <fun>glGetError</fun> is that it makes it relatively easy to pinpoint where any error may be and to validate the proper use of OpenGL. Let's say you get a black screen and you have no idea what's causing it: is the framebuffer not properly set? Did I forget to bind a texture? By calling <fun>glGetError</fun> all over your codebase, you can quickly catch the first place an OpenGL error starts showing up. -</p> - -<p> - By default <fun>glGetError</fun> only prints error numbers, which isn't easy to understand unless you've memorized the error codes. It often makes sense to write a small helper function to easily print out the error strings together with where the error check function was called: -</p> - -<pre><code> -GLenum glCheckError_(const char *file, int line) -{ - GLenum errorCode; - while ((errorCode = glGetError()) != GL_NO_ERROR) - { - std::string error; - switch (errorCode) - { - case GL_INVALID_ENUM: error = "INVALID_ENUM"; break; - case GL_INVALID_VALUE: error = "INVALID_VALUE"; break; - case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break; - case GL_STACK_OVERFLOW: error = "STACK_OVERFLOW"; break; - case GL_STACK_UNDERFLOW: error = "STACK_UNDERFLOW"; break; - case GL_OUT_OF_MEMORY: error = "OUT_OF_MEMORY"; break; - case GL_INVALID_FRAMEBUFFER_OPERATION: error = "INVALID_FRAMEBUFFER_OPERATION"; break; - } - std::cout << error << " | " << file << " (" << line << ")" << std::endl; - } - return errorCode; -} -#define glCheckError() glCheckError_(__FILE__, __LINE__) -</code></pre> - -<p> - In case you're unaware of what the preprocessor directives <code>__FILE__</code> and <code>__LINE__</code> are: these variables get replaced during compile time with the respective file and line they were compiled in. If we decide to stick a large number of these <fun>glCheckError</fun> calls in our codebase it's helpful to more precisely know which <fun>glCheckError</fun> call returned the error. -</p> - -<pre><code> -<function id='32'>glBindBuffer</function>(GL_VERTEX_ARRAY, vbo); -glCheckError(); -</code></pre> - -<p> - This will give us the following output: -</p> - -<img src="/img/in-practice/debugging_glgeterror.png" alt="Output of glGetError in OpenGL debugging."/> - -<p> - <fun>glGetError</fun> doesn't help you too much as the information it returns is rather simple, but it does often help you catch typos or quickly pinpoint where in your code things went wrong; a simple but effective tool in your debugging toolkit. -</p> - - -<h2>Debug output</h2> -<p> - A less common, but more useful tool than <fun>glCheckError</fun> is an OpenGL extension called <def>debug output</def> that became part of core OpenGL since version 4.3. With the debug output extension, OpenGL itself will directly send an error or warning message to the user with a lot more details compared to <fun>glCheckError</fun>. Not only does it provide more information, it can also help you catch errors exactly where they occur by intelligently using a debugger. -</p> - -<note> - Debug output is core since OpenGL version 4.3, which means you'll find this functionality on any machine that runs OpenGL 4.3 or higher. If they're not available, its functionality can be queried from the <code>ARB_debug_output</code> or <code>AMD_debug_output</code> extension. Note that OS X does not seem to support debug output functionality (as gathered online). -</note> - -<p> - In order to start using debug output we have to request a debug output context from OpenGL at our initialization process. This process varies based on whatever windowing system you use; here we will discuss setting it up on GLFW, but you can find info on other systems in the additional resources at the end of the chapter. -</p> - -<h3>Debug output in GLFW</h3> -<p> - Requesting a debug context in GLFW is surprisingly easy as all we have to do is pass a hint to GLFW that we'd like to have a debug output context. We have to do this before we call <fun><function id='20'>glfwCreateWindow</function></fun>: -</p> - -<pre><code> -<function id='18'>glfwWindowHint</function>(GLFW_OPENGL_DEBUG_CONTEXT, true); -</code></pre> - -<p> - Once we've then initialized GLFW, we should have a debug context if we're using OpenGL version 4.3 or higher. If not, we have to take our chances and hope the system is still able to request a debug context. Otherwise we have to request debug output using its OpenGL extension(s). -</p> - -<note> - Using OpenGL in debug context can be significantly slower compared to a non-debug context, so when working on optimizations or releasing your application you want to remove GLFW's debug request hint. -</note> - -<p> - To check if we successfully initialized a debug context we can query OpenGL: -</p> - -<pre><code> -int flags; glGetIntegerv(GL_CONTEXT_FLAGS, &flags); -if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) -{ - // initialize debug output -} -</code></pre> - -<p> - The way debug output works is that we pass OpenGL an error logging function callback (similar to GLFW's input callbacks) and in the callback function we are free to process the OpenGL error data as we see fit; in our case we'll be displaying useful error data to the console. Below is the callback function prototype that OpenGL expects for debug output: -</p> - -<pre><code> -void APIENTRY glDebugOutput(GLenum source, GLenum type, unsigned int id, GLenum severity, - GLsizei length, const char *message, const void *userParam); -</code></pre> - -<p> - Given the large set of data we have at our exposal, we can create a useful error printing tool like below: -</p> - -<pre><code> -void APIENTRY glDebugOutput(GLenum source, - GLenum type, - unsigned int id, - GLenum severity, - GLsizei length, - const char *message, - const void *userParam) -{ - // ignore non-significant error/warning codes - if(id == 131169 || id == 131185 || id == 131218 || id == 131204) return; - - std::cout << "---------------" << std::endl; - std::cout << "Debug message (" << id << "): " << message << std::endl; - - switch (source) - { - case GL_DEBUG_SOURCE_API: std::cout << "Source: API"; break; - case GL_DEBUG_SOURCE_WINDOW_SYSTEM: std::cout << "Source: Window System"; break; - case GL_DEBUG_SOURCE_SHADER_COMPILER: std::cout << "Source: Shader Compiler"; break; - case GL_DEBUG_SOURCE_THIRD_PARTY: std::cout << "Source: Third Party"; break; - case GL_DEBUG_SOURCE_APPLICATION: std::cout << "Source: Application"; break; - case GL_DEBUG_SOURCE_OTHER: std::cout << "Source: Other"; break; - } std::cout << std::endl; - - switch (type) - { - case GL_DEBUG_TYPE_ERROR: std::cout << "Type: Error"; break; - case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: std::cout << "Type: Deprecated Behaviour"; break; - case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: std::cout << "Type: Undefined Behaviour"; break; - case GL_DEBUG_TYPE_PORTABILITY: std::cout << "Type: Portability"; break; - case GL_DEBUG_TYPE_PERFORMANCE: std::cout << "Type: Performance"; break; - case GL_DEBUG_TYPE_MARKER: std::cout << "Type: Marker"; break; - case GL_DEBUG_TYPE_PUSH_GROUP: std::cout << "Type: Push Group"; break; - case GL_DEBUG_TYPE_POP_GROUP: std::cout << "Type: Pop Group"; break; - case GL_DEBUG_TYPE_OTHER: std::cout << "Type: Other"; break; - } std::cout << std::endl; - - switch (severity) - { - case GL_DEBUG_SEVERITY_HIGH: std::cout << "Severity: high"; break; - case GL_DEBUG_SEVERITY_MEDIUM: std::cout << "Severity: medium"; break; - case GL_DEBUG_SEVERITY_LOW: std::cout << "Severity: low"; break; - case GL_DEBUG_SEVERITY_NOTIFICATION: std::cout << "Severity: notification"; break; - } std::cout << std::endl; - std::cout << std::endl; -} -</code></pre> - -<p> - Whenever debug output detects an OpenGL error, it will call this callback function and we'll be able to print out a large deal of information regarding the OpenGL error. Note that we ignore a few error codes that tend to not really display anything useful (like <code>131185</code> in NVidia drivers that tells us a buffer was successfully created). -</p> - -<p> - Now that we have the callback function it's time to initialize debug output: -</p> - -<pre><code> -if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) -{ - <function id='60'>glEnable</function>(GL_DEBUG_OUTPUT); - <function id='60'>glEnable</function>(GL_DEBUG_OUTPUT_SYNCHRONOUS); - glDebugMessageCallback(glDebugOutput, nullptr); - glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE); -} -</code></pre> - -<p> - Here we tell OpenGL to enable debug output. The <code><function id='60'>glEnable</function>(GL_DEBUG_SYNCRHONOUS)</code> call tells OpenGL to directly call the callback function the moment an error occurred. -</p> - -<h3>Filter debug output</h3> -<p> - With <fun>glDebugMessageControl</fun> you can potentially filter the type(s) of errors you'd like to receive a message from. In our case we decided to not filter on any of the sources, types, or severity rates. If we wanted to only show messages from the OpenGL API, that are errors, and have a high severity, we'd configure it as follows: -</p> - -<pre><code> -glDebugMessageControl(GL_DEBUG_SOURCE_API, - GL_DEBUG_TYPE_ERROR, - GL_DEBUG_SEVERITY_HIGH, - 0, nullptr, GL_TRUE); -</code></pre> - -<p> - Given our configuration, and assuming you have a context that supports debug output, every incorrect OpenGL command will now print a large bundle of useful data: -</p> - -<img src="/img/in-practice/debugging_debug_output.png" alt="Output of OpenGL debug output on a text console."/> - -<h3>Backtracking the debug error source</h3> -<p> - Another great trick with debug output is that you can relatively easy figure out the exact line or call an error occurred. By setting a breakpoint in <fun>DebugOutput</fun> at a specific error type (or at the top of the function if you don't care), the debugger will catch the error thrown and you can move up the call stack to whatever function caused the message dispatch: -</p> - - <img src="/img/in-practice/debugging_debug_output_breakpoint.png" alt="Setting a breaking and using the callstack in OpenGL to catch the line of an error with debug output."/> - -<p> - It requires some manual intervention, but if you roughly know what you're looking for it's incredibly useful to quickly determine which call causes an error. -</p> - -<h3>Custom error output</h3> -<p> - Aside from reading messages, we can also push messages to the debug output system with <fun>glDebugMessageInsert</fun>: -</p> - -<pre><code> -glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_ERROR, 0, - GL_DEBUG_SEVERITY_MEDIUM, -1, "error message here"); -</code></pre> - -<p> - This is especially useful if you're hooking into other application or OpenGL code that makes use of a debug output context. Other developers can quickly figure out any <em>reported</em> bug that occurs in your custom OpenGL code. -</p> - -<p> - In summary, debug output (if you can use it) is incredibly useful for quickly catching errors and is well worth the effort in setting up as it saves considerable development time. You can find a source code example <a href="/code_viewer_gh.php?code=src/7.in_practice/1.debugging/debugging.cpp" target="_blank">here</a> with both <fun>glGetError</fun> and debug output context configured; see if you can fix all the errors. -</p> - -<h2>Debugging shader output</h2> -<p> - When it comes to GLSL, we unfortunately don't have access to a function like <fun>glGetError</fun> nor the ability to step through the shader code. When you end up with a black screen or the completely wrong visuals, it's often difficult to figure out if something's wrong with the shader code. Yes, we have the compilation error reports that report syntax errors, but catching the semantic errors is another beast. -</p> - -<p> - One frequently used trick to figure out what is wrong with a shader is to evaluate all the relevant variables in a shader program by sending them directly to the fragment shader's output channel. By outputting shader variables directly to the output color channels, we can convey interesting information by inspecting the visual results. For instance, let's say we want to check if a model has correct normal vectors. We can pass them (either transformed or untransformed) from the vertex shader to the fragment shader where we'd then output the normals as follows: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; -in vec3 Normal; -[...] - -void main() -{ - [...] - FragColor.rgb = Normal; - FragColor.a = 1.0f; -} -</code></pre> - -<p> - By outputting a (non-color) variable to the output color channel like this we can quickly inspect if the variable is, as far as you can tell, displaying correct values. If, for instance, the visual result is completely black it is clear the normal vectors aren't correctly passed to the shaders; and when they are displayed it's relatively easy to check if they're (sort of) correct or not: -</p> - -<img src="/img/in-practice/debugging_glsl_output.png" alt="The image of a 3D model with its normal vectors displayed as the fragment shader output in OpenGL for debugging"/> - -<p> - From the visual results we can see the world-space normal vectors appear to be correct as the right sides of the backpack model is mostly colored red (which would mean the normals roughly point (correctly) towards the positive x axis). Similarly, the front side of the backpack is mostly colored towards the positive z axis (blue). -</p> - -<p> - This approach can easily extend to any type of variable you'd like to test. Whenever you get stuck and suspect there's something wrong with your shaders, try displaying multiple variables and/or intermediate results to see at which part of the algorithm something's missing or seemingly incorrect. -</p> - -<h2>OpenGL GLSL reference compiler</h2> -<p> - Each driver has its own quirks and tidbits; for instance, NVIDIA drivers are more flexible and tend to overlook some restrictions on the specification, while ATI/AMD drivers tend to better enforce the OpenGL specification (which is the better approach in my opinion). The result of this is that shaders on one machine may not work on the other due to driver differences. -</p> - -<p> - With years of experience you'll eventually get to learn the minor differences between GPU vendors, but if you want to be sure your shader code runs on all kinds of machines you can directly check your shader code against the official specification using OpenGL's GLSL <a href="https://www.khronos.org/opengles/sdk/tools/Reference-Compiler/" target="_blank">reference compiler</a>. You can download the so called <def>GLSL lang validator</def> binaries from <a href="https://www.khronos.org/opengles/sdk/tools/Reference-Compiler/" target="_blank">here</a> or its complete source code from <a href="https://github.com/KhronosGroup/glslang" target="_blank">here</a>. -</p> - -<p> - Given the binary GLSL lang validator you can easily check your shader code by passing it as the binary's first argument. Keep in mind that the GLSL lang validator determines the type of shader by a list of fixed extensions: -<p> - -<ul> - <li><code>.vert</code>: vertex shader.</li> - <li><code>.frag</code>: fragment shader.</li> - <li><code>.geom</code>: geometry shader.</li> - <li><code>.tesc</code>: tessellation control shader.</li> - <li><code>.tese</code>: tessellation evaluation shader.</li> - <li><code>.comp</code>: compute shader.</li> -</ul> - -<p> - Running the GLSL reference compiler is as simple as: -</p> - -<pre><code> -glsllangvalidator shaderFile.vert -</code></pre> - -<p> - Note that if it detects no error, it returns no output. Testing the GLSL reference compiler on a broken vertex shader gives the following output: -</p> - - <img src="/img/in-practice/debugging_glsl_reference_compiler.png" alt="Output of the GLSL reference compiler (GLSL lang validator) in OpenGL"/> - -<p> - It won't show you the subtle differences between AMD, NVidia, or Intel GLSL compilers, nor will it help you completely bug proof your shaders, but it does at least help you to check your shaders against the direct GLSL specification. -</p> - -<h2>Framebuffer output</h2> -<p> - Another useful trick for your debugging toolkit is displaying a framebuffer's content(s) in some pre-defined region of your screen. You're likely to use <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">framebuffers</a> quite often and, as most of their magic happens behind the scenes, it's sometimes difficult to figure out what's going on. Displaying the content(s) of a framebuffer on your screen is a useful trick to quickly see if things look correct. -</p> - -<note> - Note that displaying the contents (attachments) of a framebuffer as explained here only works on texture attachments, not render buffer objects. -</note> - -<p> - Using a simple shader that only displays a texture, we can easily write a small helper function to quickly display any texture at the top-right of the screen: -</p> - -<pre><code> -// vertex shader -#version 330 core -layout (location = 0) in vec2 position; -layout (location = 1) in vec2 texCoords; - -out vec2 TexCoords; - -void main() -{ - gl_Position = vec4(position, 0.0f, 1.0f); - TexCoords = texCoords; -} - -// fragment shader -#version 330 core -out vec4 FragColor; -in vec2 TexCoords; - -uniform sampler2D fboAttachment; - -void main() -{ - FragColor = texture(fboAttachment, TexCoords); -} -</code></pre> - -<pre><code> -void DisplayFramebufferTexture(unsigned int textureID) -{ - if (!notInitialized) - { - // initialize shader and vao w/ NDC vertex coordinates at top-right of the screen - [...] - } - - <function id='49'>glActiveTexture</function>(GL_TEXTURE0); - <function id='28'>glUseProgram</function>(shaderDisplayFBOOutput); - <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, textureID); - <function id='27'>glBindVertexArray</function>(vaoDebugTexturedRect); - <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); - <function id='27'>glBindVertexArray</function>(0); - <function id='28'>glUseProgram</function>(0); -} - -int main() -{ - [...] - while (!<function id='14'>glfwWindowShouldClose</function>(window)) - { - [...] - DisplayFramebufferTexture(fboAttachment0); - - <function id='24'>glfwSwapBuffers</function>(window); - } -} -</code></pre> - -<p> - This will give you a nice little window at the corner of your screen for debugging framebuffer output. Useful, for example, for determining if the normal vectors of the geometry pass in a deferred renderer look correct: -</p> - - <img src="/img/in-practice/debugging_fbo_output.png" alt="Framebuffer attachment output to a texture for debugging purposes in OpenGL"/> - -<p> - You can of course extend such a utility function to support rendering more than one texture. This is a quick and dirty way to get continuous feedback from whatever is in your framebuffer(s). -</p> - -<h2>External debugging software</h2> -<p> - When all else fails there is still the option to use a 3rd party tool to help us in our debugging efforts. Third party applications often inject themselves in the OpenGL drivers and are able to intercept all kinds of OpenGL calls to give you a large array of interesting data. These tools can help you in all kinds of ways like: profiling OpenGL function usage, finding bottlenecks, inspecting buffer memory, and displaying textures and framebuffer attachments. When you're working on (large) production code, these kinds of tools can become invaluable in your development process. -</p> - -<p> - I've listed some of the more popular debugging tools here; try out several of them to see which fits your needs the best. -</p> - -<!--<h3>gDebugger</h3> -<p> - gDebugger is a cross-platform and easy to use debugging tool for OpenGL applications. gDebugger sits alongside your running OpenGL application and provides a detailed overview of the running OpenGL state. You can pause the application at any moment to inspect the current state, texture memory and/or buffer usage. You can download gDebugger <a href="http://www.gremedy.com/" target="_blank">here</a>. -</p> - -<p> - Running gDebugger is as easy as opening the application, creating a new project and giving it the location and working directory of your OpenGL executable. -</p> - - <img src="/img/in-practice/debugging_external_gdebugger.png" alt="Image of gDebugger running on an OpenGL application."> ---> - -<!--<h3>APItrace</h3> -<p> - <a href="https://github.com/apitrace/apitrace" target="_blank">APItrace</a> -</p>--> - -<h3>RenderDoc</h3> -<p> - RenderDoc is a great (completely <a href="https://github.com/baldurk/renderdoc" target="_blank">open source</a>) standalone debugging tool. To start a capture, you specify the executable you'd like to capture and a working directory. The application then runs as usual, and whenever you want to inspect a particular frame, you let RenderDoc capture one or more frames at the executable's current state. Within the captured frame(s) you can view the pipeline state, all OpenGL commands, buffer storage, and textures in use. -</p> - -<img src="/img/in-practice/debugging_external_renderdoc.png" alt="Image of RenderDoc running on an OpenGL application."/> - -<h3>CodeXL</h3> -<p> - <a href="https://gpuopen.com/compute-product/codexl/" target="_blank">CodeXL</a> is GPU debugging tool released as both a standalone tool and a Visual Studio plugin. CodeXL gives a good set of information and is great for profiling graphics applications. CodeXL also works on NVidia or Intel cards, but without support for OpenCL debugging. -</p> - - <img src="/img/in-practice/debugging_external_codexl.png" alt="Image of CodeXL running on an OpenGL application."/> - -<p> - I personally don't have much experience with CodeXL since I found RenderDoc easier to use, but I've included it anyways as it looks to be a pretty solid tool and developed by one of the larger GPU manufacturers. -</p> - -<h3>NVIDIA Nsight</h3> -<p> - NVIDIA's popular <a href="https://developer.nvidia.com/nvidia-nsight-visual-studio-edition" target="_blank">Nsight</a> GPU debugging tool is not a standalone tool, but a plugin to either the Visual Studio IDE or the Eclipse IDE (NVIDIA now has a <a href="https://developer.nvidia.com/nsight-graphics" target="_blank">standalone version</a> as well). The Nsight plugin is an incredibly useful tool for graphics developers as it gives a large host of run-time statistics regarding GPU usage and the frame-by-frame GPU state. - </p> - -<p> - The moment you start your application from within Visual Studio (or Eclipse), using Nsight's debugging or profiling commands, Nsight will run within the application itself. The great thing about Nsight is that it renders an overlay GUI system from within your application that you can use to gather all kinds of interesting information about your application, both at run-time and during frame-by-frame analysis. -</p> - - <img src="/img/in-practice/debugging_external_nsight.png" alt="Image of RenderDoc running on an OpenGL application."/> - -<p> - Nsight is an incredibly useful tool, but it does come with one major drawback in that it only works on NVIDIA cards. If you are working on NVIDIA cards (and use Visual Studio) it's definitely worth a shot. -</p> - -<p> - I'm sure there's plenty of other debugging tools I've missed (some that come to mind are Valve's <a href="https://github.com/ValveSoftware/vogl" target="_blank">VOGL</a> and <a href="https://apitrace.github.io/" target="_blank">APItrace</a>), but I feel this list should already get you plenty of tools to experiment with. -</p> - -<h2>Additional resources</h2> -<ul> - <li><a href="http://retokoradi.com/2014/04/21/opengl-why-is-your-code-producing-a-black-window/" target="_blank">Why is your code producing a black window</a>: list of general causes by Reto Koradi of why your screen may not be producing any output.</li> - <li><a href="https://vallentin.dev/2015/02/23/debugging-opengl" target="_blank">Debug Output in OpenGL</a>: an extensive debug output write-up by Vallentin with detailed information on setting up a debug context on multiple windowing systems.</li> -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/In-Practice/Text-Rendering.html b/translation/In-Practice/Text-Rendering.html @@ -1,693 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Text Rendering</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Text Rendering</h1> -<h1 id="content-url" style='display:none;'>In-Practice/Text-Rendering</h1> -<p> - At some stage of your graphics adventures you will want to draw text in OpenGL. Contrary to what you may expect, getting a simple string to render on screen is all but easy with a low-level API like OpenGL. If you don't care about rendering more than 128 different same-sized characters, then it's probably not too difficult. Things are getting difficult as soon as each character has a different width, height, and margin. Based on where you live, you may also need more than 128 characters, and what if you want to express special symbols for like mathematical expressions or sheet music symbols, and what about rendering text from top to bottom? Once you think about all these complicated matters of text, it wouldn't surprise you that this probably doesn't belong in a low-level API like OpenGL. -</p> - -<p> - Since there is no support for text capabilities within OpenGL, it is up to us to define a system for rendering text to the screen. There are no graphical primitives for text characters, we have to get creative. Some example techniques are: drawing letter shapes via <var>GL_LINES</var>, create 3D meshes of letters, or render character textures to 2D quads in a 3D environment. -</p> - -<p> - Most developers choose to render character textures onto quads. Rendering textured quads by itself shouldn't be too difficult, but getting the relevant character(s) onto a texture could prove challenging. In this chapter we'll explore several methods and implement a more advanced, but flexible technique for rendering text using the FreeType library. -</p> - -<h2>Classical text rendering: bitmap fonts</h2> -<p> - In the early days, rendering text involved selecting a font (or create one yourself) you'd like for your application and extracting all relevant characters out of this font to place them within a single large texture. Such a texture, that we call a <def>bitmap font</def>, contains all character symbols we want to use in predefined regions of the texture. These character symbols of the font are known as <def>glyphs</def>. Each glyph has a specific region of texture coordinates associated with them. Whenever you want to render a character, you select the corresponding glyph by rendering this section of the bitmap font to a 2D quad. -</p> - -<img src="/img/in-practice/bitmapfont.png" alt="Sprite sheet of characters"/> - -<p> - Here you can see how we would render the text 'OpenGL' by taking a bitmap font and sampling the corresponding glyphs from the texture (carefully choosing the texture coordinates) that we render on top of several quads. By enabling <a href="https://learnopengl.com/Advanced-OpenGL/Blending" target="_blank">blending</a> and keeping the background transparent, we will end up with just a string of characters rendered to the screen. This particular bitmap font was generated using Codehead's Bitmap <a href="http://www.codehead.co.uk/cbfg/" target="_blank">Font Generator</a>. -</p> - -<p> - This approach has several advantages and disadvantages. It is relatively easy to implement and because bitmap fonts are pre-rasterized, they're quite efficient. However, it is not particularly flexible. When you want to use a different font, you need to recompile a complete new bitmap font and the system is limited to a single resolution; zooming will quickly show pixelated edges. Furthermore, it is limited to a small character set, so Extended or Unicode characters are often out of the question. -</p> - -<p> - This approach was quite popular back in the day (and still is) since it is fast and works on any platform, but as of today more flexible approaches exist. One of these approaches is loading TrueType fonts using the FreeType library. -</p> - -<h2>Modern text rendering: FreeType</h2> -<p> - FreeType is a software development library that is able to load fonts, render them to bitmaps, and provide support for several font-related operations. It is a popular library used by Mac OS X, Java, PlayStation, Linux, and Android to name a few. What makes FreeType particularly attractive is that it is able to load TrueType fonts. -</p> - -<p> - A TrueType font is a collection of character glyphs not defined by pixels or any other non-scalable solution, but by mathematical equations (combinations of splines). Similar to vector images, the rasterized font images can be procedurally generated based on the preferred font height you'd like to obtain them in. By using TrueType fonts you can easily render character glyphs of various sizes without any loss of quality. -</p> - -<p> - FreeType can be downloaded from their <a href="http://www.freetype.org/" target="_blank">website</a>. You can choose to compile the library yourself or use one of their precompiled libraries if your target platform is listed. Be sure to link to <code>freetype.lib</code> and make sure your compiler knows where to find the header files. - </p> - -<p> - Then include the appropriate headers: -</p> - -<pre><code> -#include <ft2build.h> -#include FT_FREETYPE_H -</code></pre> - -<warning> - Due to how FreeType is developed (at least at the time of this writing), you cannot put their header files in a new directory; they should be located at the root of your include directories. Including FreeType like <code>#include <FreeType/ft2build.h></code> will likely cause several header conflicts. -</warning> - -<p> - FreeType loads these TrueType fonts and, for each glyph, generates a bitmap image and calculates several metrics. We can extract these bitmap images for generating textures and position each character glyph appropriately using the loaded metrics. -</p> - -<p> - To load a font, all we have to do is initialize the FreeType library and load the font as a <def>face</def> as FreeType likes to call it. Here we load the <code>arial.ttf</code> TrueType font file that was copied from the <code>Windows/Fonts</code> directory: -</p> - -<pre><code> -FT_Library ft; -if (FT_Init_FreeType(&ft)) -{ - std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl; - return -1; -} - -FT_Face face; -if (FT_New_Face(ft, "fonts/arial.ttf", 0, &face)) -{ - std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl; - return -1; -} -</code></pre> - -<p> - Each of these FreeType functions returns a non-zero integer whenever an error occurred. - </p> - - <p> - Once we've loaded the face, we should define the pixel font size we'd like to extract from this face: -</p> - -<pre class="cpp"><code> -FT_Set_Pixel_Sizes(face, 0, 48); -</code></pre> - -<p> - The function sets the font's width and height parameters. Setting the width to <code>0</code> lets the face dynamically calculate the width based on the given height. -</p> - -<p> - A FreeType face hosts a collection of glyphs. We can set one of those glyphs as the active glyph by calling <fun>FT_Load_Char</fun>. Here we choose to load the character glyph 'X': -</p> - -<pre><code> -if (FT_Load_Char(face, 'X', FT_LOAD_RENDER)) -{ - std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl; - return -1; -} -</code></pre> - -<p> - By setting <var>FT_LOAD_RENDER</var> as one of the loading flags, we tell FreeType to create an 8-bit grayscale bitmap image for us that we can access via <code>face->glyph->bitmap</code>. -</p> - -<p> - Each of the glyphs we load with FreeType however, do not have the same size (as we had with bitmap fonts). The bitmap image generated by FreeType is just large enough to contain the visible part of a character. For example, the bitmap image of the dot character '.' is much smaller in dimensions than the bitmap image of the character 'X'. For this reason, FreeType also loads several metrics that specify how large each character should be and how to properly position them. Next is an image from FreeType that shows all of the metrics it calculates for each character glyph: -</p> - - <img src="/img/in-practice/glyph.png" alt="Image of metrics of a Glyph as loaded by FreeType"/> - -<p> - Each of the glyphs reside on a horizontal <def>baseline</def> (as depicted by the horizontal arrow) where some glyphs sit exactly on top of this baseline (like 'X') or some slightly below the baseline (like 'g' or 'p'). These metrics define the exact offsets to properly position each glyph on the baseline, how large each glyph should be, and how many pixels we need to advance to render the next glyph. Next is a small list of the properties we'll be needing: -</p> - -<ul> - <li><strong>width</strong>: the width (in pixels) of the bitmap accessed via <code>face->glyph->bitmap.width</code>.</li> - <li><strong>height</strong>: the height (in pixels) of the bitmap accessed via <code>face->glyph->bitmap.rows</code>.</li> - <li><strong>bearingX</strong>: the horizontal bearing e.g. the horizontal position (in pixels) of the bitmap relative to the origin accessed via <code>face->glyph->bitmap_left</code>.</li> - <li><strong>bearingY</strong>: the vertical bearing e.g. the vertical position (in pixels) of the bitmap relative to the baseline accessed via <code>face->glyph->bitmap_top</code>.</li> - <li><strong>advance</strong>: the horizontal advance e.g. the horizontal distance (in 1/64th pixels) from the origin to the origin of the next glyph. Accessed via <code>face->glyph->advance.x</code>.</li> -</ul> - -<p> - We could load a character glyph, retrieve its metrics, and generate a texture each time we want to render a character to the screen, but it would be inefficient to do this each frame. We'd rather store the generated data somewhere in the application and query it whenever we want to render a character. We'll define a convenient <code>struct</code> that we'll store in a <fun>map</fun>: -</p> - -<pre><code> -struct Character { - unsigned int TextureID; // ID handle of the glyph texture - glm::ivec2 Size; // Size of glyph - glm::ivec2 Bearing; // Offset from baseline to left/top of glyph - unsigned int Advance; // Offset to advance to next glyph -}; - -std::map<char, Character> Characters; -</code></pre> - -<p> - For this chapter we'll keep things simple by restricting ourselves to the first 128 characters of the ASCII character set. For each character, we generate a texture and store its relevant data into a <fun>Character</fun> struct that we add to the <var>Characters</var> map. This way, all data required to render each character is stored for later use. -</p> - -<pre><code> -glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // disable byte-alignment restriction - -for (unsigned char c = 0; c < 128; c++) -{ - // load character glyph - if (FT_Load_Char(face, c, FT_LOAD_RENDER)) - { - std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl; - continue; - } - // generate texture - unsigned int texture; - <function id='50'>glGenTextures</function>(1, &texture); - <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, texture); - <function id='52'>glTexImage2D</function>( - GL_TEXTURE_2D, - 0, - GL_RED, - face->glyph->bitmap.width, - face->glyph->bitmap.rows, - 0, - GL_RED, - GL_UNSIGNED_BYTE, - face->glyph->bitmap.buffer - ); - // set texture options - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - // now store character for later use - Character character = { - texture, - glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows), - glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top), - face->glyph->advance.x - }; - Characters.insert(std::pair<char, Character>(c, character)); -} -</code></pre> - -<p> - Within the for loop we list over all the 128 characters of the ASCII set and retrieve their corresponding character glyphs. For each character: we generate a texture, set its options, and store its metrics. What is interesting to note here is that we use <var>GL_RED</var> as the texture's <code>internalFormat</code> and <code>format</code> arguments. The bitmap generated from the glyph is a grayscale 8-bit image where each color is represented by a single byte. For this reason we'd like to store each byte of the bitmap buffer as the texture's single color value. We accomplish this by creating a texture where each byte corresponds to the texture color's red component (first byte of its color vector). If we use a single byte to represent the colors of a texture we do need to take care of a restriction of OpenGL: -</p> - -<pre class="cpp"><code> -glPixelStorei(GL_UNPACK_ALIGNMENT, 1); -</code></pre> - -<p> - OpenGL requires that textures all have a 4-byte alignment e.g. their size is always a multiple of 4 bytes. Normally this won't be a problem since most textures have a width that is a multiple of 4 and/or use 4 bytes per pixel, but since we now only use a single byte per pixel, the texture can have any possible width. By setting its unpack alignment to <code>1</code> we ensure there are no alignment issues (which could cause segmentation faults). -</p> - -<p> - Be sure to clear FreeType's resources once you're finished processing the glyphs: -</p> - -<pre><code> -FT_Done_Face(face); -FT_Done_FreeType(ft); -</code></pre> - -<h3>Shaders</h3> -<p> - To render the glyphs we'll be using the following vertex shader: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex> -out vec2 TexCoords; - -uniform mat4 projection; - -void main() -{ - gl_Position = projection * vec4(vertex.xy, 0.0, 1.0); - TexCoords = vertex.zw; -} -</code></pre> - -<p> - We combine both the position and texture coordinate data into one <fun>vec4</fun>. The vertex shader multiplies the coordinates with a projection matrix and forwards the texture coordinates to the fragment shader: -</p> - -<pre><code> -#version 330 core -in vec2 TexCoords; -out vec4 color; - -uniform sampler2D text; -uniform vec3 textColor; - -void main() -{ - vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r); - color = vec4(textColor, 1.0) * sampled; -} -</code></pre> - -<p> - The fragment shader takes two uniforms: one is the mono-colored bitmap image of the glyph, and the other is a color uniform for adjusting the text's final color. We first sample the color value of the bitmap texture. Because the texture's data is stored in just its red component, we sample the <code>r</code> component of the texture as the sampled alpha value. By varying the output color's alpha value, the resulting pixel will be transparent for all the glyph's background colors and non-transparent for the actual character pixels. We also multiply the RGB colors by the <var>textColor</var> uniform to vary the text color. -</p> - -<p> - We do need to enable <a href="https://learnopengl.com/Advanced-OpenGL/Blending" target="_blank">blending</a> for this to work though: -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_BLEND); -<function id='70'>glBlendFunc</function>(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -</code></pre> - -<p> - For the projection matrix we'll be using an orthographic projection matrix. For rendering text we (usually) do not need perspective, and using an orthographic projection matrix also allows us to specify all vertex coordinates in screen coordinates if we set it up as follows: -</p> - -<pre><code> -glm::mat4 projection = <function id='59'>glm::ortho</function>(0.0f, 800.0f, 0.0f, 600.0f); -</code></pre> - -<p> - We set the projection matrix's bottom parameter to <code>0.0f</code> and its top parameter equal to the window's height. The result is that we specify coordinates with <code>y</code> values ranging from the bottom part of the screen (<code>0.0f</code>) to the top part of the screen (<code>600.0f</code>). This means that the point (<code>0.0</code>, <code>0.0</code>) now corresponds to the bottom-left corner. -</p> - -<p> - Last up is creating a VBO and VAO for rendering the quads. For now we reserve enough memory when initiating the VBO so that we can later update the VBO's memory when rendering characters: -</p> - -<pre><code> -unsigned int VAO, VBO; -<function id='33'>glGenVertexArrays</function>(1, &VAO); -<function id='12'>glGenBuffers</function>(1, &VBO); -<function id='27'>glBindVertexArray</function>(VAO); -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, VBO); -<function id='31'>glBufferData</function>(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW); -<function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(0); -<function id='30'>glVertexAttribPointer</function>(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0); -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, 0); -<function id='27'>glBindVertexArray</function>(0); -</code></pre> - -<p> - The 2D quad requires <code>6</code> vertices of <code>4</code> floats each, so we reserve <code>6 * 4</code> floats of memory. Because we'll be updating the content of the VBO's memory quite often we'll allocate the memory with <var>GL_DYNAMIC_DRAW</var>. -</p> - -<h3>Render line of text</h3> -<p> - To render a character, we extract the corresponding <fun>Character</fun> struct of the <var>Characters</var> map and calculate the quad's dimensions using the character's metrics. With the quad's calculated dimensions we dynamically generate a set of 6 vertices that we use to update the content of the memory managed by the VBO using <fun><function id='90'>glBufferSubData</function></fun>. -</p> - -<p> - We create a function called <fun>RenderText</fun> that renders a string of characters: -</p> - -<pre><code> -void RenderText(Shader &s, std::string text, float x, float y, float scale, glm::vec3 color) -{ - // activate corresponding render state - s.Use(); - <function id='44'>glUniform</function>3f(<function id='45'>glGetUniformLocation</function>(s.Program, "textColor"), color.x, color.y, color.z); - <function id='49'>glActiveTexture</function>(GL_TEXTURE0); - <function id='27'>glBindVertexArray</function>(VAO); - - // iterate through all characters - std::string::const_iterator c; - for (c = text.begin(); c != text.end(); c++) - { - Character ch = Characters[*c]; - - float xpos = x + ch.Bearing.x * scale; - float ypos = y - (ch.Size.y - ch.Bearing.y) * scale; - - float w = ch.Size.x * scale; - float h = ch.Size.y * scale; - // update VBO for each character - float vertices[6][4] = { - { xpos, ypos + h, 0.0f, 0.0f }, - { xpos, ypos, 0.0f, 1.0f }, - { xpos + w, ypos, 1.0f, 1.0f }, - - { xpos, ypos + h, 0.0f, 0.0f }, - { xpos + w, ypos, 1.0f, 1.0f }, - { xpos + w, ypos + h, 1.0f, 0.0f } - }; - // render glyph texture over quad - <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, ch.textureID); - // update content of VBO memory - <function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, VBO); - <function id='90'>glBufferSubData</function>(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); - <function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, 0); - // render quad - <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); - // now advance cursors for next glyph (note that advance is number of 1/64 pixels) - x += (ch.Advance >> 6) * scale; // bitshift by 6 to get value in pixels (2^6 = 64) - } - <function id='27'>glBindVertexArray</function>(0); - <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, 0); -} -</code></pre> - -<p> - Most of the content of the function should be relatively self-explanatory: we first calculate the origin position of the quad (as <var>xpos</var> and <var>ypos</var>) and the quad's size (as <var>w</var> and <var>h</var>) and generate a set of 6 vertices to form the 2D quad; note that we scale each metric by <var>scale</var>. We then update the content of the VBO and render the quad. -</p> - -<p> - The following line of code requires some extra attention though: -</p> - -<pre><code> -float ypos = y - (ch.Size.y - ch.Bearing.y); -</code></pre> - -<p> - Some characters (like 'p' or 'g') are rendered slightly below the baseline, so the quad should also be positioned slightly below <fun>RenderText</fun>'s <var>y</var> value. The exact amount we need to offset <var>ypos</var> below the baseline can be figured out from the glyph metrics: -</p> - -<img src="/img/in-practice/glyph_offset.png" alt="Offset below baseline of glyph to position 2D quad"/> - -<p> - To calculate this distance e.g. offset we need to figure out the distance a glyph extends below the baseline; this distance is indicated by the red arrow. As you can see from the glyph metrics, we can calculate the length of this vector by subtracting <code>bearingY</code> from the glyph's (bitmap) height. This value is then <code>0.0</code> for characters that rest on the baseline (like 'X') and positive for characters that reside slightly below the baseline (like 'g' or 'j'). -</p> - -<p> - If you did everything correct you should now be able to successfully render strings of text with the following statements: -</p> - -<pre><code> -RenderText(shader, "This is sample text", 25.0f, 25.0f, 1.0f, glm::vec3(0.5, 0.8f, 0.2f)); -RenderText(shader, "(C) LearnOpenGL.com", 540.0f, 570.0f, 0.5f, glm::vec3(0.3, 0.7f, 0.9f)); -</code></pre> - -<p> - This should then look similar to the following image: -</p> - - <img src="/img/in-practice/text_rendering.png" class="clean" alt="Image of text rendering with OpenGL using FreeType"/> - -<p> - You can find the code of this example <a href="/code_viewer_gh.php?code=src/7.in_practice/2.text_rendering/text_rendering.cpp" target="_blank">here</a>. -</p> - -<p> - To give you a feel for how we calculated the quad's vertices, we can disable blending to see what the actual rendered quads look like: -</p> - - <img src="/img/in-practice/text_rendering_quads.png" class="clean" alt="Image of quads without transparency for text rendering in OpenGL"/> - -<p> - Here you can clearly see most quads resting on the (imaginary) baseline while the quads that corresponds to glyphs like 'p' or '(' are shifted downwards. -</p> - -<h2>Going further</h2> -<p> - This chapter demonstrated a text rendering technique with TrueType fonts using the FreeType library. The approach is flexible, scalable, and works with many character encodings. However, this approach is likely going to be overkill for your application as we generate and render textures for each glyph. Performance-wise, bitmap fonts are preferable as we only need one texture for all our glyphs. The best approach would be to combine the two approaches by dynamically generating a bitmap font texture featuring all character glyphs as loaded with FreeType. This saves the renderer from a significant amount of texture switches and, based on how tight each glyph is packed, could save quite some performance. -</p> - -<p> - Another issue with FreeType font bitmaps is that the glyph textures are stored with a fixed font size, so a significant amount of scaling may introduce jagged edges. Furthermore, rotations applied to the glyphs will cause them to appear blurry. This can be mitigated by, instead of storing the actual rasterized pixel colors, storing the distance to the closest glyph outline per pixel. This technique is called <def>signed distance field fonts</def> and Valve published a <a href="https://steamcdn-a.akamaihd.net/apps/valve/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf" target="_blank">paper</a> a few years ago about their implementation of this technique which works surprisingly well for 3D rendering applications. -</p> - -<h2>Further reading</h2> -<ul> - <li><a href="https://www.websiteplanet.com/blog/best-free-fonts/" target="_blank">70+ Best Free Fonts for Designers</a>: summarized list of a large group of fonts to use in your project for personal or commercial use.</li> -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Lighting/Basic-Lighting.html b/translation/Lighting/Basic-Lighting.html @@ -1,664 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Basic Lighting</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Basic Lighting</h1> -<h1 id="content-url" style='display:none;'>Lighting/Basic-Lighting</h1> -<p> - Lighting in the real world is extremely complicated and depends on way too many factors, something we can't afford to calculate on the limited processing power we have. Lighting in OpenGL is therefore based on approximations of reality using simplified models that are much easier to process and look relatively similar. These lighting models are based on the physics of light as we understand it. One of those models is called the <def>Phong lighting model</def>. The major building blocks of the Phong lighting model consist of 3 components: ambient, diffuse and specular lighting. Below you can see what these lighting components look like on their own and combined: -</p> - -<img src="/img/lighting/basic_lighting_phong.png"/> - -<p> - <ul> - <li><def>Ambient lighting</def>: even when it is dark there is usually still some light somewhere in the world (the moon, a distant light) so objects are almost never completely dark. To simulate this we use an ambient lighting constant that always gives the object some color.</li> - <li><def>Diffuse lighting</def>: simulates the directional impact a light object has on an object. This is the most visually significant component of the lighting model. The more a part of an object faces the light source, the brighter it becomes.</li> - <li><def>Specular lighting</def>: simulates the bright spot of a light that appears on shiny objects. Specular highlights are more inclined to the color of the light than the color of the object.</li> - </ul> -</p> - -<p> - To create visually interesting scenes we want to at least simulate these 3 lighting components. We'll start with the simplest one: <em>ambient lighting</em>. -</p> - -<h1>Ambient lighting</h1> -<p> - Light usually does not come from a single light source, but from many light sources scattered all around us, even when they're not immediately visible. One of the properties of light is that it can scatter and bounce in many directions, reaching spots that aren't directly visible; light can thus <em>reflect</em> on other surfaces and have an indirect impact on the lighting of an object. Algorithms that take this into consideration are called <def>global illumination</def> algorithms, but these are complicated and expensive to calculate. -</p> - -<p> - Since we're not big fans of complicated and expensive algorithms we'll start by using a very simplistic model of global illumination, namely <def>ambient lighting</def>. As you've seen in the previous section we use a small constant (light) color that we add to the final resulting color of the object's fragments, thus making it look like there is always some scattered light even when there's not a direct light source. -</p> - -<p> - Adding ambient lighting to the scene is really easy. We take the light's color, multiply it with a small constant ambient factor, multiply this with the object's color, and use that as the fragment's color in the cube object's shader: -</p> - -<pre><code> -void main() -{ - float ambientStrength = 0.1; - vec3 ambient = ambientStrength * lightColor; - - vec3 result = ambient * objectColor; - FragColor = vec4(result, 1.0); -} -</code></pre> - -<p> - If you'd now run the program, you'll notice that the first stage of lighting is now successfully applied to the object. The object is quite dark, but not completely since ambient lighting is applied (note that the light cube is unaffected because we use a different shader). It should look something like this: -</p> - -<img src="/img/lighting/ambient_lighting.png" class="clean"/> - -<h1>Diffuse lighting</h1> -<p> - Ambient lighting by itself doesn't produce the most interesting results, but diffuse lighting however will start to give a significant visual impact on the object. Diffuse lighting gives the object more brightness the closer its fragments are aligned to the light rays from a light source. To give you a better understanding of diffuse lighting take a look at the following image: -</p> - -<img src="/img/lighting/diffuse_light.png" class="clean"/> - -<p> - To the left we find a light source with a light ray targeted at a single fragment of our object. We need to measure at what angle the light ray touches the fragment. If the light ray is perpendicular to the object's surface the light has the greatest impact. To measure the angle between the light ray and the fragment we use something called a <def>normal vector</def>, that is a vector perpendicular to the fragment's surface (here depicted as a yellow arrow); we'll get to that later. The angle between the two vectors can then easily be calculated with the dot product. -</p> - -<p> - You may remember from the <a href="https://learnopengl.com/Getting-started/Transformations" target="_blank">transformations</a> chapter that, the lower the angle between two unit vectors, the more the dot product is inclined towards a value of 1. When the angle between both vectors is 90 degrees, the dot product becomes 0. The same applies to \(\theta\): the larger \(\theta\) becomes, the less of an impact the light should have on the fragment's color. -</p> - -<note> - Note that to get (only) the cosine of the angle between both vectors we will work with <em>unit vectors</em> (vectors of length <code>1</code>) so we need to make sure all the vectors are normalized, otherwise the dot product returns more than just the cosine (see <a href="https://learnopengl.com/Getting-started/Transformations" target="_blank">Transformations</a>). -</note> - -<p> - The resulting dot product thus returns a scalar that we can use to calculate the light's impact on the fragment's color, resulting in differently lit fragments based on their orientation towards the light. -</p> - -<p> - So, what do we need to calculate diffuse lighting: - <ul> - <li>Normal vector: a vector that is perpendicular to the vertex' surface.</li> - <li>The directed light ray: a direction vector that is the difference vector between the light's position and the fragment's position. To calculate this light ray we need the light's position vector and the fragment's position vector.</li> - </ul> -</p> - -<h2>Normal vectors</h2> -<p> - A normal vector is a (unit) vector that is perpendicular to the surface of a vertex. Since a vertex by itself has no surface (it's just a single point in space) we retrieve a normal vector by using its surrounding vertices to figure out the surface of the vertex. We can use a little trick to calculate the normal vectors for all the cube's vertices by using the cross product, but since a 3D cube is not a complicated shape we can simply manually add them to the vertex data. The updated vertex data array can be found <a href="/code_viewer.php?code=lighting/basic_lighting_vertex_data" target="_blank">here</a>. Try to visualize that the normals are indeed vectors perpendicular to each plane's surface (a cube consists of 6 planes). -</p> - -<p> - Since we added extra data to the vertex array we should update the cube's vertex shader: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec3 aNormal; -... -</code></pre> - -<p> - Now that we added a normal vector to each of the vertices and updated the vertex shader we should update the vertex attribute pointers as well. Note that the light source's cube uses the same vertex array for its vertex data, but the lamp shader has no use of the newly added normal vectors. We don't have to update the lamp's shaders or attribute configurations, but we have to at least modify the vertex attribute pointers to reflect the new vertex array's size: -</p> - -<pre><code> -<function id='30'>glVertexAttribPointer</function>(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); -<function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(0); -</code></pre> - -<p> - We only want to use the first <code>3</code> floats of each vertex and ignore the last <code>3</code> floats so we only need to update the <em>stride</em> parameter to <code>6</code> times the size of a <code>float</code> and we're done. -</p> - -<note> - It may look inefficient using vertex data that is not completely used by the lamp shader, but the vertex data is already stored in the GPU's memory from the container object so we don't have to store new data into the GPU's memory. This actually makes it more efficient compared to allocating a new VBO specifically for the lamp. -</note> - -<p> - All the lighting calculations are done in the fragment shader so we need to forward the normal vectors from the vertex shader to the fragment shader. Let's do that: -</p> - -<pre><code> -out vec3 Normal; - -void main() -{ - gl_Position = projection * view * model * vec4(aPos, 1.0); - Normal = aNormal; -} -</code></pre> - -<p> - What's left to do is declare the corresponding input variable in the fragment shader: -</p> - -<pre><code> -in vec3 Normal; -</code></pre> - -<h2>Calculating the diffuse color</h2> -<p> - We now have the normal vector for each vertex, but we still need the light's position vector and the fragment's position vector. Since the light's position is a single static variable we can declare it as a uniform in the fragment shader: -</p> - -<pre><code> -uniform vec3 lightPos; -</code></pre> - -<p> - And then update the uniform in the render loop (or outside since it doesn't change per frame). We use the <var>lightPos</var> vector declared in the previous chapter as the location of the diffuse light source: -</p> - -<pre><code> -lightingShader.setVec3("lightPos", lightPos); -</code></pre> - -<p> - Then the last thing we need is the actual fragment's position. We're going to do all the lighting calculations in world space so we want a vertex position that is in world space first. We can accomplish this by multiplying the vertex position attribute with the model matrix only (not the view and projection matrix) to transform it to world space coordinates. This can easily be accomplished in the vertex shader so let's declare an output variable and calculate its world space coordinates: -</p> - -<pre><code> -out vec3 FragPos; -out vec3 Normal; - -void main() -{ - gl_Position = projection * view * model * vec4(aPos, 1.0); - FragPos = vec3(model * vec4(aPos, 1.0)); - Normal = aNormal; -} -</code></pre> - -<p> - And lastly add the corresponding input variable to the fragment shader: -</p> - -<pre><code> -in vec3 FragPos; -</code></pre> - -<p> - This <code>in</code> variable will be interpolated from the 3 world position vectors of the triangle to form the <var>FragPos</var> vector that is the per-fragment world position. Now that all the required variables are set we can start the lighting calculations. -</p> - - -<p> - The first thing we need to calculate is the direction vector between the light source and the fragment's position. From the previous section we know that the light's direction vector is the difference vector between the light's position vector and the fragment's position vector. As you may remember from the <a href="https://learnopengl.com/Getting-started/Transformations" target="_blank">transformations</a> chapter we can easily calculate this difference by subtracting both vectors from each other. We also want to make sure all the relevant vectors end up as unit vectors so we normalize both the normal and the resulting direction vector: -</p> - -<pre><code> -vec3 norm = normalize(Normal); -vec3 lightDir = normalize(lightPos - FragPos); -</code></pre> - -<note> - When calculating lighting we usually do not care about the magnitude of a vector or their position; we only care about their direction. Because we only care about their direction almost all the calculations are done with unit vectors since it simplifies most calculations (like the dot product). So when doing lighting calculations, make sure you always normalize the relevant vectors to ensure they're actual unit vectors. Forgetting to normalize a vector is a popular mistake. -</note> - -<p> - Next we need to calculate the diffuse impact of the light on the current fragment by taking the dot product between the <var>norm</var> and <var>lightDir</var> vectors. The resulting value is then multiplied with the light's color to get the diffuse component, resulting in a darker diffuse component the greater the angle between both vectors: -</p> - -<pre><code> -float diff = max(dot(norm, lightDir), 0.0); -vec3 diffuse = diff * lightColor; -</code></pre> - -<p> - If the angle between both vectors is greater than <code>90</code> degrees then the result of the dot product will actually become negative and we end up with a negative diffuse component. - For that reason we use the <fun>max</fun> function that returns the highest of both its parameters to make sure the diffuse component (and thus the colors) never become negative. Lighting for negative colors is not really defined so it's best to stay away from that, unless you're one of those eccentric artists. -</p> - -<p> - Now that we have both an ambient and a diffuse component we add both colors to each other and then multiply the result with the color of the object to get the resulting fragment's output color: -</p> - -<pre><code> -vec3 result = (ambient + diffuse) * objectColor; -FragColor = vec4(result, 1.0); -</code></pre> - -<p> - If your application (and shaders) compiled successfully you should see something like this: -</p> - -<img src="/img/lighting/basic_lighting_diffuse.png" class="clean"/> - -<p> - You can see that with diffuse lighting the cube starts to look like an actual cube again. Try visualizing the normal vectors in your head and move the camera around the cube to see that the larger the angle between the normal vector and the light's direction vector, the darker the fragment becomes. -</p> - -<p> - Feel free to compare your source code with the complete source code <a href="/code_viewer_gh.php?code=src/2.lighting/2.1.basic_lighting_diffuse/basic_lighting_diffuse.cpp" target="_blank">here</a> if you're stuck. -</p> - -<h2>One last thing</h2> -<p> - in the previous section we passed the normal vector directly from the vertex shader to the fragment shader. However, the calculations in the fragment shader are all done in world space, so shouldn't we transform the normal vectors to world space coordinates as well? Basically yes, but it's not as simple as simply multiplying it with a model matrix. -</p> - -<p> - First of all, normal vectors are only direction vectors and do not represent a specific position in space. Second, normal vectors do not have a homogeneous coordinate (the <code>w</code> component of a vertex position). This means that translations should not have any effect on the normal vectors. So if we want to multiply the normal vectors with a model matrix we want to remove the translation part of the matrix by taking the upper-left <code>3x3</code> matrix of the model matrix (note that we could also set the <code>w</code> component of a normal vector to <code>0</code> and multiply with the 4x4 matrix). -</p> - -<p> - Second, if the model matrix would perform a non-uniform scale, the vertices would be changed in such a way that the normal vector is not perpendicular to the surface anymore. The following image shows the effect such a model matrix (with non-uniform scaling) has on a normal vector: -</p> - -<img src="/img/lighting/basic_lighting_normal_transformation.png" class="clean"/> - -<p> - Whenever we apply a non-uniform scale (note: a uniform scale only changes the normal's magnitude, not its direction, which is easily fixed by normalizing it) the normal vectors are not perpendicular to the corresponding surface anymore which distorts the lighting. -</p> - -<p> - The trick of fixing this behavior is to use a different model matrix specifically tailored for normal vectors. This matrix is called the <def>normal matrix</def> and uses a few linear algebraic operations to remove the effect of wrongly scaling the normal vectors. If you want to know how this matrix is calculated I suggest the following <a href="http://www.lighthouse3d.com/tutorials/glsl-tutorial/the-normal-matrix/" target="_blank">article</a>. -</p> - -<p> - The normal matrix is defined as 'the transpose of the inverse of the upper-left 3x3 part of the model matrix'. Phew, that's a mouthful and if you don't really understand what that means, don't worry; we haven't discussed inverse and transpose matrices yet. Note that most resources define the normal matrix as derived from the model-view matrix, but since we're working in world space (and not in view space) we will derive it from the model matrix. -</p> - -<p> - In the vertex shader we can generate the normal matrix by using the <fun>inverse</fun> and <fun>transpose</fun> functions in the vertex shader that work on any matrix type. Note that we cast the matrix to a 3x3 matrix to ensure it loses its translation properties and that it can multiply with the <code>vec3</code> normal vector: -</p> - -<pre><code> -Normal = mat3(transpose(inverse(model))) * aNormal; -</code></pre> - -<warning> - Inversing matrices is a costly operation for shaders, so wherever possible try to avoid doing inverse operations since they have to be done on each vertex of your scene. For learning purposes this is fine, but for an efficient application you'll likely want to calculate the normal matrix on the CPU and send it to the shaders via a uniform before drawing (just like the model matrix). -</warning> - -<p> - In the diffuse lighting section the lighting was fine because we didn't do any scaling on the object, so there was not really a need to use a normal matrix and we could've just multiplied the normals with the model matrix. If you are doing a non-uniform scale however, it is essential that you multiply your normal vectors with the normal matrix. -</p> - - -<h1>Specular Lighting</h1> -<p> - If you're not exhausted already by all the lighting talk we can start finishing the Phong lighting model by adding specular highlights. -</p> - -<p> - Similar to diffuse lighting, specular lighting is based on the light's direction vector and the object's normal vectors, but this time it is also based on the view direction e.g. from what direction the player is looking at the fragment. Specular lighting is based on the reflective properties of surfaces. If we think of the object's surface as a mirror, the specular lighting is the strongest wherever we would see the light reflected on the surface. You can see this effect in the following image: -</p> - -<img src="/img/lighting/basic_lighting_specular_theory.png" class="clean"/> - -<p> - We calculate a reflection vector by reflecting the light direction around the normal vector. Then we calculate the angular distance between this reflection vector and the view direction. The closer the angle between them, the greater the impact of the specular light. The resulting effect is that we see a bit of a highlight when we're looking at the light's direction reflected via the surface. -</p> - -<p> - The view vector is the one extra variable we need for specular lighting which we can calculate using the viewer's world space position and the fragment's position. Then we calculate the specular's intensity, multiply this with the light color and add this to the ambient and diffuse components. -</p> - -<note> - We chose to do the lighting calculations in world space, but most people tend to prefer doing lighting in view space. An advantage of view space is that the viewer's position is always at <code>(0,0,0)</code> so you already got the position of the viewer for free. However, I find calculating lighting in world space more intuitive for learning purposes. If you still want to calculate lighting in view space you want to transform all the relevant vectors with the view matrix as well (don't forget to change the normal matrix too). -</note> - -<p> - To get the world space coordinates of the viewer we simply take the position vector of the camera object (which is the viewer of course). So let's add another uniform to the fragment shader and pass the camera position vector to the shader: -</p> - -<pre><code> -uniform vec3 viewPos; -</code></pre> - -<pre><code> -lightingShader.setVec3("viewPos", camera.Position); -</code></pre> - -<p> - Now that we have all the required variables we can calculate the specular intensity. First we define a specular intensity value to give the specular highlight a medium-bright color so that it doesn't have too much of an impact: -</p> - -<pre><code> -float specularStrength = 0.5; -</code></pre> - -<p> - If we would set this to <code>1.0f</code> we'd get a really bright specular component which is a bit too much for a coral cube. In the <a href="https://learnopengl.com/Lighting/Materials" target="_blank">next</a> chapter we'll talk about properly setting all these lighting intensities and how they affect the objects. Next we calculate the view direction vector and the corresponding reflect vector along the normal axis: -</p> - -<pre><code> -vec3 viewDir = normalize(viewPos - FragPos); -vec3 reflectDir = reflect(-lightDir, norm); -</code></pre> - -<p> - Note that we negate the <code>lightDir</code> vector. The <code>reflect</code> function expects the first vector to point <strong>from</strong> the light source towards the fragment's position, but the <code>lightDir</code> vector is currently pointing the other way around: from the fragment <strong>towards</strong> the light source (this depends on the order of subtraction earlier on when we calculated the <code>lightDir</code> vector). To make sure we get the correct <code>reflect</code> vector we reverse its direction by negating the <code>lightDir</code> vector first. The second argument expects a normal vector so we supply the normalized <code>norm</code> vector. -</p> - -<p> - Then what's left to do is to actually calculate the specular component. This is accomplished with the following formula: - -<pre><code> -float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); -vec3 specular = specularStrength * spec * lightColor; -</code></pre> - -<p> - We first calculate the dot product between the view direction and the reflect direction (and make sure it's not negative) and then raise it to the power of <code>32</code>. This <code>32</code> value is the <def>shininess</def> value of the highlight. The higher the shininess value of an object, the more it properly reflects the light instead of scattering it all around and thus the smaller the highlight becomes. Below you can see an image that shows the visual impact of different shininess values: -</p> - -<img src="/img/lighting/basic_lighting_specular_shininess.png"/> - -<p> - We don't want the specular component to be too distracting so we keep the exponent at <code>32</code>. The only thing left to do is to add it to the ambient and diffuse components and multiply the combined result with the object's color: -</p> - -<pre><code> -vec3 result = (ambient + diffuse + specular) * objectColor; -FragColor = vec4(result, 1.0); -</code></pre> - -<p> - We now calculated all the lighting components of the Phong lighting model. Based on your point of view you should see something like this: -</p> - -<img src="/img/lighting/basic_lighting_specular.png" class="clean"/> - -<p> - You can find the complete source code of the application <a href="/code_viewer_gh.php?code=src/2.lighting/2.2.basic_lighting_specular/basic_lighting_specular.cpp" target="_blank">here</a>. -</p> - -<note> - <p> - In the earlier days of lighting shaders, developers used to implement the Phong lighting model in the vertex shader. The advantage of doing lighting in the vertex shader is that it is a lot more efficient since there are generally a lot less vertices compared to fragments, so the (expensive) lighting calculations are done less frequently. However, the resulting color value in the vertex shader is the resulting lighting color of that vertex only and the color values of the surrounding fragments are then the result of interpolated lighting colors. The result was that the lighting was not very realistic unless large amounts of vertices were used: - </p> - - <img src="/img/lighting/basic_lighting_gouruad.png"/> - - <p> - When the Phong lighting model is implemented in the vertex shader it is called <def>Gouraud shading</def> instead of <def>Phong shading</def>. Note that due to the interpolation the lighting looks somewhat off. The Phong shading gives much smoother lighting results. - </p> -</note> - -<p> - By now you should be starting to see just how powerful shaders are. With little information shaders are able to calculate how lighting affects the fragment's colors for all our objects. In the <a href="https://learnopengl.com/Lighting/Materials" target="_blank">next</a> chapters we'll be delving much deeper into what we can do with the lighting model. -</p> - -<h2>Exercises</h2> -<ul> - <li>Right now the light source is a boring static light source that doesn't move. Try to move the light source around the scene over time using either <fun>sin</fun> or <fun>cos</fun>. Watching the lighting change over time gives you a good understanding of Phong's lighting model: <a href="/code_viewer_gh.php?code=src/2.lighting/2.3.basic_lighting_exercise1/basic_lighting_exercise1.cpp" target="_blank">solution</a>. </li> - <li>Play around with different ambient, diffuse and specular strengths and see how they impact the result. Also experiment with the shininess factor. Try to comprehend why certain values have a certain visual output.</li> - <li>Do Phong shading in view space instead of world space: <a href="/code_viewer_gh.php?code=src/2.lighting/2.4.basic_lighting_exercise2/basic_lighting_exercise2.cpp" target="_blank">solution</a>. </li> - <li>Implement Gouraud shading instead of Phong shading. If you did things right the lighting should <a href="/img/lighting/basic_lighting_exercise3.png" target="_blank">look a bit off</a> (especially the specular highlights) with the cube object. Try to reason why it looks so weird: <a href="/code_viewer_gh.php?code=src/2.lighting/2.5.basic_lighting_exercise3/basic_lighting_exercise3.cpp" target="_blank">solution</a>.</li> -</ul> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Lighting/Colors.html b/translation/Lighting/Colors.html @@ -1,494 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Colors</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Colors</h1> -<h1 id="content-url" style='display:none;'>Lighting/Colors</h1> -<p> - We briefly used and manipulated colors in the previous chapters, but never defined them properly. Here we'll discuss what colors are and start building the scene for the upcoming Lighting chapters. -</p> - -<p> - In the real world, colors can take any known color value with each object having its own color(s). In the digital world we need to map the (infinite) real colors to (limited) digital values and therefore not all real-world colors can be represented digitally. Colors are digitally represented using a <code>red</code>, <code>green</code> and <code>blue</code> component commonly abbreviated as <code>RGB</code>. Using different combinations of just those 3 values, within a range of <code>[0,1]</code>, we can represent almost any color there is. For example, to get a <em>coral</em> color, we define a color vector as: -</p> - -<pre><code> -glm::vec3 coral(1.0f, 0.5f, 0.31f); -</code></pre> - -<p> - The color of an object we see in real life is not the color it actually has, but is the color <def>reflected</def> from the object. The colors that aren't absorbed (rejected) by the object is the color we perceive of it. As an example, the light of the sun is perceived as a white light that is the combined sum of many different colors (as you can see in the image). If we would shine this white light on a blue toy, it would absorb all the white color's sub-colors except the blue color. Since the toy does not absorb the blue color part, it is reflected. This reflected light enters our eye, making it look like the toy has a blue color. The following image shows this for a coral colored toy where it reflects several colors with varying intensity: -</p> - -<img src="/img/lighting/light_reflection.png" class="clean"/> - -<p> - You can see that the white sunlight is a collection of all the visible colors and the object absorbs a large portion of those colors. It only reflects those colors that represent the object's color and the combination of those is what we perceive (in this case a coral color). -</p> - -<note> - Technically it's a bit more complicated, but we'll get to that in the PBR chapters. -</note> - -<p> - These rules of color reflection apply directly in graphics-land. When we define a light source in OpenGL we want to give this light source a color. In the previous paragraph we had a white color so we'll give the light source a white color as well. If we would then multiply the light source's color with an object's color value, the resulting color would be the reflected color of the object (and thus its perceived color). Let's revisit our toy (this time with a coral value) and see how we would calculate its perceived color in graphics-land. We get the resulting color vector by doing a component-wise multiplication between the light and object color vectors: -</p> - -<pre><code> -glm::vec3 lightColor(1.0f, 1.0f, 1.0f); -glm::vec3 toyColor(1.0f, 0.5f, 0.31f); -glm::vec3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f); -</code></pre> - -<p> - We can see that the toy's color <em>absorbs</em> a large portion of the white light, but reflects several red, green and blue values based on its own color value. This is a representation of how colors would work in real life. We can thus define an object's color as <em>the amount of each color component it reflects from a light source</em>. Now what would happen if we used a green light? -</p> - -<pre><code> -glm::vec3 lightColor(0.0f, 1.0f, 0.0f); -glm::vec3 toyColor(1.0f, 0.5f, 0.31f); -glm::vec3 result = lightColor * toyColor; // = (0.0f, 0.5f, 0.0f); -</code></pre> - -<p> - As we can see, the toy has no red and blue light to absorb and/or reflect. The toy also absorbs half of the light's green value, but also reflects half of the light's green value. The toy's color we perceive would then be a dark-greenish color. We can see that if we use a green light, only the green color components can be reflected and thus perceived; no red and blue colors are perceived. As a result the coral object suddenly becomes a dark-greenish object. Let's try one more example with a dark olive-green light: -</p> - -<pre><code> -glm::vec3 lightColor(0.33f, 0.42f, 0.18f); -glm::vec3 toyColor(1.0f, 0.5f, 0.31f); -glm::vec3 result = lightColor * toyColor; // = (0.33f, 0.21f, 0.06f); -</code></pre> - -<p> - As you can see, we can get interesting colors from objects using different light colors. It's not hard to get creative with colors. -</p> - -<p> - But enough about colors, let's start building a scene where we can experiment in. -</p> - -<h1>A lighting scene</h1> -<p> - In the upcoming chapters we'll be creating interesting visuals by simulating real-world lighting making extensive use of colors. Since now we'll be using light sources we want to display them as visual objects in the scene and add at least one object to simulate the lighting from. -</p> - -<p> - The first thing we need is an object to cast the light on and we'll use the infamous container cube from the previous chapters. We'll also be needing a light object to show where the light source is located in the 3D scene. For simplicity's sake we'll represent the light source with a cube as well (we already have the <a href="https://learnopengl.com/code_viewer.php?code=getting-started/cube_vertices_pos" target="_blank">vertex data</a> right?). -</p> - -<p> - So, filling a vertex buffer object, setting vertex attribute pointers and all that jazz should be familiar for you by now so we won't walk you through those steps. If you still have no idea what's going on with those I suggest you review the <a href="https://learnopengl.com/Getting-started/Hello-Triangle" target="_blank">previous chapters</a>, and work through the exercises if possible, before continuing. -</p> - -<p> - So, the first thing we'll need is a vertex shader to draw the container. The vertex positions of the container remain the same (although we won't be needing texture coordinates this time) so the code should be nothing new. We'll be using a stripped down version of the vertex shader from the last chapters: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; - -uniform mat4 model; -uniform mat4 view; -uniform mat4 projection; - -void main() -{ - gl_Position = projection * view * model * vec4(aPos, 1.0); -} -</code></pre> - -<p> - Make sure to update the vertex data and attribute pointers to match the new vertex shader (if you want, you can actually keep the texture data and attribute pointers active; we're just not using them right now). -</p> - -<p> - Because we're also going to render a light source cube, we want to generate a new VAO specifically for the light source. We could render the light source with the same VAO and then do a few light position transformations on the <var>model</var> matrix, but in the upcoming chapters we'll be changing the vertex data and attribute pointers of the container object quite often and we don't want these changes to propagate to the light source object (we only care about the light cube's vertex positions), so we'll create a new VAO: -</p> - -<pre><code> -unsigned int lightVAO; -<function id='33'>glGenVertexArrays</function>(1, &lightVAO); -<function id='27'>glBindVertexArray</function>(lightVAO); -// we only need to bind to the VBO, the container's VBO's data already contains the data. -<function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, VBO); -// set the vertex attribute -<function id='30'>glVertexAttribPointer</function>(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); -<function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(0); -</code></pre> - -<p> - The code should be relatively straightforward. Now that we created both the container and the light source cube there is one thing left to define and that is the fragment shader for both the container and the light source: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -uniform vec3 objectColor; -uniform vec3 lightColor; - -void main() -{ - FragColor = vec4(lightColor * objectColor, 1.0); -} -</code></pre> - -<p> - The fragment shader accepts both an object color and a light color from a uniform variable. Here we multiply the light's color with the object's (reflected) color like we discussed at the beginning of this chapter. Again, this shader should be easy to understand. Let's set the object's color to the last section's coral color with a white light: -</p> - -<pre><code> -// don't forget to use the corresponding shader program first (to set the uniform) -lightingShader.use(); -lightingShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f); -lightingShader.setVec3("lightColor", 1.0f, 1.0f, 1.0f); -</code></pre> - -<p> - One thing left to note is that when we start to update these <em>lighting shaders</em> in the next chapters, the light source cube would also be affected and this is not what we want. We don't want the light source object's color to be affected the lighting calculations, but rather keep the light source isolated from the rest. We want the light source to have a constant bright color, unaffected by other color changes (this makes it look like the light source cube really is the source of the light). -</p> - -<p> - To accomplish this we need to create a second set of shaders that we'll use to draw the light source cube, thus being safe from any changes to the lighting shaders. The vertex shader is the same as the lighting vertex shader so you can simply copy the source code over. The fragment shader of the light source cube ensures the cube's color remains bright by defining a constant white color on the lamp: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -void main() -{ - FragColor = vec4(1.0); // set all 4 vector values to 1.0 -} -</code></pre> - -<p> - When we want to render, we want to render the container object (or possibly many other objects) using the lighting shader we just defined, and when we want to draw the light source we use the light source's shaders. During the Lighting chapters we'll gradually be updating the lighting shaders to slowly achieve more realistic results. -</p> - - <p> - The main purpose of the light source cube is to show where the light comes from. We usually define a light source's position somewhere in the scene, but this is simply a position that has no visual meaning. To show where the light source actually is we render a cube at the same location of the light source. We render this cube with the light source cube shader to make sure the cube always stays white, regardless of the light conditions of the scene. -</p> - -<p> - So let's declare a global <code>vec3</code> variable that represents the light source's location in world-space coordinates: -</p> - -<pre><code> -glm::vec3 lightPos(1.2f, 1.0f, 2.0f); -</code></pre> - -<p> - We then translate the light source cube to the light source's position and scale it down before rendering it: -</p> - -<pre><code> -model = glm::mat4(1.0f); -model = <function id='55'>glm::translate</function>(model, lightPos); -model = <function id='56'>glm::scale</function>(model, glm::vec3(0.2f)); -</code></pre> - -<p> - The resulting render code for the light source cube should then look something like this: -</p> - -<pre><code> -lightCubeShader.use(); -// set the model, view and projection matrix uniforms -[...] -// draw the light cube object -<function id='27'>glBindVertexArray</function>(lightCubeVAO); -<function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 36); -</code></pre> - -<p> - Injecting all the code fragments at their appropriate locations would then result in a clean OpenGL application properly configured for experimenting with lighting. If everything compiles it should look like this: -</p> - -<img src="/img/lighting/colors_scene.png" class="clean"/> - -<p> - Not really much to look at right now, but I'll promise it'll get more interesting in the upcoming chapters. -</p> - -<p> - If you have difficulties finding out where all the code snippets fit together in the application as a whole, check the source code <a href="/code_viewer_gh.php?code=src/2.lighting/1.colors/colors.cpp" target="_blank">here</a> and carefully work your way through the code/comments. -</p> - -<p> - Now that we have a fair bit of knowledge about colors and created a basic scene for experimenting with lighting we can jump to the <a href="https://learnopengl.com/Lighting/Basic-Lighting" target="_blank">next</a> chapter where the real magic begins. -</p> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Lighting/Light-casters.html b/translation/Lighting/Light-casters.html @@ -1,821 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Light casters</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Light casters</h1> -<h1 id="content-url" style='display:none;'>Lighting/Light-casters</h1> -<p> - All the lighting we've used so far came from a single source that is a single point in space. It gives good results, but in the real world we have several types of light that each act different. A light source that <em>casts</em> light upon objects is called a <def>light caster</def>. In this chapter we'll discuss several different types of light casters. Learning to simulate different light sources is yet another tool in your toolbox to further enrich your environments. -</p> - -<p> - We'll first discuss a directional light, then a point light which is an extension of what we had before, and lastly we'll discuss spotlights. In the <a href="https://learnopengl.com/Lighting/Multiple-lights" target="_blank">next</a> chapter we'll combine several of these different light types into one scene. -</p> - -<h1>Directional Light</h1> -<p> - When a light source is far away the light rays coming from the light source are close to parallel to each other. It looks like all the light rays are coming from the same direction, regardless of where the object and/or the viewer is. When a light source is modeled to be <em>infinitely</em> far away it is called a <def>directional light</def> since all its light rays have the same direction; it is independent of the location of the light source. -</p> - -<p> - A fine example of a directional light source is the sun as we know it. The sun is not infinitely far away from us, but it is so far away that we can perceive it as being infinitely far away in the lighting calculations. All the light rays from the sun are then modeled as parallel light rays as we can see in the following image: -</p> - - <img src="/img/lighting/light_casters_directional.png" class="clean"/> - -<p> - Because all the light rays are parallel it does not matter how each object relates to the light source's position since the light direction remains the same for each object in the scene. Because the light's direction vector stays the same, the lighting calculations will be similar for each object in the scene. - </p> - -<p> - We can model such a directional light by defining a light direction vector instead of a position vector. The shader calculations remain mostly the same except this time we directly use the light's <var>direction</var> vector instead of calculating the <var>lightDir</var> vector using the light's <var>position</var> vector: -</p> - -<pre><code> -struct Light { - // vec3 position; // no longer necessary when using directional lights. - vec3 direction; - - vec3 ambient; - vec3 diffuse; - vec3 specular; -}; -[...] -void main() -{ - vec3 lightDir = normalize(-light.direction); - [...] -} -</code></pre> - - <p> - Note that we first negate the <var>light.direction</var> vector. The lighting calculations we used so far expect the light direction to be a direction from the fragment <strong>towards</strong> the light source, but people generally prefer to specify a directional light as a global direction pointing <strong>from</strong> the light source. Therefore we have to negate the global light direction vector to switch its direction; it's now a direction vector pointing towards the light source. Also, be sure to normalize the vector since it is unwise to assume the input vector to be a unit vector. - </p> - -<p> - The resulting <var>lightDir</var> vector is then used as before in the diffuse and specular computations. -</p> - -<p> - To clearly demonstrate that a directional light has the same effect on multiple objects we revisit the container party scene from the end of the <a href="https://learnopengl.com/Getting-started/Coordinate-Systems" target="_blank">Coordinate systems</a> chapter. In case you missed the party we defined 10 different <a href="/code_viewer.php?code=lighting/light_casters_container_positions" target="_blank">container positions</a> and generated a different model matrix per container where each model matrix contained the appropriate local-to-world transformations: -</p> - -<pre><code> -for(unsigned int i = 0; i < 10; i++) -{ - glm::mat4 model = glm::mat4(1.0f); - model = <function id='55'>glm::translate</function>(model, cubePositions[i]); - float angle = 20.0f * i; - model = <function id='57'>glm::rotate</function>(model, <function id='63'>glm::radians</function>(angle), glm::vec3(1.0f, 0.3f, 0.5f)); - lightingShader.setMat4("model", model); - - <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 36); -} -</code></pre> - -<p> - Also, don't forget to actually specify the direction of the light source (note that we define the direction as a direction <strong>from</strong> the light source; you can quickly see the light's direction is pointing downwards): -</p> - -<pre><code> -lightingShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f); -</code></pre> - -<note> - <p> - We've been passing the light's position and direction vectors as <code>vec3</code>s for a while now, but some people tend to prefer to keep all the vectors defined as <code>vec4</code>. When defining position vectors as a <code>vec4</code> it is important to set the <code>w</code> component to <code>1.0</code> so translation and projections are properly applied. However, when defining a direction vector as a <code>vec4</code> we don't want translations to have an effect (since they just represent directions, nothing more) so then we define the <code>w</code> component to be <code>0.0</code>. - </p> - - <p> - Direction vectors can then be represented as: <code>vec4(-0.2f, -1.0f, -0.3f, 0.0f)</code>. This can also function as an easy check for light types: you could check if the <code>w</code> component is equal to <code>1.0</code> to see that we now have a light's position vector and if <code>w</code> is equal to <code>0.0</code> we have a light's direction vector; so adjust the calculations based on that: - </p> - -<pre><code> -if(lightVector.w == 0.0) // note: be careful for floating point errors - // do directional light calculations -else if(lightVector.w == 1.0) - // do light calculations using the light's position (as in previous chapters) -</code></pre> - - <p> - Fun fact: this is actually how the old OpenGL (fixed-functionality) determined if a light source was a directional light or a positional light source and adjusted its lighting based on that. - </p> -</note> - -<p> - If you'd now compile the application and fly through the scene it looks like there is a sun-like light source casting light on all the objects. Can you see that the diffuse and specular components all react as if there was a light source somewhere in the sky? It'll look something like this: -</p> - - <img src="/img/lighting/light_casters_directional_light.png" class="clean"/> - -<p> - You can find the full source code of the application <a href="/code_viewer_gh.php?code=src/2.lighting/5.1.light_casters_directional/light_casters_directional.cpp" target="_blank">here</a>. -</p> - -<h1>Point lights</h1> -<p> - Directional lights are great for global lights that illuminate the entire scene, but we usually also want several <def>point lights</def> scattered throughout the scene. A point light is a light source with a given position somewhere in a world that illuminates in all directions, where the light rays fade out over distance. Think of light bulbs and torches as light casters that act as a point light. -</p> - - <img src="/img/lighting/light_casters_point.png" class="clean"/> - - <p> - In the earlier chapters we've been working with a simplistic point light. We had a light source at a given position that scatters light in all directions from that given light position. However, the light source we defined simulated light rays that never fade out thus making it look like the light source is extremely strong. In most 3D applications we'd like to simulate a light source that only illuminates an area close to the light source and not the entire scene. -</p> - - <p> - If you'd add the 10 containers to the lighting scene from the previous chapters, you'd notice that the container all the way in the back is lit with the same intensity as the container in front of the light; there is no logic yet that diminishes light over distance. We want the container in the back to only be slightly lit in comparison to the containers close to the light source. - </p> - -<h2>Attenuation</h2> -<p> - To reduce the intensity of light over the distance a light ray travels is generally called <def>attenuation</def>. One way to reduce the light intensity over distance is to simply use a linear equation. Such an equation would linearly reduce the light intensity over the distance thus making sure that objects at a distance are less bright. However, such a linear function tends to look a bit fake. In the real world, lights are generally quite bright standing close by, but the brightness of a light source diminishes quickly at a distance; the remaining light intensity then slowly diminishes over distance. We are thus in need of a different equation for reducing the light's intensity. -</p> - - <p> - Luckily some smart people already figured this out for us. The following formula calculates an attenuation value based on a fragment's distance to the light source which we later multiply with the light's intensity vector: - </p> - - \begin{equation} F_{att} = \frac{1.0}{K_c + K_l * d + K_q * d^2} \end{equation} - - <p> - Here \(d\) represents the distance from the fragment to the light source. Then to calculate the attenuation value we define 3 (configurable) terms: a <def>constant</def> term \(K_c\), a <def>linear</def> term \(K_l\) and a <def>quadratic</def> term \(K_q\). - <ul> - <li>The constant term is usually kept at <code>1.0</code> which is mainly there to make sure the denominator never gets smaller than <code>1</code> since it would otherwise boost the intensity with certain distances, which is not the effect we're looking for.</li> - <li>The linear term is multiplied with the distance value that reduces the intensity in a linear fashion.</li> - <li>The quadratic term is multiplied with the quadrant of the distance and sets a quadratic decrease of intensity for the light source. The quadratic term will be less significant compared to the linear term when the distance is small, but gets much larger as the distance grows. </li> - </ul> - Due to the quadratic term the light will diminish mostly at a linear fashion until the distance becomes large enough for the quadratic term to surpass the linear term and then the light intensity will decrease a lot faster. The resulting effect is that the light is quite intense when at a close range, but quickly loses its brightness over distance until it eventually loses its brightness at a more slower pace. The following graph shows the effect such an attenuation has over a distance of <code>100</code>: - </p> - - <img src="/img/lighting/attenuation.png" class="clean"/> - - <p> - You can see that the light has the highest intensity when the distance is small, but as soon as the distance grows its intensity is significantly reduced and slowly reaches <code>0</code> intensity at around a distance of <code>100</code>. This is exactly what we want. - </p> - - <h3>Choosing the right values</h3> - <p> - But at what values do we set those 3 terms? Setting the right values depend on many factors: the environment, the distance you want a light to cover, the type of light etc. In most cases, it simply is a question of experience and a moderate amount of tweaking. The following table shows some of the values these terms could take to simulate a realistic (sort of) light source that covers a specific radius (distance). The first column specifies the distance a light will cover with the given terms. These values are good starting points for most lights, with courtesy of <a href="http://www.ogre3d.org/tikiwiki/tiki-index.php?page=-Point+Light+Attenuation" target="_blank">Ogre3D's wiki</a>: - </p> - -<table> - <tr> - <th>Distance</th> - <th>Constant</th> - <th>Linear</th> - <th>Quadratic</th> - </tr> - <tr> - <td><code>7</code></td> - <td><code>1.0</code></td> - <td><code>0.7</code></td> - <td><code>1.8</code></td> - </tr> - <tr> - <td><code>13</code></td> - <td><code>1.0</code></td> - <td><code>0.35</code></td> - <td><code>0.44</code></td> - </tr> - <tr> - <td><code>20</code></td> - <td><code>1.0</code></td> - <td><code>0.22</code></td> - <td><code>0.20</code></td> - </tr> - <tr> - <td><code>32</code></td> - <td><code>1.0</code></td> - <td><code>0.14</code></td> - <td><code>0.07</code></td> - </tr><tr> - <td><code>50</code></td> - <td><code>1.0</code></td> - <td><code>0.09</code></td> - <td><code>0.032</code></td> - </tr> - <tr> - <td><code>65</code></td> - <td><code>1.0</code></td> - <td><code>0.07</code></td> - <td><code>0.017</code></td> - </tr><tr> - <td><code>100</code></td> - <td><code>1.0</code></td> - <td><code>0.045</code></td> - <td><code>0.0075</code></td> - </tr><tr> - <td><code>160</code></td> - <td><code>1.0</code></td> - <td><code>0.027</code></td> - <td><code>0.0028</code></td> - </tr> - <tr> - <td><code>200</code></td> - <td><code>1.0</code></td> - <td><code>0.022</code></td> - <td><code>0.0019</code></td> - </tr><tr> - <td><code>325</code></td> - <td><code>1.0</code></td> - <td><code>0.014</code></td> - <td><code>0.0007</code></td> - </tr> - <tr> - <td><code>600</code></td> - <td><code>1.0</code></td> - <td><code>0.007</code></td> - <td><code>0.0002</code></td> - </tr> - <tr> - <td><code>3250</code></td> - <td><code>1.0</code></td> - <td><code>0.0014</code></td> - <td><code>0.000007</code></td> - </tr> -</table> - -<p> - As you can see, the constant term \(K_c\) is kept at <code>1.0</code> in all cases. The linear term \(K_l\) is usually quite small to cover larger distances and the quadratic term \(K_q\) is even smaller. Try to experiment a bit with these values to see their effect in your implementation. In our environment a distance of <code>32</code> to <code>100</code> is generally enough for most lights. -</p> - -<h3>Implementing attenuation</h3> -<p> - To implement attenuation we'll be needing 3 extra values in the fragment shader: namely the constant, linear and quadratic terms of the equation. These are best stored in the <fun>Light</fun> struct we defined earlier. Note that we need to calculate <var>lightDir</var> again using <var>position</var> as this is a point light (as we did in the previous chapter) and not a directional light. -</p> - -<pre><code> -struct Light { - vec3 position; - - vec3 ambient; - vec3 diffuse; - vec3 specular; - - float constant; - float linear; - float quadratic; -}; -</code></pre> - -<p> - Then we set the terms in our application: we want the light to cover a distance of <code>50</code> so we'll use the appropriate constant, linear and quadratic terms from the table: -</p> - -<pre><code> -lightingShader.setFloat("light.constant", 1.0f); -lightingShader.setFloat("light.linear", 0.09f); -lightingShader.setFloat("light.quadratic", 0.032f); -</code></pre> - - <p> - Implementing attenuation in the fragment shader is relatively straightforward: we simply calculate an attenuation value based on the equation and multiply this with the ambient, diffuse and specular components. - </p> - -<p> - We do need the distance to the light source for the equation to work though. Remember how we can calculate the length of a vector? We can retrieve the distance term by calculating the difference vector between the fragment and the light source and take that resulting vector's length. We can use GLSL's built-in <fun>length</fun> function for that purpose: - </p> - -<pre><code> -float distance = length(light.position - FragPos); -float attenuation = 1.0 / (light.constant + light.linear * distance + - light.quadratic * (distance * distance)); -</code></pre> - -<p> - Then we include this attenuation value in the lighting calculations by multiplying the attenuation value with the ambient, diffuse and specular colors. - </p> - - <note> - We could leave the ambient component alone so ambient lighting is not decreased over distance, but if we were to use more than 1 light source all the ambient components will start to stack up. In that case we want to attenuate ambient lighting as well. Simply play around with what's best for your environment. - </note> - -<pre><code> -ambient *= attenuation; -diffuse *= attenuation; -specular *= attenuation; -</code></pre> - -<p> - If you'd run the application you'd get something like this: -</p> - - <img src="/img/lighting/light_casters_point_light.png" class="clean"/> - - <p> - You can see that right now only the front containers are lit with the closest container being the brightest. The containers in the back are not lit at all since they're too far from the light source. You can find the source code of the application <a href="/code_viewer_gh.php?code=src/2.lighting/5.2.light_casters_point/light_casters_point.cpp" target="_blank">here</a>. - </p> - - <p> - A point light is thus a light source with a configurable location and attenuation applied to its lighting calculations. Yet another type of light for our lighting arsenal. - </p> - -<h1>Spotlight</h1> -<p> - The last type of light we're going to discuss is a <def>spotlight</def>. A spotlight is a light source that is located somewhere in the environment that, instead of shooting light rays in all directions, only shoots them in a specific direction. The result is that only the objects within a certain radius of the spotlight's direction are lit and everything else stays dark. A good example of a spotlight would be a street lamp or a flashlight. -</p> - -<p> - A spotlight in OpenGL is represented by a world-space position, a direction and a <def>cutoff</def> angle that specifies the radius of the spotlight. For each fragment we calculate if the fragment is between the spotlight's cutoff directions (thus in its cone) and if so, we lit the fragment accordingly. The following image gives you an idea of how a spotlight works: -</p> - - <img src="/img/lighting/light_casters_spotlight_angles.png" class="clean"/> - -<p> - <ul> - <li><code>LightDir</code>: the vector pointing from the fragment to the light source.</li> - <li><code>SpotDir</code>: the direction the spotlight is aiming at.</li> - <li><code>Phi</code> \(\phi\): the cutoff angle that specifies the spotlight's radius. Everything outside this angle is not lit by the spotlight.</li> - <li><code>Theta</code> \(\theta\): the angle between the <var>LightDir</var> vector and the <var>SpotDir</var> vector. The \(\theta\) value should be smaller than \(\Phi\) to be inside the spotlight. </li> - </ul> -</p> - -<p> - So what we basically need to do, is calculate the dot product (returns the cosine of the angle between two unit vectors) between the <var>LightDir</var> vector and the <var>SpotDir</var> vector and compare this with the cutoff angle \(\phi\). Now that you (sort of) understand what a spotlight is all about we're going to create one in the form of a flashlight. -</p> - -<h2>Flashlight</h2> -<p> - A flashlight is a spotlight located at the viewer's position and usually aimed straight ahead from the player's perspective. A flashlight is basically a normal spotlight, but with its position and direction continually updated based on the player's position and orientation. -</p> - -<p> - So, the values we're going to need for the fragment shader are the spotlight's position vector (to calculate the fragment-to-light's direction vector), the spotlight's direction vector and the cutoff angle. We can store these values in the <fun>Light</fun> struct: -</p> - -<pre><code> -struct Light { - vec3 position; - vec3 direction; - float cutOff; - ... -}; -</code></pre> - -<p> - Next we pass the appropriate values to the shader: -</p> - -<pre><code> -lightingShader.setVec3("light.position", camera.Position); -lightingShader.setVec3("light.direction", camera.Front); -lightingShader.setFloat("light.cutOff", glm::cos(<function id='63'>glm::radians</function>(12.5f))); -</code></pre> - -<p> - As you can see we're not setting an angle for the cutoff value but calculate the cosine value based on an angle and pass the cosine result to the fragment shader. The reason for this is that in the fragment shader we're calculating the dot product between the <code>LightDir</code> and the <code>SpotDir</code> vector and the dot product returns a cosine value and not an angle; and we can't directly compare an angle with a cosine value. To get the angle in the shader we then have to calculate the inverse cosine of the dot product's result which is an expensive operation. So to save some performance we calculate the cosine value of a given cutoff angle beforehand and pass this result to the fragment shader. Since both angles are now represented as cosines, we can directly compare between them without expensive operations. -</p> - -<p> - Now what's left to do is calculate the theta \(\theta\) value and compare this with the cutoff \(\phi\) value to determine if we're in or outside the spotlight: -</p> - -<pre><code> -float theta = dot(lightDir, normalize(-light.direction)); - -if(theta > light.cutOff) -{ - // do lighting calculations -} -else // else, use ambient light so scene isn't completely dark outside the spotlight. - color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0); -</code></pre> - -<p> - We first calculate the dot product between the <var>lightDir</var> vector and the negated <var>direction</var> vector (negated, because we want the vectors to point towards the light source, instead of from). Be sure to normalize all the relevant vectors. -</p> - -<note> - <p> - You may be wondering why there is a <code>></code> sign instead of a <code><</code> sign in the <code>if</code> guard. Shouldn't <var>theta</var> be smaller than the light's cutoff value to be inside the spotlight? That is right, but don't forget angle values are represented as cosine values and an angle of <code>0</code> degrees is represented as the cosine value of <code>1.0</code> while an angle of <code>90</code> degrees is represented as the cosine value of <code>0.0</code> as you can see here: - </p> - - <img src="/img/lighting/light_casters_cos.png"/> - - <p> - You can now see that the closer the cosine value is to <code>1.0</code> the smaller its angle. Now it makes sense why <var>theta</var> needs to be larger than the cutoff value. The cutoff value is currently set at the cosine of <code>12.5</code> which is equal to <code>0.976</code> so a cosine <var>theta</var> value between <code>0.976</code> and <code>1.0</code> would result in the fragment being lit as if inside the spotlight. - </p> -</note> - - <p> - Running the application results in a spotlight that only lights the fragments that are directly inside the cone of the spotlight. It'll look something like this: - </p> - - <img src="/img/lighting/light_casters_spotlight_hard.png" class="clean"/> - - <p> - You can find the full source code <a href="/code_viewer_gh.php?code=src/2.lighting/5.3.light_casters_spot/light_casters_spot.cpp" target="_blank">here</a>. - </p> - - <p> - It still looks a bit fake though, mostly because the spotlight has hard edges. Wherever a fragment reaches the edge of the spotlight's cone it is shut down completely instead of with a nice smooth fade. A realistic spotlight would reduce the light gradually around its edges. - </p> - -<h2>Smooth/Soft edges</h2> -<p> - To create the effect of a smoothly-edged spotlight we want to simulate a spotlight having an <def>inner</def> and an <def>outer</def> cone. We can set the inner cone as the cone defined in the previous section, but we also want an outer cone that gradually dims the light from the inner to the edges of the outer cone. -</p> - -<p> - To create an outer cone we simply define another cosine value that represents the angle between the spotlight's direction vector and the outer cone's vector (equal to its radius). Then, if a fragment is between the inner and the outer cone it should calculate an intensity value between <code>0.0</code> and <code>1.0</code>. If the fragment is inside the inner cone its intensity is equal to <code>1.0</code> and <code>0.0</code> if the fragment is outside the outer cone. -</p> - -<p> - We can calculate such a value using the following equation: - - \begin{equation} I = \frac{\theta - \gamma}{\epsilon} \end{equation} - - Here \(\epsilon\) (epsilon) is the cosine difference between the inner (\(\phi\)) and the outer cone (\(\gamma\)) (\(\epsilon = \phi - \gamma\)). The resulting \(I\) value is then the intensity of the spotlight at the current fragment. - </p> - - <p> - It is a bit hard to visualize how this formula actually works so let's try it out with a few sample values: -</p> - - -<table> - <tr> - <th>\(\theta\)</th> - <th>\(\theta\) in degrees</th> - <th>\(\phi\) (inner cutoff)</th> - <th>\(\phi\) in degrees</th> - <th>\(\gamma\) (outer cutoff)</th> - <th>\(\gamma\) in degrees</th> - <th>\(\epsilon\)</th> - <th>\(I\)</th> - </tr> - <tr> - <td><code>0.87</code></td> - <td><code>30</code></td> - <td><code>0.91</code></td> - <td><code>25</code></td> - <td><code>0.82</code></td> - <td><code>35</code></td> - <td><code>0.91 - 0.82 = 0.09</code></td> - <td><code>0.87 - 0.82 / 0.09 = 0.56</code></td> - </tr> - <tr> - <td><code>0.9</code></td> - <td><code>26</code></td> - <td><code>0.91</code></td> - <td><code>25</code></td> - <td><code>0.82</code></td> - <td><code>35</code></td> - <td><code>0.91 - 0.82 = 0.09</code></td> - <td><code>0.9 - 0.82 / 0.09 = 0.89</code></td> - </tr> - <tr> - <td><code>0.97</code></td> - <td><code>14</code></td> - <td><code>0.91</code></td> - <td><code>25</code></td> - <td><code>0.82</code></td> - <td><code>35</code></td> - <td><code>0.91 - 0.82 = 0.09</code></td> - <td><code>0.97 - 0.82 / 0.09 = 1.67</code></td> - </tr> - <tr> - <td><code>0.83</code></td> - <td><code>34</code></td> - <td><code>0.91</code></td> - <td><code>25</code></td> - <td><code>0.82</code></td> - <td><code>35</code></td> - <td><code>0.91 - 0.82 = 0.09</code></td> - <td><code>0.83 - 0.82 / 0.09 = 0.11</code></td> - </tr> - <tr> - <td><code>0.64</code></td> - <td><code>50</code></td> - <td><code>0.91</code></td> - <td><code>25</code></td> - <td><code>0.82</code></td> - <td><code>35</code></td> - <td><code>0.91 - 0.82 = 0.09</code></td> - <td><code>0.64 - 0.82 / 0.09 = -2.0</code></td> - </tr> - <tr> - <td><code>0.966</code></td> - <td><code>15</code></td> - <td><code>0.9978</code></td> - <td><code>12.5</code></td> - <td><code>0.953</code></td> - <td><code>17.5</code></td> - <td><code>0.9978 - 0.953 = 0.0448</code></td> - <td><code>0.966 - 0.953 / 0.0448 = 0.29</code></td> - </tr> -</table> - -<p> - As you can see we're basically interpolating between the outer cosine and the inner cosine based on the \(\theta\) value. If you still don't really see what's going on, don't worry, you can simply take the formula for granted and return here when you're much older and wiser. -</p> - -<p> - We now have an intensity value that is either negative when outside the spotlight, higher than <code>1.0</code> when inside the inner cone, and somewhere in between around the edges. If we properly clamp the values we don't need an <code>if-else</code> in the fragment shader anymore and we can simply multiply the light components with the calculated intensity value: -</p> - -<pre><code> -float theta = dot(lightDir, normalize(-light.direction)); -float epsilon = light.cutOff - light.outerCutOff; -float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0); -... -// we'll leave ambient unaffected so we always have a little light. -diffuse *= intensity; -specular *= intensity; -... -</code></pre> - -<p> - Note that we use the <fun>clamp</fun> function that <def>clamps</def> its first argument between the values <code>0.0</code> and <code>1.0</code>. This makes sure the intensity values won't end up outside the [<code>0</code>, <code>1</code>] range. -</p> - -<p> - Make sure you add the <var>outerCutOff</var> value to the <fun>Light</fun> struct and set its uniform value in the application. For the following image an inner cutoff angle of <code>12.5</code> and an outer cutoff angle of <code>17.5</code> was used: -</p> - - <img src="/img/lighting/light_casters_spotlight.png" class="clean"/> - - <p> - Ahhh, that's much better. Play around with the inner and outer cutoff angles and try to create a spotlight that better suits your needs. You can find the source code of the application <a href="/code_viewer_gh.php?code=src/2.lighting/5.4.light_casters_spot_soft/light_casters_spot_soft.cpp" target="_blank">here</a>. - </p> - -<p> - Such a flashlight/spotlight type of lamp is perfect for horror games and combined with directional and point lights the environment will really start to light up. -</p> - -<h2>Exercises</h2> - <ul> - <li>Try experimenting with all the different light types and their fragment shaders. Try inverting some vectors and/or use < instead of >. Try to explain the different visual outcomes.</li> - </ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Lighting/Lighting-maps.html b/translation/Lighting/Lighting-maps.html @@ -1,484 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Lighting maps</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Lighting maps</h1> -<h1 id="content-url" style='display:none;'>Lighting/Lighting-maps</h1> -<p> - In the <a href="https://learnopengl.com/Lighting/Materials" target="_blank">previous</a> chapter we discussed the possibility of each object having a unique material of its own that reacts differently to light. This is great for giving each object a unique look in comparison to other objects, but still doesn't offer much flexibility on the visual output of an object. -</p> - -<p> - In the previous chapter we defined a material for an entire object as a whole. Objects in the real world however usually do not consist of a single material, but of several materials. Think of a car: its exterior consists of a shiny fabric, it has windows that partly reflect the surrounding environment, its tires are all but shiny so they don't have specular highlights and it has rims that are super shiny (if you actually washed your car alright). The car also has diffuse and ambient colors that are not the same for the entire object; a car displays many different ambient/diffuse colors. All by all, such an object has different material properties for each of its different parts. -</p> - -<p> - So the material system in the previous chapter isn't sufficient for all but the simplest models so we need to extend the system by introducing <em>diffuse</em> and <em>specular</em> maps. These allow us to influence the diffuse (and indirectly the ambient component since they should be the same anyways) and the specular component of an object with much more precision. -</p> - -<h1>Diffuse maps</h1> -<p> - What we want is some way to set the diffuse colors of an object for each individual fragment. Some sort of system where we can retrieve a color value based on the fragment's position on the object? -</p> - -<p> - This should probably all sound familiar and we've been using such a system for a while now. This sounds just like <em>textures</em> we've extensively discussed in one of the <a href="https://learnopengl.com/Getting-started/Textures" target="_blank">earlier</a> chapters and it basically is just that: a texture. We're just using a different name for the same underlying principle: using an image wrapped around an object that we can index for unique color values per fragment. In lit scenes this is usually called a <def>diffuse map</def> (this is generally how 3D artists call them before PBR) since a texture image represents all of the object's diffuse colors. -</p> - -<p> - To demonstrate diffuse maps we're going to use the <a href="/img/textures/container2.png" target="_blank">following image</a> of a wooden container with a steel border: -</p> - -<img src="/img/textures/container2.png" style="width:300px; height:300px"/> - -<p> - Using a diffuse map in shaders is exactly like we showed in the texture chapter. This time however we store the texture as a <code>sampler2D</code> inside the <fun>Material</fun> struct. We replace the earlier defined <code>vec3</code> diffuse color vector with the diffuse map. -</p> - -<warning>Keep in mind that <code>sampler2D</code> is a so called <def>opaque type</def> which means we can't instantiate these types, but only define them as uniforms. If the struct would be instantiated other than as a uniform (like a function parameter) GLSL could throw strange errors; the same thus applies to any struct holding such opaque types. -</warning> - -<p> - We also remove the ambient material color vector since the ambient color is equal to the diffuse color anyways now that we control ambient with the light. So there's no need to store it separately: -</p> - -<pre><code> -struct Material { - sampler2D diffuse; - vec3 specular; - float shininess; -}; -... -in vec2 TexCoords; -</code></pre> - -<note> - If you're a bit stubborn and still want to set the ambient colors to a different value (other than the diffuse value) you can keep the ambient <code>vec3</code>, but then the ambient colors would still remain the same for the entire object. To get different ambient values for each fragment you'd have to use another texture for ambient values alone. -</note> - -<p> - Note that we are going to need texture coordinates again in the fragment shader, so we declared an extra input variable. Then we simply sample from the texture to retrieve the fragment's diffuse color value: -</p> - -<pre><code> -vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); -</code></pre> - -<p> - Also, don't forget to set the ambient material's color equal to the diffuse material's color as well: -</p> - -<pre><code> -vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); -</code></pre> - -<p> - And that's all it takes to use a diffuse map. As you can see it is nothing new, but it does provide a dramatic increase in visual quality. To get it working we do need to update the vertex data with texture coordinates, transfer them as vertex attributes to the fragment shader, load the texture, and bind the texture to the appropriate texture unit. -</p> - -<p> - The updated vertex data can be found <a href="/code_viewer.php?code=lighting/vertex_data_textures" target="_blank">here</a>. The vertex data now includes vertex positions, normal vectors, and texture coordinates for each of the cube's vertices. Let's update the vertex shader to accept texture coordinates as a vertex attribute and forward them to the fragment shader: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec3 aNormal; -layout (location = 2) in vec2 aTexCoords; -... -out vec2 TexCoords; - -void main() -{ - ... - TexCoords = aTexCoords; -} -</code></pre> - -<p> - Be sure to update the vertex attribute pointers of both VAOs to match the new vertex data and load the container image as a texture. Before rendering the cube we want to assign the right texture unit to the <var>material.diffuse</var> uniform sampler and bind the container texture to this texture unit: -</p> - -<pre><code> -lightingShader.setInt("material.diffuse", 0); -... -<function id='49'>glActiveTexture</function>(GL_TEXTURE0); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, diffuseMap); -</code></pre> - -<p> - Now using a diffuse map we get an enormous boost in detail again and this time the container really starts to shine (quite literally). Your container now probably looks something like this: -</p> - -<img src="/img/lighting/materials_diffuse_map.png" class="clean"/> - -<p> - You can find the full source code of the application <a href="/code_viewer_gh.php?code=src/2.lighting/4.1.lighting_maps_diffuse_map/lighting_maps_diffuse.cpp" target="_blank">here</a>. -</p> - -<h1>Specular maps</h1> -<p> - You probably noticed that the specular highlight looks a bit odd since the object is a container that mostly consists of wood and wood doesn't have specular highlights like that. We can fix this by setting the specular material of the object to <code>vec3(0.0)</code> but that would mean that the steel borders of the container would stop showing specular highlights as well and steel <strong>should</strong> show specular highlights. We would like to control what parts of the object should show a specular highlight with varying intensity. This is a problem that sounds familiar. Coincidence? I think not. -</p> - -<p> - We can also use a texture map just for specular highlights. This means we need to generate a black and white (or colors if you feel like it) texture that defines the specular intensities of each part of the object. An example of a <a href="/img/textures/container2_specular.png" target="_blank">specular map</a> is the following image: -</p> - -<img src="/img/textures/container2_specular.png" style="width:300px; height:300px"/> - -<p> - The intensity of the specular highlight comes from the brightness of each pixel in the image. Each pixel of the specular map can be displayed as a color vector where black represents the color vector <code>vec3(0.0)</code> and gray the color vector <code>vec3(0.5)</code> for example. In the fragment shader we then sample the corresponding color value and multiply this value with the light's specular intensity. The more 'white' a pixel is, the higher the result of the multiplication and thus the brighter the specular component of an object becomes. -</p> - -<p> - Because the container mostly consists of wood, and wood as a material should have no specular highlights, the entire wooden section of the diffuse texture was converted to black: black sections do not have any specular highlight. The steel border of the container has varying specular intensities with the steel itself being relatively susceptible to specular highlights while the cracks are not. -</p> - -<note> - Technically wood also has specular highlights although with a much lower shininess value (more light scattering) and less impact, but for learning purposes we can just pretend wood doesn't have any reaction to specular light. -</note> - -<p> -Using tools like <em>Photoshop</em> or <em>Gimp</em> it is relatively easy to transform a diffuse texture to a specular image like this by cutting out some parts, transforming it to black and white and increasing the brightness/contrast. -</p> - -<h2>Sampling specular maps</h2> -<p> - A specular map is just like any other texture so the code is similar to the diffuse map code. Make sure to properly load the image and generate a texture object. Since we're using another texture sampler in the same fragment shader we have to use a different texture unit (see <a href="https://learnopengl.com/Getting-started/Textures" target="_blank">Textures</a>) for the specular map so let's bind it to the appropriate texture unit before rendering: -</p> - -<pre><code> -lightingShader.setInt("material.specular", 1); -... -<function id='49'>glActiveTexture</function>(GL_TEXTURE1); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, specularMap); -</code></pre> - -<p> - Then update the material properties of the fragment shader to accept a <code>sampler2D</code> as its specular component instead of a <code>vec3</code>: -</p> - -<pre><code> -struct Material { - sampler2D diffuse; - sampler2D specular; - float shininess; -}; -</code></pre> - -<p> - And lastly we want to sample the specular map to retrieve the fragment's corresponding specular intensity: -</p> - -<pre><code> -vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); -vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); -vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); -FragColor = vec4(ambient + diffuse + specular, 1.0); -</code></pre> - -<p> - By using a specular map we can specify with enormous detail what parts of an object have <em>shiny</em> properties and we can even control the corresponding intensity. Specular maps give us an added layer of control over lighting on top of the diffuse map. -</p> - -<note> - If you don't want to be too mainstream you could also use actual colors in the specular map to not only set the specular intensity of each fragment, but also the color of the specular highlight. Realistically however, the color of the specular highlight is mostly determined by the light source itself so it wouldn't generate realistic visuals (that's why the images are usually black and white: we only care about the intensity). -</note> - -<p> - If you would now run the application you can clearly see that the container's material now closely resembles that of an actual wooden container with steel frames: -</p> - -<img src="/img/lighting/materials_specular_map.png" class="clean"/> - -<p> - You can find the full source code of the application <a href="/code_viewer_gh.php?code=src/2.lighting/4.2.lighting_maps_specular_map/lighting_maps_specular.cpp" target="_blank">here</a>. -</p> - -<p> - Using diffuse and specular maps we can really add an enormous amount of detail into relatively simple objects. We can even add more detail into the objects using other texture maps like <def>normal/bump maps</def> and/or <def>reflection maps</def>, but that is something we'll reserve for later chapters. Show your container to all your friends and family and be content with the fact that our container can one day become even prettier than it already is! -</p> - -<h2>Exercises</h2> -<p> - <ul> - <li>Fool around with the light source's ambient, diffuse and specular vectors and see how they affect the visual output of the container.</li> - <li>Try inverting the color values of the specular map in the fragment shader so that the wood shows specular highlights and the steel borders do not (note that due to the cracks in the steel border the borders still show some specular highlight, although with less intensity): <a href="/code_viewer_gh.php?code=src/2.lighting/4.3.lighting_maps_exercise2/lighting_maps_exercise2.cpp" target="_blank">solution</a>.</li> - <li>Try creating a specular map from the diffuse texture that uses actual colors instead of black and white and see that the result doesn't look too realistic. You can use this <a href="/img/lighting/lighting_maps_specular_color.png" target="_blank">colored specular map</a> if you can't generate one yourself: <a href="/img/lighting/lighting_maps_exercise3.png" target="_blank">result</a>.</li> - <li> - Also add something they call an <def>emission map</def> which is a texture that stores emission values per fragment. Emission values are colors an object may <em>emit</em> as if it contains a light source itself; this way an object can glow regardless of the light conditions. Emission maps are often what you see when objects in a game glow (like <a href="/img/lighting/lighting_maps_eyes_robot.jpg" target="_blank">eyes of a robot</a>, or <a href="/img/lighting/lighting_maps_strips_container.png" target="_blank">light strips on a container</a>). Add the <a href="/img/textures/matrix.jpg" target="_blank">following</a> texture (by creativesam) as an emission map onto the container as if the letters emit light: <a href="/code_viewer_gh.php?code=src/2.lighting/4.4.lighting_maps_exercise4/lighting_maps_exercise4.cpp" target="_blank">solution</a>; <a href="/img/lighting/lighting_maps_exercise4.png" target="_blank">result</a>.</li> - </ul> -</p> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Lighting/Materials.html b/translation/Lighting/Materials.html @@ -1,493 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Materials</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Materials</h1> -<h1 id="content-url" style='display:none;'>Lighting/Materials</h1> -<p> - In the real world, each object has a different reaction to light. Steel objects are often shinier than a clay vase for example and a wooden container doesn't react the same to light as a steel container. Some objects reflect the light without much scattering resulting in small specular highlights and others scatter a lot giving the highlight a larger radius. If we want to simulate several types of objects in OpenGL we have to define <def>material</def> properties specific to each surface. -</p> - -<p> - In the previous chapter we defined an object and light color to define the visual output of the object, combined with an ambient and specular intensity component. When describing a surface we can define a material color for each of the 3 lighting components: ambient, diffuse and specular lighting. By specifying a color for each of the components we have fine-grained control over the color output of the surface. Now add a shininess component to those 3 colors and we have all the material properties we need: -</p> - -<pre><code> -#version 330 core -struct Material { - vec3 ambient; - vec3 diffuse; - vec3 specular; - float shininess; -}; - -uniform Material material; -</code></pre> - -<p> - In the fragment shader we create a <code>struct</code> to store the material properties of the surface. We can also store them as individual uniform values, but storing them as a struct keeps it more organized. We first define the layout of the struct and then simply declare a uniform variable with the newly created struct as its type. -</p> - -<p> - As you can see, we define a color vector for each of the Phong lighting's components. The <var>ambient</var> material vector defines what color the surface reflects under ambient lighting; this is usually the same as the surface's color. The <var>diffuse</var> material vector defines the color of the surface under diffuse lighting. The diffuse color is (just like ambient lighting) set to the desired surface's color. The <var>specular</var> material vector sets the color of the specular highlight on the surface (or possibly even reflect a surface-specific color). Lastly, the <var>shininess</var> impacts the scattering/radius of the specular highlight. -</p> - -<p> - With these 4 components that define an object's material we can simulate many real-world materials. A table as found at <a href="http://devernay.free.fr/cours/opengl/materials.html" target="_blank">devernay.free.fr</a> shows a list of material properties that simulate real materials found in the outside world. The following image shows the effect several of these real world material values have on our cube: -</p> - -<img src="/img/lighting/materials_real_world.png"/> - -<p> - As you can see, by correctly specifying the material properties of a surface it seems to change the perception we have of the object. The effects are clearly noticeable, but for the more realistic results we'll need to replace the cube with something more complicated. In the <a href="https://learnopengl.com/Model-Loading/Assimp" target="_blank">Model Loading</a> chapters we'll discuss more complicated shapes. -</p> - -<p> - Figuring out the right material settings for an object is a difficult feat that mostly requires experimentation and a lot of experience. It's not that uncommon to completely destroy the visual quality of an object by a misplaced material. -</p> - -<p> - Let's try implementing such a material system in the shaders. -</p> - -<h1>Setting materials</h1> -<p> - We created a uniform material struct in the fragment shader so next we want to change the lighting calculations to comply with the new material properties. Since all the material variables are stored in a struct we can access them from the <var>material</var> uniform: -</p> - -<pre><code> -void main() -{ - // ambient - vec3 ambient = lightColor * material.ambient; - - // diffuse - vec3 norm = normalize(Normal); - vec3 lightDir = normalize(lightPos - FragPos); - float diff = max(dot(norm, lightDir), 0.0); - vec3 diffuse = lightColor * (diff * material.diffuse); - - // specular - vec3 viewDir = normalize(viewPos - FragPos); - vec3 reflectDir = reflect(-lightDir, norm); - float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); - vec3 specular = lightColor * (spec * material.specular); - - vec3 result = ambient + diffuse + specular; - FragColor = vec4(result, 1.0); -} -</code></pre> - -<p> - As you can see we now access all of the material struct's properties wherever we need them and this time calculate the resulting output color with the help of the material's colors. Each of the object's material attributes are multiplied with their respective lighting components. -</p> - -<p> - We can set the material of the object in the application by setting the appropriate uniforms. A struct in GLSL however is not special in any regard when setting uniforms; a struct only really acts as a namespace of uniform variables. If we want to fill the struct we will have to set the individual uniforms, but prefixed with the struct's name: -</p> - -<pre><code> -lightingShader.setVec3("material.ambient", 1.0f, 0.5f, 0.31f); -lightingShader.setVec3("material.diffuse", 1.0f, 0.5f, 0.31f); -lightingShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f); -lightingShader.setFloat("material.shininess", 32.0f); -</code></pre> - -<p> - We set the ambient and diffuse component to the color we'd like the object to have and set the specular component of the object to a medium-bright color; we don't want the specular component to be too strong. We also keep the shininess at <code>32</code>. -</p> - -<p> - We can now easily influence the object's material from the application. Running the program gives you something like this: -</p> - -<img src="/img/lighting/materials_with_material.png" class="clean"/> - -<p> - It doesn't really look right though? -</p> - -<h2>Light properties</h2> -<p> - The object is way too bright. The reason for the object being too bright is that the ambient, diffuse and specular colors are reflected with full force from any light source. Light sources also have different intensities for their ambient, diffuse and specular components respectively. In the previous chapter we solved this by varying the ambient and specular intensities with a strength value. We want to do something similar, but this time by specifying intensity vectors for each of the lighting components. If we'd visualize <var>lightColor</var> as <code>vec3(1.0)</code> the code would look like this: -</p> - -<pre><code> -vec3 ambient = vec3(1.0) * material.ambient; -vec3 diffuse = vec3(1.0) * (diff * material.diffuse); -vec3 specular = vec3(1.0) * (spec * material.specular); -</code></pre> - -<p> - So each material property of the object is returned with full intensity for each of the light's components. These <code>vec3(1.0)</code> values can be influenced individually as well for each light source and this is usually what we want. Right now the ambient component of the object is fully influencing the color of the cube. The ambient component shouldn't really have such a big impact on the final color so we can restrict the ambient color by setting the light's ambient intensity to a lower value: -</p> - -<pre><code> -vec3 ambient = vec3(0.1) * material.ambient; -</code></pre> - -<p> - We can influence the diffuse and specular intensity of the light source in the same way. This is closely similar to what we did in the <a href="https://learnopengl.com/Lighting/Basic-Lighting" target="_blank">previous</a> chapter; you could say we already created some light properties to influence each lighting component individually. We'll want to create something similar to the material struct for the light properties: -</p> - -<pre><code> -struct Light { - vec3 position; - - vec3 ambient; - vec3 diffuse; - vec3 specular; -}; - -uniform Light light; -</code></pre> - -<p> - A light source has a different intensity for its <var>ambient</var>, <var>diffuse</var> and <var>specular</var> components. The ambient light is usually set to a low intensity because we don't want the ambient color to be too dominant. The diffuse component of a light source is usually set to the exact color we'd like a light to have; often a bright white color. The specular component is usually kept at <code>vec3(1.0)</code> shining at full intensity. Note that we also added the light's position vector to the struct. - -<p> - Just like with the material uniform we need to update the fragment shader: -</p> - -<pre><code> -vec3 ambient = light.ambient * material.ambient; -vec3 diffuse = light.diffuse * (diff * material.diffuse); -vec3 specular = light.specular * (spec * material.specular); -</code></pre> - -<p> - We then want to set the light intensities in the application: -</p> - -<pre><code> -lightingShader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f); -lightingShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f); // darken diffuse light a bit -lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f); -</code></pre> - -<p> - Now that we modulated how the light influences the object's material we get a visual output that looks much like the output from the previous chapter. This time however we got full control over the lighting and the material of the object: -</p> - -<img src="/img/lighting/materials_light.png" class="clean"/> - -<p> - Changing the visual aspects of objects is relatively easy right now. Let's spice things up a bit! -</p> - -<h2>Different light colors</h2> -<p> - So far we used light colors to only vary the intensity of their individual components by choosing colors that range from white to gray to black, not affecting the actual colors of the object (only its intensity). Since we now have easy access to the light's properties we can change their colors over time to get some really interesting effects. Since everything is already set up in the fragment shader, changing the light's colors is easy and immediately creates some funky effects: -</p> - -<div class="video paused" onclick="ClickVideo(this)"> - <video width="600" height="450" loop> - <source src="/video/lighting/materials.mp4" type="video/mp4" /> - <img src="/img/lighting/materials_light_colors.png"/> - </video> -</div> - - -<p> - As you can see, a different light color greatly influences the object's color output. Since the light color directly influences what colors the object can reflect (as you may remember from the <a href="https://learnopengl.com/Lighting/Colors" target="_blank">Colors</a> chapter) it has a significant impact on the visual output. -</p> - -<p> - We can easily change the light's colors over time by changing the light's ambient and diffuse colors via <fun>sin</fun> and <fun><function id='47'>glfwGetTime</function></fun>: -</p> - -<pre><code> -glm::vec3 lightColor; -lightColor.x = sin(<function id='47'>glfwGetTime</function>() * 2.0f); -lightColor.y = sin(<function id='47'>glfwGetTime</function>() * 0.7f); -lightColor.z = sin(<function id='47'>glfwGetTime</function>() * 1.3f); - -glm::vec3 diffuseColor = lightColor * glm::vec3(0.5f); -glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f); - -lightingShader.setVec3("light.ambient", ambientColor); -lightingShader.setVec3("light.diffuse", diffuseColor); -</code></pre> - -<p> - Try and experiment with several lighting and material values and see how they affect the visual output. You can find the source code of the application <a href="/code_viewer_gh.php?code=src/2.lighting/3.1.materials/materials.cpp" target="_blank">here</a>. -</p> - -<h2>Exercises</h2> -<p> - <ul> - <li>Can you make it so that changing the light color changes the color of the light's cube object?</li> - <li>Can you simulate some of the real-world objects by defining their respective materials like we've seen at the start of this chapter? Note that the <a href="http://devernay.free.fr/cours/opengl/materials.html" target="_blank">table</a>'s ambient values are not the same as the diffuse values; they didn't take light intensities into account. To correctly set their values you'd have to set all the light intensities to <code>vec3(1.0)</code> to get the same output: <a href="/code_viewer_gh.php?code=src/2.lighting/3.2.materials_exercise1/materials_exercise1.cpp" target="_blank">solution</a> of cyan plastic container.</li> - </ul> -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Lighting/Multiple-lights.html b/translation/Lighting/Multiple-lights.html @@ -1,541 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Multiple lights</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Multiple lights</h1> -<h1 id="content-url" style='display:none;'>Lighting/Multiple-lights</h1> -<p> - In the previous chapters we learned a lot about lighting in OpenGL. We learned about Phong shading, materials, lighting maps and different types of light casters. In this chapter we're going to combine all the previously obtained knowledge by creating a fully lit scene with 6 active light sources. We are going to simulate a sun-like light as a directional light source, 4 point lights scattered throughout the scene and we'll be adding a flashlight as well. -</p> - -<p> - To use more than one light source in the scene we want to encapsulate the lighting calculations into GLSL <def>functions</def>. The reason for that is that the code quickly gets nasty when we do lighting computations with multiple light types, each requiring different computations. If we were to do all these calculations in the <fun>main</fun> function only, the code quickly becomes difficult to understand. -</p> - -<p> - Functions in GLSL are just like C-functions. We have a function name, a return type and we need to declare a prototype at the top of the code file if the function hasn't been declared yet before the main function. We'll create a different function for each of the light types: directional lights, point lights and spotlights. -</p> - -<p> - When using multiple lights in a scene the approach is usually as follows: we have a single color vector that represents the fragment's output color. For each light, the light's contribution to the fragment is added to this output color vector. So each light in the scene will calculate its individual impact and contribute that to the final output color. A general structure would look something like this: -</p> - -<pre><code> -out vec4 FragColor; - -void main() -{ - // define an output color value - vec3 output = vec3(0.0); - // add the directional light's contribution to the output - output += someFunctionToCalculateDirectionalLight(); - // do the same for all point lights - for(int i = 0; i < nr_of_point_lights; i++) - output += someFunctionToCalculatePointLight(); - // and add others lights as well (like spotlights) - output += someFunctionToCalculateSpotLight(); - - FragColor = vec4(output, 1.0); -} -</code></pre> - -<p> - The actual code will likely differ per implementation, but the general structure remains the same. We define several functions that calculate the impact per light source and add its resulting color to an output color vector. If for example two light sources are close to the fragment, their combined contribution would result in a more brightly lit fragment compared to the fragment being lit by a single light source. -</p> - -<h2>Directional light</h2> -<p> - We want to define a function in the fragment shader that calculates the contribution a directional light has on the corresponding fragment: a function that takes a few parameters and returns the calculated directional lighting color. -</p> - -<p> - First we need to set the required variables that we minimally need for a directional light source. We can store the variables in a struct called <fun>DirLight</fun> and define it as a uniform. The struct's variables should be familiar from the <a href="https://learnopengl.com/Lighting/Light-casters" target="_blank">previous</a> chapter: -</p> - -<pre><code> -struct DirLight { - vec3 direction; - - vec3 ambient; - vec3 diffuse; - vec3 specular; -}; -uniform DirLight dirLight; -</code></pre> - -<p> - We can then pass the <var>dirLight</var> uniform to a function with the following prototype: -</p> - -<pre><code> -vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir); -</code></pre> - -<note> - Just like C and C++, when we want to call a function (in this case inside the <fun>main</fun> function) the function should be defined somewhere before the caller's line number. In this case we'd prefer to define the functions below the <fun>main</fun> function so this requirement doesn't hold. Therefore we declare the function's prototypes somewhere above the <fun>main</fun> function, just like we would in C. -</note> - -<p> - You can see that the function requires a <fun>DirLight</fun> struct and two other vectors required for its computation. If you successfully completed the previous chapter then the content of this function should come as no surprise: -</p> - -<pre><code> -vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir) -{ - vec3 lightDir = normalize(-light.direction); - // diffuse shading - float diff = max(dot(normal, lightDir), 0.0); - // specular shading - vec3 reflectDir = reflect(-lightDir, normal); - float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); - // combine results - vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); - vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); - vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); - return (ambient + diffuse + specular); -} -</code></pre> - -<p> - We basically copied the code from the previous chapter and used the vectors given as function arguments to calculate the directional light's contribution vector. The resulting ambient, diffuse and specular contributions are then returned as a single color vector. -</p> - -<h2>Point light</h2> -<p> - Similar to directional lights we also want to define a function that calculates the contribution a point light has on the given fragment, including its attenuation. Just like directional lights we want to define a struct that specifies all the variables required for a point light: -</p> - -<pre><code> -struct PointLight { - vec3 position; - - float constant; - float linear; - float quadratic; - - vec3 ambient; - vec3 diffuse; - vec3 specular; -}; -#define NR_POINT_LIGHTS 4 -uniform PointLight pointLights[NR_POINT_LIGHTS]; -</code></pre> - -<p> - As you can see we used a pre-processor directive in GLSL to define the number of point lights we want to have in our scene. We then use this <var>NR_POINT_LIGHTS</var> constant to create an array of <fun>PointLight</fun> structs. Arrays in GLSL are just like C arrays and can be created by the use of two square brackets. Right now we have 4 <fun>PointLight</fun> structs to fill with data. -</p> - -<!--<note> - We could also simply define <strong>one</strong> large struct (instead of different structs per light type) that contains all the necessary variables for <strong>all</strong> the different light types and use that struct for each function, and simply ignore the variables we don't need. However, I personally find the current approach more intuitive and aside from a few extra lines of code it could save up some memory since not all light types need all variables. -</note>--> - -<p> - The prototype of the point light's function is as follows: -</p> - -<pre><code> -vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir); -</code></pre> - -<p> - The function takes all the data it needs as its arguments and returns a <code>vec3</code> that represents the color contribution that this specific point light has on the fragment. Again, some intelligent copy-and-pasting results in the following function: -</p> - -<pre><code> -vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir) -{ - vec3 lightDir = normalize(light.position - fragPos); - // diffuse shading - float diff = max(dot(normal, lightDir), 0.0); - // specular shading - vec3 reflectDir = reflect(-lightDir, normal); - float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); - // attenuation - float distance = length(light.position - fragPos); - float attenuation = 1.0 / (light.constant + light.linear * distance + - light.quadratic * (distance * distance)); - // combine results - vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); - vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); - vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); - ambient *= attenuation; - diffuse *= attenuation; - specular *= attenuation; - return (ambient + diffuse + specular); -} -</code></pre> - -<p> - Abstracting this functionality away in a function like this has the advantage that we can easily calculate the lighting for multiple point lights without the need for duplicated code. In the <fun>main</fun> function we simply create a loop that iterates over the point light array that calls <fun>CalcPointLight</fun> for each point light. -</p> - -<h2>Putting it all together</h2> -<p> - Now that we defined both a function for directional lights and a function for point lights we can put it all together in the <fun>main</fun> function. -</p> - -<pre><code> -void main() -{ - // properties - vec3 norm = normalize(Normal); - vec3 viewDir = normalize(viewPos - FragPos); - - // phase 1: Directional lighting - vec3 result = CalcDirLight(dirLight, norm, viewDir); - // phase 2: Point lights - for(int i = 0; i < NR_POINT_LIGHTS; i++) - result += CalcPointLight(pointLights[i], norm, FragPos, viewDir); - // phase 3: Spot light - //result += CalcSpotLight(spotLight, norm, FragPos, viewDir); - - FragColor = vec4(result, 1.0); -} -</code></pre> - -<p> - Each light type adds its contribution to the resulting output color until all light sources are processed. The resulting color contains the color impact of all the light sources in the scene combined. We leave the <fun>CalcSpotLight</fun> function as an exercise for the reader. -</p> - -<note> - There are lot of duplicated calculations in this approach spread out over the light type functions (e.g. calculating the reflect vector, diffuse and specular terms, and sampling the material textures) so there's room for optimization here. -</note> - -<p> - Setting the uniforms for the directional light struct shouldn't be too unfamiliar, but you may be wondering how to set the uniform values of the point lights since the point light uniform is actually an array of <fun>PointLight</fun> structs. This isn't something we've discussed before. -</p> - -<p> - Luckily for us, it isn't too complicated. Setting the uniform values of an array of structs works just like setting the uniforms of a single struct, although this time we also have to define the appropriate index when querying the uniform's location: -</p> - -<pre><code> -lightingShader.setFloat("pointLights[0].constant", 1.0f); -</code></pre> - -<p> - Here we index the first <fun>PointLight</fun> struct in the <var>pointLights</var> array and internally retrieve the location of its <var>constant</var> variable, which we set to <code>1.0</code>. -</p> - -<p> - Let's not forget that we also need to define a position vector for each of the 4 point lights so let's spread them up a bit around the scene. We'll define another <code>glm::vec3</code> array that contains the pointlights' positions: -</p> - -<pre><code> -glm::vec3 pointLightPositions[] = { - glm::vec3( 0.7f, 0.2f, 2.0f), - glm::vec3( 2.3f, -3.3f, -4.0f), - glm::vec3(-4.0f, 2.0f, -12.0f), - glm::vec3( 0.0f, 0.0f, -3.0f) -}; -</code></pre> - -<p> - Then we index the corresponding <fun>PointLight</fun> struct from the <var>pointLights</var> array and set its <var>position</var> attribute as one of the positions we just defined. Also be sure to now draw 4 light cubes instead of just 1. Simply create a different model matrix for each of the light objects just like we did with the containers. -</p> - -<p> - If you'd also use a flashlight, the result of all the combined lights looks something like this: -</p> - -<img src="/img/lighting/multiple_lights_combined.png" class="clean"/> - -<p> - As you can see there appears to be some form of a global light (like a sun) somewhere in the sky, we have 4 lights scattered throughout the scene and a flashlight is visible from the player's perspective. Looks pretty neat doesn't it? -</p> - -<p> - You can find the full source code of the final application <a href="/code_viewer_gh.php?code=src/2.lighting/6.multiple_lights/multiple_lights.cpp" target="_blank">here</a>. -</p> - -<p> - The image shows all the light sources set with the default light properties we've used in the previous chapters, but if you play around with these values you can get pretty interesting results. Artists and level designers generally tweak all these lighting variables in a large editor to make sure the lighting matches the environment. Using our simple environment you can already create some pretty interesting visuals simply by tweaking the lights' attributes: -</p> - -<img src="/img/lighting/multiple_lights_atmospheres.png" class="clean" style="border-radius: 0px;"/> - -<p> - We also changed the clear color to better reflect the lighting. You can see that by simply adjusting some of the lighting parameters you can create completely different atmospheres. -</p> - -<p> - By now you should have a pretty good understanding of lighting in OpenGL. With the knowledge so far we can already create interesting and visually rich environments and atmospheres. Try playing around with all the different values to create your own atmospheres. -</p> - -<h2>Exercises</h2> -<p> - <ul> - <li>Can you (sort of) re-create the different atmospheres of the last image by tweaking the light's attribute values? <a href="/code_viewer_gh.php?code=src/2.lighting/6.multiple_lights_exercise1/multiple_lights_exercise1.cpp" target="_blank">solution</a>.</li> - </ul> -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Lighting/Review.html b/translation/Lighting/Review.html @@ -1,314 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Review</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Review</h1> -<h1 id="content-url" style='display:none;'>Lighting/Review</h1> -<p> - Congratulations on making it this far! I'm not sure if you noticed, but over all the lighting chapters we learned nothing new about OpenGL itself aside from a few minor items like accessing uniform arrays. All of the lighting chapters so far were all about manipulating shaders using techniques and equations to achieve realistic lighting results. This again shows you the power of shaders. Shaders are extremely flexible and you witnessed first-hand that with just a few 3D vectors and some configurable variables we were able to create amazing graphics! -</p> - -<p> - The last few chapters you learned about colors, the Phong lighting model (that includes ambient, diffuse and specular lighting), object materials, configurable light properties, diffuse and specular maps, different types of lights, and how to combine all the knowledge into a single fully lit scene. Be sure to experiment with different lights, material colors, light properties, and try to create your own environments with the help of a little bit of creativity. -</p> - -<p> - In the next chapters we'll be adding more advanced geometry shapes to our scene that look really well in the lighting models we've discussed. -</p> - -<h2>Glossary</h2> -<p> -<ul> - <li><code>Color vector</code>: a vector portraying most of the real world colors via a combination of red, green and blue components (abbreviated to <code>RGB</code>). The color of an object is the reflected color components that an object did not absorb.</li> - <li><code>Phong lighting model</code>: a model for approximating real-world lighting by computing an ambient, diffuse and specular component.</li> - <li><code>Ambient lighting</code>: approximation of global illumination by giving each object a small brightness so that objects aren't completely dark if not directly lit.</li> - <li><code>Diffuse shading</code>: lighting that gets stronger the more a vertex/fragment is aligned to a light source. Makes use of normal vectors to calculate the angles.</li> - <li><code>Normal vector</code>: a unit vector that is perpendicular to a surface.</li> - <li><code>Normal matrix</code>: a 3x3 matrix that is the model (or model-view) matrix without translation. It is also modified in such a way (inverse-transpose) that it keeps normal vectors facing in the correct direction when applying non-uniform scaling. Otherwise normal vectors get distorted when using non-uniform scaling.</li> - <li><code>Specular lighting</code>: sets a specular highlight the closer the viewer is looking at the reflection of a light source on a surface. Based on the viewer's direction, the light's direction and a shininess value that sets the amount of scattering of the highlight.</li> - <li><code>Phong shading</code>: the Phong lighting model applied in the fragment shader.</li> - <li><code>Gouraud shading</code>: the Phong lighting model applied in the vertex shader. Produces noticeable artifacts when using a small number of vertices. Gains efficiency for loss of visual quality.</li> - <li><code>GLSL struct</code>: a C-like struct that acts as a container for shader variables. Mostly used for organizing input, output, and uniforms.</li> - <li><code>Material</code>: the ambient, diffuse and specular color an object reflects. These set the colors an object has.</li> - <li><code>Light (properties)</code>: the ambient, diffuse and specular intensity of a light. These can take any color value and define at what color/intensity a light source shines for each specific Phong component.</li> - <li><code>Diffuse map</code>: a texture image that sets the diffuse color per fragment.</li> - <li><code>Specular map</code>: a texture map that sets the specular intensity/color per fragment. Allows for specular highlights only on certain areas of an object.</li> - <li><code>Directional light</code>: a light source with only a direction. It is modeled to be at an infinite distance which has the effect that all its light rays seem parallel and its direction vector thus stays the same over the entire scene. </li> - <li><code>Point light</code>: a light source with a location in a scene with light that fades out over distance.</li> - <li><code>Attenuation</code>: the process of light reducing its intensity over distance, used in point lights and spotlights.</li> - <li><code>Spotlight</code>: a light source that is defined by a cone in one specific direction.</li> - <li><code>Flashlight</code>: a spotlight positioned from the viewer's perspective.</li> - <li><code>GLSL uniform array</code>: an array of uniform values. Work just like a C-array, except that they can't be dynamically allocated. </li> -</ul> -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Model-Loading/Assimp.html b/translation/Model-Loading/Assimp.html @@ -1,363 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Assimp</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Assimp</h1> -<h1 id="content-url" style='display:none;'>Model-Loading/Assimp</h1> -<p> - In all the scenes so far we've been extensively playing with our little container friend, but over time, even our best friends can get a little boring. In bigger graphics applications, there are usually lots of complicated and interesting models that are much prettier to look at than a static container. However, unlike the container object, we can't really manually define all the vertices, normals, and texture coordinates of complicated shapes like houses, vehicles, or human-like characters. What we want instead, is to <em>import</em> these models into the application; models that were carefully designed by 3D artists in tools like <a href="http://www.blender.org/" target="_blank">Blender</a>, <a href="http://www.autodesk.nl/products/3ds-max/overview" target="_blank">3DS Max</a> or <a href="http://www.autodesk.com/products/autodesk-maya/overview" target="_blank">Maya</a>. -</p> - -<p> - These so called <def>3D modeling tools</def> allow artists to create complicated shapes and apply textures to them via a process called <def>uv-mapping</def>. The tools then automatically generate all the vertex coordinates, vertex normals, and texture coordinates while exporting them to a model file format we can use. This way, artists have an extensive toolkit to create high quality models without having to care too much about the technical details. All the technical aspects are hidden in the exported model file. We, as graphics programmers, <strong>do</strong> have to care about these technical details though. -</p> - -<p> - It is our job to parse these exported model files and extract all the relevant information so we can store them in a format that OpenGL understands. A common issue is that there are dozens of different file formats where each exports the model data in its own unique way. Model formats like the <a href="http://en.wikipedia.org/wiki/Wavefront_.obj_file" target="_blank">Wavefront .obj</a> only contains model data with minor material information like model colors and diffuse/specular maps, while model formats like the XML-based <a href="http://en.wikipedia.org/wiki/COLLADA" target="_blank">Collada file format</a> are extremely extensive and contain models, lights, many types of materials, animation data, cameras, complete scene information, and much more. The wavefront object format is generally considered to be an easy-to-parse model format. It is recommended to visit the Wavefront's wiki page at least once to see how such a file format's data is structured. This should give you a basic perception of how model file formats are generally structured. -</p> - -<p> - All by all, there are many different file formats where a common general structure between them usually does not exist. So if we want to import a model from these file formats, we'd have to write an importer ourselves for each of the file formats we want to import. Luckily for us, there just happens to be a library for this. -</p> - -<h2>A model loading library</h2> -<p> - A very popular model importing library out there is called <a href="http://assimp.org/" target="_blank">Assimp</a> that stands for <em>Open Asset Import Library</em>. Assimp is able to import dozens of different model file formats (and export to some as well) by loading all the model's data into Assimp's generalized data structures. As soon as Assimp has loaded the model, we can retrieve all the data we need from Assimp's data structures. Because the data structure of Assimp stays the same, regardless of the type of file format we imported, it abstracts us from all the different file formats out there. -</p> - -<p> - When importing a model via Assimp it loads the entire model into a <em>scene</em> object that contains all the data of the imported model/scene. Assimp then has a collection of nodes where each node contains indices to data stored in the scene object where each node can have any number of children. A (simplistic) model of Assimp's structure is shown below: -</p> - -<img src="/img/model_loading/assimp_structure.png" class="clean"/> - -<p> - <ul> - <li>All the data of the scene/model is contained in the <u>Scene</u> object like all the materials and the meshes. It also contains a reference to the root node of the scene.</li> - <li>The <u>Root node</u> of the scene may contain children nodes (like all other nodes) and could have a set of indices that point to mesh data in the scene object's <var>mMeshes</var> array. The scene's <var>mMeshes</var> array contains the actual <u>Mesh</u> objects, the values in the <var>mMeshes</var> array of a node are only indices for the scene's meshes array.</li> - <li>A <u>Mesh</u> object itself contains all the relevant data required for rendering, think of vertex positions, normal vectors, texture coordinates, faces, and the material of the object.</li> - <li>A mesh contains several faces. A <u>Face</u> represents a render primitive of the object (triangles, squares, points). A face contains the indices of the vertices that form a primitive. Because the vertices and the indices are separated, this makes it easy for us to render via an index buffer (see <a href="https://learnopengl.com/Getting-started/Hello-Triangle" target="_blank">Hello Triangle</a>).</li> - <li>Finally a mesh also links to a <u>Material</u> object that hosts several functions to retrieve the material properties of an object. Think of colors and/or texture maps (like diffuse and specular maps).</li> - </ul> -</p> - -<p> - What we want to do is: first load an object into a <u>Scene</u> object, recursively retrieve the corresponding <u>Mesh</u> objects from each of the nodes (we recursively search each node's children), and process each <u>Mesh</u> object to retrieve the vertex data, indices, and its material properties. The result is then a collection of mesh data that we want to contain in a single <code>Model</code> object. -</p> - -<note> - <strong>Mesh</strong><br/> - When modeling objects in modeling toolkits, artists generally do not create an entire model out of a single shape. Usually, each model has several sub-models/shapes that it consists of. Each of those single shapes is called a <def>mesh</def>. Think of a human-like character: artists usually model the head, limbs, clothes, and weapons all as separate components, and the combined result of all these meshes represents the final model. A single mesh is the minimal representation of what we need to draw an object in OpenGL (vertex data, indices, and material properties). A model (usually) consists of several meshes. -</note> - -<p> - In the <a href="https://learnopengl.com/Model-Loading/Mesh" target="_blank">next</a> chapters we'll create our own <fun>Model</fun> and <fun>Mesh</fun> class that load and store imported models using the structure we've just described. If we then want to draw a model, we do not render the model as a whole, but we render all of the individual meshes that the model is composed of. However, before we can start importing models, we first need to actually include Assimp in our project. -</p> - -<h2>Building Assimp</h2> -<p> - You can download Assimp from their <a href="http://assimp.org/index.php/downloads" target="_blank">download</a> page and choose the corresponding version. For this writing, the Assimp version used was version <code>3.1.1</code>. It is advised to compile the libraries by yourself, since their pre-compiled libraries don't always work on all systems. Review the <a href="https://learnopengl.com/Getting-started/Creating-a-window" target="_blank">Creating a window</a> chapter if you forgot how to compile a library by yourself via CMake. -</p> - -<p> - A few issues can come up while building Assimp, so I'll note them down here with their solutions in case any of you get the same errors: - <ul> - <li>CMake continually gives errors while retrieving the configuration list about DirectX libraries missing, messages like: -<pre><code> -Could not locate DirectX -CMake Error at cmake-modules/FindPkgMacros.cmake:110 (message): -Required library DirectX not found! Install the library (including dev packages) -and try again. If the library is already installed, set the missing variables -manually in cmake. -</code></pre> - The solution here is to install the DirectX SDK in case you haven't installed this before. You can download the SDK from <a href="http://www.microsoft.com/en-us/download/details.aspx?id=6812" target="_blank">here</a>.</li> - <li>While installing the DirectX SDK, a possible error code of <code>s1023</code> could pop up. In that case you first want to de-install the C++ Redistributable package(s) before installing the SDK.</li> - </ul> -</p> - -<p> - Once the configuration is completed, you can generate a solution file, open it, and compile the libraries (either as a release version or a debug version, whatever floats your boat). Be sure to compile it for 64-bit as all LearnOpenGL code is 64 bit. -</p> - -<p> - The default configuration builds Assimp as a dynamic library so we need to include the resulting DLL named <code>assimp.dll</code> (or with some post-fix) alongside the application's binaries. You can simply copy the DLL to the same folder where your application's executable is located. -</p> - -<p> - After compiling the generated solution, the resulting library and DLL file are located in the <code>code/Debug</code> or <code>code/Release</code> folder. Then simply move the lib and DLL to their appropriate locations, link them from your solution, and be sure to copy Assimp's headers to your <code>include</code> directory (the header files are found in the <code>include</code> folder in the files downloaded from Assimp). -</p> - -<p> - By now you should have compiled Assimp and linked it to your application. If you still received any unreported error, feel free to ask for help in the comments. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Model-Loading/Mesh.html b/translation/Model-Loading/Mesh.html @@ -1,513 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Mesh</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Mesh</h1> -<h1 id="content-url" style='display:none;'>Model-Loading/Mesh</h1> -<p> - With Assimp we can load many different models into the application, but once loaded they're all stored in Assimp's data structures. What we eventually want is to transform that data to a format that OpenGL understands so that we can render the objects. We learned from the previous chapter that a mesh represents a single drawable entity, so let's start by defining a mesh class of our own. -</p> - -<p> - Let's review a bit of what we've learned so far to think about what a mesh should minimally have as its data. A mesh should at least need a set of vertices, where each vertex contains a position vector, a normal vector, and a texture coordinate vector. A mesh should also contain indices for indexed drawing, and material data in the form of textures (diffuse/specular maps). -</p> - -<p> - Now that we set the minimal requirements for a mesh class we can define a vertex in OpenGL: -</p> - -<pre><code> -struct Vertex { - glm::vec3 Position; - glm::vec3 Normal; - glm::vec2 TexCoords; -}; -</code></pre> - -<p> - We store each of the required vertex attributes in a struct called <fun>Vertex</fun>. Next to a <fun>Vertex</fun> struct we also want to organize the texture data in a <fun>Texture</fun> struct: -</p> - -<pre><code> -struct Texture { - unsigned int id; - string type; -}; -</code></pre> - -<p> - We store the id of the texture and its type e.g. a diffuse or specular texture. -</p> - -<p> - Knowing the actual representation of a vertex and a texture we can start defining the structure of the mesh class: -</p> - -<pre><code> -class Mesh { - public: - // mesh data - vector<Vertex> vertices; - vector<unsigned int> indices; - vector<Texture> textures; - - Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures); - void Draw(Shader &shader); - private: - // render data - unsigned int VAO, VBO, EBO; - - void setupMesh(); -}; -</code></pre> - -<p> - As you can see, the class isn't too complicated. In the constructor we give the mesh all the necessary data, we initialize the buffers in the <fun>setupMesh</fun> function, and finally draw the mesh via the <fun>Draw</fun> function. Note that we give a shader to the <fun>Draw</fun> function; by passing the shader to the mesh we can set several uniforms before drawing (like linking samplers to texture units). -</p> - -<p> - The function content of the constructor is pretty straightforward. We simply set the class's public variables with the constructor's corresponding argument variables. We also call the <fun>setupMesh</fun> function in the constructor: -</p> - -<pre><code> -Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures) -{ - this->vertices = vertices; - this->indices = indices; - this->textures = textures; - - setupMesh(); -} -</code></pre> - -<p> - Nothing special going on here. Let's delve right into the <fun>setupMesh</fun> function now. -</p> - -<h2>Initialization</h2> -<p> - Thanks to the constructor we now have large lists of mesh data that we can use for rendering. We do need to setup the appropriate buffers and specify the vertex shader layout via vertex attribute pointers. By now you should have no trouble with these concepts, but we've spiced it up a bit this time with the introduction of vertex data in structs: -</p> - -<pre><code> -void setupMesh() -{ - <function id='33'>glGenVertexArrays</function>(1, &VAO); - <function id='12'>glGenBuffers</function>(1, &VBO); - <function id='12'>glGenBuffers</function>(1, &EBO); - - <function id='27'>glBindVertexArray</function>(VAO); - <function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, VBO); - - <function id='31'>glBufferData</function>(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW); - - <function id='32'>glBindBuffer</function>(GL_ELEMENT_ARRAY_BUFFER, EBO); - <function id='31'>glBufferData</function>(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), - &indices[0], GL_STATIC_DRAW); - - // vertex positions - <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(0); - <function id='30'>glVertexAttribPointer</function>(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0); - // vertex normals - <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(1); - <function id='30'>glVertexAttribPointer</function>(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal)); - // vertex texture coords - <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(2); - <function id='30'>glVertexAttribPointer</function>(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords)); - - <function id='27'>glBindVertexArray</function>(0); -} -</code></pre> - -<p> - The code is not much different from what you'd expect, but a few little tricks were used with the help of the <fun>Vertex</fun> struct. -</p> - -<p> - Structs have a great property in C++ that their memory layout is sequential. That is, if we were to represent a struct as an array of data, it would only contain the struct's variables in sequential order which directly translates to a float (actually byte) array that we want for an array buffer. For example, if we have a filled <fun>Vertex</fun> struct, its memory layout would be equal to: -</p> - -<pre><code> -Vertex vertex; -vertex.Position = glm::vec3(0.2f, 0.4f, 0.6f); -vertex.Normal = glm::vec3(0.0f, 1.0f, 0.0f); -vertex.TexCoords = glm::vec2(1.0f, 0.0f); -// = [0.2f, 0.4f, 0.6f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f]; -</code></pre> - -<p> - Thanks to this useful property we can directly pass a pointer to a large list of <fun>Vertex</fun> structs as the buffer's data and they translate perfectly to what <fun><function id='31'>glBufferData</function></fun> expects as its argument: -</p> - -<pre><code> -<function id='31'>glBufferData</function>(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices[0], GL_STATIC_DRAW); -</code></pre> - -<p> - Naturally the <code>sizeof</code> operator can also be used on the struct for the appropriate size in bytes. This should be <code>32</code> bytes (<code>8</code> floats * <code>4</code> bytes each). -</p> - -<p> - Another great use of structs is a preprocessor directive called <code>offsetof(s,m)</code> that takes as its first argument a struct and as its second argument a variable name of the struct. The macro returns the byte offset of that variable from the start of the struct. This is perfect for defining the offset parameter of the <fun><function id='30'>glVertexAttribPointer</function></fun> function: -</p> - -<pre><code> -<function id='30'>glVertexAttribPointer</function>(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal)); -</code></pre> - -<p> - The offset is now defined using the <fun>offsetof</fun> macro that, in this case, sets the byte offset of the normal vector equal to the byte offset of the normal attribute in the struct which is <code>3</code> floats and thus <code>12</code> bytes. -</p> - -<p> - Using a struct like this doesn't only get us more readable code, but also allows us to easily extend the structure. If we want another vertex attribute we can simply add it to the struct and due to its flexible nature, the rendering code won't break. -</p> - -<h2>Rendering</h2> -<p> - The last function we need to define for the <fun>Mesh</fun> class to be complete is its <fun>Draw</fun> function. Before rendering the mesh, we first want to bind the appropriate textures before calling <fun><function id='2'>glDrawElements</function></fun>. However, this is somewhat difficult since we don't know from the start how many (if any) textures the mesh has and what type they may have. So how do we set the texture units and samplers in the shaders? -</p> - -<p> - To solve the issue we're going to assume a certain naming convention: each diffuse texture is named <code>texture_diffuseN</code>, and each specular texture should be named <code>texture_specularN</code> where <code>N</code> is any number ranging from <code>1</code> to the maximum number of texture samplers allowed. Let's say we have 3 diffuse textures and 2 specular textures for a particular mesh, their texture samplers should then be called: -</p> - -<pre><code> -uniform sampler2D texture_diffuse1; -uniform sampler2D texture_diffuse2; -uniform sampler2D texture_diffuse3; -uniform sampler2D texture_specular1; -uniform sampler2D texture_specular2; -</code></pre> - -<p> - By this convention we can define as many texture samplers as we want in the shaders (up to OpenGL's maximum) and if a mesh actually does contain (so many) textures, we know what their names are going to be. By this convention we can process any amount of textures on a single mesh and the shader developer is free to use as many of those as he wants by defining the proper samplers. -</p> - -<note> - There are many solutions to problems like this and if you don't like this particular solution it is up to you to get creative and come up with your own approach. -</note> - -<p> - The resulting drawing code then becomes: -</p> - -<pre><code> -void Draw(Shader &shader) -{ - unsigned int diffuseNr = 1; - unsigned int specularNr = 1; - for(unsigned int i = 0; i < textures.size(); i++) - { - <function id='49'>glActiveTexture</function>(GL_TEXTURE0 + i); // activate proper texture unit before binding - // retrieve texture number (the N in diffuse_textureN) - string number; - string name = textures[i].type; - if(name == "texture_diffuse") - number = std::to_string(diffuseNr++); - else if(name == "texture_specular") - number = std::to_string(specularNr++); - - shader.setFloat(("material." + name + number).c_str(), i); - <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, textures[i].id); - } - <function id='49'>glActiveTexture</function>(GL_TEXTURE0); - - // draw mesh - <function id='27'>glBindVertexArray</function>(VAO); - <function id='2'>glDrawElements</function>(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0); - <function id='27'>glBindVertexArray</function>(0); -} -</code></pre> - -<p> - We first calculate the N-component per texture type and concatenate it to the texture's type string to get the appropriate uniform name. We then locate the appropriate sampler, give it the location value to correspond with the currently active texture unit, and bind the texture. This is also the reason we need the shader in the <fun>Draw</fun> function. -</p> - -<p> - We also added <code>"material."</code> to the resulting uniform name because we usually store the textures in a material struct (this may differ per implementation). -</p> - -<note> - Note that we increment the diffuse and specular counters the moment we convert them to <code>string</code>. In C++ the increment call: <code>variable++</code> returns the variable as is and <strong>then</strong> increments the variable while <code>++variable</code> <strong>first</strong> increments the variable and <strong>then</strong> returns it. In our case the value passed to <code>std::string</code> is the original counter value. After that the value is incremented for the next round. -</note> - -<p> - You can find the full source code of the <fun>Mesh</fun> class <a href="/code_viewer_gh.php?code=includes/learnopengl/mesh.h" target="_blank">here</a>. -</p> - -<p> - The <fun>Mesh</fun> class we just defined is an abstraction for many of the topics we've discussed in the early chapters. In the <a href="https://learnopengl.com/Model-Loading/Model" target="_blank">next</a> chapter we'll create a model that acts as a container for several mesh objects and implements Assimp's loading interface. -</p> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Model-Loading/Model.html b/translation/Model-Loading/Model.html @@ -1,699 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Model</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Model</h1> -<h1 id="content-url" style='display:none;'>Model-Loading/Model</h1> -<p> - Now it is time to get our hands dirty with Assimp and start creating the actual loading and translation code. The goal of this chapter is to create another class that represents a model in its entirety, that is, a model that contains multiple meshes, possibly with multiple textures. A house, that contains a wooden balcony, a tower, and perhaps a swimming pool, could still be loaded as a single model. We'll load the model via Assimp and translate it to multiple <fun>Mesh</fun> objects we've created in the <a href="https://learnopengl.com/Model-Loading/Mesh" target="_blank">previous</a> chapter. -</p> - -<p> - Without further ado, I present you the class structure of the <fun>Model</fun> class: -</p> - -<pre><code> -class Model -{ - public: - Model(char *path) - { - loadModel(path); - } - void Draw(Shader &shader); - private: - // model data - vector<Mesh> meshes; - string directory; - - void loadModel(string path); - void processNode(aiNode *node, const aiScene *scene); - Mesh processMesh(aiMesh *mesh, const aiScene *scene); - vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, - string typeName); -}; -</code></pre> - -<p> - The <fun>Model</fun> class contains a vector of <fun>Mesh</fun> objects and requires us to give it a file location in its constructor. It then loads the file right away via the <fun>loadModel</fun> function that is called in the constructor. The private functions are all designed to process a part of Assimp's import routine and we'll cover them shortly. We also store the directory of the file path that we'll later need when loading textures. -</p> - -<p> - The <fun>Draw</fun> function is nothing special and basically loops over each of the meshes to call their respective <fun>Draw</fun> function: -</p> - -<pre><code> -void Draw(Shader &shader) -{ - for(unsigned int i = 0; i < meshes.size(); i++) - meshes[i].Draw(shader); -} -</code></pre> - -<h2>Importing a 3D model into OpenGL</h2> -<p> - To import a model and translate it to our own structure, we first need to include the appropriate headers of Assimp: -</p> - -<pre><code> -#include <assimp/Importer.hpp> -#include <assimp/scene.h> -#include <assimp/postprocess.h> -</code></pre> - -<p> - The first function we're calling is <fun>loadModel</fun>, that's directly called from the constructor. Within <fun>loadModel</fun>, we use Assimp to load the model into a data structure of Assimp called a <u>scene</u> object. You may remember from the <a href="https://learnopengl.com/Model-Loading/Assimp" target="_blank">first</a> chapter of the model loading series that this is the root object of Assimp's data interface. Once we have the scene object, we can access all the data we need from the loaded model. -</p> - -<p> - The great thing about Assimp is that it neatly abstracts from all the technical details of loading all the different file formats and does all this with a single one-liner: -</p> - -<pre><code> -Assimp::Importer importer; -const aiScene *scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs); -</code></pre> - -<p> - We first declare an <fun>Importer</fun> object from Assimp's namespace and then call its <fun>ReadFile</fun> function. The function expects a file path and several <def>post-processing</def> options as its second argument. Assimp allows us to specify several options that forces Assimp to do extra calculations/operations on the imported data. By setting <var>aiProcess_Triangulate</var> we tell Assimp that if the model does not (entirely) consist of triangles, it should transform all the model's primitive shapes to triangles first. The <var>aiProcess_FlipUVs</var> flips the texture coordinates on the y-axis where necessary during processing (you may remember from the <a href="https://learnopengl.com/Getting-started/Textures" target="_blank">Textures</a> chapter that most images in OpenGL were reversed around the y-axis; this little postprocessing option fixes that for us). A few other useful options are: - - <ul> - <li><var>aiProcess_GenNormals</var>: creates normal vectors for each vertex if the model doesn't contain normal vectors.</li> - <li><var>aiProcess_SplitLargeMeshes</var>: splits large meshes into smaller sub-meshes which is useful if your rendering has a maximum number of vertices allowed and can only process smaller meshes.</li> - <li><var>aiProcess_OptimizeMeshes</var>: does the reverse by trying to join several meshes into one larger mesh, reducing drawing calls for optimization.</li> - </ul> - - Assimp provides a great set of postprocessing options and you can find all of them <a href="http://assimp.sourceforge.net/lib_html/postprocess_8h.html" target="_blank">here</a>. Loading a model via Assimp is (as you can see) surprisingly easy. The hard work is in using the returned scene object to translate the loaded data to an array of <code>Mesh</code> objects. -</p> - -<p> - The complete <fun>loadModel</fun> function is listed here: -</p> - -<pre><code> -void loadModel(string path) -{ - Assimp::Importer import; - const aiScene *scene = import.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs); - - if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) - { - cout << "ERROR::ASSIMP::" << import.GetErrorString() << endl; - return; - } - directory = path.substr(0, path.find_last_of('/')); - - processNode(scene->mRootNode, scene); -} -</code></pre> - -<p> - After we load the model, we check if the scene and the root node of the scene are not null and check one of its flags to see if the returned data is incomplete. If any of these error conditions are met, we report the error retrieved from the importer's <fun>GetErrorString</fun> function and return. We also retrieve the directory path of the given file path. -</p> - -<p> - If nothing went wrong, we want to process all of the scene's nodes. We pass the first node (root node) to the recursive <fun>processNode</fun> function. Because each node (possibly) contains a set of children we want to first process the node in question, and then continue processing all the node's children and so on. This fits a recursive structure, so we'll be defining a recursive function. A recursive function is a function that does some processing and <def>recursively</def> calls the same function with different parameters until a certain condition is met. In our case the <def>exit condition</def> is met when all nodes have been processed. -</p> - -<p> - As you may remember from Assimp's structure, each node contains a set of mesh indices where each index points to a specific mesh located in the scene object. We thus want to retrieve these mesh indices, retrieve each mesh, process each mesh, and then do this all again for each of the node's children nodes. The content of the <fun>processNode</fun> function is shown below: -</p> - -<pre><code> -void processNode(aiNode *node, const aiScene *scene) -{ - // process all the node's meshes (if any) - for(unsigned int i = 0; i < node->mNumMeshes; i++) - { - aiMesh *mesh = scene->mMeshes[node->mMeshes[i]]; - meshes.push_back(processMesh(mesh, scene)); - } - // then do the same for each of its children - for(unsigned int i = 0; i < node->mNumChildren; i++) - { - processNode(node->mChildren[i], scene); - } -} -</code></pre> - -<p> - We first check each of the node's mesh indices and retrieve the corresponding mesh by indexing the scene's <var>mMeshes</var> array. The returned mesh is then passed to the <fun>processMesh</fun> function that returns a <fun>Mesh</fun> object that we can store in the <var>meshes</var> list/vector. -</p> - -<p> - Once all the meshes have been processed, we iterate through all of the node's children and call the same <fun>processNode</fun> function for each its children. Once a node no longer has any children, the recursion stops. -</p> - -<note> - A careful reader may have noticed that we could forget about processing any of the nodes and simply loop through all of the scene's meshes directly, without doing all this complicated stuff with indices. The reason we're doing this is that the initial idea for using nodes like this is that it defines a parent-child relation between meshes. By recursively iterating through these relations, we can define certain meshes to be parents of other meshes.<br/> - An example use case for such a system is when you want to translate a car mesh and make sure that all its children (like an engine mesh, a steering wheel mesh, and its tire meshes) translate as well; such a system is easily created using parent-child relations.<br/><br/> - Right now however we're not using such a system, but it is generally recommended to stick with this approach for whenever you want extra control over your mesh data. These node-like relations are after all defined by the artists who created the models. -</note> - -<p> - The next step is to process Assimp's data into the <fun>Mesh</fun> class from the previous chapter. -</p> - -<h3>Assimp to Mesh</h3> -<p> - Translating an <code>aiMesh</code> object to a mesh object of our own is not too difficult. All we need to do, is access each of the mesh's relevant properties and store them in our own object. The general structure of the <fun>processMesh</fun> function then becomes: -</p> - -<pre><code> -Mesh processMesh(aiMesh *mesh, const aiScene *scene) -{ - vector<Vertex> vertices; - vector<unsigned int> indices; - vector<Texture> textures; - - for(unsigned int i = 0; i < mesh->mNumVertices; i++) - { - Vertex vertex; - // process vertex positions, normals and texture coordinates - [...] - vertices.push_back(vertex); - } - // process indices - [...] - // process material - if(mesh->mMaterialIndex >= 0) - { - [...] - } - - return Mesh(vertices, indices, textures); -} -</code></pre> - -<p> - Processing a mesh is a 3-part process: retrieve all the vertex data, retrieve the mesh's indices, and finally retrieve the relevant material data. The processed data is stored in one of the <code>3</code> vectors and from those a <fun>Mesh</fun> is created and returned to the function's caller. -</p> - -<p> - Retrieving the vertex data is pretty simple: we define a <fun>Vertex</fun> struct that we add to the <var>vertices</var> array after each loop iteration. We loop for as much vertices there exist within the mesh (retrieved via <code>mesh->mNumVertices</code>). Within the iteration we want to fill this struct with all the relevant data. For vertex positions this is done as follows: -</p> - -<pre><code> -glm::vec3 vector; -vector.x = mesh->mVertices[i].x; -vector.y = mesh->mVertices[i].y; -vector.z = mesh->mVertices[i].z; -vertex.Position = vector; -</code></pre> - -<p> - Note that we define a temporary <code>vec3</code> for transferring Assimp's data to. This is necessary as Assimp maintains its own data types for vector, matrices, strings etc. and they don't convert that well to glm's data types. -</p> - -<note> - Assimp calls their vertex position array <var>mVertices</var> which isn't the most intuitive name. -</note> - -<p> - The procedure for normals should come as no surprise now: -</p> - -<pre><code> -vector.x = mesh->mNormals[i].x; -vector.y = mesh->mNormals[i].y; -vector.z = mesh->mNormals[i].z; -vertex.Normal = vector; -</code></pre> - -<p> - Texture coordinates are roughly the same, but Assimp allows a model to have up to 8 different texture coordinates per vertex. We're not going to use 8, we only care about the first set of texture coordinates. We'll also want to check if the mesh actually contains texture coordinates (which may not be always the case): -</p> - -<pre><code> -if(mesh->mTextureCoords[0]) // does the mesh contain texture coordinates? -{ - glm::vec2 vec; - vec.x = mesh->mTextureCoords[0][i].x; - vec.y = mesh->mTextureCoords[0][i].y; - vertex.TexCoords = vec; -} -else - vertex.TexCoords = glm::vec2(0.0f, 0.0f); -</code></pre> - -<p> - The <var>vertex</var> struct is now completely filled with the required vertex attributes and we can push it to the back of the <var>vertices</var> vector at the end of the iteration. This process is repeated for each of the mesh's vertices. -</p> - -<h3>Indices</h3> -<p> - Assimp's interface defines each mesh as having an array of faces, where each face represents a single primitive, which in our case (due to the <var>aiProcess_Triangulate</var> option) are always triangles. A face contains the indices of the vertices we need to draw in what order for its primitive. So if we iterate over all the faces and store all the face's indices in the <var>indices</var> vector we're all set: -</p> - -<pre><code> -for(unsigned int i = 0; i < mesh->mNumFaces; i++) -{ - aiFace face = mesh->mFaces[i]; - for(unsigned int j = 0; j < face.mNumIndices; j++) - indices.push_back(face.mIndices[j]); -} -</code></pre> - -<p> - After the outer loop has finished, we now have a complete set of vertices and index data for drawing the mesh via <fun><function id='2'>glDrawElements</function></fun>. However, to finish the discussion and to add some detail to the mesh, we want to process the mesh's material as well. -</p> - -<h3>Material</h3> -<p> - Similar to nodes, a mesh only contains an index to a material object. To retrieve the material of a mesh, we need to index the scene's <var>mMaterials</var> array. The mesh's material index is set in its <var>mMaterialIndex</var> property, which we can also query to check if the mesh contains a material or not: -</p> - -<pre><code> -if(mesh->mMaterialIndex >= 0) -{ - aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex]; - vector<Texture> diffuseMaps = loadMaterialTextures(material, - aiTextureType_DIFFUSE, "texture_diffuse"); - textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end()); - vector<Texture> specularMaps = loadMaterialTextures(material, - aiTextureType_SPECULAR, "texture_specular"); - textures.insert(textures.end(), specularMaps.begin(), specularMaps.end()); -} -</code></pre> - -<p> - We first retrieve the <code>aiMaterial</code> object from the scene's <var>mMaterials</var> array. Then we want to load the mesh's diffuse and/or specular textures. A material object internally stores an array of texture locations for each texture type. The different texture types are all prefixed with <code>aiTextureType_</code>. We use a helper function called <fun>loadMaterialTextures</fun> to retrieve, load, and initialize the textures from the material. The function returns a vector of <fun>Texture</fun> structs that we store at the end of the model's <var>textures</var> vector. -</p> - -<p> - The <fun>loadMaterialTextures</fun> function iterates over all the texture locations of the given texture type, retrieves the texture's file location and then loads and generates the texture and stores the information in a <fun>Vertex</fun> struct. It looks like this: -</p> - -<pre><code> -vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName) -{ - vector<Texture> textures; - for(unsigned int i = 0; i < mat->GetTextureCount(type); i++) - { - aiString str; - mat->GetTexture(type, i, &str); - Texture texture; - texture.id = TextureFromFile(str.C_Str(), directory); - texture.type = typeName; - texture.path = str; - textures.push_back(texture); - } - return textures; -} -</code></pre> - -<p> - We first check the amount of textures stored in the material via its <fun>GetTextureCount</fun> function that expects one of the texture types we've given. We retrieve each of the texture's file locations via the <fun>GetTexture</fun> function that stores the result in an <code>aiString</code>. We then use another helper function called <fun>TextureFromFile</fun> that loads a texture (with <code>stb_image.h</code>) for us and returns the texture's ID. You can check the complete code listing at the end for its content if you're not sure how such a function is written. -</p> - -<note> - Note that we make the assumption that texture file paths in model files are local to the actual model object e.g. in the same directory as the location of the model itself. We can then simply concatenate the texture location string and the directory string we retrieved earlier (in the <fun>loadModel</fun> function) to get the complete texture path (that's why the <fun>GetTexture</fun> function also needs the directory string).<br/><br/>Some models found over the internet use absolute paths for their texture locations, which won't work on each machine. In that case you probably want to manually edit the file to use local paths for the textures (if possible). -</note> - -<p> - And that is all there is to importing a model with Assimp. -</p> - -<h1>An optimization</h1> -<p> - We're not completely done yet, since there is still a large (but not completely necessary) optimization we want to make. Most scenes re-use several of their textures onto several meshes; think of a house again that has a granite texture for its walls. This texture could also be applied to the floor, its ceilings, the staircase, perhaps a table, and maybe even a small well close by. Loading textures is not a cheap operation and in our current implementation a new texture is loaded and generated for each mesh, even though the exact same texture could have been loaded several times before. This quickly becomes the bottleneck of your model loading implementation. -</p> - -<p> - So we're going to add one small tweak to the model code by storing all of the loaded textures globally. Wherever we want to load a texture, we first check if it hasn't been loaded already. If so, we take that texture and skip the entire loading routine, saving us a lot of processing power. To be able to compare textures we need to store their path as well: -</p> - -<pre><code> -struct Texture { - unsigned int id; - string type; - string path; // we store the path of the texture to compare with other textures -}; -</code></pre> - -<p> - Then we store all the loaded textures in another vector declared at the top of the model's class file as a private variable: -</p> - -<pre><code> -vector<Texture> textures_loaded; -</code></pre> - -<p> - In the <fun>loadMaterialTextures</fun> function, we want to compare the texture path with all the textures in the <var>textures_loaded</var> vector to see if the current texture path equals any of those. If so, we skip the texture loading/generation part and simply use the located texture struct as the mesh's texture. The (updated) function is shown below: -</p> - -<pre><code> -vector<Texture> loadMaterialTextures(aiMaterial *mat, aiTextureType type, string typeName) -{ - vector<Texture> textures; - for(unsigned int i = 0; i < mat->GetTextureCount(type); i++) - { - aiString str; - mat->GetTexture(type, i, &str); - bool skip = false; - for(unsigned int j = 0; j < textures_loaded.size(); j++) - { - if(std::strcmp(textures_loaded[j].path.data(), str.C_Str()) == 0) - { - textures.push_back(textures_loaded[j]); - skip = true; - break; - } - } - if(!skip) - { // if texture hasn't been loaded already, load it - Texture texture; - texture.id = TextureFromFile(str.C_Str(), directory); - texture.type = typeName; - texture.path = str.C_Str(); - textures.push_back(texture); - textures_loaded.push_back(texture); // add to loaded textures - } - } - return textures; -} -</code></pre> - -<warning> - Some versions of Assimp tend to load models quite slow when using the debug version and/or the debug mode of your IDE, so be sure to test it out with release versions as well if you run into slow loading times. -</warning> - -<p> - You can find the complete source code of the <fun>Model</fun> class <a href="/code_viewer_gh.php?code=includes/learnopengl/model.h" target="_blank">here</a>. -</p> - -<h1>No more containers!</h1> -<p> - So let's give our implementation a spin by actually importing a model created by genuine artists, not something done by the creative genius that I am. Because I don't want to give myself too much credit, I'll occasionally allow some other artists to join the ranks and this time we're going to load this amazing <a href="https://sketchfab.com/3d-models/survival-guitar-backpack-low-poly-799f8c4511f84fab8c3f12887f7e6b36" target="_blank">Survival Guitar Backpack</a> by Berk Gedik. I've modified the material and paths a bit so it works directly with the way we've set up the model loading. The model is exported as a <code>.obj</code> file together with a <code>.mtl</code> file that links to the model's diffuse, specular, and normal maps (we'll get to those later). You can download the adjusted model for this chapter <a href="/data/models/backpack.zip" target="_blank">here</a>. Note that there's a few extra texture types we won't be using yet, and that all the textures and the model file(s) should be located in the same directory for the textures to load. -</p> - -<note> - The modified version of the backpack uses local relative texture paths, and renamed the albedo and metallic textures to diffuse and specular respectively. -</note> - -<p> - Now, declare a <fun>Model</fun> object and pass in the model's file location. The model should then automatically load and (if there were no errors) render the object in the render loop using its <fun>Draw</fun> function and that is it. No more buffer allocations, attribute pointers, and render commands, just a simple one-liner. If you create a simple set of shaders where the fragment shader only outputs the object's diffuse texture, the result looks a bit like this: -</p> - -<img src="/img/model_loading/model_diffuse.png"/> - -<p> - You can find the complete source code <a href="/code_viewer_gh.php?code=src/3.model_loading/1.model_loading/model_loading.cpp" target="_blank">here</a>. Note that we tell <code>stb_image.h</code> to flip textures vertically, if you haven't done so already, before we load the model. Otherwise the textures will look all messed up. -</p> - -<p> - We can also get more creative and introduce point lights to the render equation as we learned from the <a href="https://learnopengl.com/Lighting/Light-casters" target="_blank">Lighting</a> chapters and together with specular maps get amazing results: -</p> - -<img src="/img/model_loading/model_lighting.png"/> - -<p> - Even I have to admit that this is maybe a bit more fancy than the containers we've used so far. - Using Assimp you can load tons of models found over the internet. There are quite a few resource websites that offer free 3D models for you to download in several file formats. Do note that some models still won't load properly, have texture paths that won't work, or are simply exported in a format even Assimp can't read. -</p> - -<h2>Further reading</h2> -<ul> - <li><a href="https://www.youtube.com/watch?v=4DQquG_o-Ac" target="_blank">How-To Texture Wavefront (.obj) Models for OpenGL</a>: great video guide by Matthew Early on how to set up 3D models in Blender so they directly work with the current model loader (as the texture setup we've chosen doesn't always work out of the box).</li> -</ul> -<!-- -<h2>Exercises</h2> -<p> - <ul> - <li>Can you re-create the last scene with the two point lights?: <a href="/code_viewer.php?code=model_loading/model-exercise1" target="_blank">solution</a>, <a href="/code_viewer.php?code=model_loading/model-exercise1-shaders" target="_blank">shaders</a>.</li> - </ul> -</p> ---> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/PBR/IBL/Diffuse-irradiance.html b/translation/PBR/IBL/Diffuse-irradiance.html @@ -1,944 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Diffuse irradiance</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Diffuse irradiance</h1> -<h1 id="content-url" style='display:none;'>PBR/IBL/Diffuse-irradiance</h1> -<p> - IBL, or <def>image based lighting</def>, is a collection of techniques to light objects, not by direct analytical lights as in the <a href="https://learnopengl.com/PBR/Lighting" target="_blank">previous</a> chapter, but by treating the surrounding environment as one big light source. This is generally accomplished by manipulating a cubemap environment map (taken from the real world or generated from a 3D scene) such that we can directly use it in our lighting equations: treating each cubemap texel as a light emitter. This way we can effectively capture an environment's global lighting and general feel, giving objects a better sense of <em>belonging</em> in their environment. -</p> - -<p> - As image based lighting algorithms capture the lighting of some (global) environment, its input is considered a more precise form of ambient lighting, even a crude approximation of global illumination. This makes IBL interesting for PBR as objects look significantly more physically accurate when we take the environment's lighting into account. -</p> - -<p> - To start introducing IBL into our PBR system let's again take a quick look at the reflectance equation: -</p> - - -\[ - L_o(p,\omega_o) = \int\limits_{\Omega} - (k_d\frac{c}{\pi} + k_s\frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)}) - L_i(p,\omega_i) n \cdot \omega_i d\omega_i -\] - -<p> - As described before, our main goal is to solve the integral of all incoming light directions \(w_i\) over the hemisphere \(\Omega\) . Solving the integral in the previous chapter was easy as we knew beforehand the exact few light directions \(w_i\) that contributed to the integral. - This time however, <strong>every</strong> incoming light direction \(w_i\) from the surrounding environment could potentially have some radiance making it less trivial to solve the integral. This gives us two main requirements for solving the integral: -</p> - -<ul> - <li>We need some way to retrieve the scene's radiance given any direction vector \(w_i\).</li> - <li>Solving the integral needs to be fast and real-time.</li> -</ul> - -<p> - Now, the first requirement is relatively easy. We've already hinted it, but one way of representing an environment or scene's irradiance is in the form of a (processed) environment cubemap. Given such a cubemap, we can visualize every texel of the cubemap as one single emitting light source. By sampling this cubemap with any direction vector \(w_i\), we retrieve the scene's radiance from that direction. -</p> - -<p> - Getting the scene's radiance given any direction vector \(w_i\) is then as simple as: -</p> - -<pre><code> -vec3 radiance = texture(_cubemapEnvironment, w_i).rgb; -</code></pre> - -<p> - Still, solving the integral requires us to sample the environment map from not just one direction, but all possible directions \(w_i\) over the hemisphere \(\Omega\) which is far too expensive for each fragment shader invocation. To solve the integral in a more efficient fashion we'll want to <em>pre-process</em> or <def>pre-compute</def> most of the computations. For this we'll have to delve a bit deeper into the reflectance equation: -</p> - -\[ - L_o(p,\omega_o) = \int\limits_{\Omega} - (k_d\frac{c}{\pi} + k_s\frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)}) - L_i(p,\omega_i) n \cdot \omega_i d\omega_i -\] - -<p> - Taking a good look at the reflectance equation we find that the diffuse \(k_d\) and specular \(k_s\) term of the BRDF are independent from each other and we can split the integral in two: - </p> - -\[ - L_o(p,\omega_o) = - \int\limits_{\Omega} (k_d\frac{c}{\pi}) L_i(p,\omega_i) n \cdot \omega_i d\omega_i - + - \int\limits_{\Omega} (k_s\frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)}) - L_i(p,\omega_i) n \cdot \omega_i d\omega_i -\] - -<p> - By splitting the integral in two parts we can focus on both the diffuse and specular term individually; the focus of this chapter being on the diffuse integral. -</p> - -<p> - Taking a closer look at the diffuse integral we find that the diffuse lambert term is a constant term (the color \(c\), the refraction ratio \(k_d\), and \(\pi\) are constant over the integral) and not dependent on any of the integral variables. Given this, we can move the constant term out of the diffuse integral: -</p> - -\[ - L_o(p,\omega_o) = - k_d\frac{c}{\pi} \int\limits_{\Omega} L_i(p,\omega_i) n \cdot \omega_i d\omega_i -\] - -<p> - This gives us an integral that only depends on \(w_i\) (assuming \(p\) is at the center of the environment map). With this knowledge, we can calculate or <em>pre-compute</em> a new cubemap that stores in each sample direction (or texel) \(w_o\) the diffuse integral's result by <def>convolution</def>. -</p> - -<p> - Convolution is applying some computation to each entry in a data set considering all other entries in the data set; the data set being the scene's radiance or environment map. Thus for every sample direction in the cubemap, we take all other sample directions over the hemisphere \(\Omega\) into account. -</p> - -<p> - To convolute an environment map we solve the integral for each output \(w_o\) sample direction by discretely sampling a large number of directions \(w_i\) over the hemisphere \(\Omega\) and averaging their radiance. The hemisphere we build the sample directions \(w_i\) from is oriented towards the output \(w_o\) sample direction we're convoluting. -</p> - -<img src="/img/pbr/ibl_hemisphere_sample.png" class="clean" alt="Convoluting a cubemap on a hemisphere for a PBR irradiance map."/> - -<p> - This pre-computed cubemap, that for each sample direction \(w_o\) stores the integral result, can be thought of as the pre-computed sum of all indirect diffuse light of the scene hitting some surface aligned along direction \(w_o\). Such a cubemap is known as an <def>irradiance map</def> seeing as the convoluted cubemap effectively allows us to directly sample the scene's (pre-computed) irradiance from any direction \(w_o\). -</p> - -<note> - The radiance equation also depends on a position \(p\), which we've assumed to be at the center of the irradiance map. This does mean all diffuse indirect light must come from a single environment map which may break the illusion of reality (especially indoors). Render engines solve this by placing <def>reflection probes</def> all over the scene where each reflection probes calculates its own irradiance map of its surroundings. This way, the irradiance (and radiance) at position \(p\) is the interpolated irradiance between its closest reflection probes. For now, we assume we always sample the environment map from its center. -</note> - -<p> - Below is an example of a cubemap environment map and its resulting irradiance map (courtesy of <a href="http://www.indiedb.com/features/using-image-based-lighting-ibl" target="_blank">wave engine</a>), averaging the scene's radiance for every direction \(w_o\). -</p> - -<img src="/img/pbr/ibl_irradiance.png" class="clean" alt="The effect of convoluting a cubemap environment map."/> - -<p> - By storing the convoluted result in each cubemap texel (in the direction of \(w_o\)), the irradiance map displays somewhat like an average color or lighting display of the environment. Sampling any direction from this environment map will give us the scene's irradiance in that particular direction. -</p> - - -<h2>PBR and HDR</h2> -<p> - We've briefly touched upon it in the <a href="https://learnopengl.com/PBR/Lighting" target="_blank">previous</a> chapter: taking the high dynamic range of your scene's lighting into account in a PBR pipeline is incredibly important. As PBR bases most of its inputs on real physical properties and measurements it makes sense to closely match the incoming light values to their physical equivalents. Whether we make educated guesses on each light's radiant flux or use their <a href="https://en.wikipedia.org/wiki/Lumen_(unit)" target="_blank">direct physical equivalent</a>, the difference between a simple light bulb or the sun is significant either way. Without working in an <a href="https://learnopengl.com/Advanced-Lighting/HDR" target="_blank">HDR</a> render environment it's impossible to correctly specify each light's relative intensity. -</p> - -<p> - So, PBR and HDR go hand in hand, but how does it all relate to image based lighting? We've seen in the previous chapter that it's relatively easy to get PBR working in HDR. However, seeing as for image based lighting we base the environment's indirect light intensity on the color values of an environment cubemap we need some way to store the lighting's high dynamic range into an environment map. -</p> - -<p> - The environment maps we've been using so far as cubemaps (used as <a href="https://learnopengl.com/Advanced-OpenGL/Cubemaps" target="_blank">skyboxes</a> for instance) are in low dynamic range (LDR). We directly used their color values from the individual face images, ranged between <code>0.0</code> and <code>1.0</code>, and processed them as is. While this may work fine for visual output, when taking them as physical input parameters it's not going to work. -</p> - -<h3>The radiance HDR file format</h3> -<p> - Enter the radiance file format. The radiance file format (with the <code>.hdr</code> extension) stores a full cubemap with all 6 faces as floating point data. This allows us to specify color values outside the <code>0.0</code> to <code>1.0</code> range to give lights their correct color intensities. The file format also uses a clever trick to store each floating point value, not as a 32 bit value per channel, but 8 bits per channel using the color's alpha channel as an exponent (this does come with a loss of precision). This works quite well, but requires the parsing program to re-convert each color to their floating point equivalent. -</p> - -<p> - There are quite a few radiance HDR environment maps freely available from sources like <a href="http://www.hdrlabs.com/sibl/archive.html" target="_blank">sIBL archive</a> of which you can see an example below: -</p> - -<img src="/img/pbr/ibl_hdr_radiance.png" alt="Example of an equirectangular map"/> - -<p> - This may not be exactly what you were expecting, as the image appears distorted and doesn't show any of the 6 individual cubemap faces of environment maps we've seen before. This environment map is projected from a sphere onto a flat plane such that we can more easily store the environment into a single image known as an <def>equirectangular map</def>. This does come with a small caveat as most of the visual resolution is stored in the horizontal view direction, while less is preserved in the bottom and top directions. In most cases this is a decent compromise as with almost any renderer you'll find most of the interesting lighting and surroundings in the horizontal viewing directions. -</p> - -<h3>HDR and stb_image.h</h3> -<p> - Loading radiance HDR images directly requires some knowledge of the <a href="http://radsite.lbl.gov/radiance/refer/Notes/picture_format.html" target="_blank">file format</a> which isn't too difficult, but cumbersome nonetheless. Lucky for us, the popular one header library <a href="https://github.com/nothings/stb/blob/master/stb_image.h" target="_blank">stb_image.h</a> supports loading radiance HDR images directly as an array of floating point values which perfectly fits our needs. With <code>stb_image</code> added to your project, loading an HDR image is now as simple as follows: -</p> - -<pre><code> -#include "stb_image.h" -[...] - -stbi_set_flip_vertically_on_load(true); -int width, height, nrComponents; -float *data = stbi_loadf("newport_loft.hdr", &width, &height, &nrComponents, 0); -unsigned int hdrTexture; -if (data) -{ - <function id='50'>glGenTextures</function>(1, &hdrTexture); - <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, hdrTexture); - <function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGB16F, width, height, 0, GL_RGB, GL_FLOAT, data); - - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - stbi_image_free(data); -} -else -{ - std::cout << "Failed to load HDR image." << std::endl; -} -</code></pre> - -<p> - <code>stb_image.h</code> automatically maps the HDR values to a list of floating point values: 32 bits per channel and 3 channels per color by default. This is all we need to store the equirectangular HDR environment map into a 2D floating point texture. -</p> - -<h3>From Equirectangular to Cubemap</h3> -<p> - It is possible to use the equirectangular map directly for environment lookups, but these operations can be relatively expensive in which case a direct cubemap sample is more performant. Therefore, in this chapter we'll first convert the equirectangular image to a cubemap for further processing. Note that in the process we also show how to sample an equirectangular map as if it was a 3D environment map in which case you're free to pick whichever solution you prefer. -</p> - -<p> - To convert an equirectangular image into a cubemap we need to render a (unit) cube and project the equirectangular map on all of the cube's faces from the inside and take 6 images of each of the cube's sides as a cubemap face. The vertex shader of this cube simply renders the cube as is and passes its local position to the fragment shader as a 3D sample vector: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; - -out vec3 localPos; - -uniform mat4 projection; -uniform mat4 view; - -void main() -{ - localPos = aPos; - gl_Position = projection * view * vec4(localPos, 1.0); -} -</code></pre> - -<p> - For the fragment shader, we color each part of the cube as if we neatly folded the equirectangular map onto each side of the cube. To accomplish this, we take the fragment's sample direction as interpolated from the cube's local position and then use this direction vector and some trigonometry magic (spherical to cartesian) to sample the equirectangular map as if it's a cubemap itself. We directly store the result onto the cube-face's fragment which should be all we need to do: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; -in vec3 localPos; - -uniform sampler2D equirectangularMap; - -const vec2 invAtan = vec2(0.1591, 0.3183); -vec2 SampleSphericalMap(vec3 v) -{ - vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); - uv *= invAtan; - uv += 0.5; - return uv; -} - -void main() -{ - vec2 uv = SampleSphericalMap(normalize(localPos)); // make sure to normalize localPos - vec3 color = texture(equirectangularMap, uv).rgb; - - FragColor = vec4(color, 1.0); -} - -</code></pre> - -<p> - If you render a cube at the center of the scene given an HDR equirectangular map you'll get something that looks like this: -</p> - - <img src="/img/pbr/ibl_equirectangular_projection.png" alt="OpenGL render of an equirectangular map converted to a cubemap."/> - -<p> - This demonstrates that we effectively mapped an equirectangular image onto a cubic shape, but doesn't yet help us in converting the source HDR image to a cubemap texture. To accomplish this we have to render the same cube 6 times, looking at each individual face of the cube, while recording its visual result with a <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">framebuffer</a> object: -</p> - -<pre><code> -unsigned int captureFBO, captureRBO; -<function id='76'>glGenFramebuffers</function>(1, &captureFBO); -<function id='82'>glGenRenderbuffers</function>(1, &captureRBO); - -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, captureFBO); -<function id='83'>glBindRenderbuffer</function>(GL_RENDERBUFFER, captureRBO); -<function id='88'>glRenderbufferStorage</function>(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512); -<function id='89'>glFramebufferRenderbuffer</function>(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, captureRBO); -</code></pre> - -<p> - Of course, we then also generate the corresponding cubemap color textures, pre-allocating memory for each of its 6 faces: -</p> - -<pre><code> -unsigned int envCubemap; -<function id='50'>glGenTextures</function>(1, &envCubemap); -<function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, envCubemap); -for (unsigned int i = 0; i < 6; ++i) -{ - // note that we store each face with 16 bit floating point values - <function id='52'>glTexImage2D</function>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, - 512, 512, 0, GL_RGB, GL_FLOAT, nullptr); -} -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -</code></pre> - -<p> - Then what's left to do is capture the equirectangular 2D texture onto the cubemap faces. -</p> - -<p> - I won't go over the details as the code details topics previously discussed in the <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">framebuffer</a> and <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows" target="_blank">point shadows</a> chapters, but it effectively boils down to setting up 6 different view matrices (facing each side of the cube), set up a projection matrix with a fov of <code>90</code> degrees to capture the entire face, and render a cube 6 times storing the results in a floating point framebuffer: -</p> - -<pre><code> -glm::mat4 captureProjection = <function id='58'>glm::perspective</function>(<function id='63'>glm::radians</function>(90.0f), 1.0f, 0.1f, 10.0f); -glm::mat4 captureViews[] = -{ - <function id='62'>glm::lookAt</function>(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), - <function id='62'>glm::lookAt</function>(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), - <function id='62'>glm::lookAt</function>(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)), - <function id='62'>glm::lookAt</function>(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f)), - <function id='62'>glm::lookAt</function>(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f)), - <function id='62'>glm::lookAt</function>(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f)) -}; - -// convert HDR equirectangular environment map to cubemap equivalent -equirectangularToCubemapShader.use(); -equirectangularToCubemapShader.setInt("equirectangularMap", 0); -equirectangularToCubemapShader.setMat4("projection", captureProjection); -<function id='49'>glActiveTexture</function>(GL_TEXTURE0); -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, hdrTexture); - -<function id='22'>glViewport</function>(0, 0, 512, 512); // don't forget to configure the viewport to the capture dimensions. -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, captureFBO); -for (unsigned int i = 0; i < 6; ++i) -{ - equirectangularToCubemapShader.setMat4("view", captureViews[i]); - <function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, envCubemap, 0); - <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - renderCube(); // renders a 1x1 cube -} -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -</code></pre> - -<p> - We take the color attachment of the framebuffer and switch its texture target around for every face of the cubemap, directly rendering the scene into one of the cubemap's faces. Once this routine has finished (which we only have to do once), the cubemap <var>envCubemap</var> should be the cubemapped environment version of our original HDR image. -</p> - -<p> - Let's test the cubemap by writing a very simple skybox shader to display the cubemap around us: -</p> - -<pre><code> -#version 330 core -layout (location = 0) in vec3 aPos; - -uniform mat4 projection; -uniform mat4 view; - -out vec3 localPos; - -void main() -{ - localPos = aPos; - - mat4 rotView = mat4(mat3(view)); // remove translation from the view matrix - vec4 clipPos = projection * rotView * vec4(localPos, 1.0); - - gl_Position = clipPos.xyww; -} -</code></pre> - -<p> - Note the <code>xyww</code> trick here that ensures the depth value of the rendered cube fragments always end up at <code>1.0</code>, the maximum depth value, as described in the <a href="https://learnopengl.com/Advanced-OpenGL/Cubemaps" target="_blank">cubemap</a> chapter. Do note that we need to change the depth comparison function to <var>GL_LEQUAL</var>: -</p> - -<pre><code> -<function id='66'>glDepthFunc</function>(GL_LEQUAL); -</code></pre> - -<p> - The fragment shader then directly samples the cubemap environment map using the cube's local fragment position: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; - -in vec3 localPos; - -uniform samplerCube environmentMap; - -void main() -{ - vec3 envColor = texture(environmentMap, localPos).rgb; - - envColor = envColor / (envColor + vec3(1.0)); - envColor = pow(envColor, vec3(1.0/2.2)); - - FragColor = vec4(envColor, 1.0); -} -</code></pre> - -<p> - We sample the environment map using its interpolated vertex cube positions that directly correspond to the correct direction vector to sample. Seeing as the camera's translation components are ignored, rendering this shader over a cube should give you the environment map as a non-moving background. Also, as we directly output the environment map's HDR values to the default LDR framebuffer, we want to properly tone map the color values. Furthermore, almost all HDR maps are in linear color space by default so we need to apply <a href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction" target="_blank">gamma correction</a> before writing to the default framebuffer. -</p> - -<p> - Now rendering the sampled environment map over the previously rendered spheres should look something like this: -</p> - - <img src="/img/pbr/ibl_hdr_environment_mapped.png" alt="Render the converted cubemap as a skybox."/> - -<p> - Well... it took us quite a bit of setup to get here, but we successfully managed to read an HDR environment map, convert it from its equirectangular mapping to a cubemap, and render the HDR cubemap into the scene as a skybox. Furthermore, we set up a small system to render onto all 6 faces of a cubemap, which we'll need again when <def>convoluting</def> the environment map. You can find the source code of the entire conversion process <a href="/code_viewer_gh.php?code=src/6.pbr/2.1.1.ibl_irradiance_conversion/ibl_irradiance_conversion.cpp" target="_blank">here</a>. -</p> - -<h2>Cubemap convolution</h2> -<p> - As described at the start of the chapter, our main goal is to solve the integral for all diffuse indirect lighting given the scene's irradiance in the form of a cubemap environment map. We know that we can get the radiance of the scene \(L(p, w_i)\) in a particular direction by sampling an HDR environment map in direction \(w_i\). To solve the integral, we have to sample the scene's radiance from all possible directions within the hemisphere \(\Omega\) for each fragment. - </p> - -<p> - It is however computationally impossible to sample the environment's lighting from every possible direction in \(\Omega\), the number of possible directions is theoretically infinite. We can however, approximate the number of directions by taking a finite number of directions or samples, spaced uniformly or taken randomly from within the hemisphere, to get a fairly accurate approximation of the irradiance; effectively solving the integral \(\int\) discretely -</p> - -<p> - It is however still too expensive to do this for every fragment in real-time as the number of samples needs to be significantly large for decent results, so we want to <def>pre-compute</def> this. Since the orientation of the hemisphere decides where we capture the irradiance, we can pre-calculate the irradiance for every possible hemisphere orientation oriented around all outgoing directions \(w_o\): -</p> - -\[ - L_o(p,\omega_o) = - k_d\frac{c}{\pi} \int\limits_{\Omega} L_i(p,\omega_i) n \cdot \omega_i d\omega_i -\] - -<p> - Given any direction vector \(w_i\) in the lighting pass, we can then sample the pre-computed irradiance map to retrieve the total diffuse irradiance from direction \(w_i\). To determine the amount of indirect diffuse (irradiant) light at a fragment surface, we retrieve the total irradiance from the hemisphere oriented around its surface normal. Obtaining the scene's irradiance is then as simple as: -</p> - -<pre><code> -vec3 irradiance = texture(irradianceMap, N).rgb; -</code></pre> - -<p> - Now, to generate the irradiance map, we need to convolute the environment's lighting as converted to a cubemap. Given that for each fragment the surface's hemisphere is oriented along the normal vector \(N\), convoluting a cubemap equals calculating the total averaged radiance of each direction \(w_i\) in the hemisphere \(\Omega\) oriented along \(N\). -</p> - -<img src="/img/pbr/ibl_hemisphere_sample_normal.png" class="clean" alt="Convoluting a cubemap on a hemisphere (oriented around the normal) for a PBR irradiance map."/> - -<p> - Thankfully, all of the cumbersome setup of this chapter isn't all for nothing as we can now directly take the converted cubemap, convolute it in a fragment shader, and capture its result in a new cubemap using a framebuffer that renders to all 6 face directions. As we've already set this up for converting the equirectangular environment map to a cubemap, we can take the exact same approach but use a different fragment shader: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; -in vec3 localPos; - -uniform samplerCube environmentMap; - -const float PI = 3.14159265359; - -void main() -{ - // the sample direction equals the hemisphere's orientation - vec3 normal = normalize(localPos); - - vec3 irradiance = vec3(0.0); - - [...] // convolution code - - FragColor = vec4(irradiance, 1.0); -} -</code></pre> - -<p> - With <var>environmentMap</var> being the HDR cubemap as converted from the equirectangular HDR environment map. -</p> - -<p> - There are many ways to convolute the environment map, but for this chapter we're going to generate a fixed amount of sample vectors for each cubemap texel along a hemisphere \(\Omega\) oriented around the sample direction and average the results. The fixed amount of sample vectors will be uniformly spread inside the hemisphere. Note that an integral is a continuous function and discretely sampling its function given a fixed amount of sample vectors will be an approximation. The more sample vectors we use, the better we approximate the integral. -</p> - -<p> - The integral \(\int\) of the reflectance equation revolves around the solid angle \(dw\) which is rather difficult to work with. Instead of integrating over the solid angle \(dw\) we'll integrate over its equivalent spherical coordinates \(\theta\) and \(\phi\). -</p> - - - <img src="/img/pbr/ibl_spherical_integrate.png" class="clean" alt="Converting the solid angle over the equivalent polar azimuth and inclination angle for PBR"/> - -<p> - We use the polar azimuth \(\phi\) angle to sample around the ring of the hemisphere between \(0\) and \(2\pi\), and use the inclination zenith \(\theta\) angle between \(0\) and \(\frac{1}{2}\pi\) to sample the increasing rings of the hemisphere. This will give us the updated reflectance integral: -</p> - -\[ - L_o(p,\phi_o, \theta_o) = - k_d\frac{c}{\pi} \int_{\phi = 0}^{2\pi} \int_{\theta = 0}^{\frac{1}{2}\pi} L_i(p,\phi_i, \theta_i) \cos(\theta) \sin(\theta) d\phi d\theta -\] - -<p> - Solving the integral requires us to take a fixed number of discrete samples within the hemisphere \(\Omega\) and averaging their results. This translates the integral to the following discrete version as based on the <a href="https://en.wikipedia.org/wiki/Riemann_sum" target="_blank">Riemann sum</a> given \(n1\) and \(n2\) discrete samples on each spherical coordinate respectively: -</p> - -\[ - L_o(p,\phi_o, \theta_o) = - k_d \frac{c\pi}{n1 n2} \sum_{\phi = 0}^{n1} \sum_{\theta = 0}^{n2} L_i(p,\phi_i, \theta_i) \cos(\theta) \sin(\theta) d\phi d\theta -\] - - -<p> - As we sample both spherical values discretely, each sample will approximate or average an area on the hemisphere as the image before shows. Note that (due to the general properties of a spherical shape) the hemisphere's discrete sample area gets smaller the higher the zenith angle \(\theta\) as the sample regions converge towards the center top. To compensate for the smaller areas, we weigh its contribution by scaling the area by \(\sin \theta\). -</p> - -<p> - Discretely sampling the hemisphere given the integral's spherical coordinates translates to the following fragment code: -</p> - -<pre><code> -vec3 irradiance = vec3(0.0); - -vec3 up = vec3(0.0, 1.0, 0.0); -vec3 right = normalize(cross(up, normal)); -up = normalize(cross(normal, right)); - -float sampleDelta = 0.025; -float nrSamples = 0.0; -for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta) -{ - for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta) - { - // spherical to cartesian (in tangent space) - vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); - // tangent space to world - vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; - - irradiance += texture(environmentMap, sampleVec).rgb * cos(theta) * sin(theta); - nrSamples++; - } -} -irradiance = PI * irradiance * (1.0 / float(nrSamples)); -</code></pre> - -<p> - We specify a fixed <var>sampleDelta</var> delta value to traverse the hemisphere; decreasing or increasing the sample delta will increase or decrease the accuracy respectively. -</p> - -<p> - From within both loops, we take both spherical coordinates to convert them to a 3D Cartesian sample vector, convert the sample from tangent to world space oriented around the normal, and use this sample vector to directly sample the HDR environment map. We add each sample result to <var>irradiance</var> which at the end we divide by the total number of samples taken, giving us the average sampled irradiance. Note that we scale the sampled color value by <code>cos(theta)</code> due to the light being weaker at larger angles and by <code>sin(theta)</code> to account for the smaller sample areas in the higher hemisphere areas. -</p> - -<p> - Now what's left to do is to set up the OpenGL rendering code such that we can convolute the earlier captured <var>envCubemap</var>. First we create the irradiance cubemap (again, we only have to do this once before the render loop): -</p> - -<pre><code> -unsigned int irradianceMap; -<function id='50'>glGenTextures</function>(1, &irradianceMap); -<function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, irradianceMap); -for (unsigned int i = 0; i < 6; ++i) -{ - <function id='52'>glTexImage2D</function>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 32, 32, 0, - GL_RGB, GL_FLOAT, nullptr); -} -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -</code></pre> - -<p> - As the irradiance map averages all surrounding radiance uniformly it doesn't have a lot of high frequency details, so we can store the map at a low resolution (32x32) and let OpenGL's linear filtering do most of the work. Next, we re-scale the capture framebuffer to the new resolution: -</p> - -<pre class="cpp"><code> -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, captureFBO); -<function id='83'>glBindRenderbuffer</function>(GL_RENDERBUFFER, captureRBO); -<function id='88'>glRenderbufferStorage</function>(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 32, 32); -</code></pre> - -<p> - Using the convolution shader, we render the environment map in a similar way to how we captured the environment cubemap: -</p> - -<pre><code> -irradianceShader.use(); -irradianceShader.setInt("environmentMap", 0); -irradianceShader.setMat4("projection", captureProjection); -<function id='49'>glActiveTexture</function>(GL_TEXTURE0); -<function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, envCubemap); - -<function id='22'>glViewport</function>(0, 0, 32, 32); // don't forget to configure the viewport to the capture dimensions. -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, captureFBO); -for (unsigned int i = 0; i < 6; ++i) -{ - irradianceShader.setMat4("view", captureViews[i]); - <function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, irradianceMap, 0); - <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - renderCube(); -} -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -</code></pre> - -<p> - Now after this routine we should have a pre-computed irradiance map that we can directly use for our diffuse image based lighting. To see if we successfully convoluted the environment map we'll substitute the environment map for the irradiance map as the skybox's environment sampler: -</p> - - <img src="/img/pbr/ibl_irradiance_map_background.png" alt="Displaying a PBR irradiance map as the skybox background."/> - -<p> - If it looks like a heavily blurred version of the environment map you've successfully convoluted the environment map. -</p> - -<h2>PBR and indirect irradiance lighting</h2> -<p> - The irradiance map represents the diffuse part of the reflectance integral as accumulated from all surrounding indirect light. Seeing as the light doesn't come from direct light sources, but from the surrounding environment, we treat both the diffuse and specular indirect lighting as the ambient lighting, replacing our previously set constant term. -</p> - -<p> - First, be sure to add the pre-calculated irradiance map as a cube sampler: -</p> - -<pre><code> -uniform samplerCube irradianceMap; -</code></pre> - -<p> - Given the irradiance map that holds all of the scene's indirect diffuse light, retrieving the irradiance influencing the fragment is as simple as a single texture sample given the surface normal: -</p> - -<pre><code> -// vec3 ambient = vec3(0.03); -vec3 ambient = texture(irradianceMap, N).rgb; -</code></pre> - -<p> - However, as the indirect lighting contains both a diffuse and specular part (as we've seen from the split version of the reflectance equation) we need to weigh the diffuse part accordingly. Similar to what we did in the previous chapter, we use the Fresnel equation to determine the surface's indirect reflectance ratio from which we derive the refractive (or diffuse) ratio: -</p> - -<pre><code> -vec3 kS = fresnelSchlick(max(dot(N, V), 0.0), F0); -vec3 kD = 1.0 - kS; -vec3 irradiance = texture(irradianceMap, N).rgb; -vec3 diffuse = irradiance * albedo; -vec3 ambient = (kD * diffuse) * ao; -</code></pre> - -<p> - As the ambient light comes from all directions within the hemisphere oriented around the normal <var>N</var>, there's no single halfway vector to determine the Fresnel response. To still simulate Fresnel, we calculate the Fresnel from the angle between the normal and view vector. However, earlier we used the micro-surface halfway vector, influenced by the roughness of the surface, as input to the Fresnel equation. As we currently don't take roughness into account, the surface's reflective ratio will always end up relatively high. Indirect light follows the same properties of direct light so we expect rougher surfaces to reflect less strongly on the surface edges. Because of this, the indirect Fresnel reflection strength looks off on rough non-metal surfaces (slightly exaggerated for demonstration purposes): -</p> - <img src="/img/pbr/lighting_fresnel_no_roughness.png" alt="The Fresnel equation for IBL without taking roughness into account."/> - -<p> - We can alleviate the issue by injecting a roughness term in the Fresnel-Schlick equation as described by <a href="https://seblagarde.wordpress.com/2011/08/17/hello-world/" target="_blank">Sébastien Lagarde</a>: -</p> - -<pre><code> -vec3 fresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness) -{ - return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); -} -</code></pre> - -<p> - By taking account of the surface's roughness when calculating the Fresnel response, the ambient code ends up as: -</p> - -<pre><code> -vec3 kS = fresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); -vec3 kD = 1.0 - kS; -vec3 irradiance = texture(irradianceMap, N).rgb; -vec3 diffuse = irradiance * albedo; -vec3 ambient = (kD * diffuse) * ao; -</code></pre> - -<p> - As you can see, the actual image based lighting computation is quite simple and only requires a single cubemap texture lookup; most of the work is in pre-computing or convoluting the irradiance map. -</p> - -<p> - If we take the initial scene from the PBR <a href="https://learnopengl.com/PBR/Lighting" target="_blank">lighting</a> chapter, where each sphere has a vertically increasing metallic and a horizontally increasing roughness value, and add the diffuse image based lighting it'll look a bit like this: -</p> - - <img src="/img/pbr/ibl_irradiance_result.png" alt="Result of convoluting an irradiance map in OpenGL used by the PBR shader."/> - -<p> - It still looks a bit weird as the more metallic spheres <strong>require</strong> some form of reflection to properly start looking like metallic surfaces (as metallic surfaces don't reflect diffuse light) which at the moment are only (barely) coming from the point light sources. Nevertheless, you can already tell the spheres do feel more <em>in place</em> within the environment (especially if you switch between environment maps) as the surface response reacts accordingly to the environment's ambient lighting. -</p> - -<p> - You can find the complete source code of the discussed topics <a href="/code_viewer_gh.php?code=src/6.pbr/2.1.2.ibl_irradiance/ibl_irradiance.cpp" target="_blank">here</a>. In the <a href="https://learnopengl.com/PBR/IBL/Specular-IBL" target="_blank">next</a> chapter we'll add the indirect specular part of the reflectance integral at which point we're really going to see the power of PBR. -</p> - -<h2>Further reading</h2> -<ul> - <li><a href="http://www.codinglabs.net/article_physically_based_rendering.aspx" target="_blank">Coding Labs: Physically based rendering</a>: an introduction to PBR and how and why to generate an irradiance map. </li> - <li><a href="http://www.scratchapixel.com/lessons/mathematics-physics-for-computer-graphics/mathematics-of-shading" target="_blank">The Mathematics of Shading</a>: a brief introduction by ScratchAPixel on several of the mathematics described in this tutorial, specifically on polar coordinates and integrals.</li> -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/PBR/IBL/Specular-IBL.html b/translation/PBR/IBL/Specular-IBL.html @@ -1,1126 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Specular IBL</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Specular IBL</h1> -<h1 id="content-url" style='display:none;'>PBR/IBL/Specular-IBL</h1> -<p> - In the <a href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance" target="_blank">previous</a> chapter we've set up PBR in combination with image based lighting by pre-computing an irradiance map as the lighting's indirect diffuse portion. In this chapter we'll focus on the specular part of the reflectance equation: -</p> - - \[ - L_o(p,\omega_o) = \int\limits_{\Omega} - (k_d\frac{c}{\pi} + k_s\frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)}) - L_i(p,\omega_i) n \cdot \omega_i d\omega_i - \] - -<p> - You'll notice that the Cook-Torrance specular portion (multiplied by \(kS\)) isn't constant over the integral and is dependent on the incoming light direction, but <strong>also</strong> the incoming view direction. Trying to solve the integral for all incoming light directions including all possible view directions is a combinatorial overload and way too expensive to calculate on a real-time basis. Epic Games proposed a solution where they were able to pre-convolute the specular part for real time purposes, given a few compromises, known as the <def>split sum approximation</def>. -</p> - -<p> - The split sum approximation splits the specular part of the reflectance equation into two separate parts that we can individually convolute and later combine in the PBR shader for specular indirect image based lighting. Similar to how we pre-convoluted the irradiance map, the split sum approximation requires an HDR environment map as its convolution input. To understand the split sum approximation we'll again look at the reflectance equation, but this time focus on the specular part: -</p> - -\[ - L_o(p,\omega_o) = - \int\limits_{\Omega} (k_s\frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)} - L_i(p,\omega_i) n \cdot \omega_i d\omega_i - = - \int\limits_{\Omega} f_r(p, \omega_i, \omega_o) L_i(p,\omega_i) n \cdot \omega_i d\omega_i -\] - -<p> - For the same (performance) reasons as the irradiance convolution, we can't solve the specular part of the integral in real time and expect a reasonable performance. So preferably we'd pre-compute this integral to get something like a specular IBL map, sample this map with the fragment's normal, and be done with it. However, this is where it gets a bit tricky. We were able to pre-compute the irradiance map as the integral only depended on \(\omega_i\) and we could move the constant diffuse albedo terms out of the integral. This time, the integral depends on more than just \(\omega_i\) as evident from the BRDF: -</p> - -\[ - f_r(p, w_i, w_o) = \frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)} -\] - -<p> - The integral also depends on \(w_o\), and we can't really sample a pre-computed cubemap with two direction vectors. The position \(p\) is irrelevant here as described in the previous chapter. Pre-computing this integral for every possible combination of \(\omega_i\) and \(\omega_o\) isn't practical in a real-time setting. -</p> - -<p> - Epic Games' split sum approximation solves the issue by splitting the pre-computation into 2 individual parts that we can later combine to get the resulting pre-computed result we're after. The split sum approximation splits the specular integral into two separate integrals: -</p> - -\[ - L_o(p,\omega_o) = - \int\limits_{\Omega} L_i(p,\omega_i) d\omega_i - * - \int\limits_{\Omega} f_r(p, \omega_i, \omega_o) n \cdot \omega_i d\omega_i -\] - -<p> - The first part (when convoluted) is known as the <def>pre-filtered environment map</def> which is (similar to the irradiance map) a pre-computed environment convolution map, but this time taking roughness into account. For increasing roughness levels, the environment map is convoluted with more scattered sample vectors, creating blurrier reflections. For each roughness level we convolute, we store the sequentially blurrier results in the pre-filtered map's mipmap levels. For instance, a pre-filtered environment map storing the pre-convoluted result of 5 different roughness values in its 5 mipmap levels looks as follows: -</p> - -<img src="/img/pbr/ibl_prefilter_map.png" class="clean" alt="Pre-convoluted environment map over 5 roughness levels for PBR"/> - - - <p> - We generate the sample vectors and their scattering amount using the normal distribution function (NDF) of the Cook-Torrance BRDF that takes as input both a normal and view direction. As we don't know beforehand the view direction when convoluting the environment map, Epic Games makes a further approximation by assuming the view direction (and thus the specular reflection direction) to be equal to the output sample direction \(\omega_o\). This translates itself to the following code: -</p> - -<pre><code> -vec3 N = normalize(w_o); -vec3 R = N; -vec3 V = R; -</code></pre> - -<p> - This way, the pre-filtered environment convolution doesn't need to be aware of the view direction. This does mean we don't get nice grazing specular reflections when looking at specular surface reflections from an angle as seen in the image below (courtesy of the <em>Moving Frostbite to PBR</em> article); this is however generally considered an acceptable compromise: -</p> - -<img src="/img/pbr/ibl_grazing_angles.png" class="clean" alt="Removing grazing specular reflections with the split sum approximation of V = R = N."/> - -<p> - The second part of the split sum equation equals the BRDF part of the specular integral. If we pretend the incoming radiance is completely white for every direction (thus \(L(p, x) = 1.0\)) we can pre-calculate the BRDF's response given an input roughness and an input angle between the normal \(n\) and light direction \(\omega_i\), or \(n \cdot \omega_i\). Epic Games stores the pre-computed BRDF's response to each normal and light direction combination on varying roughness values in a 2D lookup texture (LUT) known as the <def>BRDF integration</def> map. The 2D lookup texture outputs a scale (red) and a bias value (green) to the surface's Fresnel response giving us the second part of the split specular integral: -</p> - - <img src="/img/pbr/ibl_brdf_lut.png" alt="Visualization of the 2D BRDF LUT according to the split sum approximation for PBR in OpenGL."/> - -<p> - We generate the lookup texture by treating the horizontal texture coordinate (ranged between <code>0.0</code> and <code>1.0</code>) of a plane as the BRDF's input \(n \cdot \omega_i\), and its vertical texture coordinate as the input roughness value. With this BRDF integration map and the pre-filtered environment map we can combine both to get the result of the specular integral: -</p> - -<pre><code> -float lod = getMipLevelFromRoughness(roughness); -vec3 prefilteredColor = textureCubeLod(PrefilteredEnvMap, refVec, lod); -vec2 envBRDF = texture2D(BRDFIntegrationMap, vec2(NdotV, roughness)).xy; -vec3 indirectSpecular = prefilteredColor * (F * envBRDF.x + envBRDF.y) -</code></pre> - -<p> - This should give you a bit of an overview on how Epic Games' split sum approximation roughly approaches the indirect specular part of the reflectance equation. Let's now try and build the pre-convoluted parts ourselves. -</p> - -<h2>Pre-filtering an HDR environment map</h2> -<p> - Pre-filtering an environment map is quite similar to how we convoluted an irradiance map. The difference being that we now account for roughness and store sequentially rougher reflections in the pre-filtered map's mip levels. -</p> - -<p> - First, we need to generate a new cubemap to hold the pre-filtered environment map data. To make sure we allocate enough memory for its mip levels we call <fun><function id='51'>glGenerateMipmap</function></fun> as an easy way to allocate the required amount of memory: -</p> - -<pre><code> -unsigned int prefilterMap; -<function id='50'>glGenTextures</function>(1, &prefilterMap); -<function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, prefilterMap); -for (unsigned int i = 0; i < 6; ++i) -{ - <function id='52'>glTexImage2D</function>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 128, 128, 0, GL_RGB, GL_FLOAT, nullptr); -} -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - -<function id='51'>glGenerateMipmap</function>(GL_TEXTURE_CUBE_MAP); -</code></pre> - -<p> - Note that because we plan to sample <var>prefilterMap</var>'s mipmaps you'll need to make sure its minification filter is set to <var>GL_LINEAR_MIPMAP_LINEAR</var> to enable trilinear filtering. We store the pre-filtered specular reflections in a per-face resolution of 128 by 128 at its base mip level. This is likely to be enough for most reflections, but if you have a large number of smooth materials (think of car reflections) you may want to increase the resolution. -</p> - -<p> - In the previous chapter we convoluted the environment map by generating sample vectors uniformly spread over the hemisphere \(\Omega\) using spherical coordinates. While this works just fine for irradiance, for specular reflections it's less efficient. When it comes to specular reflections, based on the roughness of a surface, the light reflects closely or roughly around a reflection vector \(r\) over a normal \(n\), but (unless the surface is extremely rough) around the reflection vector nonetheless: -</p> - - <img src="/img/pbr/ibl_specular_lobe.png" class="clean" alt="Specular lobe according to the PBR microfacet surface model."/> - -<p> - The general shape of possible outgoing light reflections is known as the <def>specular lobe</def>. As roughness increases, the specular lobe's size increases; and the shape of the specular lobe changes on varying incoming light directions. The shape of the specular lobe is thus highly dependent on the material. -</p> - -<p> - When it comes to the microsurface model, we can imagine the specular lobe as the reflection orientation about the microfacet halfway vectors given some incoming light direction. Seeing as most light rays end up in a specular lobe reflected around the microfacet halfway vectors, it makes sense to generate the sample vectors in a similar fashion as most would otherwise be wasted. This process is known as <def>importance sampling</def>. -</p> - -<h3>Monte Carlo integration and importance sampling</h3> -<p> - To fully get a grasp of importance sampling it's relevant we first delve into the mathematical construct known as <def>Monte Carlo integration</def>. Monte Carlo integration revolves mostly around a combination of statistics and probability theory. Monte Carlo helps us in discretely solving the problem of figuring out some statistic or value of a population without having to take <strong>all</strong> of the population into consideration. -</p> - -<p> - For instance, let's say you want to count the average height of all citizens of a country. To get your result, you could measure <strong>every</strong> citizen and average their height which will give you the <strong>exact</strong> answer you're looking for. However, since most countries have a considerable population this isn't a realistic approach: it would take too much effort and time. -</p> - -<p> - A different approach is to pick a much smaller <strong>completely random</strong> (unbiased) subset of this population, measure their height, and average the result. This population could be as small as a 100 people. While not as accurate as the exact answer, you'll get an answer that is relatively close to the ground truth. This is known as the <def>law of large numbers</def>. The idea is that if you measure a smaller set of size \(N\) of truly random samples from the total population, the result will be relatively close to the true answer and gets closer as the number of samples \(N\) increases. -</p> - -<p> - Monte Carlo integration builds on this law of large numbers and takes the same approach in solving an integral. Rather than solving an integral for all possible (theoretically infinite) sample values \(x\), simply generate \(N\) sample values randomly picked from the total population and average. As \(N\) increases, we're guaranteed to get a result closer to the exact answer of the integral: -</p> - -\[ - O = \int\limits_{a}^{b} f(x) dx - = - \frac{1}{N} \sum_{i=0}^{N-1} \frac{f(x)}{pdf(x)} -\] - -<p> - To solve the integral, we take \(N\) random samples over the population \(a\) to \(b\), add them together, and divide by the total number of samples to average them. The \(pdf\) stands for the <def>probability density function</def> that tells us the probability a specific sample occurs over the total sample set. For instance, the pdf of the height of a population would look a bit like this: -</p> - -<img src="/img/pbr/ibl_pdf.png" class="clean" alt="Example PDF (probability distribution function)."/> - -<p> - From this graph we can see that if we take any random sample of the population, there is a higher chance of picking a sample of someone of height 1.70, compared to the lower probability of the sample being of height 1.50. - </p> - -<p> -When it comes to Monte Carlo integration, some samples may have a higher probability of being generated than others. This is why for any general Monte Carlo estimation we divide or multiply the sampled value by the sample probability according to a pdf. So far, in each of our cases of estimating an integral, the samples we've generated were uniform, having the exact same chance of being generated. Our estimations so far were <def>unbiased</def>, meaning that given an ever-increasing amount of samples we will eventually <def>converge</def> to the <strong>exact</strong> solution of the integral. -</p> - -<p> - However, some Monte Carlo estimators are <def>biased</def>, meaning that the generated samples aren't completely random, but focused towards a specific value or direction. These biased Monte Carlo estimators have a <def>faster rate of convergence</def>, meaning they can converge to the exact solution at a much faster rate, but due to their biased nature it's likely they won't ever converge to the exact solution. This is generally an acceptable tradeoff, especially in computer graphics, as the exact solution isn't too important as long as the results are visually acceptable. - As we'll soon see with importance sampling (which uses a biased estimator), the generated samples are biased towards specific directions in which case we account for this by multiplying or dividing each sample by its corresponding pdf. - </p> - -<p> - Monte Carlo integration is quite prevalent in computer graphics as it's a fairly intuitive way to approximate continuous integrals in a discrete and efficient fashion: take any area/volume to sample over (like the hemisphere \(\Omega\)), generate \(N\) amount of random samples within the area/volume, and sum and weigh every sample contribution to the final result. -</p> - -<p> - Monte Carlo integration is an extensive mathematical topic and I won't delve much further into the specifics, but we'll mention that there are multiple ways of generating the <em>random samples</em>. By default, each sample is completely (pseudo)random as we're used to, but by utilizing certain properties of semi-random sequences we can generate sample vectors that are still random, but have interesting properties. For instance, we can do Monte Carlo integration on something called <def>low-discrepancy sequences</def> which still generate random samples, but each sample is more evenly distributed (image courtesy of James Heald): -</p> - - <img src="/img/pbr/ibl_low_discrepancy_sequence.png" class="clean" alt="Low discrepancy sequence."/> - -<p> - When using a low-discrepancy sequence for generating the Monte Carlo sample vectors, the process is known as <def>Quasi-Monte Carlo integration</def>. Quasi-Monte Carlo methods have a faster <def>rate of convergence</def> which makes them interesting for performance heavy applications. -</p> - -<p> - Given our newly obtained knowledge of Monte Carlo and Quasi-Monte Carlo integration, there is an interesting property we can use for an even faster rate of convergence known as <def>importance sampling</def>. We've mentioned it before in this chapter, but when it comes to specular reflections of light, the reflected light vectors are constrained in a specular lobe with its size determined by the roughness of the surface. Seeing as any (quasi-)randomly generated sample outside the specular lobe isn't relevant to the specular integral it makes sense to focus the sample generation to within the specular lobe, at the cost of making the Monte Carlo estimator biased. -</p> - -<p> - This is in essence what importance sampling is about: generate sample vectors in some region constrained by the roughness oriented around the microfacet's halfway vector. By combining Quasi-Monte Carlo sampling with a low-discrepancy sequence and biasing the sample vectors using importance sampling, we get a high rate of convergence. Because we reach the solution at a faster rate, we'll need significantly fewer samples to reach an approximation that is sufficient enough. -</p> - -<h3>A low-discrepancy sequence</h3> -<p> - In this chapter we'll pre-compute the specular portion of the indirect reflectance equation using importance sampling given a random low-discrepancy sequence based on the Quasi-Monte Carlo method. The sequence we'll be using is known as the <def>Hammersley Sequence</def> as carefully described by <a href="http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html" target="_blank">Holger Dammertz</a>. The Hammersley sequence is based on the <def>Van Der Corput</def> sequence which mirrors a decimal binary representation around its decimal point. -</p> - -<p> - Given some neat bit tricks, we can quite efficiently generate the Van Der Corput sequence in a shader program which we'll use to get a Hammersley sequence sample <var>i</var> over <code>N</code> total samples: -</p> - -<pre><code> -float RadicalInverse_VdC(uint bits) -{ - bits = (bits << 16u) | (bits >> 16u); - bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); - bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); - bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); - bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); - return float(bits) * 2.3283064365386963e-10; // / 0x100000000 -} -// ---------------------------------------------------------------------------- -vec2 Hammersley(uint i, uint N) -{ - return vec2(float(i)/float(N), RadicalInverse_VdC(i)); -} -</code></pre> - -<p> - The GLSL <fun>Hammersley</fun> function gives us the low-discrepancy sample <var>i</var> of the total sample set of size <var>N</var>. -</p> - -<note> -<strong>Hammersley sequence without bit operator support</strong><br/> -<p> - Not all OpenGL related drivers support bit operators (WebGL and OpenGL ES 2.0 for instance) in which case you may want to use an alternative version of the Van Der Corput Sequence that doesn't rely on bit operators: -</p> - -<pre><code> -float VanDerCorput(uint n, uint base) -{ - float invBase = 1.0 / float(base); - float denom = 1.0; - float result = 0.0; - - for(uint i = 0u; i < 32u; ++i) - { - if(n > 0u) - { - denom = mod(float(n), 2.0); - result += denom * invBase; - invBase = invBase / 2.0; - n = uint(float(n) / 2.0); - } - } - - return result; -} -// ---------------------------------------------------------------------------- -vec2 HammersleyNoBitOps(uint i, uint N) -{ - return vec2(float(i)/float(N), VanDerCorput(i, 2u)); -} -</code></pre> - -<p> - Note that due to GLSL loop restrictions in older hardware, the sequence loops over all possible <code>32</code> bits. This version is less performant, but does work on all hardware if you ever find yourself without bit operators. -</p> -</note> - -<h3>GGX Importance sampling</h3> -<p> - Instead of uniformly or randomly (Monte Carlo) generating sample vectors over the integral's hemisphere \(\Omega\), we'll generate sample vectors biased towards the general reflection orientation of the microsurface halfway vector based on the surface's roughness. The sampling process will be similar to what we've seen before: begin a large loop, generate a random (low-discrepancy) sequence value, take the sequence value to generate a sample vector in tangent space, transform to world space, and sample the scene's radiance. What's different is that we now use a low-discrepancy sequence value as input to generate a sample vector: -</p> - -<pre><code> -const uint SAMPLE_COUNT = 4096u; -for(uint i = 0u; i < SAMPLE_COUNT; ++i) -{ - vec2 Xi = Hammersley(i, SAMPLE_COUNT); -</code></pre> - -<p> - Additionally, to build a sample vector, we need some way of orienting and biasing the sample vector towards the specular lobe of some surface roughness. We can take the NDF as described in the <a href="https://learnopengl.com/PBR/Theory" target="_blank">theory</a> chapter and combine the GGX NDF in the spherical sample vector process as described by Epic Games: -</p> - -<pre><code> -vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness) -{ - float a = roughness*roughness; - - float phi = 2.0 * PI * Xi.x; - float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)); - float sinTheta = sqrt(1.0 - cosTheta*cosTheta); - - // from spherical coordinates to cartesian coordinates - vec3 H; - H.x = cos(phi) * sinTheta; - H.y = sin(phi) * sinTheta; - H.z = cosTheta; - - // from tangent-space vector to world-space sample vector - vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); - vec3 tangent = normalize(cross(up, N)); - vec3 bitangent = cross(N, tangent); - - vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; - return normalize(sampleVec); -} -</code></pre> - -<p> - This gives us a sample vector somewhat oriented around the expected microsurface's halfway vector based on some input roughness and the low-discrepancy sequence value <var>Xi</var>. Note that Epic Games uses the squared roughness for better visual results as based on Disney's original PBR research. -</p> - -<p> - With the low-discrepancy Hammersley sequence and sample generation defined, we can finalize the pre-filter convolution shader: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; -in vec3 localPos; - -uniform samplerCube environmentMap; -uniform float roughness; - -const float PI = 3.14159265359; - -float RadicalInverse_VdC(uint bits); -vec2 Hammersley(uint i, uint N); -vec3 ImportanceSampleGGX(vec2 Xi, vec3 N, float roughness); - -void main() -{ - vec3 N = normalize(localPos); - vec3 R = N; - vec3 V = R; - - const uint SAMPLE_COUNT = 1024u; - float totalWeight = 0.0; - vec3 prefilteredColor = vec3(0.0); - for(uint i = 0u; i < SAMPLE_COUNT; ++i) - { - vec2 Xi = Hammersley(i, SAMPLE_COUNT); - vec3 H = ImportanceSampleGGX(Xi, N, roughness); - vec3 L = normalize(2.0 * dot(V, H) * H - V); - - float NdotL = max(dot(N, L), 0.0); - if(NdotL > 0.0) - { - prefilteredColor += texture(environmentMap, L).rgb * NdotL; - totalWeight += NdotL; - } - } - prefilteredColor = prefilteredColor / totalWeight; - - FragColor = vec4(prefilteredColor, 1.0); -} - -</code></pre> - -<p> - We pre-filter the environment, based on some input roughness that varies over each mipmap level of the pre-filter cubemap (from <code>0.0</code> to <code>1.0</code>), and store the result in <var>prefilteredColor</var>. The resulting <var>prefilteredColor</var> is divided by the total sample weight, where samples with less influence on the final result (for small <var>NdotL</var>) contribute less to the final weight. -</p> - -<h3>Capturing pre-filter mipmap levels</h3> -<p> - What's left to do is let OpenGL pre-filter the environment map with different roughness values over multiple mipmap levels. This is actually fairly easy to do with the original setup of the <a href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance" target="_blank">irradiance</a> chapter: -</p> - -<pre><code> -prefilterShader.use(); -prefilterShader.setInt("environmentMap", 0); -prefilterShader.setMat4("projection", captureProjection); -<function id='49'>glActiveTexture</function>(GL_TEXTURE0); -<function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, envCubemap); - -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, captureFBO); -unsigned int maxMipLevels = 5; -for (unsigned int mip = 0; mip < maxMipLevels; ++mip) -{ - // reisze framebuffer according to mip-level size. - unsigned int mipWidth = 128 * std::pow(0.5, mip); - unsigned int mipHeight = 128 * std::pow(0.5, mip); - <function id='83'>glBindRenderbuffer</function>(GL_RENDERBUFFER, captureRBO); - <function id='88'>glRenderbufferStorage</function>(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mipWidth, mipHeight); - <function id='22'>glViewport</function>(0, 0, mipWidth, mipHeight); - - float roughness = (float)mip / (float)(maxMipLevels - 1); - prefilterShader.setFloat("roughness", roughness); - for (unsigned int i = 0; i < 6; ++i) - { - prefilterShader.setMat4("view", captureViews[i]); - <function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, prefilterMap, mip); - - <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - renderCube(); - } -} -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -</code></pre> - -<p> - The process is similar to the irradiance map convolution, but this time we scale the framebuffer's dimensions to the appropriate mipmap scale, each mip level reducing the dimensions by a scale of 2. Additionally, we specify the mip level we're rendering into in <fun><function id='81'>glFramebufferTexture2D</function></fun>'s last parameter and pass the roughness we're pre-filtering for to the pre-filter shader. -</p> - -<p> - This should give us a properly pre-filtered environment map that returns blurrier reflections the higher mip level we access it from. If we use the pre-filtered environment cubemap in the skybox shader and forcefully sample somewhat above its first mip level like so: -</p> - -<pre><code> -vec3 envColor = textureLod(environmentMap, WorldPos, 1.2).rgb; -</code></pre> - -<p> - We get a result that indeed looks like a blurrier version of the original environment: -</p> - -<img src="/img/pbr/ibl_prefilter_map_sample.png" alt="Visualizing a LOD mip level of the pre-filtered environment map in the skybox."/> - -<p> - If it looks somewhat similar you've successfully pre-filtered the HDR environment map. Play around with different mipmap levels to see the pre-filter map gradually change from sharp to blurry reflections on increasing mip levels. -</p> - - -<h2>Pre-filter convolution artifacts</h2> -<p> - While the current pre-filter map works fine for most purposes, sooner or later you'll come across several render artifacts that are directly related to the pre-filter convolution. I'll list the most common here including how to fix them. -</p> - -<h3>Cubemap seams at high roughness</h3> -<p> - Sampling the pre-filter map on surfaces with a rough surface means sampling the pre-filter map on some of its lower mip levels. When sampling cubemaps, OpenGL by default doesn't linearly interpolate <strong>across</strong> cubemap faces. Because the lower mip levels are both of a lower resolution and the pre-filter map is convoluted with a much larger sample lobe, the lack of <em>between-cube-face filtering</em> becomes quite apparent: -</p> - -<img src="/img/pbr/ibl_prefilter_seams.png" alt="Visible cubemap seams in the pre-filter map."/> - -<p> - Luckily for us, OpenGL gives us the option to properly filter across cubemap faces by enabling <var>GL_TEXTURE_CUBE_MAP_SEAMLESS</var>: -</p> - -<pre><code> -<function id='60'>glEnable</function>(GL_TEXTURE_CUBE_MAP_SEAMLESS); -</code></pre> - -<p> - Simply enable this property somewhere at the start of your application and the seams will be gone. -</p> - -<h3>Bright dots in the pre-filter convolution</h3> -<p> - Due to high frequency details and wildly varying light intensities in specular reflections, convoluting the specular reflections requires a large number of samples to properly account for the wildly varying nature of HDR environmental reflections. We already take a very large number of samples, but on some environments it may still not be enough at some of the rougher mip levels in which case you'll start seeing dotted patterns emerge around bright areas: -</p> - - <img src="/img/pbr/ibl_prefilter_dots.png" alt="Visible dots on high frequency HDR maps in the deeper mip LOD levels of a pre-filter map."/> - -<p> - One option is to further increase the sample count, but this won't be enough for all environments. As described by <a href="https://chetanjags.wordpress.com/2015/08/26/image-based-lighting/" target="_blank">Chetan Jags</a> we can reduce this artifact by (during the pre-filter convolution) not directly sampling the environment map, but sampling a mip level of the environment map based on the integral's PDF and the roughness: -</p> - -<pre><code> -float D = DistributionGGX(NdotH, roughness); -float pdf = (D * NdotH / (4.0 * HdotV)) + 0.0001; - -float resolution = 512.0; // resolution of source cubemap (per face) -float saTexel = 4.0 * PI / (6.0 * resolution * resolution); -float saSample = 1.0 / (float(SAMPLE_COUNT) * pdf + 0.0001); - -float mipLevel = roughness == 0.0 ? 0.0 : 0.5 * log2(saSample / saTexel); -</code></pre> - -<p> - Don't forget to enable trilinear filtering on the environment map you want to sample its mip levels from: -</p> - -<pre><code> -<function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, envCubemap); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); -</code></pre> - -<p> - And let OpenGL generate the mipmaps <strong>after</strong> the cubemap's base texture is set: -</p> - -<pre><code> -// convert HDR equirectangular environment map to cubemap equivalent -[...] -// then generate mipmaps -<function id='48'>glBindTexture</function>(GL_TEXTURE_CUBE_MAP, envCubemap); -<function id='51'>glGenerateMipmap</function>(GL_TEXTURE_CUBE_MAP); -</code></pre> - -<p> - This works surprisingly well and should remove most, if not all, dots in your pre-filter map on rougher surfaces. -</p> - -<h2>Pre-computing the BRDF</h2> -<p> - With the pre-filtered environment up and running, we can focus on the second part of the split-sum approximation: the BRDF. Let's briefly review the specular split sum approximation again: -</p> - -\[ - L_o(p,\omega_o) = - \int\limits_{\Omega} L_i(p,\omega_i) d\omega_i - * - \int\limits_{\Omega} f_r(p, \omega_i, \omega_o) n \cdot \omega_i d\omega_i -\] - -<p> - We've pre-computed the left part of the split sum approximation in the pre-filter map over different roughness levels. The right side requires us to convolute the BRDF equation over the angle \(n \cdot \omega_o\), the surface roughness, and Fresnel's \(F_0\). This is similar to integrating the specular BRDF with a solid-white environment or a constant radiance \(L_i\) of <code>1.0</code>. Convoluting the BRDF over 3 variables is a bit much, but we can try to move \(F_0\) out of the specular BRDF equation: -</p> - -\[ - \int\limits_{\Omega} f_r(p, \omega_i, \omega_o) n \cdot \omega_i d\omega_i = \int\limits_{\Omega} f_r(p, \omega_i, \omega_o) \frac{F(\omega_o, h)}{F(\omega_o, h)} n \cdot \omega_i d\omega_i -\] - -<p> - With \(F\) being the Fresnel equation. Moving the Fresnel denominator to the BRDF gives us the following equivalent equation: -</p> - -\[ - \int\limits_{\Omega} \frac{f_r(p, \omega_i, \omega_o)}{F(\omega_o, h)} F(\omega_o, h) n \cdot \omega_i d\omega_i -\] - -<p> - Substituting the right-most \(F\) with the Fresnel-Schlick approximation gives us: - </p> - -\[ - \int\limits_{\Omega} \frac{f_r(p, \omega_i, \omega_o)}{F(\omega_o, h)} (F_0 + (1 - F_0){(1 - \omega_o \cdot h)}^5) n \cdot \omega_i d\omega_i -\] - -<p> - Let's replace \({(1 - \omega_o \cdot h)}^5\) by \(\alpha\) to make it easier to solve for \(F_0\): -</p> - -\[ - \int\limits_{\Omega} \frac{f_r(p, \omega_i, \omega_o)}{F(\omega_o, h)} (F_0 + (1 - F_0)\alpha) n \cdot \omega_i d\omega_i -\] - -\[ - \int\limits_{\Omega} \frac{f_r(p, \omega_i, \omega_o)}{F(\omega_o, h)} (F_0 + 1*\alpha - F_0*\alpha) n \cdot \omega_i d\omega_i -\] - -\[ - \int\limits_{\Omega} \frac{f_r(p, \omega_i, \omega_o)}{F(\omega_o, h)} (F_0 * (1 - \alpha) + \alpha) n \cdot \omega_i d\omega_i -\] - -<p> - Then we split the Fresnel function \(F\) over two integrals: -</p> - -\[ - \int\limits_{\Omega} \frac{f_r(p, \omega_i, \omega_o)}{F(\omega_o, h)} (F_0 * (1 - \alpha)) n \cdot \omega_i d\omega_i - + - \int\limits_{\Omega} \frac{f_r(p, \omega_i, \omega_o)}{F(\omega_o, h)} (\alpha) n \cdot \omega_i d\omega_i -\] - -<p> - This way, \(F_0\) is constant over the integral and we can take \(F_0\) out of the integral. Next, we substitute \(\alpha\) back to its original form giving us the final split sum BRDF equation: -</p> - -\[ - F_0 \int\limits_{\Omega} f_r(p, \omega_i, \omega_o)(1 - {(1 - \omega_o \cdot h)}^5) n \cdot \omega_i d\omega_i - + - \int\limits_{\Omega} f_r(p, \omega_i, \omega_o) {(1 - \omega_o \cdot h)}^5 n \cdot \omega_i d\omega_i -\] - -<p> - The two resulting integrals represent a scale and a bias to \(F_0\) respectively. Note that as \(f_r(p, \omega_i, \omega_o)\) already contains a term for \(F\) they both cancel out, removing \(F\) from \(f_r\). -</p> - - <p> - In a similar fashion to the earlier convoluted environment maps, we can convolute the BRDF equations on their inputs: the angle between \(n\) and \(\omega_o\), and the roughness. We store the convoluted results in a 2D lookup texture (LUT) known as a <def>BRDF integration</def> map that we later use in our PBR lighting shader to get the final convoluted indirect specular result. -</p> - -<p> - The BRDF convolution shader operates on a 2D plane, using its 2D texture coordinates directly as inputs to the BRDF convolution (<var>NdotV</var> and <var>roughness</var>). The convolution code is largely similar to the pre-filter convolution, except that it now processes the sample vector according to our BRDF's geometry function and Fresnel-Schlick's approximation: -</p> - -<pre><code> -vec2 IntegrateBRDF(float NdotV, float roughness) -{ - vec3 V; - V.x = sqrt(1.0 - NdotV*NdotV); - V.y = 0.0; - V.z = NdotV; - - float A = 0.0; - float B = 0.0; - - vec3 N = vec3(0.0, 0.0, 1.0); - - const uint SAMPLE_COUNT = 1024u; - for(uint i = 0u; i < SAMPLE_COUNT; ++i) - { - vec2 Xi = Hammersley(i, SAMPLE_COUNT); - vec3 H = ImportanceSampleGGX(Xi, N, roughness); - vec3 L = normalize(2.0 * dot(V, H) * H - V); - - float NdotL = max(L.z, 0.0); - float NdotH = max(H.z, 0.0); - float VdotH = max(dot(V, H), 0.0); - - if(NdotL > 0.0) - { - float G = GeometrySmith(N, V, L, roughness); - float G_Vis = (G * VdotH) / (NdotH * NdotV); - float Fc = pow(1.0 - VdotH, 5.0); - - A += (1.0 - Fc) * G_Vis; - B += Fc * G_Vis; - } - } - A /= float(SAMPLE_COUNT); - B /= float(SAMPLE_COUNT); - return vec2(A, B); -} -// ---------------------------------------------------------------------------- -void main() -{ - vec2 integratedBRDF = IntegrateBRDF(TexCoords.x, TexCoords.y); - FragColor = integratedBRDF; -} -</code></pre> - -<p> - As you can see, the BRDF convolution is a direct translation from the mathematics to code. We take both the angle \(\theta\) and the roughness as input, generate a sample vector with importance sampling, process it over the geometry and the derived Fresnel term of the BRDF, and output both a scale and a bias to \(F_0\) for each sample, averaging them in the end. -</p> - -<p> - You may recall from the <a href="https://learnopengl.com/PBR/Theory" target="_blank">theory</a> chapter that the geometry term of the BRDF is slightly different when used alongside IBL as its \(k\) variable has a slightly different interpretation: -</p> - -\[ - k_{direct} = \frac{(\alpha + 1)^2}{8} -\] - -\[ - k_{IBL} = \frac{\alpha^2}{2} -\] - -<p> - Since the BRDF convolution is part of the specular IBL integral we'll use \(k_{IBL}\) for the Schlick-GGX geometry function: -</p> - -<pre><code> -float GeometrySchlickGGX(float NdotV, float roughness) -{ - float a = roughness; - float k = (a * a) / 2.0; - - float nom = NdotV; - float denom = NdotV * (1.0 - k) + k; - - return nom / denom; -} -// ---------------------------------------------------------------------------- -float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) -{ - float NdotV = max(dot(N, V), 0.0); - float NdotL = max(dot(N, L), 0.0); - float ggx2 = GeometrySchlickGGX(NdotV, roughness); - float ggx1 = GeometrySchlickGGX(NdotL, roughness); - - return ggx1 * ggx2; -} -</code></pre> - -<p> - Note that while \(k\) takes <var>a</var> as its parameter we didn't square <var>roughness</var> as <var>a</var> as we originally did for other interpretations of <var>a</var>; likely as <var>a</var> is squared here already. I'm not sure whether this is an inconsistency on Epic Games' part or the original Disney paper, but directly translating <var>roughness</var> to <var>a</var> gives the BRDF integration map that is identical to Epic Games' version. -</p> - -<p> - Finally, to store the BRDF convolution result we'll generate a 2D texture of a 512 by 512 resolution: -</p> - -<pre><code> -unsigned int brdfLUTTexture; -<function id='50'>glGenTextures</function>(1, &brdfLUTTexture); - -// pre-allocate enough memory for the LUT texture. -<function id='48'>glBindTexture</function>(GL_TEXTURE_2D, brdfLUTTexture); -<function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RG16F, 512, 512, 0, GL_RG, GL_FLOAT, 0); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); -<function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); -</code></pre> - -<p> - Note that we use a 16-bit precision floating format as recommended by Epic Games. Be sure to set the wrapping mode to <var>GL_CLAMP_TO_EDGE</var> to prevent edge sampling artifacts. -</p> - -<p> - Then, we re-use the same framebuffer object and run this shader over an NDC screen-space quad: -</p> - -<pre class="cpp"><code> -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, captureFBO); -<function id='83'>glBindRenderbuffer</function>(GL_RENDERBUFFER, captureRBO); -<function id='88'>glRenderbufferStorage</function>(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512); -<function id='81'>glFramebufferTexture2D</function>(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, brdfLUTTexture, 0); - -<function id='22'>glViewport</function>(0, 0, 512, 512); -brdfShader.use(); -<function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -RenderQuad(); - -<function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); -</code></pre> - -<p> - The convoluted BRDF part of the split sum integral should give you the following result: -</p> - -<img src="/img/pbr/ibl_brdf_lut.png" alt="BRDF LUT"/> - -<p> - With both the pre-filtered environment map and the BRDF 2D LUT we can re-construct the indirect specular integral according to the split sum approximation. The combined result then acts as the indirect or ambient specular light. -</p> - - <h2>Completing the IBL reflectance</h2> -<p> - To get the indirect specular part of the reflectance equation up and running we need to stitch both parts of the split sum approximation together. Let's start by adding the pre-computed lighting data to the top of our PBR shader: -</p> - -<pre><code> -uniform samplerCube prefilterMap; -uniform sampler2D brdfLUT; -</code></pre> - -<p> - First, we get the indirect specular reflections of the surface by sampling the pre-filtered environment map using the reflection vector. Note that we sample the appropriate mip level based on the surface roughness, giving rougher surfaces <em>blurrier</em> specular reflections: -</p> - -<pre><code> -void main() -{ - [...] - vec3 R = reflect(-V, N); - - const float MAX_REFLECTION_LOD = 4.0; - vec3 prefilteredColor = textureLod(prefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb; - [...] -} -</code></pre> - -<p> - In the pre-filter step we only convoluted the environment map up to a maximum of 5 mip levels (0 to 4), which we denote here as <var>MAX_REFLECTION_LOD</var> to ensure we don't sample a mip level where there's no (relevant) data. -</p> - -<p> - Then we sample from the BRDF lookup texture given the material's roughness and the angle between the normal and view vector: -</p> - -<pre><code> -vec3 F = FresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); -vec2 envBRDF = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg; -vec3 specular = prefilteredColor * (F * envBRDF.x + envBRDF.y); -</code></pre> - -<p> - Given the scale and bias to \(F_0\) (here we're directly using the indirect Fresnel result <var>F</var>) from the BRDF lookup texture, we combine this with the left pre-filter portion of the IBL reflectance equation and re-construct the approximated integral result as <var>specular</var>. -</p> - -<p> - This gives us the indirect specular part of the reflectance equation. Now, combine this with the diffuse IBL part of the reflectance equation from the <a href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance" target="_blank">last</a> chapter and we get the full PBR IBL result: -</p> - -<pre><code> -vec3 F = FresnelSchlickRoughness(max(dot(N, V), 0.0), F0, roughness); - -vec3 kS = F; -vec3 kD = 1.0 - kS; -kD *= 1.0 - metallic; - -vec3 irradiance = texture(irradianceMap, N).rgb; -vec3 diffuse = irradiance * albedo; - -const float MAX_REFLECTION_LOD = 4.0; -vec3 prefilteredColor = textureLod(prefilterMap, R, roughness * MAX_REFLECTION_LOD).rgb; -vec2 envBRDF = texture(brdfLUT, vec2(max(dot(N, V), 0.0), roughness)).rg; -vec3 specular = prefilteredColor * (F * envBRDF.x + envBRDF.y); - -vec3 ambient = (kD * diffuse + specular) * ao; -</code></pre> - -<p> - Note that we don't multiply <var>specular</var> by <var>kS</var> as we already have a Fresnel multiplication in there. -</p> - -<p> - Now, running this exact code on the series of spheres that differ by their roughness and metallic properties, we finally get to see their true colors in the final PBR renderer: -</p> - - <img src="/img/pbr/ibl_specular_result.png" alt="Render in OpenGL of full PBR with IBL (image based lighting) on spheres with varying roughness and metallic properties."/> - -<p> - We could even go wild, and use some cool textured <a href="http://freepbr.com" target="_blank">PBR materials</a>: -</p> - - <img src="/img/pbr/ibl_specular_result_textured.png" alt="Render in OpenGL of full PBR with IBL (image based lighting) on textured spheres."/> - -<p> - Or load <a href="http://artisaverb.info/PBT.html" target="_blank">this awesome free 3D PBR model</a> by Andrew Maximov: -</p> - - <img src="/img/pbr/ibl_specular_result_model.png" alt="Render in OpenGL of full PBR with IBL (image based lighting) on a 3D PBR model."/> - -<p> - I'm sure we can all agree that our lighting now looks a lot more convincing. What's even better, is that our lighting looks physically correct regardless of which environment map we use. Below you'll see several different pre-computed HDR maps, completely changing the lighting dynamics, but still looking physically correct without changing a single lighting variable! -</p> - -<img src="/img/pbr/ibl_specular_result_different_environments.png" alt="Render in OpenGL of full PBR with IBL (image based lighting) on a 3D PBR model over multiple different environments (with changing light conditions)."/> - - -<p> - Well, this PBR adventure turned out to be quite a long journey. There are a lot of steps and thus a lot that could go wrong so carefully work your way through the <a href="/code_viewer_gh.php?code=src/6.pbr/2.2.1.ibl_specular/ibl_specular.cpp" target="_blank">sphere scene</a> or <a href="/code_viewer_gh.php?code=src/6.pbr/2.2.2.ibl_specular_textured/ibl_specular_textured.cpp" target="_blank">textured scene</a> code samples (including all shaders) if you're stuck, or check and ask around in the comments. -</p> - -<h3>What's next?</h3> -<p> - Hopefully, by the end of this tutorial you should have a pretty clear understanding of what PBR is about, and even have an actual PBR renderer up and running. In these tutorials, we've pre-computed all the relevant PBR image-based lighting data at the start of our application, before the render loop. This was fine for educational purposes, but not too great for any practical use of PBR. First, the pre-computation only really has to be done once, not at every startup. And second, the moment you use multiple environment maps you'll have to pre-compute each and every one of them at every startup which tends to build up. -</p> - -<p> - For this reason you'd generally pre-compute an environment map into an irradiance and pre-filter map just once, and then store it on disk (note that the BRDF integration map isn't dependent on an environment map so you only need to calculate or load it once). This does mean you'll need to come up with a custom image format to store HDR cubemaps, including their mip levels. Or, you'll store (and load) it as one of the available formats (like .dds that supports storing mip levels). -</p> - -<p> - Furthermore, we've described the <strong>total</strong> process in these tutorials, including generating the pre-computed IBL images to help further our understanding of the PBR pipeline. But, you'll be just as fine by using several great tools like <a href="https://github.com/dariomanesku/cmftStudio" target="_blank">cmftStudio</a> or <a href="https://github.com/derkreature/IBLBaker" target="_blank">IBLBaker</a> to generate these pre-computed maps for you. -</p> - -<p> - One point we've skipped over is pre-computed cubemaps as <def>reflection probes</def>: cubemap interpolation and parallax correction. This is the process of placing several reflection probes in your scene that take a cubemap snapshot of the scene at that specific location, which we can then convolute as IBL data for that part of the scene. By interpolating between several of these probes based on the camera's vicinity we can achieve local high-detail image-based lighting that is simply limited by the amount of reflection probes we're willing to place. This way, the image-based lighting could correctly update when moving from a bright outdoor section of a scene to a darker indoor section for instance. I'll write a tutorial about reflection probes somewhere in the future, but for now I recommend the article by Chetan Jags below to give you a head start. -</p> - - -<h2>Further reading</h2> -<ul> - <li><a href="http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf" target="_blank">Real Shading in Unreal Engine 4</a>: explains Epic Games' split sum approximation. This is the article the IBL PBR code is based of.</li> - <li><a href="http://www.trentreed.net/blog/physically-based-shading-and-image-based-lighting/" target="_blank">Physically Based Shading and Image Based Lighting</a>: great blog post by Trent Reed about integrating specular IBL into a PBR pipeline in real time. </li> - <li><a href="https://chetanjags.wordpress.com/2015/08/26/image-based-lighting/" target="_blank">Image Based Lighting</a>: very extensive write-up by Chetan Jags about specular-based image-based lighting and several of its caveats, including light probe interpolation.</li> - <li><a href="https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf" target="_blank">Moving Frostbite to PBR</a>: well written and in-depth overview of integrating PBR into a AAA game engine by Sébastien Lagarde and Charles de Rousiers.</li> - <li><a href="https://jmonkeyengine.github.io/wiki/jme3/advanced/pbr_part3.html" target="_blank">Physically Based Rendering – Part Three</a>: high level overview of IBL lighting and PBR by the JMonkeyEngine team.</li> - <li><a href="https://placeholderart.wordpress.com/2015/07/28/implementation-notes-runtime-environment-map-filtering-for-image-based-lighting/" target="_blank">Implementation Notes: Runtime Environment Map Filtering for Image Based Lighting</a>: extensive write-up by Padraic Hennessy about pre-filtering HDR environment maps and significantly optimizing the sample process.</li> -</ul> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/PBR/Lighting.html b/translation/PBR/Lighting.html @@ -1,696 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Lighting</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Lighting</h1> -<h1 id="content-url" style='display:none;'>PBR/Lighting</h1> -<p> - In the <a href="https://learnopengl.com/PBR/Theory" target="_blank">previous</a> chapter we laid the foundation for getting a realistic physically based renderer off the ground. In this chapter we'll focus on translating the previously discussed theory into an actual renderer that uses direct (or analytic) light sources: think of point lights, directional lights, and/or spotlights. -</p> - -<p> - Let's start by re-visiting the final reflectance equation from the previous chapter: -</p> - - \[ - L_o(p,\omega_o) = \int\limits_{\Omega} - (k_d\frac{c}{\pi} + \frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)}) - L_i(p,\omega_i) n \cdot \omega_i d\omega_i - \] - -<p> - We now know mostly what's going on, but what still remained a big unknown is how exactly we're going to represent irradiance, the total radiance \(L\), of the scene. We know that radiance \(L\) (as interpreted in computer graphics land) measures the radiant flux \(\phi\) or light energy of a light source over a given solid angle \(\omega\). In our case we assumed the solid angle \(\omega\) to be infinitely small in which case radiance measures the flux of a light source over a single light ray or direction vector. -</p> - -<p> - Given this knowledge, how do we translate this into some of the lighting knowledge we've accumulated from previous chapters? Well, imagine we have a single point light (a light source that shines equally bright in all directions) with a radiant flux of <code>(23.47, 21.31, 20.79)</code> as translated to an RGB triplet. The radiant intensity of this light source equals its radiant flux at all outgoing direction rays. However, when shading a specific point \(p\) on a surface, of all possible incoming light directions over its hemisphere \(\Omega\), only one incoming direction vector \(w_i\) directly comes from the point light source. As we only have a single light source in our scene, assumed to be a single point in space, all other possible incoming light directions have zero radiance observed over the surface point \(p\): -</p> - -<img src="/img/pbr/lighting_radiance_direct.png" class="clean" alt="Radiance on a point p of a non-attenuated point light source only returning non-zero at the infitely small solid angle Wi or light direction vector Wi"/> - -<p> - If at first, we assume that light attenuation (dimming of light over distance) does not affect the point light source, the radiance of the incoming light ray is the same regardless of where we position the light (excluding scaling the radiance by the incident angle \(\cos \theta\)). This, because the point light has the same radiant intensity regardless of the angle we look at it, effectively modeling its radiant intensity as its radiant flux: a constant vector <code>(23.47, 21.31, 20.79)</code>. -</p> - -<p> - However, radiance also takes a position \(p\) as input and as any realistic point light source takes light attenuation into account, the radiant intensity of the point light source is scaled by some measure of the distance between point \(p\) and the light source. Then, as extracted from the original radiance equation, the result is scaled by the dot product between the surface normal \(n\) and the incoming light direction \(w_i\). -</p> - -<p> - To put this in more practical terms: in the case of a direct point light the radiance function \(L\) measures the light color, attenuated over its distance to \(p\) and scaled by \(n \cdot w_i\), but only over the single light ray \(w_i\) that hits \(p\) which equals the light's direction vector from \(p\). - In code this translates to: -</p> - -<pre><code> -vec3 lightColor = vec3(23.47, 21.31, 20.79); -vec3 wi = normalize(lightPos - fragPos); -float cosTheta = max(dot(N, Wi), 0.0); -float attenuation = calculateAttenuation(fragPos, lightPos); -vec3 radiance = lightColor * attenuation * cosTheta; -</code></pre> - -<p> - Aside from the different terminology, this piece of code should be awfully familiar to you: this is exactly how we've been doing diffuse lighting so far. When it comes to direct lighting, radiance is calculated similarly to how we've calculated lighting before as only a single light direction vector contributes to the surface's radiance. -</p> - -<note> - Note that this assumption holds as point lights are infinitely small and only a single point in space. If we were to model a light that has area or volume, its radiance would be non-zero in more than one incoming light direction. -</note> - -<p> - For other types of light sources originating from a single point we calculate radiance similarly. For instance, a directional light source has a constant \(w_i\) without an attenuation factor. And a spotlight would not have a constant radiant intensity, but one that is scaled by the forward direction vector of the spotlight. -</p> - -<p> - This also brings us back to the integral \(\int\) over the surface's hemisphere \(\Omega\) . As we know beforehand the single locations of all the contributing light sources while shading a single surface point, it is not required to try and solve the integral. We can directly take the (known) number of light sources and calculate their total irradiance, given that each light source has only a single light direction that influences the surface's radiance. This makes PBR on direct light sources relatively simple as we effectively only have to loop over the contributing light sources. When we later take environment lighting into account in the <a href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance" target="_blank">IBL</a> chapters we do have to take the integral into account as light can come from any direction. -</p> - -<h2>A PBR surface model</h2> -<p> - Let's start by writing a fragment shader that implements the previously described PBR models. First, we need to take the relevant PBR inputs required for shading the surface: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; -in vec2 TexCoords; -in vec3 WorldPos; -in vec3 Normal; - -uniform vec3 camPos; - -uniform vec3 albedo; -uniform float metallic; -uniform float roughness; -uniform float ao; -</code></pre> - -<p> - We take the standard inputs as calculated from a generic vertex shader and a set of constant material properties over the surface of the object. -</p> - -<p> - Then at the start of the fragment shader we do the usual calculations required for any lighting algorithm: -</p> - -<pre><code> -void main() -{ - vec3 N = normalize(Normal); - vec3 V = normalize(camPos - WorldPos); - [...] -} -</code></pre> - -<h3>Direct lighting</h3> -<p> - In this chapter's example demo we have a total of 4 point lights that together represent the scene's irradiance. To satisfy the reflectance equation we loop over each light source, calculate its individual radiance and sum its contribution scaled by the BRDF and the light's incident angle. We can think of the loop as solving the integral \(\int\) over \(\Omega\) for direct light sources. First, we calculate the relevant per-light variables: -</p> - -<pre><code> -vec3 Lo = vec3(0.0); -for(int i = 0; i < 4; ++i) -{ - vec3 L = normalize(lightPositions[i] - WorldPos); - vec3 H = normalize(V + L); - - float distance = length(lightPositions[i] - WorldPos); - float attenuation = 1.0 / (distance * distance); - vec3 radiance = lightColors[i] * attenuation; - [...] -</code></pre> - -<p> - As we calculate lighting in linear space (we'll <a href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction" target="_blank">gamma correct</a> at the end of the shader) we attenuate the light sources by the more physically correct <def>inverse-square law</def>. -</p> - -<note> - While physically correct, you may still want to use the constant-linear-quadratic attenuation equation that (while not physically correct) can offer you significantly more control over the light's energy falloff. -</note> - -<p> - Then, for each light we want to calculate the full Cook-Torrance specular BRDF term: -</p> - - \[ - \frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)} - \] - -<p> - The first thing we want to do is calculate the ratio between specular and diffuse reflection, or how much the surface reflects light versus how much it refracts light. We know from the <a href="https://learnopengl.com/PBR/Theory" target="_blank">previous</a> chapter that the Fresnel equation calculates just that (note the <code>clamp</code> here to prevent black spots): -</p> - -<pre><code> -vec3 fresnelSchlick(float cosTheta, vec3 F0) -{ - return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); -} -</code></pre> - -<p> - The Fresnel-Schlick approximation expects a <var>F0</var> parameter which is known as the <em>surface reflection at zero incidence</em> or how much the surface reflects if looking directly at the surface. The <var>F0</var> varies per material and is tinted on metals as we find in large material databases. In the PBR metallic workflow we make the simplifying assumption that most dielectric surfaces look visually correct with a constant <var>F0</var> of <code>0.04</code>, while we do specify <var>F0</var> for metallic surfaces as then given by the albedo value. This translates to code as follows: -</p> - -<pre><code> -vec3 F0 = vec3(0.04); -F0 = mix(F0, albedo, metallic); -vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); -</code></pre> - -<p> - As you can see, for non-metallic surfaces <var>F0</var> is always <code>0.04</code>. For metallic surfaces, we vary <var>F0</var> by linearly interpolating between the original <var>F0</var> and the albedo value given the <var>metallic</var> property. -</p> - -<p> - Given \(F\), the remaining terms to calculate are the normal distribution function \(D\) and the geometry function \(G\). -</p> - -<p> - In a direct PBR lighting shader their code equivalents are: -</p> - -<pre><code> -float DistributionGGX(vec3 N, vec3 H, float roughness) -{ - float a = roughness*roughness; - float a2 = a*a; - float NdotH = max(dot(N, H), 0.0); - float NdotH2 = NdotH*NdotH; - - float num = a2; - float denom = (NdotH2 * (a2 - 1.0) + 1.0); - denom = PI * denom * denom; - - return num / denom; -} - -float GeometrySchlickGGX(float NdotV, float roughness) -{ - float r = (roughness + 1.0); - float k = (r*r) / 8.0; - - float num = NdotV; - float denom = NdotV * (1.0 - k) + k; - - return num / denom; -} -float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) -{ - float NdotV = max(dot(N, V), 0.0); - float NdotL = max(dot(N, L), 0.0); - float ggx2 = GeometrySchlickGGX(NdotV, roughness); - float ggx1 = GeometrySchlickGGX(NdotL, roughness); - - return ggx1 * ggx2; -} -</code></pre> - -<p> - What's important to note here is that in contrast to the <a href="https://learnopengl.com/PBR/Theory" target="_blank">theory</a> chapter, we pass the roughness parameter directly to these functions; this way we can make some term-specific modifications to the original roughness value. Based on observations by Disney and adopted by Epic Games, the lighting looks more correct squaring the roughness in both the geometry and normal distribution function. -</p> - -<p> - With both functions defined, calculating the NDF and the G term in the reflectance loop is straightforward: -</p> - -<pre><code> -float NDF = DistributionGGX(N, H, roughness); -float G = GeometrySmith(N, V, L, roughness); -</code></pre> - -<p> - This gives us enough to calculate the Cook-Torrance BRDF: -</p> - -<pre><code> -vec3 numerator = NDF * G * F; -float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; -vec3 specular = numerator / denominator; -</code></pre> - -<p> - Note that we add <code>0.0001</code> to the denominator to prevent a divide by zero in case any dot product ends up <code>0.0</code>. -</p> - -<p> - Now we can finally calculate each light's contribution to the reflectance equation. As the Fresnel value directly corresponds to \(k_S\) we can use <var>F</var> to denote the specular contribution of any light that hits the surface. From \(k_S\) we can then calculate the ratio of refraction \(k_D\): -</p> - -<pre><code> -vec3 kS = F; -vec3 kD = vec3(1.0) - kS; - -kD *= 1.0 - metallic; -</code></pre> - -<p> - Seeing as <var>kS</var> represents the energy of light that gets reflected, the remaining ratio of light energy is the light that gets refracted which we store as <var>kD</var>. Furthermore, because metallic surfaces don't refract light and thus have no diffuse reflections we enforce this property by nullifying <var>kD</var> if the surface is metallic. This gives us the final data we need to calculate each light's outgoing reflectance value: -</p> - -<pre><code> - const float PI = 3.14159265359; - - float NdotL = max(dot(N, L), 0.0); - Lo += (kD * albedo / PI + specular) * radiance * NdotL; -} -</code></pre> - -<p> - The resulting <var>Lo</var> value, or the outgoing radiance, is effectively the result of the reflectance equation's integral \(\int\) over \(\Omega\). We don't really have to try and solve the integral for all possible incoming light directions as we know exactly the 4 incoming light directions that can influence the fragment. Because of this, we can directly loop over these incoming light directions e.g. the number of lights in the scene. -</p> - -<p> - What's left is to add an (improvised) ambient term to the direct lighting result <var>Lo</var> and we have the final lit color of the fragment: -</p> - -<pre><code> -vec3 ambient = vec3(0.03) * albedo * ao; -vec3 color = ambient + Lo; -</code></pre> - -<h3>Linear and HDR rendering</h3> -<p> - So far we've assumed all our calculations to be in linear color space and to account for this we need to <a href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction" target="_blank">gamma correct</a> at the end of the shader. Calculating lighting in linear space is incredibly important as PBR requires all inputs to be linear. Not taking this into account will result in incorrect lighting. Additionally, we want light inputs to be close to their physical equivalents such that their radiance or color values can vary wildly over a high spectrum of values. As a result, <var>Lo</var> can rapidly grow really high which then gets clamped between <code>0.0</code> and <code>1.0</code> due to the default low dynamic range (LDR) output. We fix this by taking <var>Lo</var> and tone or exposure map the <a href="https://learnopengl.com/Advanced-Lighting/HDR" target="_blank">high dynamic range</a> (HDR) value correctly to LDR before gamma correction: -</p> - -<pre><code> -color = color / (color + vec3(1.0)); -color = pow(color, vec3(1.0/2.2)); -</code></pre> - -<p> - Here we tone map the HDR color using the Reinhard operator, preserving the high dynamic range of a possibly highly varying irradiance, after which we gamma correct the color. We don't have a separate framebuffer or post-processing stage so we can directly apply both the tone mapping and gamma correction step at the end of the forward fragment shader. -</p> - - <img src="/img/pbr/lighting_linear_vs_non_linear_and_hdr.png" alt="The difference linear and HDR rendering makes in an OpenGL PBR renderer."/> - -<p> - Taking both linear color space and high dynamic range into account is incredibly important in a PBR pipeline. Without these it's impossible to properly capture the high and low details of varying light intensities and your calculations end up incorrect and thus visually unpleasing. -</p> - -<h3>Full direct lighting PBR shader</h3> -<p> - All that's left now is to pass the final tone mapped and gamma corrected color to the fragment shader's output channel and we have ourselves a direct PBR lighting shader. For completeness' sake, the complete <fun>main</fun> function is listed below: -</p> - -<pre><code> -#version 330 core -out vec4 FragColor; -in vec2 TexCoords; -in vec3 WorldPos; -in vec3 Normal; - -// material parameters -uniform vec3 albedo; -uniform float metallic; -uniform float roughness; -uniform float ao; - -// lights -uniform vec3 lightPositions[4]; -uniform vec3 lightColors[4]; - -uniform vec3 camPos; - -const float PI = 3.14159265359; - -float DistributionGGX(vec3 N, vec3 H, float roughness); -float GeometrySchlickGGX(float NdotV, float roughness); -float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness); -vec3 fresnelSchlick(float cosTheta, vec3 F0); - -void main() -{ - vec3 N = normalize(Normal); - vec3 V = normalize(camPos - WorldPos); - - vec3 F0 = vec3(0.04); - F0 = mix(F0, albedo, metallic); - - // reflectance equation - vec3 Lo = vec3(0.0); - for(int i = 0; i < 4; ++i) - { - // calculate per-light radiance - vec3 L = normalize(lightPositions[i] - WorldPos); - vec3 H = normalize(V + L); - float distance = length(lightPositions[i] - WorldPos); - float attenuation = 1.0 / (distance * distance); - vec3 radiance = lightColors[i] * attenuation; - - // cook-torrance brdf - float NDF = DistributionGGX(N, H, roughness); - float G = GeometrySmith(N, V, L, roughness); - vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); - - vec3 kS = F; - vec3 kD = vec3(1.0) - kS; - kD *= 1.0 - metallic; - - vec3 numerator = NDF * G * F; - float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001; - vec3 specular = numerator / denominator; - - // add to outgoing radiance Lo - float NdotL = max(dot(N, L), 0.0); - Lo += (kD * albedo / PI + specular) * radiance * NdotL; - } - - vec3 ambient = vec3(0.03) * albedo * ao; - vec3 color = ambient + Lo; - - color = color / (color + vec3(1.0)); - color = pow(color, vec3(1.0/2.2)); - - FragColor = vec4(color, 1.0); -} -</code></pre> - -<p> - Hopefully, with the <a href="https://learnopengl.com/PBR/Theory" target="_blank">theory</a> from the previous chapter and the knowledge of the reflectance equation this shader shouldn't be as daunting anymore. If we take this shader, 4 point lights, and quite a few spheres where we vary both their metallic and roughness values on their vertical and horizontal axis respectively, we'd get something like this: -</p> - - <img src="/img/pbr/lighting_result.png" alt="Render of PBR spheres with varying roughness and metallic values in OpenGL."/> - -<p> - From bottom to top the metallic value ranges from <code>0.0</code> to <code>1.0</code>, with roughness increasing left to right from <code>0.0</code> to <code>1.0</code>. You can see that by only changing these two simple to understand parameters we can already display a wide array of different materials. -</p> - -<iframe src="https://oneshader.net/embed/6b8a7c6363" style="width:80%; height:440px; border:0;margin-left:10.0%; margin-right:12.5%;" frameborder="0" allowfullscreen></iframe> - -<p> - You can find the full source code of the demo <a href="/code_viewer_gh.php?code=src/6.pbr/1.1.lighting/lighting.cpp" target="_blank">here</a>. -</p> - -<h2>Textured PBR</h2> -<p> - Extending the system to now accept its surface parameters as textures instead of uniform values gives us per-fragment control over the surface material's properties: -</p> - -<pre><code> -[...] -uniform sampler2D albedoMap; -uniform sampler2D normalMap; -uniform sampler2D metallicMap; -uniform sampler2D roughnessMap; -uniform sampler2D aoMap; - -void main() -{ - vec3 albedo = pow(texture(albedoMap, TexCoords).rgb, 2.2); - vec3 normal = getNormalFromNormalMap(); - float metallic = texture(metallicMap, TexCoords).r; - float roughness = texture(roughnessMap, TexCoords).r; - float ao = texture(aoMap, TexCoords).r; - [...] -} -</code></pre> - -<p> - Note that the albedo textures that come from artists are generally authored in sRGB space which is why we first convert them to linear space before using albedo in our lighting calculations. Based on the system artists use to generate ambient occlusion maps you may also have to convert these from sRGB to linear space as well. Metallic and roughness maps are almost always authored in linear space. -</p> - -<p> - Replacing the material properties of the previous set of spheres with textures, already shows a major visual improvement over the previous lighting algorithms we've used: -</p> - - <img src="/img/pbr/lighting_textured.png" alt="Render of PBR spheres with a textured PBR material in OpenGL."/> - -<p> - You can find the full source code of the textured demo <a href="/code_viewer_gh.php?code=src/6.pbr/1.2.lighting_textured/lighting_textured.cpp" target="_blank">here</a> and the texture set used <a href="http://freepbr.com/materials/rusted-iron-pbr-metal-material-alt/" target="_blank">here</a> (with a white ao map). Keep in mind that metallic surfaces tend to look too dark in direct lighting environments as they don't have diffuse reflectance. They do look more correct when taking the environment's specular ambient lighting into account, which is what we'll focus on in the next chapters. -</p> - -<p> - While not as visually impressive as some of the PBR render demos you find out there, given that we don't yet have <a href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance" target="_blank">image based lighting</a> built in, the system we have now is still a physically based renderer, and even without IBL you'll see your lighting look a lot more realistic. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/PBR/Theory.html b/translation/PBR/Theory.html @@ -1,877 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Theory</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Theory</h1> -<h1 id="content-url" style='display:none;'>PBR/Theory</h1> -<p> - PBR, or more commonly known as <def>physically based rendering</def>, is a collection of render techniques that are more or less based on the same underlying theory that more closely matches that of the physical world. As physically based rendering aims to mimic light in a physically plausible way, it generally looks more realistic compared to our original lighting algorithms like Phong and Blinn-Phong. Not only does it look better, as it closely approximates actual physics, we (and especially the artists) can author surface materials based on physical parameters without having to resort to cheap hacks and tweaks to make the lighting look right. One of the bigger advantages of authoring materials based on physical parameters is that these materials will look correct regardless of lighting conditions; something that is not true in non-PBR pipelines. -</p> - -<p> - Physically based rendering is still nonetheless an approximation of reality (based on the principles of physics) which is why it's not called physical shading, but physically <em>based</em> shading. For a PBR lighting model to be considered physically based, it has to satisfy the following 3 conditions (don't worry, we'll get to them soon enough): -</p> - -<ol> - <li>Be based on the microfacet surface model.</li> - <li>Be energy conserving.</li> - <li>Use a physically based BRDF.</li> -</ol> - -<p> - In the next PBR chapters we'll be focusing on the PBR approach as originally explored by Disney and adopted for real-time display by Epic Games. Their approach, based on the <def>metallic workflow</def>, is decently documented, widely adopted on most popular engines, and looks visually amazing. By the end of these chapters we'll have something that looks like this: -</p> - -<img src="/img/pbr/ibl_specular_result_textured.png" class="" alt="An example of a PBR render (with IBL) in OpenGL on textured materials."/> - -<p> - Keep in mind, the topics in these chapters are rather advanced so it is advised to have a good understanding of OpenGL and shader lighting. Some of the more advanced knowledge you'll need for this series are: <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">framebuffers</a>, <a href="https://learnopengl.com/Advanced-OpenGL/Cubemaps" target="_blank">cubemaps</a>, <a href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction" target="_blank">gamma correction</a>, <a href="https://learnopengl.com/Advanced-Lighting/HDR" target="_blank">HDR</a>, and <a href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping" target="_blank">normal mapping</a>. We'll also delve into some advanced mathematics, but I'll do my best to explain the concepts as clear as possible. -</p> - -<h2>The microfacet model</h2> -<p> - All the PBR techniques are based on the theory of microfacets. The theory describes that any surface at a microscopic scale can be described by tiny little perfectly reflective mirrors called <def>microfacets</def>. Depending on the roughness of a surface, the alignment of these tiny little mirrors can differ quite a lot: -</p> - -<img src="/img/pbr/microfacets.png" class="clean" alt="Different surface types for OpenGL PBR"/> - -<p> - The rougher a surface is, the more chaotically aligned each microfacet will be along the surface. The effect of these tiny-like mirror alignments is, that when specifically talking about specular lighting/reflection, the incoming light rays are more likely to <def>scatter</def> along completely different directions on rougher surfaces, resulting in a more widespread specular reflection. In contrast, on a smooth surface the light rays are more likely to reflect in roughly the same direction, giving us smaller and sharper reflections: -</p> - -<img src="/img/pbr/microfacets_light_rays.png" class="clean" alt="Effect of light scattering on different surface types for OpenGL PBR"/> - -<p> - No surface is completely smooth on a microscopic level, but seeing as these microfacets are small enough that we can't make a distinction between them on a per-pixel basis, we statistically approximate the surface's microfacet roughness given a <def>roughness</def> parameter. Based on the roughness of a surface, we can calculate the ratio of microfacets roughly aligned to some vector \(h\). This vector \(h\) is the <def>halfway vector</def> that sits halfway between the light \(l\) and view \(v\) vector. We've discussed the halfway vector before in the <a href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting" target="_blank">advanced lighting</a> chapter which is calculated as the sum of \(l\) and \(v\) divided by its length: -</p> - -\[ - h = \frac{l + v}{\|l + v\|} -\] - -<p> - The more the microfacets are aligned to the halfway vector, the sharper and stronger the specular reflection. Together with a roughness parameter that varies between 0 and 1, we can statistically approximate the alignment of the microfacets: -</p> - -<img src="/img/pbr/ndf.png" alt="Visualized NDF (Normalized Distribution Function) in OpenGL PBR"/> - -<p> - We can see that higher roughness values display a much larger specular reflection shape, in contrast with the smaller and sharper specular reflection shape of smooth surfaces. -</p> - -<h2>Energy conservation</h2> -<p> - The microfacet approximation employs a form of <def>energy conservation</def>: outgoing light energy should never exceed the incoming light energy (excluding emissive surfaces). Looking at the above image we see the specular reflection area increase, but also its brightness decrease at increasing roughness levels. If the specular intensity were to be the same at each pixel (regardless of the size of the specular shape) the rougher surfaces would emit much more energy, violating the energy conservation principle. This is why we see specular reflections more intensely on smooth surfaces and more dimly on rough surfaces. -</p> - -<p> - For energy conservation to hold, we need to make a clear distinction between diffuse and specular light. The moment a light ray hits a surface, it gets split in both a <def>refraction</def> part and a <def>reflection</def> part. The reflection part is light that directly gets reflected and doesn't enter the surface; this is what we know as specular lighting. The refraction part is the remaining light that enters the surface and gets absorbed; this is what we know as diffuse lighting. - </p> - -<p> - There are some nuances here as refracted light doesn't immediately get absorbed by touching the surface. From physics, we know that light can be modeled as a beam of energy that keeps moving forward until it loses all of its energy; the way a light beam loses energy is by collision. Each material consists of tiny little particles that can collide with the light ray as illustrated in the following image. The particles absorb some, or all, of the light's energy at each collision which is converted into heat. -</p> - -<img src="/img/pbr/surface_reaction.png" class="clean" alt="Light as reflected and refracted light with absorption in OpenGL PBR"/> - -<p> - Generally, not all energy is absorbed and the light will continue to <def>scatter</def> in a (mostly) random direction at which point it collides with other particles until its energy is depleted or it leaves the surface again. Light rays re-emerging out of the surface contribute to the surface's observed (diffuse) color. In physically based rendering however, we make the simplifying assumption that all refracted light gets absorbed and scattered at a very small area of impact, ignoring the effect of scattered light rays that would've exited the surface at a distance. Specific shader techniques that do take this into account are known as <def>subsurface scattering</def> techniques that significantly improve the visual quality on materials like skin, marble, or wax, but come at the price of performance. - </p> - -<p> - An additional subtlety when it comes to reflection and refraction are surfaces that are <def>metallic</def>. Metallic surfaces react different to light compared to non-metallic surfaces (also known as <def>dielectrics</def>). Metallic surfaces follow the same principles of reflection and refraction, but <strong>all</strong> refracted light gets directly absorbed without scattering. This means metallic surfaces only leave reflected or specular light; metallic surfaces show no diffuse colors. Because of this apparent distinction between metals and dielectrics, they're both treated differently in the PBR pipeline which we'll delve into further down the chapter. -</p> - -<p> - This distinction between reflected and refracted light brings us to another observation regarding energy preservation: they're <strong>mutually exclusive</strong>. Whatever light energy gets reflected will no longer be absorbed by the material itself. Thus, the energy left to enter the surface as refracted light is directly the resulting energy after we've taken reflection into account. -</p> - -<p> - We preserve this energy conserving relation by first calculating the specular fraction that amounts the percentage the incoming light's energy is reflected. The fraction of refracted light is then directly calculated from the specular fraction as: -</p> - -<pre><code> -float kS = calculateSpecularComponent(...); // reflection/specular fraction -float kD = 1.0 - kS; // refraction/diffuse fraction -</code></pre> - -<p> - This way we know both the amount the incoming light reflects and the amount the incoming light refracts, while adhering to the energy conservation principle. Given this approach, it is impossible for both the refracted/diffuse and reflected/specular contribution to exceed <code>1.0</code>, thus ensuring the sum of their energy never exceeds the incoming light energy. Something we did not take into account in the previous lighting chapters. -</p> - -<h2>The reflectance equation</h2> -<p> - This brings us to something called the <a href="https://en.wikipedia.org/wiki/Rendering_equation" target="_blank">render equation</a>, an elaborate equation some very smart folks out there came up with that is currently the best model we have for simulating the visuals of light. Physically based rendering strongly follows a more specialized version of the render equation known as the <def>reflectance equation</def>. To properly understand PBR, it's important to first build a solid understanding of the reflectance equation: -</p> - - \[ - L_o(p,\omega_o) = \int\limits_{\Omega} f_r(p,\omega_i,\omega_o) L_i(p,\omega_i) n \cdot \omega_i d\omega_i - \] - -<p> - The reflectance equation appears daunting at first, but as we'll dissect it you'll see it slowly starts to makes sense. To understand the equation, we have to delve into a bit of <def>radiometry</def>. Radiometry is the measurement of electromagnetic radiation, including visible light. There are several radiometric quantities we can use to measure light over surfaces and directions, but we will only discuss a single one that's relevant to the reflectance equation known as <def>radiance</def>, denoted here as \(L\). Radiance is used to quantify the magnitude or strength of light coming from a single direction. It's a bit tricky to understand at first as radiance is a combination of multiple physical quantities so we'll focus on those first: -</p> - -<p> - <strong>Radiant flux</strong>: radiant flux \(\Phi\) is the transmitted energy of a light source measured in Watts. Light is a collective sum of energy over multiple different wavelengths, each wavelength associated with a particular (visible) color. The emitted energy of a light source can therefore be thought of as a function of all its different wavelengths. Wavelengths between 390nm to 700nm (nanometers) are considered part of the visible light spectrum i.e. wavelengths the human eye is able to perceive. Below you'll find an image of the different energies per wavelength of daylight: -</p> - - <img src="/img/pbr/daylight_spectral_distribution.png" class="clean" alt="Spectral distribution of daylight"/> - -<p> - The radiant flux measures the total area of this function of different wavelengths. Directly taking this measure of wavelengths as input is slightly impractical so we often make the simplification of representing radiant flux, not as a function of varying wavelength strengths, but as a light color triplet encoded as <code>RGB</code> (or as we'd commonly call it: light color). This encoding does come at quite a loss of information, but this is generally negligible for visual aspects. -</p> - -<p> - <strong>Solid angle</strong>: the solid angle, denoted as \(\omega\), tells us the size or area of a shape projected onto a unit sphere. The area of the projected shape onto this unit sphere is known as the <def>solid angle</def>; you can visualize the solid angle as a direction with volume: -</p> - - <img src="/img/pbr/solid_angle.png" class="clean" alt="Solid angle"/> - - <p> - Think of being an observer at the center of this unit sphere and looking in the direction of the shape; the size of the silhouette you make out of it is the solid angle. -</p> - -<p> - <strong>Radiant intensity</strong>: radiant intensity measures the amount of radiant flux per solid angle, or the strength of a light source over a projected area onto the unit sphere. For instance, given an omnidirectional light that radiates equally in all directions, the radiant intensity can give us its energy over a specific area (solid angle): -</p> - - <img src="/img/pbr/radiant_intensity.png" class="clean" alt="Radiant intensity"/> - -<p> - The equation to describe the radiant intensity is defined as follows: -</p> - - \[I = \frac{d\Phi}{d\omega}\] - -<p> - Where \(I\) is the radiant flux \(\Phi\) over the solid angle \(\omega\). -</p> - -<p> - With knowledge of radiant flux, radiant intensity, and the solid angle, we can finally describe the equation for <strong>radiance</strong>. Radiance is described as the total observed energy in an area \(A\) over the solid angle \(\omega\) of a light of radiant intensity \(\Phi\): -</p> - - \[L=\frac{d^2\Phi}{ dA d\omega \cos\theta}\] - - <img src="/img/pbr/radiance.png" class="clean" alt="Diagram of radiance"/> - -<p> - Radiance is a radiometric measure of the amount of light in an area, scaled by the <def>incident</def> (or incoming) angle \(\theta\) of the light to the surface's normal as \(\cos \theta\): light is weaker the less it directly radiates onto the surface, and strongest when it is directly perpendicular to the surface. This is similar to our perception of diffuse lighting from the <a href="https://learnopengl.com/Lighting/Basic-lighting" target="_blank">basic lighting</a> chapter as \(\cos\theta\) directly corresponds to the dot product between the light's direction vector and the surface normal: -</p> - -<pre><code> -float cosTheta = dot(lightDir, N); -</code></pre> - -<p> - The radiance equation is quite useful as it contains most physical quantities we're interested in. If we consider the solid angle \(\omega\) and the area \(A\) to be infinitely small, we can use radiance to measure the flux of a single ray of light hitting a single point in space. This relation allows us to calculate the radiance of a single light ray influencing a single (fragment) point; we effectively translate the solid angle \(\omega\) into a direction vector \(\omega\), and \(A\) into a point \(p\). This way, we can directly use radiance in our shaders to calculate a single light ray's per-fragment contribution. -</p> - -<p> - In fact, when it comes to radiance we generally care about <strong>all</strong> incoming light onto a point \(p\), which is the sum of all radiance known as <def>irradiance</def>. With knowledge of both radiance and irradiance we can get back to the reflectance equation: -</p> - - - \[ - L_o(p,\omega_o) = \int\limits_{\Omega} f_r(p,\omega_i,\omega_o) L_i(p,\omega_i) n \cdot \omega_i d\omega_i - \] - -<p> - We now know that \(L\) in the render equation represents the radiance of some point \(p\) and some incoming infinitely small solid angle \(\omega_i\) which can be thought of as an incoming direction vector \(\omega_i\). Remember that \(\cos \theta\) scales the energy based on the light's incident angle to the surface, which we find in the reflectance equation as \(n \cdot \omega_i\). The reflectance equation calculates the sum of reflected radiance \(L_o(p, \omega_o)\) of a point \(p\) in direction \(\omega_o\) which is the outgoing direction to the viewer. Or to put it differently: \(L_o\) measures the reflected sum of the lights' irradiance onto point \(p\) as viewed from \(\omega_o\). -</p> - -<p> - The reflectance equation is based around irradiance, which is the sum of all incoming radiance we measure light of. Not just of a single incoming light direction, but of all incoming light directions within a hemisphere \(\Omega\) centered around point \(p\). A <def>hemisphere</def> can be described as half a sphere aligned around a surface's normal \(n\): - </p> - - <img src="/img/pbr/hemisphere.png" class="clean" alt="Hemisphere"/> - -<p> - To calculate the total of values inside an area or (in the case of a hemisphere) a volume, we use a mathematical construct called an <def>integral</def> denoted in the reflectance equation as \(\int\) over all incoming directions \(d\omega_i\) within the hemisphere \(\Omega\) . An integral measures the area of a function, which can either be calculated analytically or numerically. As there is no analytical solution to both the render and reflectance equation, we'll want to numerically solve the integral discretely. This translates to taking the result of small discrete steps of the reflectance equation over the hemisphere \(\Omega\) and averaging their results over the step size. This is known as the <def>Riemann sum</def> that we can roughly visualize in code as follows: -</p> - -<pre><code> -int steps = 100; -float sum = 0.0f; -vec3 P = ...; -vec3 Wo = ...; -vec3 N = ...; -float dW = 1.0f / steps; -for(int i = 0; i < steps; ++i) -{ - vec3 Wi = getNextIncomingLightDir(i); - sum += Fr(P, Wi, Wo) * L(P, Wi) * dot(N, Wi) * dW; -} -</code></pre> - -<p> - By scaling the steps by <code>dW</code>, the sum will equal the total area or volume of the integral function. The <code>dW</code> to scale each discrete step can be thought of as \(d\omega_i\) in the reflectance equation. Mathematically \(d\omega_i\) is the continuous symbol over which we calculate the integral, and while it does not directly relate to <code>dW</code> in code (as this is a discrete step of the Riemann sum), it helps to think of it this way. Keep in mind that taking discrete steps will always give us an approximation of the total area of the function. A careful reader will notice we can increase the <em>accuracy</em> of the Riemann Sum by increasing the number of steps. - </p> - -<p> - The reflectance equation sums up the radiance of all incoming light directions \(\omega_i\) over the hemisphere \(\Omega\) scaled by \(f_r\) that hit point \(p\) and returns the sum of reflected light \(L_o\) in the viewer's direction. The incoming radiance can come from <a href="https://learnopengl.com/PBR/Lighting" target="_blank">light sources</a> as we're familiar with, or from an environment map measuring the radiance of every incoming direction as we'll discuss in the <a href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance" target="_blank">IBL</a> chapters. -</p> - -<p> - Now the only unknown left is the \(f_r\) symbol known as the <def>BRDF</def> or <def>bidirectional reflective distribution function</def> that scales or weighs the incoming radiance based on the surface's material properties. -</p> - - -<h2>BRDF</h2> -<p> - The <def>BRDF</def>, or <def>bidirectional reflective distribution function</def>, is a function that takes as input the incoming (light) direction \(\omega_i\), the outgoing (view) direction \(\omega_o\), the surface normal \(n\), and a surface parameter \(a\) that represents the microsurface's roughness. The BRDF approximates how much each individual light ray \(\omega_i\) contributes to the final reflected light of an opaque surface given its material properties. For instance, if the surface has a perfectly smooth surface (~like a mirror) the BRDF function would return 0.0 for all incoming light rays \(\omega_i\) except the one ray that has the same (reflected) angle as the outgoing ray \(\omega_o\) at which the function returns 1.0. </p> - - <p> - A BRDF approximates the material's reflective and refractive properties based on the previously discussed microfacet theory. For a BRDF to be physically plausible it has to respect the law of energy conservation i.e. the sum of reflected light should never exceed the amount of incoming light. Technically, Blinn-Phong is considered a BRDF taking the same \(\omega_i\) and \(\omega_o\) as inputs. However, Blinn-Phong is not considered physically based as it doesn't adhere to the energy conservation principle. There are several physically based BRDFs out there to approximate the surface's reaction to light. However, almost all real-time PBR render pipelines use a BRDF known as the <def>Cook-Torrance BRDF</def>. - </p> - -<p> - The Cook-Torrance BRDF contains both a diffuse and specular part: -</p> - - \[f_r = k_d f_{lambert} + k_s f_{cook-torrance}\] - -<p> - Here \(k_d\) is the earlier mentioned ratio of incoming light energy that gets <em>refracted</em> with \(k_s\) being the ratio that gets <em>reflected</em>. The left side of the BRDF states the diffuse part of the equation denoted here as \(f_{lambert}\). This is known as <def>Lambertian diffuse</def> similar to what we used for diffuse shading, which is a constant factor denoted as: -</p> - - \[ f_{lambert} = \frac{c}{\pi}\] - -<p> - With \(c\) being the albedo or surface color (think of the diffuse surface texture). The divide by pi is there to normalize the diffuse light as the earlier denoted integral that contains the BRDF is scaled by \(\pi\) (we'll get to that in the <a href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance" target="_blank">IBL</a> chapters). -</p> - - <note> - You may wonder how this Lambertian diffuse relates to the diffuse lighting we've been using before: the surface color multiplied by the dot product between the surface's normal and the light direction. The dot product is still there, but moved out of the BRDF as we find \(n \cdot \omega_i\) at the end of the \(L_o\) integral. - </note> - -<p> - There exist different equations for the diffuse part of the BRDF which tend to look more realistic, but are also more computationally expensive. As concluded by Epic Games however, the Lambertian diffuse is sufficient enough for most real-time rendering purposes. -</p> - -<p> - The specular part of the BRDF is a bit more advanced and is described as: -</p> - - \[ - f_{CookTorrance} = \frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)} - \] - -<p> - The Cook-Torrance specular BRDF is composed three functions and a normalization factor in the denominator. Each of the D, F and G symbols represent a type of function that approximates a specific part of the surface's reflective properties. These are defined as the normal <strong>D</strong>istribution function, the <strong>F</strong>resnel equation and the <strong>G</strong>eometry function: -</p> - -<ul> - <li><strong>Normal distribution function</strong>: approximates the amount the surface's microfacets are aligned to the halfway vector, influenced by the roughness of the surface; this is the primary function approximating the microfacets.</li> - <li><strong>Geometry function</strong>: describes the self-shadowing property of the microfacets. When a surface is relatively rough, the surface's microfacets can overshadow other microfacets reducing the light the surface reflects.</li> - <li><strong>Fresnel equation</strong>: The Fresnel equation describes the ratio of surface reflection at different surface angles.</li> -</ul> - -<p> - Each of these functions are an approximation of their physics equivalents and you'll find more than one version of each that aims to approximate the underlying physics in different ways; some more realistic, others more efficient. It is perfectly fine to pick whatever approximated version of these functions you want to use. Brian Karis from Epic Games did a great deal of research on the multiple types of approximations <a href="http://graphicrants.blogspot.nl/2013/08/specular-brdf-reference.html" target="_blank">here</a>. We're going to pick the same functions used by Epic Game's Unreal Engine 4 which are the Trowbridge-Reitz GGX for D, the Fresnel-Schlick approximation for F, and the Smith's Schlick-GGX for G. -</p> - -<h3>Normal distribution function</h3> -<p> - The <def>normal distribution function</def> \(D\) statistically approximates the relative surface area of microfacets exactly aligned to the (halfway) vector \(h\). There are a multitude of NDFs that statistically approximate the general alignment of the microfacets given some roughness parameter and the one we'll be using is known as the Trowbridge-Reitz GGX: -</p> - - \[ - NDF_{GGX TR}(n, h, \alpha) = \frac{\alpha^2}{\pi((n \cdot h)^2 (\alpha^2 - 1) + 1)^2} - \] - -<p> - Here \(h\) is the halfway vector to measure against the surface's microfacets, with \(a\) being a measure of the surface's roughness. If we take \(h\) as the halfway vector between the surface normal and light direction over varying roughness parameters we get the following visual result: -</p> - - <img src="/img/pbr/ndf.png" alt="Visualized NDF in OpenGL PBR"/> - -<p> - When the roughness is low (thus the surface is smooth), a highly concentrated number of microfacets are aligned to halfway vectors over a small radius. Due to this high concentration, the NDF displays a very bright spot. On a rough surface however, where the microfacets are aligned in much more random directions, you'll find a much larger number of halfway vectors \(h\) somewhat aligned to the microfacets (but less concentrated), giving us the more grayish results. -</p> - -<p> - In GLSL the Trowbridge-Reitz GGX normal distribution function translates to the following code: -</p> - -<pre><code> -float DistributionGGX(vec3 N, vec3 H, float a) -{ - float a2 = a*a; - float NdotH = max(dot(N, H), 0.0); - float NdotH2 = NdotH*NdotH; - - float nom = a2; - float denom = (NdotH2 * (a2 - 1.0) + 1.0); - denom = PI * denom * denom; - - return nom / denom; -} -</code></pre> - - -<h3>Geometry function</h3> -<p> - The geometry function statistically approximates the relative surface area where its micro surface-details overshadow each other, causing light rays to be occluded. -</p> - - <img src="/img/pbr/geometry_shadowing.png" class="clean" alt="Light being either shadowed or obstructed due to microfacet model."/> - -<p> - Similar to the NDF, the Geometry function takes a material's roughness parameter as input with rougher surfaces having a higher probability of overshadowing microfacets. The geometry function we will use is a combination of the GGX and Schlick-Beckmann approximation known as Schlick-GGX: -</p> - - \[ - G_{SchlickGGX}(n, v, k) - = - \frac{n \cdot v} - {(n \cdot v)(1 - k) + k } - \] - -<p> - Here \(k\) is a remapping of \(\alpha\) based on whether we're using the geometry function for either direct lighting or IBL lighting: -</p> - -\[ - k_{direct} = \frac{(\alpha + 1)^2}{8} -\] - -\[ - k_{IBL} = \frac{\alpha^2}{2} -\] - -<p> - Note that the value of \(\alpha\) may differ based on how your engine translates roughness to \(\alpha\). In the following chapters we'll extensively discuss how and where this remapping becomes relevant. - </p> - -<p> - To effectively approximate the geometry we need to take account of both the view direction (geometry obstruction) and the light direction vector (geometry shadowing). We can take both into account using <def>Smith's method</def>: -</p> - -\[ - G(n, v, l, k) = G_{sub}(n, v, k) G_{sub}(n, l, k) -\] - -<p> - Using Smith's method with Schlick-GGX as \(G_{sub}\) gives the following visual appearance over varying roughness <code>R</code>: -</p> - - - <img src="/img/pbr/geometry.png" alt="Visualized Geometry function in OpenGL PBR"/> - -<p> - The geometry function is a multiplier between [0.0, 1.0] with 1.0 (or white) measuring no microfacet shadowing, and 0.0 (or black) complete microfacet shadowing. -</p> - -<p> - In GLSL the geometry function translates to the following code: -</p> - -<pre><code> -float GeometrySchlickGGX(float NdotV, float k) -{ - float nom = NdotV; - float denom = NdotV * (1.0 - k) + k; - - return nom / denom; -} - -float GeometrySmith(vec3 N, vec3 V, vec3 L, float k) -{ - float NdotV = max(dot(N, V), 0.0); - float NdotL = max(dot(N, L), 0.0); - float ggx1 = GeometrySchlickGGX(NdotV, k); - float ggx2 = GeometrySchlickGGX(NdotL, k); - - return ggx1 * ggx2; -} -</code></pre> - - -<h3>Fresnel equation</h3> -<p> - The Fresnel equation (pronounced as Freh-nel) describes the ratio of light that gets reflected over the light that gets refracted, which varies over the angle we're looking at a surface. The moment light hits a surface, based on the surface-to-view angle, the Fresnel equation tells us the percentage of light that gets reflected. From this ratio of reflection and the energy conservation principle we can directly obtain the refracted portion of light. - </p> - - <p> - Every surface or material has a level of <def>base reflectivity</def> when looking straight at its surface, but when looking at the surface from an angle <a href="http://filmicworlds.com/blog/everything-has-fresnel/" target="_blank">all</a> reflections become more apparent compared to the surface's base reflectivity. You can check this for yourself by looking at your (presumably) wooden/metallic desk which has a certain level of base reflectivity from a perpendicular view angle, but by looking at your desk from an almost 90 degree angle you'll see the reflections become much more apparent. All surfaces theoretically fully reflect light if seen from perfect 90-degree angles. This phenomenon is known as <def>Fresnel</def> and is described by the Fresnel equation. -</p> - -<p> - The Fresnel equation is a rather complex equation, but luckily the Fresnel equation can be approximated using the <def>Fresnel-Schlick</def> approximation: -</p> - -\[ - F_{Schlick}(h, v, F_0) = - F_0 + (1 - F_0) ( 1 - (h \cdot v))^5 -\] - -<p> - \(F_0\) represents the base reflectivity of the surface, which we calculate using something called the <em>indices of refraction</em> or IOR. As you can see on a sphere surface, the more we look towards the surface's grazing angles (with the halfway-view angle reaching 90 degrees), the stronger the Fresnel and thus the reflections: -</p> - - <img src="/img/pbr/fresnel.png" alt="Visualized Fresnel equation on a sphere."/> - -<p> - There are a few subtleties involved with the Fresnel equation. One is that the Fresnel-Schlick approximation is only really defined for <def>dielectric</def> or non-metal surfaces. For <def>conductor</def> surfaces (metals), calculating the base reflectivity with indices of refraction doesn't properly hold and we need to use a different Fresnel equation for conductors altogether. As this is inconvenient, we further approximate by pre-computing the surface's response at <def>normal incidence</def> (\(F_0\)) at a 0 degree angle as if looking directly onto a surface. We interpolate this value based on the view angle, as per the Fresnel-Schlick approximation, such that we can use the same equation for both metals and non-metals. -</p> - -<p> - The surface's response at normal incidence, or the base reflectivity, can be found in large databases like <a href="http://refractiveindex.info/" target="_blank">these</a> with some of the more common values listed below as taken from Naty Hoffman's course notes: -</p> - -<table> - <tr> - <th>Material</th> - <th>\(F_0\) (Linear)</th> - <th>\(F_0\) (sRGB)</th> - <th>Color</th> - </tr> - <tr> - <td>Water</td> - <td><code>(0.02, 0.02, 0.02)</code></td> - <td><code> (0.15, 0.15, 0.15)</code> </td> - <td style="background-color: #262626"></td> - </tr> - <tr> - <td>Plastic / Glass (Low)</td> - <td><code>(0.03, 0.03, 0.03)</code></td> - <td><code>(0.21, 0.21, 0.21)</code></td> - <td style="background-color: #363636"></td> - </tr> - <tr> - <td>Plastic High</td> - <td><code>(0.05, 0.05, 0.05)</code></td> - <td><code>(0.24, 0.24, 0.24)</code></td> - <td style="background-color: #3D3D3D"></td> - </tr> - <tr> - <td>Glass (high) / Ruby</td> - <td><code>(0.08, 0.08, 0.08)</code></td> - <td><code>(0.31, 0.31, 0.31)</code></td> - <td style="background-color: #4F4F4F"></td> - </tr> - <tr> - <td>Diamond</td> - <td><code>(0.17, 0.17, 0.17)</code></td> - <td><code>(0.45, 0.45, 0.45)</code></td> - <td style="background-color: #737373"></td> - </tr> - <tr> - <td>Iron</td> - <td><code>(0.56, 0.57, 0.58)</code></td> - <td><code>(0.77, 0.78, 0.78)</code></td> - <td style="background-color: #C5C8C8"></td> - </tr> - <tr> - <td>Copper</td> - <td><code>(0.95, 0.64, 0.54)</code></td> - <td><code>(0.98, 0.82, 0.76)</code></td> - <td style="background-color: #FBD2C3"></td> - </tr> - <tr> - <td>Gold</td> - <td><code>(1.00, 0.71, 0.29)</code></td> - <td><code>(1.00, 0.86, 0.57)</code></td> - <td style="background-color: #FFDC92"></td> - </tr> - <tr> - <td>Aluminium</td> - <td><code>(0.91, 0.92, 0.92)</code></td> - <td><code>(0.96, 0.96, 0.97)</code></td> - <td style="background-color: #F6F6F8"></td> - </tr> - <tr> - <td>Silver</td> - <td><code>(0.95, 0.93, 0.88)</code></td> - <td><code>(0.98, 0.97, 0.95)</code></td> - <td style="background-color: #FBF8F3"></td> - </tr> - -</table> - -<p> - What is interesting to observe here is that for all dielectric surfaces the base reflectivity never gets above 0.17 which is the exception rather than the rule, while for conductors the base reflectivity starts much higher and (mostly) varies between 0.5 and 1.0. Furthermore, for conductors (or metallic surfaces) the base reflectivity is tinted. This is why \(F_0\) is presented as an RGB triplet (reflectivity at normal incidence can vary per wavelength); this is something we <strong>only</strong> see at metallic surfaces. -</p> - -<p> - These specific attributes of metallic surfaces compared to dielectric surfaces gave rise to something called the <def>metallic workflow</def>. In the metallic workflow we author surface materials with an extra parameter known as <def>metalness</def> that describes whether a surface is either a metallic or a non-metallic surface. -</p> - -<note> - Theoretically, the metalness of a material is binary: it's either a metal or it isn't; it can't be both. However, most render pipelines allow configuring the metalness of a surface linearly between 0.0 and 1.0. This is mostly because of the lack of material texture precision. For instance, a surface having small (non-metal) dust/sand-like particles/scratches over a metallic surface is difficult to render with binary metalness values. -</note> - -<p> - By pre-computing \(F_0\) for both dielectrics and conductors we can use the same Fresnel-Schlick approximation for both types of surfaces, but we do have to tint the base reflectivity if we have a metallic surface. We generally accomplish this as follows: -</p> - -<pre><code> -vec3 F0 = vec3(0.04); -F0 = mix(F0, surfaceColor.rgb, metalness); -</code></pre> - -<p> - We define a base reflectivity that is approximated for most dielectric surfaces. This is yet another approximation as \(F_0\) is averaged around most common dielectrics. A base reflectivity of 0.04 holds for most dielectrics and produces physically plausible results without having to author an additional surface parameter. Then, based on how metallic a surface is, we either take the dielectric base reflectivity or take \(F_0\) authored as the surface color. Because metallic surfaces absorb all refracted light they have no diffuse reflections and we can directly use the surface color texture as their base reflectivity. -</p> - - <p> - In code, the Fresnel Schlick approximation translates to: -</p> - -<pre><code> -vec3 fresnelSchlick(float cosTheta, vec3 F0) -{ - return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); -} -</code></pre> - -<p> - With <code>cosTheta</code> being the dot product result between the surface's normal \(n\) and the halfway \(h\) (or view \(v\)) direction. -</p> - - -<h3>Cook-Torrance reflectance equation</h3> -<p> - With every component of the Cook-Torrance BRDF described, we can include the physically based BRDF into the now final reflectance equation: -</p> - - \[ - L_o(p,\omega_o) = \int\limits_{\Omega} - (k_d\frac{c}{\pi} + k_s\frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)}) - L_i(p,\omega_i) n \cdot \omega_i d\omega_i - \] - -<p> - This equation is not fully mathematically correct however. You may remember that the Fresnel term \(F\) represents the ratio of light that gets <em>reflected</em> on a surface. This is effectively our ratio \(k_s\), meaning the specular (BRDF) part of the reflectance equation implicitly contains the reflectance ratio \(k_s\). Given this, our final final reflectance equation becomes: -</p> - - \[ - L_o(p,\omega_o) = \int\limits_{\Omega} - (k_d\frac{c}{\pi} + \frac{DFG}{4(\omega_o \cdot n)(\omega_i \cdot n)}) - L_i(p,\omega_i) n \cdot \omega_i d\omega_i - \] - -<p> - This equation now completely describes a physically based render model that is generally recognized as what we commonly understand as physically based rendering, or PBR. Don't worry if you didn't yet completely understand how we'll need to fit all the discussed mathematics together in code. In the next chapters, we'll explore how to utilize the reflectance equation to get much more physically plausible results in our rendered lighting and all the bits and pieces should slowly start to fit together. -</p> - -<h2>Authoring PBR materials</h2> -<p> - With knowledge of the underlying mathematical model of PBR we'll finalize the discussion by describing how artists generally author the physical properties of a surface that we can directly feed into the PBR equations. Each of the surface parameters we need for a PBR pipeline can be defined or modeled by textures. Using textures gives us per-fragment control over how each specific surface point should react to light: whether that point is metallic, rough or smooth, or how the surface responds to different wavelengths of light. -</p> - -<p> - Below you'll see a list of textures you'll frequently find in a PBR pipeline together with its visual output if supplied to a PBR renderer: -</p> - - <img src="/img/pbr/textures.png" class="clean" alt="Example of how artists author a PBR material with its relevant textures (OpenGL)."/> - -<p> - <strong>Albedo</strong>: the <def>albedo</def> texture specifies for each texel the color of the surface, or the base reflectivity if that texel is metallic. This is largely similar to what we've been using before as a diffuse texture, but all lighting information is extracted from the texture. Diffuse textures often have slight shadows or darkened crevices inside the image which is something you don't want in an albedo texture; it should only contain the color (or refracted absorption coefficients) of the surface. -</p> - -<p> - <strong>Normal</strong>: the normal map texture is exactly as we've been using before in the <a href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping" target="_blank">normal mapping</a> chapter. The normal map allows us to specify, per fragment, a unique normal to give the illusion that a surface is <em>bumpier</em> than its flat counterpart. -</p> - -<p> - <strong>Metallic</strong>: the metallic map specifies per texel whether a texel is either metallic or it isn't. Based on how the PBR engine is set up, artists can author metalness as either grayscale values or as binary black or white. -</p> - -<p> - <strong>Roughness</strong>: the roughness map specifies how rough a surface is on a per texel basis. The sampled roughness value of the roughness influences the statistical microfacet orientations of the surface. A rougher surface gets wider and blurrier reflections, while a smooth surface gets focused and clear reflections. Some PBR engines expect a <def>smoothness</def> map instead of a roughness map which some artists find more intuitive. These values are then translated (<code>1.0 - smoothness</code>) to roughness the moment they're sampled. -</p> - -<p> - <strong>AO</strong>: the <def>ambient occlusion</def> or <def>AO</def> map specifies an extra shadowing factor of the surface and potentially surrounding geometry. If we have a brick surface for instance, the albedo texture should have no shadowing information inside the brick's crevices. The AO map however does specify these darkened edges as it's more difficult for light to escape. Taking ambient occlusion in account at the end of the lighting stage can significantly boost the visual quality of your scene. The ambient occlusion map of a mesh/surface is either manually generated, or pre-calculated in 3D modeling programs. -</p> - -<p> - Artists set and tweak these physically based input values on a per-texel basis and can base their texture values on the physical surface properties of real-world materials. This is one of the biggest advantages of a PBR render pipeline as these physical properties of a surface remain the same, regardless of environment or lighting setup, making life easier for artists to get physically plausible results. Surfaces authored in a PBR pipeline can easily be shared among different PBR render engines, will look correct regardless of the environment they're in, and as a result look much more natural. -</p> - -<h2>Further reading</h2> -<ul> - <li><a href="http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf" target="_blank">Background: Physics and Math of Shading by Naty Hoffmann</a>: there is too much theory to fully discuss in a single article so the theory here barely scratches the surface; if you want to know more about the physics of light and how it relates to the theory of PBR <strong>this</strong> is the resource you want to read.</li> - <li><a href="http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf" target="_blank">Real shading in Unreal Engine 4</a>: discusses the PBR model adopted by Epic Games in their 4th Unreal Engine installment. The PBR system we'll focus on in these chapters is based on this model of PBR.</li> - <li><a href="https://www.shadertoy.com/view/4sSfzK" target="_blank">[SH17C] Physically Based Shading, by knarkowicz</a>: great showcase of all individual PBR elements in an interactive ShaderToy demo.</li> - <li><a href="https://www.marmoset.co/toolbag/learn/pbr-theory" target="_blank">Marmoset: PBR Theory</a>: an introduction to PBR mostly meant for artists, but nevertheless a good read.</li> - <li><a href="http://www.codinglabs.net/article_physically_based_rendering.aspx" target="_blank">Coding Labs: Physically based rendering</a>: an introduction to the render equation and how it relates to PBR. </li> - <li><a href="http://www.codinglabs.net/article_physically_based_rendering_cook_torrance.aspx" target="_blank">Coding Labs: Physically Based Rendering - Cook–Torrance</a>: an introduction to the Cook-Torrance BRDF. </li> - <li><a href="http://blog.wolfire.com/2015/10/Physically-based-rendering" target="_blank">Wolfire Games - Physically based rendering</a>: an introduction to PBR by Lukas Orsvärn.</li> - <li><a href="https://www.shadertoy.com/view/4sSfzK" target="_blank">[SH17C] Physically Based Shading</a>: a great interactive shadertoy example (warning: may take a while to load) by Krzysztof Narkowi showcasing light-material interaction in a PBR fashion.</li> -</ul> - - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file diff --git a/translation/Translations.html b/translation/Translations.html @@ -1,324 +0,0 @@ - - -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"/> - <title>LearnOpenGL - Translations</title> <!--<title>Learn OpenGL, extensive tutorial resource for learning Modern OpenGL</title>--> - <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> - <meta name="description" content="Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners."> - <meta name="fragment" content="!"> - <script> - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-51879160-1', 'learnopengl.com'); - ga('send', 'pageview'); - - </script> - <!--<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>--> - <script> - (adsbygoogle = window.adsbygoogle || []).push({ - google_ad_client: "ca-pub-7855791439695850", - enable_page_level_ads: true - }); - </script> - <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script> - <script> - var googletag = googletag || {}; - googletag.cmd = googletag.cmd || []; - </script> - <script> - googletag.cmd.push(function() { - googletag.defineSlot('/8491498/learnopengl_video', [300, 225], 'div-gpt-ad-1540574378241-0').addService(googletag.pubads()); - googletag.pubads().enableSingleRequest(); - googletag.pubads().collapseEmptyDivs(); - googletag.enableServices(); - }); - </script> - <script type="text/javascript" src="https://d31vxm9ubutrmw.cloudfront.net/static/js/1681.js"></script> - <script src="/js/jquery-1.11.0.min.js"></script> - <script src="/js/hoverintent.js"></script> - <link rel="stylesheet" type="text/css" href="/layout.css"> - <link rel="stylesheet" type="text/css" href="/js/styles/obsidian.css"> - <script src="/js/highlight.pack.js"></script> - <script src="/js/functions.js"></script> - <script type="text/javascript" src="/js/mathjax/MathJax.js?config=TeX-AMS_HTML"></script> - <script> - // Has to be loaded last due to content bug - MathJax.Hub.Config({ - TeX: { equationNumbers: { autoNumber: "AMS" } } - }); - </script> - <script>hljs.initHighlightingOnLoad();</script> - <script> - $(document).ready(function() { - // check if user visited from the old # based urls, re-direct to ?p= form - if(window.location.hash) - { - var name = window.location.hash.substring(2); - // name = name.replace(/-/g," "); - var index = name.indexOf('#'); // Remove any hash fragments from the url (Disquss adds hash fragments for comments, but results in 404 pages) - if(index >= 0) - name = name.substring(0, index); - - window.location.href = "https://learnopengl.com/" + name; - } else { - // Check if data has been succesfully loaded, if so: change title bar as ajax hash fragment - var title = $('#content-url').text(); - - // Refresh syntax highlighting - // $('pre').each(function(i, e) {hljs.highlightBlock(e)}); - - // Reset DISQUS - // if(title == '/dev/') - // title = ''; - // alert('hoi'); - - // Adjust ads for correct bottom positioning based on content size - window.setTimeout(function() { - AdPositioning(); - }, 3000); - - - // set API resets after time-out (once content is properly loaded) - window.setTimeout(function() { - MathJax.Hub.Queue(["Typeset",MathJax.Hub]); - MathJax.Hub.Queue(["resetEquationNumbers", MathJax.InputJax.TeX]); - - var page_url = title == "" ? "http://www.learnopengl.com/" : "http://www.learnopengl.com/" + title; - if(typeof DISQUS !== 'undefined') { - DISQUS.reset({ - reload: true, - config: function () { - this.page.identifier = title; - this.page.url = page_url; - } - }); - $('#disqus_thread').show(); - } - // Refresh callbacks on <function> tags - SetFunctionTagCallbacks(); - }, 1000); - - // Zet ook de juiste button op 'selected' - $('#nav li span, #nav li a').removeClass('selected'); - if(title != '') - { - $('#nav li[id=\'' + title + '\']').children('span, a').addClass('selected'); - } - // En open menu waar nodig - var parents = $('#nav span.selected, #nav a.selected').parents('li').children('span.closed, a.closed'); - var index = 0; - for(index = parents.length - 1; index >= 0; index--) - { - - var id = $(parents[index]).attr("id").replace( /^\D+/g, ''); - MenuClick(id, false); - } - - } - }); - // var initialized = false; - // window.onpopstate = function() { - // if(initialized) - // LoadPage(); - // else - // initialized = true; - // }; - - // Set up DISQUS - // $(document).ready(function() { - var disqus_shortname = 'learnopengl'; - (function() { - var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; - dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; - (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); - })(); - // }); - </script> -</head> -<body> -<a href="https://learnopengl.com"> -<div id="header"> -</div> -</a> - -<div id="supercontainer"> - <!-- 728x90/320x50 --> - <div id="header_ad"> - <div id="waldo-tag-6194"></div> - </div> - <div id="rightad_container"> - <div id="rightad"> - <!-- /8491498/learnopengl_video --> - <!--<div id='div-gpt-ad-1540574378241-0' style='height:225px; width:300px;'> - <script> - googletag.cmd.push(function() { googletag.display('div-gpt-ad-1540574378241-0'); }); - </script> - </div> - <br/>--> - - <div id="waldo-tag-1715"></div> - </div> - - <div id="admessage"> - If you're running AdBlock, please consider whitelisting this site if you'd like to support LearnOpenGL; and no worries, I won't be mad if you don't :) - <!--<br/><br/> - Also, check out this little local multiplayer-only game I've made: <a href="https://store.steampowered.com/app/983590/Tank_Blazers/" target="_blank">Tank Blazers</a>. - <br/> - <a href="https://store.steampowered.com/app/983590/Tank_Blazers" target="_blank"><img src="/img/tank_blazers.jpg" style="width:278px; margin-top: 9px; margin-left: -3px;"/></a>--> - </div> - - <div id="rightonethirdad"> - <div id="waldo-tag-2246"></div> - </div> - - <div id="rightbottomad"> - <div id="waldo-tag-2247"></div> - </div> - </div> - <div id="container"> - <div id="loading"></div> -<script> -$(document).ready(function() { -$('#menu-item4').mousedown(function() { MenuClick(4, true) }); -$('#menu-item48').mousedown(function() { MenuClick(48, true) }); -$('#menu-item56').mousedown(function() { MenuClick(56, true) }); -$('#menu-item63').mousedown(function() { MenuClick(63, true) }); -$('#menu-item100').mousedown(function() { MenuClick(100, true) }); -$('#menu-item102').mousedown(function() { MenuClick(102, true) }); -$('#menu-item113').mousedown(function() { MenuClick(113, true) }); -$('#menu-item116').mousedown(function() { MenuClick(116, true) }); -$('#menu-item78').mousedown(function() { MenuClick(78, true) }); -$('#menu-item81').mousedown(function() { MenuClick(81, true) }); -$('#menu-item85').mousedown(function() { MenuClick(85, true) }); -$('#menu-item125').mousedown(function() { MenuClick(125, true) }); -$('#menu-item128').mousedown(function() { MenuClick(128, true) }); -$('#menu-item129').mousedown(function() { MenuClick(129, true) }); -$('#menu-item133').mousedown(function() { MenuClick(133, true) }); -$('#menu-item134').mousedown(function() { MenuClick(134, true) }); -}); -</script> - <div id="nav"> - <div id="social"> - <a href="https://github.com/JoeyDeVries/LearnOpenGL" target="_blank"> - <img src="/img/github.png" class="social_ico"> - </a> - <!-- <a href="https://www.facebook.com/Learnopengl-2199631333595544/" target="_blank"> - <img src="/img/facebook.png" class="social_ico"> - </a>--> - <a href="https://twitter.com/JoeyDeVriez" target="_blank"> - <img src="/img/twitter.png" class="social_ico"> - </a> - - </div> - <img src='img/nav-button_bottom-arrow.png' style='display: none'><ol><li id='Introduction'><a id="menu-item1" href="https://learnopengl.com/Introduction">Introduction </a></li><li id='Getting-started'><span id="menu-item4" class="closed">Getting started </span><ol id="menu-items-of4" style="display:none;"><li id='Getting-started/OpenGL'><a id="menu-item49" href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a></li><li id='Getting-started/Creating-a-window'><a id="menu-item5" href="https://learnopengl.com/Getting-started/Creating-a-window">Creating a window </a></li><li id='Getting-started/Hello-Window'><a id="menu-item6" href="https://learnopengl.com/Getting-started/Hello-Window">Hello Window </a></li><li id='Getting-started/Hello-Triangle'><a id="menu-item38" href="https://learnopengl.com/Getting-started/Hello-Triangle">Hello Triangle </a></li><li id='Getting-started/Shaders'><a id="menu-item39" href="https://learnopengl.com/Getting-started/Shaders">Shaders </a></li><li id='Getting-started/Textures'><a id="menu-item40" href="https://learnopengl.com/Getting-started/Textures">Textures </a></li><li id='Getting-started/Transformations'><a id="menu-item43" href="https://learnopengl.com/Getting-started/Transformations">Transformations </a></li><li id='Getting-started/Coordinate-Systems'><a id="menu-item44" href="https://learnopengl.com/Getting-started/Coordinate-Systems">Coordinate Systems </a></li><li id='Getting-started/Camera'><a id="menu-item47" href="https://learnopengl.com/Getting-started/Camera">Camera </a></li><li id='Getting-started/Review'><a id="menu-item50" href="https://learnopengl.com/Getting-started/Review">Review </a></li></ol></li><li id='Lighting'><span id="menu-item48" class="closed">Lighting </span><ol id="menu-items-of48" style="display:none;"><li id='Lighting/Colors'><a id="menu-item51" href="https://learnopengl.com/Lighting/Colors">Colors </a></li><li id='Lighting/Basic-Lighting'><a id="menu-item52" href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a></li><li id='Lighting/Materials'><a id="menu-item53" href="https://learnopengl.com/Lighting/Materials">Materials </a></li><li id='Lighting/Lighting-maps'><a id="menu-item54" href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a></li><li id='Lighting/Light-casters'><a id="menu-item55" href="https://learnopengl.com/Lighting/Light-casters">Light casters </a></li><li id='Lighting/Multiple-lights'><a id="menu-item58" href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a></li><li id='Lighting/Review'><a id="menu-item57" href="https://learnopengl.com/Lighting/Review">Review </a></li></ol></li><li id='Model-Loading'><span id="menu-item56" class="closed">Model Loading </span><ol id="menu-items-of56" style="display:none;"><li id='Model-Loading/Assimp'><a id="menu-item59" href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a></li><li id='Model-Loading/Mesh'><a id="menu-item60" href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a></li><li id='Model-Loading/Model'><a id="menu-item61" href="https://learnopengl.com/Model-Loading/Model">Model </a></li></ol></li><li id='Advanced-OpenGL'><span id="menu-item63" class="closed">Advanced OpenGL </span><ol id="menu-items-of63" style="display:none;"><li id='Advanced-OpenGL/Depth-testing'><a id="menu-item72" href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a></li><li id='Advanced-OpenGL/Stencil-testing'><a id="menu-item73" href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a></li><li id='Advanced-OpenGL/Blending'><a id="menu-item74" href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a></li><li id='Advanced-OpenGL/Face-culling'><a id="menu-item77" href="https://learnopengl.com/Advanced-OpenGL/Face-culling">Face culling </a></li><li id='Advanced-OpenGL/Framebuffers'><a id="menu-item65" href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a></li><li id='Advanced-OpenGL/Cubemaps'><a id="menu-item66" href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a></li><li id='Advanced-OpenGL/Advanced-Data'><a id="menu-item69" href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a></li><li id='Advanced-OpenGL/Advanced-GLSL'><a id="menu-item67" href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a></li><li id='Advanced-OpenGL/Geometry-Shader'><a id="menu-item68" href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a></li><li id='Advanced-OpenGL/Instancing'><a id="menu-item70" href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a></li><li id='Advanced-OpenGL/Anti-Aliasing'><a id="menu-item75" href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a></li></ol></li><li id='Advanced-Lighting'><span id="menu-item100" class="closed">Advanced Lighting </span><ol id="menu-items-of100" style="display:none;"><li id='Advanced-Lighting/Advanced-Lighting'><a id="menu-item101" href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a></li><li id='Advanced-Lighting/Gamma-Correction'><a id="menu-item110" href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a></li><li id='Advanced-Lighting/Shadows'><span id="menu-item102" class="closed">Shadows </span><ol id="menu-items-of102" style="display:none;"><li id='Advanced-Lighting/Shadows/Shadow-Mapping'><a id="menu-item103" href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a></li><li id='Advanced-Lighting/Shadows/Point-Shadows'><a id="menu-item104" href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a></li></ol></li><li id='Advanced-Lighting/Normal-Mapping'><a id="menu-item106" href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a></li><li id='Advanced-Lighting/Parallax-Mapping'><a id="menu-item107" href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a></li><li id='Advanced-Lighting/HDR'><a id="menu-item111" href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a></li><li id='Advanced-Lighting/Bloom'><a id="menu-item112" href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a></li><li id='Advanced-Lighting/Deferred-Shading'><a id="menu-item108" href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a></li><li id='Advanced-Lighting/SSAO'><a id="menu-item109" href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a></li></ol></li><li id='PBR'><span id="menu-item113" class="closed">PBR </span><ol id="menu-items-of113" style="display:none;"><li id='PBR/Theory'><a id="menu-item114" href="https://learnopengl.com/PBR/Theory">Theory </a></li><li id='PBR/Lighting'><a id="menu-item115" href="https://learnopengl.com/PBR/Lighting">Lighting </a></li><li id='PBR/IBL'><span id="menu-item116" class="closed">IBL </span><ol id="menu-items-of116" style="display:none;"><li id='PBR/IBL/Diffuse-irradiance'><a id="menu-item117" href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a></li><li id='PBR/IBL/Specular-IBL'><a id="menu-item118" href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a></li></ol></li></ol></li><li id='In-Practice'><span id="menu-item78" class="closed">In Practice </span><ol id="menu-items-of78" style="display:none;"><li id='In-Practice/Debugging'><a id="menu-item79" href="https://learnopengl.com/In-Practice/Debugging">Debugging </a></li><li id='In-Practice/Text-Rendering'><a id="menu-item80" href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a></li><li id='In-Practice/2D-Game'><span id="menu-item81" class="closed">2D Game </span><ol id="menu-items-of81" style="display:none;"><li id='In-Practice/2D-Game/Breakout'><a id="menu-item82" href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a></li><li id='In-Practice/2D-Game/Setting-up'><a id="menu-item88" href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a></li><li id='In-Practice/2D-Game/Rendering-Sprites'><a id="menu-item83" href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a></li><li id='In-Practice/2D-Game/Levels'><a id="menu-item84" href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a></li><li id='In-Practice/2D-Game/Collisions'><span id="menu-item85" class="closed">Collisions </span><ol id="menu-items-of85" style="display:none;"><li id='In-Practice/2D-Game/Collisions/Ball'><a id="menu-item95" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a></li><li id='In-Practice/2D-Game/Collisions/Collision-detection'><a id="menu-item96" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a></li><li id='In-Practice/2D-Game/Collisions/Collision-resolution'><a id="menu-item97" href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a></li></ol></li><li id='In-Practice/2D-Game/Particles'><a id="menu-item89" href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a></li><li id='In-Practice/2D-Game/Postprocessing'><a id="menu-item90" href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a></li><li id='In-Practice/2D-Game/Powerups'><a id="menu-item91" href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a></li><li id='In-Practice/2D-Game/Audio'><a id="menu-item94" href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a></li><li id='In-Practice/2D-Game/Render-text'><a id="menu-item92" href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a></li><li id='In-Practice/2D-Game/Final-thoughts'><a id="menu-item93" href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a></li></ol></li></ol></li><li id='Guest-Articles'><span id="menu-item125" class="closed">Guest Articles </span><ol id="menu-items-of125" style="display:none;"><li id='Guest-Articles/How-to-publish'><a id="menu-item126" href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a></li><li id='Guest-Articles/2020'><span id="menu-item128" class="closed">2020 </span><ol id="menu-items-of128" style="display:none;"><li id='Guest-Articles/2020/OIT'><span id="menu-item129" class="closed">OIT </span><ol id="menu-items-of129" style="display:none;"><li id='Guest-Articles/2020/OIT/Introduction'><a id="menu-item130" href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a></li><li id='Guest-Articles/2020/OIT/Weighted-Blended'><a id="menu-item132" href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a></li></ol></li><li id='Guest-Articles/2020/Skeletal-Animation'><a id="menu-item131" href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a></li></ol></li><li id='Guest-Articles/2021'><span id="menu-item133" class="closed">2021 </span><ol id="menu-items-of133" style="display:none;"><li id='Guest-Articles/2021/CSM'><a id="menu-item137" href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a></li><li id='Guest-Articles/2021/Scene'><span id="menu-item134" class="closed">Scene </span><ol id="menu-items-of134" style="display:none;"><li id='Guest-Articles/2021/Scene/Scene-Graph'><a id="menu-item135" href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a></li><li id='Guest-Articles/2021/Scene/Frustum-Culling'><a id="menu-item136" href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a></li></ol></li></ol></li></ol></li><li id='Code-repository'><a id="menu-item99" href="https://learnopengl.com/Code-repository">Code repository </a></li><li id='Translations'><a id="menu-item119" href="https://learnopengl.com/Translations">Translations </a></li><li id='About'><a id="menu-item2" href="https://learnopengl.com/About">About </a></li></ol> <div id="menu_book"> - <a href="https://geni.us/learnopengl" target="_blank"><img src="/book/below_menu.png" class="clean"/></a> - </div> - <div id="donate"> - <a href="https://www.paypal.me/learnopengl/" target="_blank"> - <div id="donate_img"></div> - <img style="display: none" src="/img/donate_button_hover.png"/> - <!--<img id="donate_img" src="img/patreon.png"/>--> - </a> - <!--<div id="alipay"> - <img style="width: 150px;" class="clean" src="/img/alipay_logo.png"/> - <img style="width: 150px; margin-top: 5px" src="/img/alipay.png"/> - </div>--> - </div> - <div class="btc"> - <h3>BTC</h3> - <p> - 1CLGKgmBSuYJ1nnvDGAepVTKNNDpUjfpRa - </p> - <img src="/img/btc_qr.png"/> - </div> - <div class="btc"> - <h3>ETH/ERC20</h3> - <p> - 0x1de59bd9e52521a46309474f8372531533bd7c43 - </p> - <img src="/img/erc20_qr.png"/> - </div> - <div id="ad"> - <!--<div id="waldo-tag-1684"></div>--> - </div> - - <div id="lefttwothirdad"> - <div id="waldo-tag-2245"></div> - </div> - </div> - - <div id="content"> - <h1 id="content-title">Translations</h1> -<h1 id="content-url" style='display:none;'>Translations</h1> -<p> - Thanks to a massive effort on the community's part, several translations have been published of LearnOpenGL's content. You'll always get the latest and most up-to-date version here, but if you find it easier to read in your native language the following translations did an amazing job: -</p> - -<img src="/img/flag_china.png" class="translation"/> <strong>Chinese</strong>: - -<ul id="translations"> - <li><a href="https://learnopengl-cn.github.io/" target="_blank">LearnOpenGL CN</a></li> - <!--<li><a href="http://bullteacher.com/category/zh_learnopengl_com" target="_blank">bullteacher.com</a></li>--> -</ul> - -<img src="/img/flag_russian.png" class="translation"/> <strong>Russian</strong>: - -<ul id="translations"> - <li><a href="https://habrahabr.ru/post/336166/" target="_blank">LearnOpenGL</a></li> -</ul> - -<img src="/img/flag_french.png" class="translation"/> <strong>French</strong>: - -<ul id="translations"> - <li><a href=" https://opengl.developpez.com/tutoriels/apprendre-opengl/" target="_blank">OpenGL Developpez</a></li> -</ul> - -<img src="/img/flag_polish.png" class="translation"/> <strong>Polish</strong>: - -<ul id="translations"> - <li><a href="https://shot511.github.io/pages/learnopengl/" target="_blank">Real-time rendering class</a></li> -</ul> - -<img src="/img/flag_korean.png" class="translation"/> <strong>Korean</strong>: - -<ul id="translations"> - <li><a href="http://heinleinsgame.tistory.com/category/OpenGL" target="_blank">Heinleinsgame OpenGL</a></li> - <li><a href="https://gyutts.tistory.com/86?category=755809" target="_blank">Game Developer Q.bot</a></li> -</ul> - -<img src="/img/flag_turkey.png" class="translation"/> <strong>Turkish</strong>: - -<ul id="translations"> - <li><a href="https://cgtranslators.gitbook.io/opengl-ogrenin/" target="_blank">OpenGL' e Hoşgeldiniz</a></li> -</ul> - - - -<p> - If you've been working on a translation of LearnOpenGL and already have a good amount of translated content, let me know and I'll be sure to add a link to your translation above. -</p> - - </div> - - <div id="hover"> - HI - </div> - <!-- 728x90/320x50 sticky footer --> -<div id="waldo-tag-6196"></div> - - <div id="disqus_thread"></div> - - - - -</div> <!-- container div --> - - -</div> <!-- super container div --> -</body> -</html> -\ No newline at end of file