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);
}