Skip to content

Instantly share code, notes, and snippets.

@D4KU
Created July 10, 2022 15:42
Show Gist options
  • Select an option

  • Save D4KU/c49a02fb15d3c452fa10aa05fe96eb0e to your computer and use it in GitHub Desktop.

Select an option

Save D4KU/c49a02fb15d3c452fa10aa05fe96eb0e to your computer and use it in GitHub Desktop.
using UnityEngine;
using MathNet.Numerics.LinearAlgebra;
/// <summary>
/// Apply transformation matrix reconstructed from four points to a material.
/// The matrix describes the transformation of the screen rectangle to the
/// quadrilateral spanned by the points after being projected in the screen
/// space of the connected camera.
/// </summary>
/// <author>David Kutschke</author>
[ExecuteAlways]
[RequireComponent(typeof(Camera))]
public class MatrixFromPoints : MonoBehaviour
{
// Four points to calculate transformation matrix from
public Transform bottomLeft;
public Transform bottomRight;
public Transform topLeft;
public Transform topRight;
[Tooltip("Material to apply the calculated matrix to")]
public Material material;
[Tooltip("Shader property to apply the calculated matrix to")]
public string propertyName = "_Matrix";
new Camera camera;
public Camera Camera
{
get
{
if (camera == null)
camera = GetComponent<Camera>();
return camera;
}
}
void Update()
{
if (!(bottomLeft && bottomRight && topLeft && topRight && material))
return;
var cam = Camera;
Matrix4x4 matrix = GetPerspective(
Vector2.zero,
Vector2.right,
Vector2.up,
Vector2.one,
cam.WorldToViewportPoint(bottomLeft.position),
cam.WorldToViewportPoint(bottomRight.position),
cam.WorldToViewportPoint(topLeft.position),
cam.WorldToViewportPoint(topRight.position));
material.SetMatrix(propertyName, matrix);
}
/// <summary>
/// Reconstruct transformation matrix from the transformation of four
/// source points to four target points.
/// c --- d g -- h
/// | | | |
/// | | -> | |
/// a --- b e -- f
/// </summary>
/// <returns>
/// 3x3 Matrix, padded as 4x4 because it's the only one Unity supports.
/// </returns>
static Matrix4x4 GetPerspective(
Vector2 a, Vector2 b, Vector2 c, Vector2 d,
Vector2 e, Vector2 f, Vector2 g, Vector2 h)
{
// Taken from
// https://stackoverflow.com/a/57280136/5634963
// Solve system of linear equations Ax = b
// Build coefficient matrix A
var A = Matrix<float>.Build.DenseOfArray(new float[,]
{
{ a.x, a.y, 1, 0, 0, 0, -a.x * e.x, -a.y * e.x },
{ b.x, b.y, 1, 0, 0, 0, -b.x * f.x, -b.y * f.x },
{ c.x, c.y, 1, 0, 0, 0, -c.x * g.x, -c.y * g.x },
{ d.x, d.y, 1, 0, 0, 0, -d.x * h.x, -d.y * h.x },
{ 0, 0, 0, a.x, a.y, 1, -a.x * e.y, -a.y * e.y },
{ 0, 0, 0, b.x, b.y, 1, -b.x * f.y, -b.y * f.y },
{ 0, 0, 0, c.x, c.y, 1, -c.x * g.y, -c.y * g.y },
{ 0, 0, 0, d.x, d.y, 1, -d.x * h.y, -d.y * h.y },
});
// Build vector b in Ax = b
var _b = Vector<float>.Build.DenseOfArray(new float[]
{
e.x, f.x, g.x, h.x, e.y, f.y, g.y, h.y
});
// Find solution vector x in Ax = b
// x = A \ b
// x = A^-1 * b
var x = A.Inverse() * _b;
return new Matrix4x4(
column0: new Vector4(x[0], x[3], x[6], 0),
column1: new Vector4(x[1], x[4], x[7], 0),
column2: new Vector4(x[2], x[5], 1, 0),
column3: new Vector4( 0, 0, 0, 1));
}
}
Shader "Unlit/SampleTransformed"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
CBUFFER_START(UnityPerMaterial)
sampler2D _MainTex;
float4 _MainTex_ST;
float4x4 _Matrix;
CBUFFER_END
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 uv = mul(_Matrix, float3(i.uv, 1));
return tex2D(_MainTex, uv.xy / uv.z);
}
ENDCG
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment