{"id":551,"date":"2016-08-29T13:50:43","date_gmt":"2016-08-29T11:50:43","guid":{"rendered":"http:\/\/blogs.igalia.com\/itoral\/?p=551"},"modified":"2016-08-29T13:50:43","modified_gmt":"2016-08-29T11:50:43","slug":"opengl-terrain-renderer","status":"publish","type":"post","link":"https:\/\/blogs.igalia.com\/itoral\/2016\/08\/29\/opengl-terrain-renderer\/","title":{"rendered":"OpenGL terrain renderer"},"content":{"rendered":"<p>Lately I have been working on a simple terrain <em>OpenGL<\/em> renderer demo, mostly to have a playground where I could try some techniques like shadow mapping and water rendering in a scenario with a non trivial amount of geometry, and I thought it would be interesting to write a bit about it.<\/p>\n<p>But first, here is a video of the demo running on my old <em>Intel IvyBridge GPU<\/em>:<\/p>\n<div align=\"center\">\n<video poster=\"https:\/\/people.igalia.com\/itoral\/opengl-terrain-demo.png\" width=\"400\" height=\"300\" controls><source src=\"https:\/\/people.igalia.com\/itoral\/opengl-terrain-demo.ogv\" type=\"video\/mp4\">Your browser does not support the video tag.<\/video>\n<\/div>\n<p><\/p>\n<p>And some screenshots too:<\/p>\n<table style=\"width:100%;border:none\">\n<tr><\/tr>\n<tr style=\"border:none\">\n<td style=\"border:none\"><a href=\"http:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s1.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s1-300x225.png\" alt=\"OpenGL Terrain screenshot 1\" width=\"300\" height=\"225\" class=\"alignnone size-medium wp-image-557\" srcset=\"https:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s1-300x225.png 300w, https:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s1-768x576.png 768w, https:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s1.png 800w\" sizes=\"auto, (max-width: 300px) 85vw, 300px\" \/><\/a><\/td>\n<td style=\"border:none\"><a href=\"http:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s2.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s2-300x225.png\" alt=\"OpenGL Terrain screenshot 2\" width=\"300\" height=\"225\" class=\"alignnone size-medium wp-image-558\" srcset=\"https:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s2-300x225.png 300w, https:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s2-768x576.png 768w, https:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s2.png 800w\" sizes=\"auto, (max-width: 300px) 85vw, 300px\" \/><\/a><\/td>\n<\/tr>\n<tr><\/tr>\n<tr style=\"border:none\">\n<td style=\"border:none\"><a href=\"http:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s3.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s3-300x225.png\" alt=\"OpenGL Terrain screenshot 3\" width=\"300\" height=\"225\" class=\"alignnone size-medium wp-image-559\" srcset=\"https:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s3-300x225.png 300w, https:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s3-768x576.png 768w, https:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s3.png 800w\" sizes=\"auto, (max-width: 300px) 85vw, 300px\" \/><\/a><\/td>\n<td style=\"border:none\"><a href=\"http:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s4.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s4-300x225.png\" alt=\"OpenGL Terrain screenshot 4\" width=\"300\" height=\"225\" class=\"alignnone size-medium wp-image-560\" srcset=\"https:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s4-300x225.png 300w, https:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s4-768x576.png 768w, https:\/\/blogs.igalia.com\/itoral\/files\/2016\/08\/s4.png 800w\" sizes=\"auto, (max-width: 300px) 85vw, 300px\" \/><\/a><\/td>\n<\/tr>\n<\/table>\n<p>Note that I did not create any of the textures or 3D models featured in the video.<\/p>\n<p>With that out of the way, let&#8217;s dig into some of the technical aspects:<\/p>\n<p>The terrain is built as a <em>251&#215;251<\/em> grid of vertices elevated with a heightmap texture, so it contains <em>63,000 vertices<\/em> and <em>125,000 triangles<\/em>. It uses a single <em>512&#215;512<\/em> texture to color the surface.<\/p>\n<p>The water is rendered in 3 passes: refraction, reflection and the final rendering. Distortion is done via a <em>dudv<\/em> map and it also uses a <em>normal<\/em> map for lighting. From a geometry perspective it is also implemented as a grid of vertices with <em>750 triangles<\/em>.<\/p>\n<p>I wrote a simple <em>OBJ file parser<\/em> so I could load some basic 3D models for the trees, the rock and the plant models. The parser is limited, but sufficient to load vertex data and simple materials. This demo features 4 models with these specs:<\/p>\n<ul>\n<li>Tree A: <em>280 triangles, 2 materials<\/em>.<\/li>\n<li>Tree B: <em>380 triangles, 2 materials<\/em>.<\/li>\n<li>Rock: <em>192 triangles, 1 material (textured)<\/em><\/li>\n<li>Grass: 8<em>96 triangles (yes, really!), 1 material<\/em>.<\/li>\n<\/ul>\n<p>The scene renders 2<em>00 instances of Tree A<\/em> another <em>200 instances of Tree B<\/em>, <em>50 instances of Rock<\/em> and <em>150 instances of Grass<\/em>, so <em>600 objects in total<\/em>.<\/p>\n<p>Object locations in the terrain are randomized at start-up, but the demo prevents trees and grass to be under water (except for maybe their base section only) because it would very weird otherwise :), rocks can be fully submerged though.<\/p>\n<p>Rendered objects fade in and out smoothly via <em>alpha blending<\/em> (so there is no <em>pop-in\/pop-out<\/em> effect as they reach clipping planes). This cannot be observed in the video because it uses a static camera but the demo supports moving the camera around in real-time using the keyboard.<\/p>\n<p>Lighting is implemented using the traditional <em>Phong reflection model<\/em> with a single <em>directional light<\/em>.<\/p>\n<p>Shadows are implemented using a <em>4096&#215;4096 shadow map<\/em> and <em>Percentage Closer Filter<\/em> with a 3<em>x3 kernel<\/em>, which, I read is (or was?) a very common technique for shadow rendering, at least in the times of the <em>PS3<\/em> and <em>Xbox 360<\/em>.<\/p>\n<p>The demo features dynamic directional lighting (that is, the sun light changes position every frame), which is rather taxing. The demo also supports static lighting, which is significantly less demanding.<\/p>\n<p>There is also a slight haze that builds up progressively with the distance from the camera. This can be seen slightly in the video, but it is more obvious in some of the screenshots above.<\/p>\n<p>The demo in the video was also configured to use <em>4-sample multisampling<\/em>. <\/p>\n<p>As for the rendering pipeline, it mostly has 4 stages:<\/p>\n<ul>\n<li>Shadow map.<\/li>\n<li>Water refraction.<\/li>\n<li>Water reflection.<\/li>\n<li>Final scene rendering.<\/li>\n<\/ul>\n<p>A few notes on performance as well: the implementation supports a number of configurable parameters that affect the framerate: resolution, shadow rendering quality, clipping distances, multi-sampling, some aspects of the water rendering, N-buffering of dynamic VBO data, etc.<\/p>\n<p>The video I show above runs at locked <em>60fps<\/em> at <em>800&#215;600<\/em> but it uses relatively high quality shadows and dynamic lighting, which are very expensive. Lowering some of these settings (very specially turning off dynamic lighting, multisampling and shadow quality) yields framerates around <em>110fps-200fps<\/em>. With these settings it can also do <em>fullscreen 1600&#215;900<\/em> with an unlocked framerate that varies in the range of <em>80fps-170fps<\/em>.<\/p>\n<p>That&#8217;s all in the <em>IvyBridge GPU<\/em>. I also tested this on an <em>Intel Haswell GPU<\/em> for significantly better results: <em>160fps-400fps<\/em> with the &#8220;low&#8221; settings at <em>800&#215;600<\/em> and roughly <em>80fps-200fps<\/em> with the same settings used in the video.<\/p>\n<p>So that&#8217;s it for today, I had a lot of fun coding this and I hope the post was interesting to some of you.  If time permits I intend to write follow-up posts that go deeper into how I implemented the various elements of the demo and I&#8217;ll probably also write some more posts about the optimization process I followed. If you are interested in any of that, stay tuned for more.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Lately I have been working on a simple terrain OpenGL renderer demo, mostly to have a playground where I could try some techniques like shadow mapping and water rendering in a scenario with a non trivial amount of geometry, and I thought it would be interesting to write a bit about it. But first, here &hellip; <a href=\"https:\/\/blogs.igalia.com\/itoral\/2016\/08\/29\/opengl-terrain-renderer\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;OpenGL terrain renderer&#8221;<\/span><\/a><\/p>\n","protected":false},"author":16,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[],"class_list":["post-551","post","type-post","status-publish","format-standard","hentry","category-graphics"],"_links":{"self":[{"href":"https:\/\/blogs.igalia.com\/itoral\/wp-json\/wp\/v2\/posts\/551","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.igalia.com\/itoral\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.igalia.com\/itoral\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/itoral\/wp-json\/wp\/v2\/users\/16"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/itoral\/wp-json\/wp\/v2\/comments?post=551"}],"version-history":[{"count":35,"href":"https:\/\/blogs.igalia.com\/itoral\/wp-json\/wp\/v2\/posts\/551\/revisions"}],"predecessor-version":[{"id":590,"href":"https:\/\/blogs.igalia.com\/itoral\/wp-json\/wp\/v2\/posts\/551\/revisions\/590"}],"wp:attachment":[{"href":"https:\/\/blogs.igalia.com\/itoral\/wp-json\/wp\/v2\/media?parent=551"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.igalia.com\/itoral\/wp-json\/wp\/v2\/categories?post=551"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.igalia.com\/itoral\/wp-json\/wp\/v2\/tags?post=551"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}