Signed distance field rendering (Blending question)

Started by
15 comments, last by simotix 13 years, 4 months ago
I have been working on implementing the Valve technique for rendering decals, although I am doing it for text. I have seen some workings on it while searching this forum, but I am confused about how it should be rendered.

For testing, I am rendering my image on quad (although I will split it up and batch the letters later). It is just a quad with a Vertex/Texture Coordinate signature. I am confused about two things for how it should be rendered.

1) Blending: From what I read, it should enable blending, and have D3D11_BLEND_SRC_ALPHA with D3D11_BLEND_INV_SRC_ALPHA. Although, I am not exactly aure. This is how I generate my blend state, is this the correct blend state?

		D3D11_BLEND_DESC blendDesc;		blendDesc.AlphaToCoverageEnable = false;		blendDesc.IndependentBlendEnable = false;		for (UINT i = 0; i < 8; ++i)		{			blendDesc.RenderTarget.BlendEnable = true;			blendDesc.RenderTarget.BlendOp = D3D11_BLEND_OP_ADD;			blendDesc.RenderTarget.BlendOpAlpha = D3D11_BLEND_OP_ADD;			blendDesc.RenderTarget.DestBlend = D3D11_BLEND_INV_SRC_ALPHA;			blendDesc.RenderTarget.DestBlendAlpha = D3D11_BLEND_ONE;			blendDesc.RenderTarget.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;			blendDesc.RenderTarget.SrcBlend = D3D11_BLEND_SRC_ALPHA;			blendDesc.RenderTarget.SrcBlendAlpha = D3D11_BLEND_ONE;		}		HRESULT hResult = m_pD3D11Device->CreateBlendState(&blendDesc, &m_pAlphaBlendState);


2) Texture Filtering. I create a basic Linear, with Wrapping sampler state. Is this would I should be creating?

		ID3D11SamplerState *pSamplerLinear;		D3D11_SAMPLER_DESC sampDesc;		ZeroMemory( &sampDesc, sizeof(sampDesc) );		sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;		sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;		sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;		sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;		sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;		sampDesc.MinLOD = 0;		sampDesc.MaxLOD = D3D11_FLOAT32_MAX;		HRESULT hResult = m_pD3D11Device->CreateSamplerState( &sampDesc, &pSamplerLinear );
Advertisement
You don't blend, you alpha test. So in D3D11 that means doing clip() or discard the pixel shader.
I suppose that I read something wrong, I was under the impression that I needed to blend.

I took your advice and I am now using clip(), is there any sort of state that I should be setting? Currently all I do is the following in the pixel shader.

	float4 color = txDiffuse.Sample(samLinear, input.Tex);	clip(color.a - 0.5);	return color;

Also, I suppose my linear sampler state is fine?
Unfortunately the text I am trying to render really does not look great. I am rendering a decal that I created using signed distance fields at several distances and anything with a curved surface does not look very good. I was wondering if anyone has ever experienced this? I am still unsure of my texture filtering is correct, so maybe that is the problem.

This is when I created it with a block size of 32. I tried 16 and 64, but I will still get bad curves (or even worse).

For hard (aliased) edged objects, you can use clip.

If you want soft edges, then turn on blending like you were originally... but you've got to calculate a sensible alpha value in your shader. Something like:
color.a = smoothstep(0.48,0.52,color.a);
Quote:Original post by Hodgman
If you want soft edges, then turn on blending like you were originally... but you've got to calculate a sensible alpha value in your shader. Something like:
color.a = smoothstep(0.48,0.52,color.a);


That did improve the look of the letters a lot, but they still look very rough at a distance and letters like X look very jagged. Do you (or anyone else) have any suggestions to what I should do? I tried to increase my scan size (it was at 100, I tried it at 200) but that did not improve anything.
How are you generating the distance fields, exactly?
The code used to generate the distance fields was done in C#, it will save a .png then I will render that .png in DirectX11.

