Jump to content

Twenty-four Bit Height Maps


MJW

Recommended Posts

This is a short comment on the advantages of 24-bit height maps.

 

A height-map is an image in which the color of each pixel corresponds to a height. Generally black represents the farthest distance, and white the nearest. Commonly, height maps are 8-bit-precision gray-scale images. (Color images are converted to gray-scale, by, for instance, using the intensity.) The problem with such a representation is that 8 bits is not sufficient to represent many smooth surfaces. This results in artifacts, such as waterlines where the levels change. It's somewhat like building a smooth surface, such as a sphere, out of Lego blocks.

 

The solution is to combine the R, G, and B color channels into a single 24-bit number. This can be efficiently done in PDN, because the 32-bit ColorBgra colors are stored in what in the C language would call a "union."  That is, the 4 BGRA bytes can also be accessed as a 32-bit integer. The integer values of ColorBgra colors can be obtained and saved using the Bgra field (e.g, src[x, y].Bgra).

 

For 24-bit height maps, every color corresponds to a height. It may seem like there would need to be special handling for gray-scale height maps, but that isn't the case. Gray-scale height maps represent a subset of 24-bit maps; specifically, they represent the heights n * 0x010101, where n is between 0 and 255. Therefore, any plugin that can handle 24-bit height maps can also handle 8-bit gray-scale height maps. (Which is the main point I wanted to make with this comment: most height-map-type plugins can have the advantages of 24-bit height maps without alternating the way they work with gray-scale maps. That is, of course referring to the user's point of view; obviously the code would need to be modified.)

 

Producing gray-scale height maps is quite simple, while producing full-precision 24-bit height maps is more difficult, since the height doesn't have an intuitive correspondence to the color. Full-precision height maps for simple objects, such as spheres, can be produced with plugins such as Render Sphere Height Map. The height maps can then be modified and combined using the Texture Merger. Gray-scale images can be smoothed into 24-bit maps using the the Texture Smoother.

 

Plugins that want to treat images as 24-bit height maps can't use the normal interpolating Surface routines, such as GetBilinearSample, since those routines interpolate the color components individually, so I wrote some extension routines (called ZSurface.cs) to access the 24-bit height maps in a similar fashion:
 

Spoiler

 


using PaintDotNet;
using PaintDotNet.Effects;
using PaintDotNet.AppModel;
using PaintDotNet.IndirectUI;
using PaintDotNet.Collections;
using PaintDotNet.PropertySystem;

namespace UtensilsPDN
{
    // These are implmented as extension methods so they can be called as Surface methods.
    public static class ZSurface
    {
        // Get the Z value from the surface.
        public static float GetZ(this Surface surf, float x, float y, bool wrapLast = false)
        {
            // This will also catch NaN and infinities.
            if ((x >= 0.0f) && (x < surf.Width) && (y >= 0.0f) && (y < surf.Height))
            {
                int leftX, rightX, lowerY, upperY;
                float alphaX, alphaY;

                // Get the four surrounding pixels, clamping to the valid ranges.
                // Also get the proportional distance.
                int maxX = surf.Width - 1, maxY = surf.Height - 1;

                leftX = (int)x;
                alphaX = x - (float)leftX;
                rightX = leftX + 1;
                if (rightX > maxX)
                    rightX = wrapLast ? 0 : maxX;

                upperY = (int)y;
                alphaY = y - (float)upperY;
                lowerY = upperY + 1;
                if (lowerY > maxY)
                    lowerY = wrapLast ? 0 : maxY;

                // Get the four Z values. (The names match Wikipedia's Bilinear Interpolation names.)
                float a00 = 0xffffff & surf.GetPointUnchecked(leftX, upperY).Bgra;
                float f01 = 0xffffff & surf.GetPointUnchecked(rightX, upperY).Bgra;
                float f10 = 0xffffff & surf.GetPointUnchecked(leftX, lowerY).Bgra;
                float f11 = 0xffffff & surf.GetPointUnchecked(rightX, lowerY).Bgra;

                // Compute the corner values.
                float a01 = f01 - a00;
                float a10 = f10 - a00;
                float a11 = f11 - f10 - a01;

                // Return linearly interpolated Z.
                return a00 + alphaX * a01 + alphaY * (a10 + alphaX * a11);
            }
            else
            {
                return 0.0f;
            }
        }

        // Get the Z value from the surface and the alpha.
        public static ColorBgra GetZPixel(this Surface surf, float x, float y, bool wrapLast = false)
        {
            if ((x >= 0.0f) && (x < surf.Width) && (y >= 0.0f) && (y < surf.Height))
            {
                int leftX, rightX, lowerY, upperY;
                float alphaX, alphaY;

                // Get the four surrounding pixels, clamping to the valid ranges.
                // Also get the proportional distance.
                int maxX = surf.Width - 1, maxY = surf.Height - 1;

                leftX = (int)x;
                alphaX = x - (float)leftX;
                rightX = leftX + 1;
                if (rightX > maxX)
                    rightX = wrapLast ? 0 : maxX;

                upperY = (int)y;
                alphaY = y - (float)upperY;
                lowerY = upperY + 1;
                if (lowerY > maxY)
                    lowerY = wrapLast ? 0 : maxY;

                uint ul = surf.GetPointUnchecked(leftX, upperY).Bgra;
                uint ur = surf.GetPointUnchecked(rightX, upperY).Bgra;
                uint ll = surf.GetPointUnchecked(leftX, lowerY).Bgra;
                uint lr = surf.GetPointUnchecked(rightX, lowerY).Bgra;

                // Get the four Z values. (The names match Wikipedia's Bilinear Interpolation names.)
                float a00 = 0xffffff & ul;
                float f01 = 0xffffff & ur;
                float f10 = 0xffffff & ll;
                float f11 = 0xffffff & lr;

                // Compute the corner values.
                float a01 = f01 - a00;
                float a10 = f10 - a00;
                float a11 = f11 - f10 - a01;

                // Linearly interpolate Z.
                uint z = (uint)((a00 + alphaX * a01 + alphaY * (a10 + alphaX * a11)) + 0.5f);

                // Get the four alpha values. Leave them shifted up 24 bits.
                a00 = 0xff000000 & ul;
                f01 = 0xff000000 & ur;
                f10 = 0xff000000 & ll;
                f11 = 0xff000000 & lr;

                // Compute the corner values.
                a01 = f01 - a00;
                a10 = f10 - a00;
                a11 = f11 - f10 - a01;

                // Linearly interpolate alpha, then round the shifted value.
                uint a = (uint)(a00 + alphaX * a01 + alphaY * (a10 + alphaX * a11));
                a = 0xff000000 & (a + 0x800000);

                // Return as a ColorBgra.
                return ColorBgra.FromUInt32(a | z);
            }
            else
            {
                return ColorBgra.FromUInt32(0);
            }
        }

