{"id":377,"date":"2011-02-10T18:04:41","date_gmt":"2011-02-10T17:04:41","guid":{"rendered":"http:\/\/shadowcovenant.com\/blog\/?p=377"},"modified":"2011-02-10T18:06:02","modified_gmt":"2011-02-10T17:06:02","slug":"ambient-occlusion-maps","status":"publish","type":"post","link":"http:\/\/shadowcovenant.com\/blog\/2011\/02\/10\/ambient-occlusion-maps\/","title":{"rendered":"Ambient Occlusion Maps"},"content":{"rendered":"<p>I\u2019m back to rendering pipeline for our current project&#8230; this time: Ambient Occlusion Maps.<\/p>\n<p>Ambient occlusion is the small dimming of ambient light in areas where the ambient light couldn\u2019t reach&#8230;<\/p>\n<p>Basically, ambient light exists to simulate the effect of trillions of photons bouncing off the objects in an area, so in fact, simulating indirect light. Note as a room is not completely dark in areas not illuminated directly by the window&#8230; that\u2019s due to surface reflection.<\/p>\n<p>While to do this normally in realtime you\u2019d have to resort to high-end and extremely complex techniques (i.e. CryEngine 3 has something akin to this), you have three low cost alternatives:<\/p>\n<ol>\n<li>Radiosity computed lightmaps<\/li>\n<li>Pre-calculated ambient occlusion maps<\/li>\n<li>Screen-space ambient occlusion algorithms<\/li>\n<\/ol>\n<p>While (1) is out of the question at the moment (because the interaction of lightmaped environments with dynamic objects is too complicated to get right), (2) and (3) can be used.<\/p>\n<p>Problem with (3) is that I really only like their effect in Crysis&#8230; I\u2019ve tried lots of different flavors of Screen Space Ambient Occlusion (SSAO) before, but results where never to my liking:<\/p>\n<p><a href=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/abstract_ssao021.jpg\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/abstract_ssao02_preview1.jpg\" border=\"0\" alt=\"abstract_ssao02\" hspace=\"8\" width=\"480\" height=\"378\" align=\"top\" \/><\/a><\/p>\n<p><a href=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/azenha_ssao021.jpg\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/azenha_ssao02_preview1.jpg\" border=\"0\" alt=\"azenha_ssao02\" hspace=\"8\" width=\"480\" height=\"378\" align=\"top\" \/><\/a><\/p>\n<p>Although some of the things that were wrong could probably be tweaked out, I\u2019m very strongly against tweaking stuff (my development time is very short, since I have a job and stuff)&#8230;<\/p>\n<p>So that leaves me with (2)&#8230;<\/p>\n<p>So I used the lightmap calculator to create some ambient occlusion maps that will be used in the realtime render of the scene.<\/p>\n<p>First, I just tried visualizing the points corresponding to the lumels:<\/p>\n<p><a href=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/01_lumel_point1.jpg\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/01_lumel_point_preview1.jpg\" border=\"0\" alt=\"01_lumel_point\" hspace=\"8\" width=\"480\" height=\"381\" align=\"top\" \/><\/a><\/p>\n<p>They didn\u2019t seem very right at the time, but I thought it was because of my UV being wacky&#8230; Then I did some experiments with simple raycasting and the results were average (no screenshot, I forgot to take one!)&#8230; I decided I needed to use multi-sampling to achieve decent results. This is where everything went wrong&#8230; I did a first iteration of the rectangle in the lumels area using the gradients of the rasterizer, and these were the results:<\/p>\n<p><a href=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/02_lumel_area_incorrect1.jpg\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/02_lumel_area_incorrect_preview1.jpg\" border=\"0\" alt=\"02_lumel_area_incorrect\" hspace=\"8\" width=\"480\" height=\"381\" align=\"top\" \/><\/a><\/p>\n<p>While some of it seems to be more or less right, most is totally wrong&#8230; I was expecting the rectangles to encompass a whole lumel, but they weren\u2019t&#8230; After some hours of pulling hair, I decided to rebuild my rasterizer, thinking the problem was there&#8230; it wasn\u2019t&#8230; \ud83d\ude41<\/p>\n<p>Anyway, after some attempts, I decided to ask for help in the <a href=\"https:\/\/lists.sourceforge.net\/lists\/listinfo\/gdalgorithms-list\" target=\"_blank\">GD-Algorithms Mailing List<\/a>, and as usual, people came through&#8230; Thanks to the help of several people, specially Olivier Galibert, I managed to change my rasterizer and ambient occlusion map generator to <a href=\"http:\/\/en.wikipedia.org\/wiki\/Barycentric_coordinate_system_(mathematics)\" target=\"_blank\">barycentric coordinates<\/a>, which fixed the problems:<\/p>\n<p><a href=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/03_lumel_area_correct1.jpg\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/03_lumel_area_correct_preview1.jpg\" border=\"0\" alt=\"03_lumel_area_correct\" hspace=\"8\" width=\"480\" height=\"381\" align=\"top\" \/><\/a><\/p>\n<p>On a triangle barycentric coordinates are a pair (U,V) such that if (U+V&lt;=1), then P(U,V)=V0+V1*U+V2*V belongs to the triangle.<\/p>\n<p>So, the triangle (and all his properties) are defined through the use of just (U,V) coordinates, which makes everything easier and more precise. Instead of just trying to find spans and filling in the insides with interpolated values, I actually just interpolated the U and V along the triangle edges, and used those values to find all the properties, achieving with this the result above.<\/p>\n<p>There\u2019s still some imperfections on the edge cases, but that\u2019s to be expected, since the triangles edges don\u2019t exactly match the pixel boundaries, but most of it can be taken care with a &#8220;bleeding&#8221; of the resulting ambient occlusion map.<\/p>\n<p>After I got this right, was just a matter of implementing the raycasting proper&#8230;<\/p>\n<p><a href=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/04_base_ambient_occlusion1.jpg\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/04_base_ambient_occlusion_preview1.jpg\" border=\"0\" alt=\"04_base_ambient_occlusion\" hspace=\"8\" width=\"480\" height=\"381\" align=\"top\" \/><\/a><\/p>\n<p>I like seeing the blocky ambient occlusion&#8230; In the above case, we only have about 16 rays per lumel.<\/p>\n<p><a href=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/04_ray_casting.jpg\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/04_ray_casting_preview1.jpg\" border=\"0\" alt=\"04_ray_casting\" hspace=\"8\" width=\"480\" height=\"381\" align=\"top\" \/><\/a><\/p>\n<p>Without multisampling, we only cast rays from the upper-left corner of the lumel (in this case it seems to be reversed, but that\u2019 because the UV space is &#8220;reversed&#8221; in the U direction on that lumel)&#8230; Adding multi-sampling:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/05_multisampling_preview1.jpg\" border=\"0\" alt=\"05_multisampling\" hspace=\"8\" width=\"480\" height=\"381\" align=\"top\" \/><\/p>\n<p>We get a more uniform spread&#8230; the quality improvement doesn\u2019t show much with this amount of rays, but with loads of them, at the &#8220;edges&#8221; of the &#8220;shadows&#8221; of objects, they definitely show.<\/p>\n<p>With 1024 raycast per lumel, on a 128&#215;128 ambient occlusion map used by the whole scene:<\/p>\n<p><a href=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/06_ray_casts_1024_point_sampling.jpg\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/06_ray_casts_1024_point_sampling_preview1.jpg\" border=\"0\" alt=\"06_ray_casts_1024_point_sampling\" hspace=\"8\" width=\"480\" height=\"381\" align=\"top\" \/><\/a><\/p>\n<p>Sampling is set to point, so I can see the lumels properly. Note that there are some artifacts on the top of the box on the left&#8230; that\u2019s due to the rasterizer not going &#8220;all the way&#8221; to the edge&#8230; this is solved through bleeding of the texture.<\/p>\n<p>On a larger scene (with some rooms, etc), with 256&#215;256 ambient occlusion maps, 1024 rays per lumel, half-lumel per unit resolution, this was the result.<\/p>\n<p><a href=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/07_large_scene_256_half_lumel_per_unit_1024_rays.jpg\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/07_large_scene_256_half_lumel_per_unit_1024_rays_preview1.jpg\" border=\"0\" alt=\"07_large_scene_256_half_lumel_per_unit_1024_rays\" hspace=\"8\" width=\"480\" height=\"381\" align=\"top\" \/><\/a><\/p>\n<p>There\u2019s no light sources in the scene, only ambient light, and still point sampling for testing purposes&#8230; it looks ugly, but there\u2019s already some feeling of space to it&#8230; Turning on linear sampling, increasing the resolution to 1024&#215;1024 and going to 256 rays per lumel:<\/p>\n<p><a href=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/08_large_scene_1024_half_lumel_per_unit_256_rays.jpg\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/08_large_scene_1024_half_lumel_per_unit_256_rays_preview1.jpg\" border=\"0\" alt=\"08_large_scene_1024_half_lumel_per_unit_256_rays\" hspace=\"8\" width=\"480\" height=\"381\" align=\"top\" \/><\/a><\/p>\n<p>Results are a bit noisy, but they\u2019re already very good for the intended purposes&#8230; Adding rays to the scene would get rid of the noise (I think, haven\u2019t tried), but with the additional &#8220;noise&#8221; of the textures, normal maps and direct lighting, I doubt it shows in a real game situation.<\/p>\n<p>There\u2019s good details on this solution:<\/p>\n<p><a href=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/09_large_scene_detail_door.jpg\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/09_large_scene_detail_door_preview1.jpg\" border=\"0\" alt=\"09_large_scene_detail_door\" hspace=\"8\" width=\"480\" height=\"381\" align=\"top\" \/><\/a><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/shadowcovenant.com\/blog\/wp-content\/uploads\/2011\/02\/10_large_scene_detail_ceiling_preview1.jpg\" border=\"0\" alt=\"10_large_scene_detail_ceiling\" hspace=\"8\" width=\"480\" height=\"381\" align=\"top\" \/><\/p>\n<p>I like this one in particular&#8230; The wall doesn\u2019t touch the ceiling (hacked scene, this is what you get), but the ambient occlusion really fleshes out the volume.<\/p>\n<p>I\u2019ve added some code to use multi-processors (since this is software only rasterization and calculation) to speed up the generation of the scene, and the raycasting isn\u2019t as good as it should at the moment (probably will build an octree with all the geometry and use it for raycasting). This scene takes about 4 minutes to compute (resulting in 3 1024&#215;1024 ambient occlusion maps), but hopefully I\u2019ll be able to chop that time down&#8230;<\/p>\n<p>My next step is really exporting the generated scene (I\u2019m lazy and haven\u2019t done that part of the code yet, the test application computes and displays the solution), and trying this with a &#8220;real&#8221; scene (which I\u2019m waiting for my artist to finish, he\u2019s been having some troubles getting some &#8220;walls&#8221; and &#8220;ceilings&#8221; he actually likes.<\/p>\n<p>After that, next stop is trying this with direct lighting&#8230; I\u2019ve added &#8220;soft-shadows&#8221; to my dynamic shadowmaps, and they will really help the scene, although they need loads of samples (16) to actually look good&#8230; Anyway, I\u2019m hoping that combining the ambient occlusion, the soft-shadows, and playing around with the update times of the shadow maps, I can have a fully dynamic lighted environment that requires little tweaking and is fast enough to work with in the game&#8230;<\/p>\n<p>Until next time, cya guys!<\/p>\n<div id=\"tweetbutton377\" class=\"tw_button\" style=\"\"><a href=\"http:\/\/twitter.com\/share?url=http%3A%2F%2Fshadowcovenant.com%2Fblog%2F2011%2F02%2F10%2Fambient-occlusion-maps%2F&amp;text=Ambient%20Occlusion%20Maps&amp;related=&amp;lang=en&amp;count=horizontal&amp;counturl=http%3A%2F%2Fshadowcovenant.com%2Fblog%2F2011%2F02%2F10%2Fambient-occlusion-maps%2F\" class=\"twitter-share-button\"  style=\"width:55px;height:22px;background:transparent url('http:\/\/shadowcovenant.com\/blog\/wp-content\/plugins\/wp-tweet-button\/tweetn.png') no-repeat  0 0;text-align:left;text-indent:-9999px;display:block;\">Tweet<\/a><\/div>","protected":false},"excerpt":{"rendered":"<p>I\u2019m back to rendering pipeline for our current project&#8230; this time: Ambient Occlusion Maps. Ambient occlusion is the small dimming of ambient light in areas where the ambient light couldn\u2019t reach&#8230; Basically, ambient light exists to simulate the effect of trillions of photons bouncing off the objects in an area, so in fact, simulating indirect [&hellip;]<\/p>\n<div id=\"tweetbutton377\" class=\"tw_button\" style=\"\"><a href=\"http:\/\/twitter.com\/share?url=http%3A%2F%2Fshadowcovenant.com%2Fblog%2F2011%2F02%2F10%2Fambient-occlusion-maps%2F&amp;text=Ambient%20Occlusion%20Maps&amp;related=&amp;lang=en&amp;count=horizontal&amp;counturl=http%3A%2F%2Fshadowcovenant.com%2Fblog%2F2011%2F02%2F10%2Fambient-occlusion-maps%2F\" class=\"twitter-share-button\"  style=\"width:55px;height:22px;background:transparent url('http:\/\/shadowcovenant.com\/blog\/wp-content\/plugins\/wp-tweet-button\/tweetn.png') no-repeat  0 0;text-align:left;text-indent:-9999px;display:block;\">Tweet<\/a><\/div>","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[23,6],"tags":[],"class_list":["post-377","post","type-post","status-publish","format-standard","hentry","category-development","category-games"],"_links":{"self":[{"href":"http:\/\/shadowcovenant.com\/blog\/wp-json\/wp\/v2\/posts\/377","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/shadowcovenant.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/shadowcovenant.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/shadowcovenant.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/shadowcovenant.com\/blog\/wp-json\/wp\/v2\/comments?post=377"}],"version-history":[{"count":2,"href":"http:\/\/shadowcovenant.com\/blog\/wp-json\/wp\/v2\/posts\/377\/revisions"}],"predecessor-version":[{"id":379,"href":"http:\/\/shadowcovenant.com\/blog\/wp-json\/wp\/v2\/posts\/377\/revisions\/379"}],"wp:attachment":[{"href":"http:\/\/shadowcovenant.com\/blog\/wp-json\/wp\/v2\/media?parent=377"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/shadowcovenant.com\/blog\/wp-json\/wp\/v2\/categories?post=377"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/shadowcovenant.com\/blog\/wp-json\/wp\/v2\/tags?post=377"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}