Create signed distance bitmap
        public static Bitmap CreateSignedDistanceBitmap(Bitmap bitmap, int scanSizeWidth, int scanSizeHeight, int blockSize)        {            int textureOutWidth = bitmap.Width / blockSize;            int textureOutHeight = bitmap.Height / blockSize;            Bitmap signedDistanceBitmap = new Bitmap(textureOutWidth, textureOutHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);            int blockWidth = blockSize;            int blockHeight = blockSize;            float[,] signedDistances = new float[signedDistanceBitmap.Width, signedDistanceBitmap.Height];            float max = 0, min = 0;            for (int x = 0; x < signedDistanceBitmap.Width; ++x)            {                for (int y = 0; y < signedDistanceBitmap.Height; ++y)                {                    float signedDistance = CalculateSignedDistance(bitmap,                                            (x * blockWidth) + (blockWidth / 2),                                            (y * blockHeight) + (blockHeight / 2),                                            scanSizeWidth, scanSizeHeight);                    signedDistances[x, y] = signedDistance;                    if (signedDistance != float.MaxValue && signedDistance > max)                        max = signedDistance;                    else if (signedDistance != float.MinValue && signedDistance < min)                        min = signedDistance;                }            }            float scale = Math.Max(Math.Abs(min), Math.Abs(max));            for (int x = 0; x < textureOutWidth; ++x)            {                for (int y = 0; y < textureOutHeight; ++y)                {                    float signedDistance = signedDistances[x, y];                    if (signedDistance == float.MaxValue)                    {                        signedDistance = 1.0f;                    }                    else if (signedDistance == float.MinValue)                    {                        signedDistance = 0.0f;                    }                    else                    {                        signedDistance /= scale;                        signedDistance /= 2;                        signedDistance += 0.5f;                    }                    signedDistances[x, y] = signedDistance;                    // Set the signed distance into the alpha channel                    signedDistanceBitmap.SetPixel(x, y, Color.FromArgb((int)Math.Round(signedDistance * 255), 255, 255, 255));                }            }            return signedDistanceBitmap;        }


Calculate signed distance
 public static float CalculateSignedDistance(Bitmap image, int imageX, int imageY, int scanWidth, int scanHeight)        {            Color baseColor = image.GetPixel(imageX, imageY);            // Check to see if it is a solid color, if it is a solid color then that means there is no texture on the texel            bool solidColor = baseColor.R > 0;            float closestDistance = float.MaxValue;            bool closestValid = false;            int startX = imageX - (scanWidth / 2);            int endX = startX + scanWidth;            int startY = imageY - (scanHeight / 2);            int endY = startY + scanHeight;            // No need in searching past the images bounds            if (startX < 0)                startX = 0;            if (endX >= image.Width)                endX = image.Width;            if (startY < 0)                startY = 0;            if (endY >= image.Height)                endY = image.Height;            for (int x = startX; x < endX; ++x)            {                for (int y = startY; y < endY; ++y)                {                    Color texelColor = image.GetPixel(x, y);                    if (solidColor)                    {                        if (texelColor.R == 0)                        {                            float dist = Separation(imageX, imageY, x, y);                            if (dist < closestDistance)                            {                                closestDistance = dist;                                closestValid = true;                            }                        }                    }                    else                    {                        if (texelColor.R > 0)                        {                            float dist = Separation(imageX, imageY, x, y);                            if (dist < closestDistance)                            {                                closestDistance = dist;                                closestValid = true;                            }                        }                    }                }            }            if (solidColor)            {                if (closestValid)                    return closestDistance;                else                    return float.MaxValue;            }            else            {                if (closestValid)                    return -closestDistance;                else                    return float.MinValue;            }        }


Seperation
private static float Separation(float startX, float startY, float endX, float endY)        {            float x = startX - endX;            float y = startY - endY;            return (float)Math.Sqrt(x * x + y * y);        }
Your blockSize stuff looks a bit suspect - I'd say that's whats causing the loss of detail from your high-res inputs. Did you get this concept of calculating the field in blocks from somewhere, or was it an optimisation you came up with?

In my tool, I generate the distance field with a blockSize of 1 (i.e. the distance field is calculated at high-resolution).
Then after you've got the high-res distance field, you shrink the image using a bilinear/box filter.
I recall reading about the block size from a source that I can't seem to find at the moment, I did not think of that optimization my self.

How did you learn to create the distance fields? Currently with my method, if I run a block size of 1 (with a scan size of 200) it will seemingly never finish. I have had it running for six hours now ...

This topic is closed to new replies.

Advertisement