From 31364e50097c4de5b9319e5e554d9d5e92a35c58 Mon Sep 17 00:00:00 2001 From: drewcassidy Date: Fri, 26 Jun 2020 16:01:35 -0700 Subject: [PATCH] add sdf aa article --- _posts/2020-6-26-sdf-antialiasing.md | 141 ++++++++++++++++++ .../aa-1-triangles.png | 3 + images/2020-6-26-sdf-antialiasing/aa-1.png | 3 + .../2020-6-26-sdf-antialiasing/aa-2-diff.png | 3 + images/2020-6-26-sdf-antialiasing/aa-2.png | 3 + .../aa-diff-small.png | 3 + images/2020-6-26-sdf-antialiasing/aa-diff.png | 3 + images/2020-6-26-sdf-antialiasing/no-aa.png | 3 + images/2020-6-26-sdf-antialiasing/sdf-1.png | 3 + images/2020-6-26-sdf-antialiasing/sdf-2.png | 3 + .../2020-6-26-sdf-antialiasing/smoothness.png | 3 + 11 files changed, 171 insertions(+) create mode 100644 _posts/2020-6-26-sdf-antialiasing.md create mode 100644 images/2020-6-26-sdf-antialiasing/aa-1-triangles.png create mode 100644 images/2020-6-26-sdf-antialiasing/aa-1.png create mode 100644 images/2020-6-26-sdf-antialiasing/aa-2-diff.png create mode 100644 images/2020-6-26-sdf-antialiasing/aa-2.png create mode 100644 images/2020-6-26-sdf-antialiasing/aa-diff-small.png create mode 100644 images/2020-6-26-sdf-antialiasing/aa-diff.png create mode 100644 images/2020-6-26-sdf-antialiasing/no-aa.png create mode 100644 images/2020-6-26-sdf-antialiasing/sdf-1.png create mode 100644 images/2020-6-26-sdf-antialiasing/sdf-2.png create mode 100644 images/2020-6-26-sdf-antialiasing/smoothness.png diff --git a/_posts/2020-6-26-sdf-antialiasing.md b/_posts/2020-6-26-sdf-antialiasing.md new file mode 100644 index 0000000..f5de302 --- /dev/null +++ b/_posts/2020-6-26-sdf-antialiasing.md @@ -0,0 +1,141 @@ +--- +title: Antialiasing For SDF Textures +description: Correctly antialiasing shapes and text rendered using + signed distance fields. +image: aa-diff-small.png +tags: KSP conformal-decals gamedev +--- + +SDF textures are commonly used for rendering text and simple graphics in both 2D and 3D applications. They allow for smooth and crisp graphics using small raster graphics as an input, and relatively simple shaders. If you're unfamiliar with them, I recommend reading the [Valve whitepaper](https://steamcdn-a.akamaihd.net/apps/valve/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf). Antialiasing when rendering SDFs seems to be a recurring problem, and I set out to find a good method that worked for any input. + +As a demo, I'll be using the following SDF texture. I'm using white to represent negative distances (inside the shape) and black to represent areas outside the shape, but the inverse is also common. + +In this article I'll be using the term "SDF size" to refer to the distance between black and white pixels in the SDF gradient, and "SDF units" to refer to the arbitrary units the textures are expressed in. 2D SDFs can also be [generated by a function inside the shader](https://www.iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm) or [sampled using multiple channels to get sharper corners](https://github.com/Chlumsky/msdfgen). As long as you have some input with a known value where the edge should occur, you can use these methods. + +{% include figure-gallery.html + src="sdf-1.png" + caption="An SDF texture of some text." %} + +## No antialiasing + +For reference, lets look at what the shader looks like with no antialiasing. If the distance is negative, make the alpha 1, otherwise, its 0. The simplest possible SDF/cutoff shader: + +{% highlight glsl linenos %} +fixed4 frag (v2f i) : SV_Target { + fixed4 color = _Color; + + // sdf distance from edge (scalar) + float dist = _Cutoff - tex2D(_MainTex, (i.uv)).r; + + color.a = dist < 0 ? 1 : 0; + + return color; +} +{% endhighlight %} + +This looks pretty bad: + +{% include figure-gallery.html + src="no-aa.png" + caption="Rendered without any antialiasing. Looks terrible, especially on smaller text" %} + +## Adding Smoothness + +The Valve whitepaper suggests using a smoothstep function to soften the edges to achieve antialiasing. This works when the image is a fixed size and distance, but when zoomed in or out the image becomes blurry or remains aliased. This method is also relative to the SDF size, so different SDF sizes will result in different smoothnesses when rendered. + +{% highlight glsl linenos %} +fixed4 frag (v2f i) : SV_Target { + fixed4 color = _Color; + + // sdf distance from edge (scalar) + float dist = _Cutoff - tex2D(_MainTex, (i.uv)).r; + + color.a = smoothstep(_Smoothness, -_Smoothness, dist); + + return color; +} +{% endhighlight %} + + +{% include figure-gallery.html + src="smoothness.png" + caption="Rendered with a _Smoothness value of 0.1. Smaller text looks ok but big text is now fuzzy" %} + +## Antialiasing with a known SDF size + +The solution then is to adjust the smoothness value with the size of the texture on screen. We can use the `fwidth` function and the texture size to determine the texels (texture pixels) per pixel on screen. Instead of using `smoothstep`, we directly calculate the pixel distance from the edge, as described in [this blog post by Edaqa Mortoray](https://mortoray.com/2015/06/19/antialiasing-with-a-signed-distance-field/). We also need to pass in the size of the SDF so we can convert SDF units to texels: + +{% highlight glsl linenos %} +fixed4 frag (v2f i) : SV_Target { + fixed4 color = _Color; + + // texel distance from edge (scalar) + float dist = (_Cutoff - tex2D(_MainTex, (i.uv)).r) * _SDFsize; + + // uv distance per pixel density for texture on screen + float2 duv = fwidth(i.uv); + + // texel-per-pixel density for texture on screen (scalar) + // nb: in unity, z and w of TexelSize are the texture dimensions + float dtex = length(duv * _MainTex_TexelSize.zw); + + // distance to edge in pixels (scalar) + float pixelDist = dist * 2 / dtex; + + color.a = saturate(0.5 - pixelDist); + + return color; +} +{% endhighlight %} + +(I'm actually not sure why the 2 on line 15 is there, but it seems to be necessary) + +{% include figure-gallery.html + src="aa-1.png" + caption="Rendered with a _SDFsize of 11, which matches the texture" %} + +As long as our SDF is of a known size, this works great! The text looks good at all sizes. The problem comes when we have an unknown SDF size, such as when using user-provided textures, textures that are a mix of multiple scaled SDF textures, or when the SDF generation tool adjusts the sdf size dynamically to best utilize the available space. + +To demonstrate, I'll add some triangles to our test texture with multiple SDF sizes: + +{% include figure-gallery.html + src="sdf-2.png" + caption="Triangles with different SDF sizes added." %} + +{% include figure-gallery.html + src="aa-1-triangles.png" + caption="The triangle on the left looks fuzzy and the one on the right looks aliased" %} + +## Antialiasing with an unknown SDF radius + +We need to be able to determine the SDF size dynamically within the shader, so `fwidth`, or rather, `ddx` and `ddy` come into play again (or `dFdx` and `dFdy` in glsl, but I'm using Unity's Shaderlab system). In fact, we dont even need to use the fwidth of the uv anymore, because the gradient of the sdf is already in pixels! + +{% highlight glsl linenos %} +fixed4 frag (v2f i) : SV_Target { + fixed4 color = _Color; + + // sdf distance from edge (scalar) + float dist = (_Cutoff - tex2D(_MainTex, (i.uv)).r); + + // sdf distance per pixel (gradient vector) + float2 ddist = float2(ddx(dist), ddy(dist)); + + // distance to edge in pixels (scalar) + float pixelDist = dist / length(ddist); + + color.a = saturate(0.5 - pixelDist); + + return color; +} +{% endhighlight %} + +{% include figure-gallery.html + src="aa-2.png" + caption="Rendered using the SDF gradient method. All the triangles now look good" %} + +The only differences in the result between this method and the last, besides the correct antialiasing of the triangles, is that smaller text ends up "tighter". This seems to be due to mipmapping coming into play and affecting the SDF gradient calculation, though I think the result still looks fine and perfectly readable. + +{% include figure-gallery.html + src="aa-2-diff.png" + caption="Difference between this and the last method. Large text is almost unchanged" %} + diff --git a/images/2020-6-26-sdf-antialiasing/aa-1-triangles.png b/images/2020-6-26-sdf-antialiasing/aa-1-triangles.png new file mode 100644 index 0000000..d8a7a83 --- /dev/null +++ b/images/2020-6-26-sdf-antialiasing/aa-1-triangles.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5eb60620027a9dcbdfbe13c51185627c35e4cf217de0cb37a889624f191aa33c +size 21615 diff --git a/images/2020-6-26-sdf-antialiasing/aa-1.png b/images/2020-6-26-sdf-antialiasing/aa-1.png new file mode 100644 index 0000000..b9caab4 --- /dev/null +++ b/images/2020-6-26-sdf-antialiasing/aa-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d54a06e214e97aa84a54d9f291a40bd3e628c077879d4989eb7460c91b00810 +size 19970 diff --git a/images/2020-6-26-sdf-antialiasing/aa-2-diff.png b/images/2020-6-26-sdf-antialiasing/aa-2-diff.png new file mode 100644 index 0000000..a35fc94 --- /dev/null +++ b/images/2020-6-26-sdf-antialiasing/aa-2-diff.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:652c20f18fe83c5d21723b7726aff562994e9ab46cb435838ff27a82f62e221c +size 5141 diff --git a/images/2020-6-26-sdf-antialiasing/aa-2.png b/images/2020-6-26-sdf-antialiasing/aa-2.png new file mode 100644 index 0000000..c666947 --- /dev/null +++ b/images/2020-6-26-sdf-antialiasing/aa-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b238e6d37d731ff1c9de41d42bf1631374ab6e88cc2462ebba1fdaceae629978 +size 21954 diff --git a/images/2020-6-26-sdf-antialiasing/aa-diff-small.png b/images/2020-6-26-sdf-antialiasing/aa-diff-small.png new file mode 100644 index 0000000..fbcf2cc --- /dev/null +++ b/images/2020-6-26-sdf-antialiasing/aa-diff-small.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9643fbab583cb6efdd298175c53b655a992bf8308d9f651dbf6dc898c1c53a92 +size 1867 diff --git a/images/2020-6-26-sdf-antialiasing/aa-diff.png b/images/2020-6-26-sdf-antialiasing/aa-diff.png new file mode 100644 index 0000000..1dc26cd --- /dev/null +++ b/images/2020-6-26-sdf-antialiasing/aa-diff.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95edd9a7e71cf439d4b96086f58b0d92ab110c6c1ba0c886df06315e5d048fce +size 3930 diff --git a/images/2020-6-26-sdf-antialiasing/no-aa.png b/images/2020-6-26-sdf-antialiasing/no-aa.png new file mode 100644 index 0000000..ef95e74 --- /dev/null +++ b/images/2020-6-26-sdf-antialiasing/no-aa.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efad15c8e354669c8408c343af4d12fed8a45bc36a28a0f6b6151af6baa95b6b +size 2775 diff --git a/images/2020-6-26-sdf-antialiasing/sdf-1.png b/images/2020-6-26-sdf-antialiasing/sdf-1.png new file mode 100644 index 0000000..78f2671 --- /dev/null +++ b/images/2020-6-26-sdf-antialiasing/sdf-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bc2b1c64d8ee61693d0b3927d9f95a75f56e157db8fea06079d21e6845f13c8 +size 9527 diff --git a/images/2020-6-26-sdf-antialiasing/sdf-2.png b/images/2020-6-26-sdf-antialiasing/sdf-2.png new file mode 100644 index 0000000..97ecc32 --- /dev/null +++ b/images/2020-6-26-sdf-antialiasing/sdf-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28259ba1577453667b05bdcf5e7200f6104f923ee28fbdc7cde1442b13c7a0bb +size 11125 diff --git a/images/2020-6-26-sdf-antialiasing/smoothness.png b/images/2020-6-26-sdf-antialiasing/smoothness.png new file mode 100644 index 0000000..4a6c97d --- /dev/null +++ b/images/2020-6-26-sdf-antialiasing/smoothness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7230c6129de4be60fe35a455bca17398e5f26b0fa9cf2e44710324d9acfd9e22 +size 40369