Bilinear vs Bicubic Interpolation and How to Get Height of Terrain in Unity from a Heightmap

Feb 3, 2022

Here's how to sample between pixels of a texture using bilinear interpolation and bicubic interpolation. These can be used for scaling images or sampling the value of an image in-between pixels.

Bilinear averages the colors of surrounding pixels producing medium quality results. Bicubic is slower but more precise and produces smoother gradations.

Bicubic example.

using UnityEngine;

public class BicubicInterpolator {
    public static float BicubicInterpolated (float x, float y, Texture2D heightmap) {
        int row = Mathf.FloorToInt(x);
        int col = Mathf.FloorToInt(y);

        float remainderX = x - row;
        float remainderY = y - col;

        float[] p0 = new float[4];
        float[] p1 = new float[4];
        float[] p2 = new float[4];
        float[] p3 = new float[4];

        p0[0] = heightmap.GetPixel(row - 1, col - 1).r;
        p0[1] = heightmap.GetPixel(row - 1, col).r;
        p0[2] = heightmap.GetPixel(row - 1, col + 1).r;
        p0[3] = heightmap.GetPixel(row - 1, col + 2).r;

        p1[0] = heightmap.GetPixel(row, col - 1).r;
        p1[1] = heightmap.GetPixel(row, col).r;
        p1[2] = heightmap.GetPixel(row, col + 1).r;
        p1[3] = heightmap.GetPixel(row, col + 2).r;

        p2[0] = heightmap.GetPixel(row + 1, col - 1).r;
        p2[1] = heightmap.GetPixel(row + 1, col).r;
        p2[2] = heightmap.GetPixel(row + 1, col + 1).r;
        p2[3] = heightmap.GetPixel(row + 1, col + 2).r;

        p3[0] = heightmap.GetPixel(row + 2, col - 1).r;
        p3[1] = heightmap.GetPixel(row + 2, col).r;
        p3[2] = heightmap.GetPixel(row + 2, col + 1).r;
        p3[3] = heightmap.GetPixel(row + 2, col + 2).r;

        return BiCubicValue(p0, p1, p2, p3, remainderX, remainderY);
    }

    static float BiCubicValue(float[] p0, float[] p1, float[] p2, float[] p3, float x, float y) {
        float[] arr = new float[4];
        arr[0] = CubicValue(p0, y);
        arr[1] = CubicValue(p1, y);
        arr[2] = CubicValue(p2, y);
        arr[3] = CubicValue(p3, y);
        return CubicValue(arr, x);
    }

    static float CubicValue(float[] p, float x) {
        return p[1] + 0.5f * x * (p[2] - p[0] + x * (2.0f * p[0] - 5.0f * p[1] + 4.0f * p[2] - p[3] + x * (3.0f * (p[1] - p[2]) + p[3] - p[0])));
    }
}

Bilinear example

using UnityEngine;

public class BilinearInterpolator {
    public static float BicubicInterpolated (float x, float y, Texture2D heightmap) {
        int row = Mathf.FloorToInt(x);
        int col = Mathf.FloorToInt(y);

        float remainderX = x - row;
        float remainderY = y - col;

        float x1 = heightmap.GetPixel(row, col).r;
        float x2 = heightmap.GetPixel(row + 1, col).r;

        float y1 = heightmap.GetPixel(row, col + 1).r;
        float y2 = heightmap.GetPixel(row + 1, col + 1).r;

        return Mathf.Lerp(Mathf.Lerp(x1, x2, remainderX), Mathf.Lerp(y1, y2, remainderX), remainderY);
    }
}

If you need to get the height of Unity's terrain from only a heightmap you can use bicubic interpolation.

Vector3 GetPosition (float x, float z, Terrain terrain, Texture2D heightmap) {
    float x1 = x / terrain.terrainData.size.x * heightmap.width;
    float z1 = z / terrain.terrainData.size.z * heightmap.height;

    float y = BicubicInterpolated(x1, z1, heightmap) * terrain.terrainData.heightmapScale.y;

    return new Vector3(x, y, z);
}