GML:
/// Point order is:
/// P1 = top-left
/// P2 = top-right
/// P3 = bottom-left
/// P4 = bottom-right
///
/// @param sprite
/// @param image
/// @param x1
/// @param y1
/// @param x2
/// @param y2
/// @param x3
/// @param y3
/// @param x4
/// @param y4
/// @param [color=white]
/// @param [alpha=1]
function DrawSpriteHomographic(_sprite, _image, _x1, _y1, _x2, _y2, _x3, _y3, _x4, _y4, _color = c_white, _alpha = 1)
{
static _vertexFormat = (function()
{
vertex_format_begin();
vertex_format_add_position_3d();
vertex_format_add_colour();
vertex_format_add_texcoord();
return vertex_format_end();
})();
static _vBuff = vertex_create_buffer();
//Use cross products to figure out necessary perspective correction
var _x14 = _x1 - _x4;
var _y14 = _y1 - _y4;
var _x23 = _x2 - _x3;
var _y23 = _y2 - _y3;
var _cross_23_14 = _x23*_y14 - _y23*_x14;
if (_cross_23_14 != 0)
{
var _x43 = _x3 - _x4;
var _y43 = _y3 - _y4;
var _cross_23_43 = (_x23 * _y43 - _y23 * _x43) / _cross_23_14;
if (_cross_23_43 != 0)
{
var _cross_14_43 = (_x14 * _y43 - _y14 * _x43) / _cross_23_14;
if (_cross_14_43 != 0)
{
//Unpack UV data
var _uvs = sprite_get_uvs(_sprite, _image);
var _uvLeft = _uvs[0];
var _uvTop = _uvs[1];
var _uvRight = _uvs[2];
var _uvBottom = _uvs[3];
//Build out the vertex buffer
//We use the z-coord of the position attribute as the w-component in the shader
//You can think of the w-component in this situation as a "keystone" factor
vertex_begin(_vBuff, _vertexFormat);
vertex_position_3d(_vBuff, _x1, _y1, _cross_23_43); vertex_colour(_vBuff, _color, _alpha); vertex_texcoord(_vBuff, _uvLeft, _uvTop );
vertex_position_3d(_vBuff, _x2, _y2, _cross_14_43); vertex_colour(_vBuff, _color, _alpha); vertex_texcoord(_vBuff, _uvRight, _uvTop );
vertex_position_3d(_vBuff, _x3, _y3, 1-_cross_14_43); vertex_colour(_vBuff, _color, _alpha); vertex_texcoord(_vBuff, _uvLeft, _uvBottom);
vertex_position_3d(_vBuff, _x4, _y4, 1-_cross_23_43); vertex_colour(_vBuff, _color, _alpha); vertex_texcoord(_vBuff, _uvRight, _uvBottom);
vertex_end(_vBuff);
//Render out as a triangle strip so we do less work GML-side
shader_set(__shdDrawSpriteHomographic);
vertex_submit(_vBuff, pr_trianglestrip, sprite_get_texture(_sprite, _image));
shader_reset();
}
}
}
}
Vertex shader:
attribute vec3 in_Position;
attribute vec4 in_Colour;
attribute vec2 in_TextureCoord;
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
void main()
{
//Calculate the world-space position separately...
vec4 wsPos = gm_Matrices[MATRIX_WORLD]*vec4(in_Position.xy, 0.0, 1.0);
//...so we can pass the "z" value from the input position as the w-component
//This tricks the GPU into doing perspective correction on texture coordinates for us
//We multiply up by the w-component to ensure the xyz positions are orthographically unchanged after texture perspective correction
gl_Position = gm_Matrices[MATRIX_PROJECTION]*gm_Matrices[MATRIX_VIEW]*vec4(wsPos.xyz*in_Position.z, in_Position.z);
v_vColour = in_Colour;
v_vTexcoord = in_TextureCoord;
}
Frag shader:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
void main()
{
gl_FragColor = v_vColour*texture2D(gm_BaseTexture, v_vTexcoord);
}