{"id":484,"date":"2010-09-09T17:59:02","date_gmt":"2010-09-10T00:59:02","guid":{"rendered":"http:\/\/the-witness.net\/news\/?p=484"},"modified":"2017-01-11T11:38:22","modified_gmt":"2017-01-11T18:38:22","slug":"computing-alpha-mipmaps","status":"publish","type":"post","link":"http:\/\/the-witness.net\/news\/2010\/09\/computing-alpha-mipmaps\/","title":{"rendered":"Computing Alpha Mipmaps"},"content":{"rendered":"<p>A little problem that we had when we started to create trees and vegetation is that the standard mipmap generation algorithms produced surprisingly bad results on alpha tested textures. As the trees moved farther away from the camera, the leafs faded out becoming almost transparent.<\/p>\n<p>Here's an example. The following tree looked OK close to the camera:<\/p>\n<p><a href=\"http:\/\/the-witness.net\/news\/wp-content\/uploads\/2010\/09\/near_nocoverage.png\"><img class=\"aligncenter size-large wp-image-495\" src=\"http:\/\/the-witness.net\/news\/wp-content\/uploads\/2010\/09\/near_nocoverage-512x320.png\" alt=\"\" width=\"512\" height=\"320\" \/><\/a><\/p>\n<p><!--more--><\/p>\n<p>but as we moved away, the leafs started to fade and thin out:<\/p>\n<p><a href=\"http:\/\/the-witness.net\/news\/wp-content\/uploads\/2010\/09\/mid_nocoverage.png\"><img class=\"aligncenter size-large wp-image-493\" src=\"http:\/\/the-witness.net\/news\/wp-content\/uploads\/2010\/09\/mid_nocoverage-512x320.png\" alt=\"\" width=\"512\" height=\"320\" \/><\/a><\/p>\n<p>until they almost disappeared:<\/p>\n<p><a href=\"http:\/\/the-witness.net\/news\/wp-content\/uploads\/2010\/09\/far_nocoverage.png\"><img class=\"aligncenter size-large wp-image-491\" src=\"http:\/\/the-witness.net\/news\/wp-content\/uploads\/2010\/09\/far_nocoverage-512x320.png\" alt=\"\" width=\"512\" height=\"320\" \/><\/a><\/p>\n<p>I had never encountered this problem before, neither had I heard much about it, but after a few google searches I found out that artists are often frustrated by it and usually work around the issue using <a href=\"http:\/\/www.polycount.com\/forum\/showthread.php?t=65261\" onclick=\"_gaq.push(['_trackEvent', 'outbound-article', 'http:\/\/www.polycount.com\/forum\/showthread.php?t=65261', 'various']);\" >various<\/a> <a href=\"http:\/\/www.gamedev.net\/community\/forums\/topic.asp?topic_id=540726\" onclick=\"_gaq.push(['_trackEvent', 'outbound-article', 'http:\/\/www.gamedev.net\/community\/forums\/topic.asp?topic_id=540726', 'hacks']);\" >hacks<\/a>. These are some of the proposed solutions that are usually suggested:<\/p>\n<ul>\n<li>Manually adjusting contrast and sharpening the alpha channel of each mipmap in Photoshop.<\/li>\n<li>Scaling the alpha in the shader based on distance or on an lod factor estimated using texture gradients.<\/li>\n<li>Limiting the number of mipmaps, so that the lowest ones aren't used by the game.<\/li>\n<\/ul>\n<p><P><br \/>\nThese solutions may work around the problem in one way or another, but none of them is entirely correct and in some cases add a significant overhead.<\/p>\n<p>In order to address the problem it's important to understand why the geometry fades out in the distance. That is because when computing alpha mipmaps using the standard algorithms, each mipmap has a different alpha test coverage. That is, the proportion of pixels that pass the alpha test changes, in most cases going down and causing the texture to become more transparent.<\/p>\n<p>A simple solution to the problem is to find a scale factor that preserves the original alpha test coverage as best as possible. We define the coverage of the first mipmap as follows:<\/p>\n<p><code>coverage = Sum(a_i &gt; A_r) \/ N<\/code><\/p>\n<p>where <code>A_r<\/code> is the alpha test value used in your application, <code>a_i<\/code> are the alpha values of the mipmap, and <code>N<\/code> is the number of texels in the mipmap. Then, for the following mipmaps you want to find a scale factor that causes the resulting coverage to stay the same:<\/p>\n<p><code>Sum(scale * a_i &gt; A_r) \/ N == coverage<\/code><\/p>\n<p>However, finding this <code>scale<\/code> directly is tricky because it's a discrete problem, in general, there's no exact solution, and the range of <code>scale<\/code> is unbounded. Instead, what you can do is to find a new alpha reference value <code>a_r<\/code> that produces the desired coverage:<\/p>\n<p><code>Sum(a_i &gt; a_r) \/ N = coverage<\/code><\/p>\n<p>This is much easier to solve, because <code>a_r<\/code> is bounded between 0 and 1. So, it's possible to use a simple bisection search to find the best solution. Once you know <code>a_r<\/code> the scale is simply:<\/p>\n<p><code>scale = A_r \/ a_r<\/code><\/p>\n<p>An implementation of this algorithm is publicly available in <a href=\"http:\/\/code.google.com\/p\/nvidia-texture-tools\/\" onclick=\"_gaq.push(['_trackEvent', 'outbound-article', 'http:\/\/code.google.com\/p\/nvidia-texture-tools\/', 'NVTT']);\" >NVTT<\/a>. The relevant code can be found in the following methods of the FloatImage class:<\/p>\n<p><code>float FloatImage::<a href=\"http:\/\/code.google.com\/p\/nvidia-texture-tools\/source\/browse\/trunk\/src\/nvimage\/FloatImage.cpp#985\" onclick=\"_gaq.push(['_trackEvent', 'outbound-article', 'http:\/\/code.google.com\/p\/nvidia-texture-tools\/source\/browse\/trunk\/src\/nvimage\/FloatImage.cpp#985', 'alphaTestCoverage']);\" >alphaTestCoverage<\/a>(float alphaRef, int alphaChannel) const;<br \/>\nvoid FloatImage::<a href=\"http:\/\/code.google.com\/p\/nvidia-texture-tools\/source\/browse\/trunk\/src\/nvimage\/FloatImage.cpp#1002\" onclick=\"_gaq.push(['_trackEvent', 'outbound-article', 'http:\/\/code.google.com\/p\/nvidia-texture-tools\/source\/browse\/trunk\/src\/nvimage\/FloatImage.cpp#1002', 'scaleAlphaToCoverage']);\" >scaleAlphaToCoverage<\/a>(float desiredCoverage, float alphaRef, int alphaChannel);<br \/>\n<\/code><\/p>\n<p>And here's a simple example of how this feature can be used through NVTT's public API:<\/p>\n<pre>\r\n\/\/ Output first mipmap.\r\ncontext.compress(image, compressionOptions, outputOptions);\r\n\r\n\/\/ Estimate original coverage.\r\nconst float coverage = image.alphaTestCoverage(A_r);\r\n\r\n\/\/ Build mipmaps and scale alpha to preserve original coverage.\r\nwhile (image.buildNextMipmap(nvtt::MipmapFilter_Kaiser))\r\n{\r\n    image.scaleAlphaToCoverage(coverage, A_r);\r\n    context.compress(tmpImage, compressionOptions, outputOptions);\r\n}\r\n<\/pre>\n<p>As seen in the following screenshot, this solves the problem entirely:<\/p>\n<p><a href=\"http:\/\/the-witness.net\/news\/wp-content\/uploads\/2010\/09\/mid_coverage.png\"><img class=\"aligncenter size-large wp-image-492\" src=\"http:\/\/the-witness.net\/news\/wp-content\/uploads\/2010\/09\/mid_coverage-512x320.png\" alt=\"\" width=\"512\" height=\"320\" \/><\/a><\/p>\n<p>even when the trees are far away from the camera:<\/p>\n<p><a href=\"http:\/\/the-witness.net\/news\/wp-content\/uploads\/2010\/09\/far_coverage.png\"><img class=\"aligncenter size-large wp-image-490\" src=\"http:\/\/the-witness.net\/news\/wp-content\/uploads\/2010\/09\/far_coverage-512x320.png\" alt=\"\" width=\"512\" height=\"320\" \/><\/a><\/p>\n<p>Note that this problem does not only show up when using alpha testing, but also when using alpha to coverage (as in these screenshots) or alpha blending in general. In those cases you don't have a specific alpha reference value, but this algorithm still works fine if you choose a value that is close to 1, since essentially what you want is to preserve the percentage of texels that are nearly opaque.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A little problem that we had when we started to create trees and vegetation is that the standard mipmap generation algorithms produced surprisingly bad results on alpha tested textures. As the trees moved farther away from the camera, the leafs faded out becoming almost transparent. Here&#8217;s an example. The following \u2026<\/p>\n<p class=\"continue-reading-button\"> <a class=\"continue-reading-link\" href=\"http:\/\/the-witness.net\/news\/2010\/09\/computing-alpha-mipmaps\/\">Continue reading<i class=\"crycon-right-dir\"><\/i><\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[7],"tags":[],"_links":{"self":[{"href":"http:\/\/the-witness.net\/news\/wp-json\/wp\/v2\/posts\/484"}],"collection":[{"href":"http:\/\/the-witness.net\/news\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/the-witness.net\/news\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/the-witness.net\/news\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"http:\/\/the-witness.net\/news\/wp-json\/wp\/v2\/comments?post=484"}],"version-history":[{"count":77,"href":"http:\/\/the-witness.net\/news\/wp-json\/wp\/v2\/posts\/484\/revisions"}],"predecessor-version":[{"id":3338,"href":"http:\/\/the-witness.net\/news\/wp-json\/wp\/v2\/posts\/484\/revisions\/3338"}],"wp:attachment":[{"href":"http:\/\/the-witness.net\/news\/wp-json\/wp\/v2\/media?parent=484"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/the-witness.net\/news\/wp-json\/wp\/v2\/categories?post=484"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/the-witness.net\/news\/wp-json\/wp\/v2\/tags?post=484"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}