        // Get the Z value and alpha.
        public static void GetZandAlpha(this Surface surf, float x, float y, out float outZ, out int outAlpha, bool wrapLast = false)
        {
            if ((x >= 0.0f) && (x < surf.Width) && (y >= 0.0f) && (y < surf.Height))
            {
                int leftX, rightX, lowerY, upperY;
                float alphaX, alphaY;

                // Get the four surrounding pixels, clamping to the valid ranges.
                // Also get the proportional distance.
                int maxX = surf.Width - 1, maxY = surf.Height - 1;

                leftX = (int)x;
                alphaX = x - (float)leftX;
                rightX = leftX + 1;
                if (rightX > maxX)
                    rightX = wrapLast ? 0 : maxX;

                upperY = (int)y;
                alphaY = y - (float)upperY;
                lowerY = upperY + 1;
                if (lowerY > maxY)
                    lowerY = wrapLast ? 0 : maxY;

                uint ul = surf.GetPointUnchecked(leftX, upperY).Bgra;
                uint ur = surf.GetPointUnchecked(rightX, upperY).Bgra;
                uint ll = surf.GetPointUnchecked(leftX, lowerY).Bgra;
                uint lr = surf.GetPointUnchecked(rightX, lowerY).Bgra;

                // Get the four Z values. (The names match Wikipedia's Bilinear Interpolation names.)
                float a00 = 0xffffff & ul;
                float f01 = 0xffffff & ur;
                float f10 = 0xffffff & ll;
                float f11 = 0xffffff & lr;

                // Compute the corner values.
                float a01 = f01 - a00;
                float a10 = f10 - a00;
                float a11 = f11 - f10 - a01;

                // Linearly interpolate Z.
                float z = a00 + alphaX * a01 + alphaY * (a10 + alphaX * a11);

                // Get the four alpha values. Leave them shifted up 24 bits.
                a00 = 0xff000000 & ul;
                f01 = 0xff000000 & ur;
                f10 = 0xff000000 & ll;
                f11 = 0xff000000 & lr;

                // Compute the corner values.
                a01 = f01 - a00;
                a10 = f10 - a00;
                a11 = f11 - f10 - a01;

                // Linearly interpolate alpha, then round the shifted value.
                uint a = (uint)(a00 + alphaX * a01 + alphaY * (a10 + alphaX * a11));
                a = 0xff000000 & (a + 0x800000);

                // Return Z and alpha.
                outZ = z; outAlpha = (int)(a >> 24);
            }
            else
            {
                outZ = 0.0f; outAlpha = 0;
            }
        }
        // Get the alpha value. This works for Z pixels and BGRA pixels.
        // If alpha is all that's needed, it should be faster because it doesn't compute
        // any other components.
        public static int GetAlpha(this Surface surf, float x, float y, bool wrapLast = false)
        {
            if ((x >= 0.0f) && (x < surf.Width) && (y >= 0.0f) && (y < surf.Height))
            {
                int leftX, rightX, lowerY, upperY;
                float alphaX, alphaY;

                // Get the four surrounding pixels, clamping to the valid ranges.
                // Also get the proportional distance.
                int maxX = surf.Width - 1, maxY = surf.Height - 1;

                leftX = (int)x;
                alphaX = x - (float)leftX;
                rightX = leftX + 1;
                if (rightX > maxX)
                    rightX = wrapLast ? 0 : maxX;

                upperY = (int)y;
                alphaY = y - (float)upperY;
                lowerY = upperY + 1;
                if (lowerY > maxY)
                    lowerY = wrapLast ? 0 : maxY;

                uint ul = surf.GetPointUnchecked(leftX, upperY).Bgra;
                uint ur = surf.GetPointUnchecked(rightX, upperY).Bgra;
                uint ll = surf.GetPointUnchecked(leftX, lowerY).Bgra;
                uint lr = surf.GetPointUnchecked(rightX, lowerY).Bgra;

                // Get the four alpha values. Leave them shifted up 24 bits.
                float a00 = 0xff000000 & ul;
                float f01 = 0xff000000 & ur;
                float f10 = 0xff000000 & ll;
                float f11 = 0xff000000 & lr;

                // Compute the corner values.
                float a01 = f01 - a00;
                float a10 = f10 - a00;
                float a11 = f11 - f10 - a01;

                // Linearly interpolate alpha, then round the shifted value.
                uint a = (uint)(a00 + alphaX * a01 + alphaY * (a10 + alphaX * a11)) + 0x800000;

                // Return alpha.
                return (int)(a >> 24);
            }
            else
            {
                return 0;
            }
        }
    }
}

 

 

 

  • Upvote 2
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...