{"id":495,"date":"2020-09-08T12:33:04","date_gmt":"2020-09-08T12:33:04","guid":{"rendered":"https:\/\/half4.xyz\/?p=495"},"modified":"2024-01-25T09:55:37","modified_gmt":"2024-01-25T09:55:37","slug":"quick-tip-sort-translucency-ue4","status":"publish","type":"post","link":"https:\/\/half4.xyz\/index.php\/2020\/09\/08\/quick-tip-sort-translucency-ue4\/","title":{"rendered":"Quick Tip: Sort Translucency (UE4)"},"content":{"rendered":"<p><img fetchpriority=\"high\" decoding=\"async\" class=\"wp-image-506\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-9-1024x497.png\" sizes=\"(max-width: 1024px) 100vw, 1024px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-9-1024x497.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-9-300x145.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-9-768x372.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-9-1536x745.png 1536w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-9-2048x993.png 2048w\" alt=\"\" width=\"1024\" height=\"497\" \/><\/p>\n<p>If you\u2019ve ever made translucent objects in Unreal Engine, or Unity (or just about any modern 3D engine), you\u2019ll have possibly encountered this issue, where parts of your mesh render incorrectly infront or behind of itself.<\/p>\n<h2 class=\"wp-block-heading\">Why does this happen?<\/h2>\n<p>The UE4 renderer will render all opaque objects to the various GBuffers first. When rendering opaque objects, UE4 writes to a Depth Buffer. The Depth Buffer stores depth information for each pixel (literally \u201cdepth from the camera\u201d \u2013 a Depth Buffer value of 100 is 100 units from the camera). When rendering a new opaque object we can test the pixel\u2019s depth against this buffer. If the pixel is closer, we draw that pixel and update the Depth Buffer, if it further away (i.e. \u201cbehind and existing pixel\u201d), we discard it.<\/p>\n<figure class=\"wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\">\n<div class=\"wp-block-embed__wrapper\"><iframe title=\"How the Z-Buffer Works - Interactive 3D Graphics\" src=\"https:\/\/www.youtube.com\/embed\/yhwg_O5HBwQ?feature=oembed\" width=\"960\" height=\"540\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/div><figcaption>Conveniently explained here by Udacity<\/figcaption><\/figure>\n<p>&nbsp;<\/p>\n<p>After rendering opaque objects, it then moves on to Translucent objects. The renderer works in a similar way \u2013 taking each object and rendering them, checking against the depth values already rendered by the opaque pass. This works fine for simple things such as windows, where you wont get a mesh with faces infront\/behind. However, it breaks down under these circumstances.<\/p>\n<p>This is because when given a mesh, the renderer tackles the faces by their index. This means it renders the triangle that uses vertices {0,1,2} <em>first<\/em>, then continues up the chain. This means it may render faces in an order that does <em>not match the order they are in front of the camera<\/em>. This means some faces are rendered in the wrong order. When rendering opaque objects, this isn\u2019t a problem as every pixel it either fully occluded or not occluded \u2013 with Translucent objects, this isn\u2019t the case and we get pixels that stack.<\/p>\n<p>&nbsp;<\/p>\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" class=\"wp-image-511\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-12-1024x208.png\" sizes=\"(max-width: 1024px) 100vw, 1024px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-12-1024x208.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-12-300x61.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-12-768x156.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-12-1536x312.png 1536w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-12-2048x415.png 2048w\" alt=\"\" width=\"1024\" height=\"208\" \/><figcaption>This is the same mesh, but with different vertex orders. Note the different pattern of overlaid faces.<\/figcaption><\/figure>\n<p>The CustomDepth buffer works identically to the Depth Buffer, aside from the fact that it <em>only<\/em> renders objects into the buffer that <em>you<\/em> define \u2013 including Translucent ones. By using this buffer and doing a simple check in our material, we can make pixels that are located behind other pixels in our mesh fully transparent, making them disappear.<\/p>\n<p>One caveat: You will not see <em>any<\/em> parts of the mesh behind itself. I.e., if your mesh was made of two spheres, with one <em>inside<\/em> the other, you would not see the internal sphere rendered <em>at all<\/em>. Its a trade-off. There are other solutions to this problem, but this is the simplest and works in most cases.<br \/>\n<img decoding=\"async\" class=\"wp-image-509\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-10-1024x537.png\" sizes=\"(max-width: 1024px) 100vw, 1024px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-10-1024x537.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-10-300x157.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-10-768x403.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-10-1536x805.png 1536w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-10.png 1797w\" alt=\"\" width=\"1024\" height=\"537\" \/><\/p>\n<p>On the left we have the default Translucency. On the right we have sorted Translucency. As you can see, we can no longer see back faces \u2013 this may <em>not<\/em> be what you want.<\/p>\n<h2 class=\"wp-block-heading\">Rendering to the CustomDepth Bufffer<\/h2>\n<p>Step 1 is to get your mesh and locate the \u201cRender CustomDepth Pass\u201d flag and enable it.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-510\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-11-510x1024.png\" sizes=\"(max-width: 510px) 100vw, 510px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-11-510x1024.png 510w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-11-149x300.png 149w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-11.png 755w\" alt=\"\" width=\"510\" height=\"1024\" \/><\/p>\n<p>Expand Rendering and check this option<\/p>\n<p>Secondly, you need to find your Material and enable the \u201cAllow CustomDepth Writes\u201d flag.<br \/>\nNOTE: There is a bug here. This flag is ignored if your Material is Opaque, even if your Material Instances are Translucent. To get around this, set your Material to Translucent, and make a Material Instance that is an Opaque version of that \u2013 use that for opaque objects, where you wish to share Material functionality.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-497\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image.png\" sizes=\"(max-width: 947px) 100vw, 947px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image.png 947w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-291x300.png 291w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-768x792.png 768w\" alt=\"\" width=\"947\" height=\"977\" \/><\/p>\n<p>I prefer to work in Material Functions using Material Attributes, but for easy-of-explaining, we\u2019ll just put the logic for the following into the Material directly.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-498\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-1.png\" sizes=\"(max-width: 1008px) 100vw, 1008px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-1.png 1008w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-1-300x201.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-1-768x516.png 768w\" alt=\"\" width=\"1008\" height=\"677\" \/><\/p>\n<p>Here\u2019s a simple material example.<\/p>\n<p>Create a new node for SceneTexture and set the SceneTexture to be CustomDepth:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-499\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-2-1024x583.png\" sizes=\"(max-width: 1024px) 100vw, 1024px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-2-1024x583.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-2-300x171.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-2-768x438.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-2-1536x875.png 1536w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-2.png 1541w\" alt=\"\" width=\"1024\" height=\"583\" \/><\/p>\n<p>Now we need a few more nodes. PixelDepth doesn\u2019t work here as a comparison for CustomDepth. We need to reconstruct the depth using this method:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-501\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-4-1024x330.png\" sizes=\"(max-width: 1024px) 100vw, 1024px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-4-1024x330.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-4-300x97.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-4-768x247.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-4.png 1074w\" alt=\"\" width=\"1024\" height=\"330\" \/><\/p>\n<p>I <em>believe<\/em> you could use PixelDepth here, but simply add a tolerance value to it. Untested, but it\u2019d probably work.<\/p>\n<p>We feed this into an If node with our opacity and a constant value of 0<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-504\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-7-1024x550.png\" sizes=\"(max-width: 1024px) 100vw, 1024px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-7-1024x550.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-7-300x161.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-7-768x413.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-7.png 1528w\" alt=\"\" width=\"1024\" height=\"550\" \/><\/p>\n<p>The Multiply by 0.999 is important here. This is the threshold \u2013 values of 1 or higher will make the mesh completely invisible. Less than 0.999 will create overlapping along edges.<\/p>\n<p>Your mesh should now correctly sort!<\/p>\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-505\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-8-1024x497.png\" sizes=\"(max-width: 1024px) 100vw, 1024px\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-8-1024x497.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-8-300x145.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-8-768x372.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-8-1536x745.png 1536w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/09\/image-8-2048x993.png 2048w\" alt=\"\" width=\"1024\" height=\"497\" \/><\/figure>\n","protected":false},"excerpt":{"rendered":"<p>If you\u2019ve ever made translucent objects in Unreal Engine, or Unity (or just about any modern 3D engine), you\u2019ll have possibly encountered this issue, where parts of your mesh render incorrectly infront or behind of itself. Why does this happen? The UE4 renderer will render all opaque objects to the various GBuffers first. When rendering opaque objects, UE4 writes to a Depth Buffer. The Depth Buffer stores depth information for each pixel (literally \u201cdepth from the camera\u201d \u2013 a Depth Buffer value of 100 is 100 units from the camera). When rendering a new opaque object we can test the pixel\u2019s depth against this buffer. If the pixel is closer, we draw that pixel and update the Depth Buffer, if it further away (i.e. \u201cbehind and existing pixel\u201d), we discard it. Conveniently explained here by Udacity &nbsp; After rendering opaque objects, it then moves on to Translucent objects. The renderer works in a similar way \u2013 taking each object and rendering them, checking against the depth values already rendered by the opaque pass. This works fine for simple things such as windows, where you wont get a mesh with faces infront\/behind. However, it breaks down under these circumstances. This is because when given a mesh, the renderer tackles the faces by their index. This means it renders the triangle that uses vertices {0,1,2} first, then continues up the chain. This means it may render faces in an order that does not match the order they are in front of the camera. This means some faces are rendered in the wrong order. When rendering opaque objects, this isn\u2019t a problem as every pixel it either fully occluded or not occluded \u2013 with Translucent objects, this isn\u2019t the case and we get pixels that stack. &nbsp; This is the same mesh, but with different vertex orders. Note the different pattern of overlaid faces. The CustomDepth buffer works identically to the Depth Buffer, aside from the fact that it only renders objects into the buffer that you define \u2013 including Translucent ones. By using this buffer and doing a simple check in our material, we can make pixels that are located behind other pixels in our mesh fully transparent, making them disappear. One caveat: You will not see any parts of the mesh behind itself. I.e., if your mesh was made of two spheres, with one inside the other, you would not see the internal sphere rendered at all. Its a trade-off. There are other solutions to this problem, but this is the simplest and works in most cases. On the left we have the default Translucency. On the right we have sorted Translucency. As you can see, we can no longer see back faces \u2013 this may not be what you want. Rendering to the CustomDepth Bufffer Step 1 is to get your mesh and locate the \u201cRender CustomDepth Pass\u201d flag and enable it. Expand Rendering and check this option Secondly, you need to find your Material and enable the \u201cAllow CustomDepth Writes\u201d flag. NOTE: There is a bug here. This flag is ignored if your Material is Opaque, even if your Material Instances are Translucent. To get around this, set your Material to Translucent, and make a Material Instance that is an Opaque version of that \u2013 use that for opaque objects, where you wish to share Material functionality. I prefer to work in Material Functions using Material Attributes, but for easy-of-explaining, we\u2019ll just put the logic for the following into the Material directly. Here\u2019s a simple material example. Create a new node for SceneTexture and set the SceneTexture to be CustomDepth: Now we need a few more nodes. PixelDepth doesn\u2019t work here as a comparison for CustomDepth. We need to reconstruct the depth using this method: I believe you could use PixelDepth here, but simply add a tolerance value to it. Untested, but it\u2019d probably work. We feed this into an If node with our opacity and a constant value of 0 The Multiply by 0.999 is important here. This is the threshold \u2013 values of 1 or higher will make the mesh completely invisible. Less than 0.999 will create overlapping along edges. Your mesh should now correctly sort!<\/p>\n","protected":false},"author":1,"featured_media":509,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,6],"tags":[],"class_list":["post-495","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-quick-tips","category-tuts"],"_links":{"self":[{"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts\/495"}],"collection":[{"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/comments?post=495"}],"version-history":[{"count":5,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts\/495\/revisions"}],"predecessor-version":[{"id":1267,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts\/495\/revisions\/1267"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/media\/509"}],"wp:attachment":[{"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/media?parent=495"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/categories?post=495"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/tags?post=495"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}