Instantly share code, notes, and snippets.

Created January 2, 2020 22:42
Show Gist options
• Save Jellonator/0686c6e74d06745957de5a96fa00ec6c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
 shader_type canvas_item; uniform mat4 TRANSFORM; uniform vec2 DEPTH; uniform bool REPEAT_X; uniform bool REPEAT_Y; uniform bool FLIP; void fragment() { // Create the matrix. A workaround is used to modify the matrix's W column // because Godot's transforms are 3x4, not 4x4. mat4 mat = mat4(1.0); mat[0].w = DEPTH.x; mat[1].w = DEPTH.y; // Transform UV into [-1, 1] range vec2 uv = UV * 2.0 - vec2(1, 1); // Turn position into 4d vector vec4 pos = vec4(uv, 1.0, 1.0); pos = mat * pos; pos.xy = pos.xy / pos.w; // Apply transformation to position float w = pos.w; pos.z = 0.0; pos.w = 1.0; // Apply depth to position pos = TRANSFORM * pos; // divide position by w coordinate; this applies perspective // Set UV to position; transform its range to [0, 1] uv = (pos.xy + vec2(1, 1)) * 0.5; // Determine if uv is in range or repeating pattern if (((uv.x > 0.0 && uv.x < 1.0) || REPEAT_X) && ((uv.y > 0.0 && uv.y < 1.0) || REPEAT_Y) && (w > 0.0 || FLIP)) { // Apply texture uv = mod(uv, 1.0); COLOR = texture(TEXTURE, uv); } else { COLOR = vec4(0, 0, 0, 0); } }

### oliviafel commented Sep 14, 2021

Great shader, I was wondering if you would also know how to correctly place sprites on top of a texture that is being transformed by this? I'm trying to create a billboarded sprite appearance and not sure what the correct position transform should be. Mode 7 math and GODOTs weird Basis/Transform objects is mixing me up.

### Jellonator commented Sep 14, 2021

Great shader, I was wondering if you would also know how to correctly place sprites on top of a texture that is being transformed by this? I'm trying to create a billboarded sprite appearance and not sure what the correct position transform should be. Mode 7 math and GODOTs weird Basis/Transform objects is mixing me up.

Yeah I wrote this a while ago, and this gist definitely needs a bit of cleaning up. That being said, I'll try my best and come up with something.
One way I like to look at this is:

• `DEPTH` is used to generate a projection matrix
• `TRANSFORM` is a view matrix
• The transform of your Node2D will be the model view matrix

In the original shader, I use the W component of vec4 as the weight. However, we will instead need to use the Z component of a vec3 as a weight instead, as Godot has no Vector4 (although you could implement something like this yourself). This is fine, because you only need three components and a 3x3 matrix type for 2d transforms. I will also be hi-jacking the `Basis` type for the 3x3 matrix.

```# Object.gd
# Assumptions:
#     * a 1920x1080 viewport size
#     * A parent object using the Mode7 shader as a ShaderMatrial, which takes up the entire screen (e.g. Polygon2D),
#        and whose UV is 0,0 in the top left corner and 1,1 in the bottom right corner.
#     * This Node2D has a child named Sprite whose position will be updated
extends Node2D

const SCREEN_SIZE := Vector2(1920, 1080)

func create_2d_perspective_matrix(depth: Vector2) -> Basis:
return Basis(
Vector3(1, 0, depth.x),
Vector3(0, 1, depth.y),
Vector3(0, 0, 1))

func mult_position_by_basis(pos: Vector2, mat: Basis) -> Vector2:
var x = Vector3(pos.x, pos.y, 1.0)
var res = mat.xform(x)
return Vector3(res.x / res.z, res.y / res.z, res.z) # Important: dividing here applies perspective
# Another important factor: the Z component is a depth. The bigger it is, the further
# away the sprite is. Ideally, the sprite should be hidden if the Z component <= 0

func _process(_delta: float):
var material = node_mode7.material # should be a ShaderMatrial
# Create perspective matrix
var perspective_matrix = create_2d_perspective_matrix(depth)
# Create view matrix from TRANFORM. Node that Z row is skipped, just like in the original shader.
var view_matrix = Basis(
transform.basis.x,
transform.basis.y,
transform.origin)
# Transform to range [-1, 1] from world space
var worldpos = (global_position / (SCREEN_SIZE / 2)) - Vector2.ONE
var mat = perspective_matrix * view_matrix # May want to reverse order? I don't remember which way is correct
var pos = mult_position_by_basis(worldpos, mat)
if (pos.z > 0.0):
# Transform back to world space
sprite.global_position = (Vector2(pos.x, pos.y) + Vector2.ONE) * (SCREEN_SIZE / 2)
sprite.show()
else:
sprite.hide()```

Disclaimer: I have not tested the above code yet. I'll test this once I get home, and I'll post any revisions.

### oliviafel commented Sep 15, 2021

Disclaimer: I have not tested the above code yet. I'll test this once I get home, and I'll post any revisions.

I'm close to getting this to work, I think? I'm translating it over to C# though and might have fumbled a few things and I'm also doing some questionable things with Viewports where I create a copy of the objects I want to display with the mode 7 and leave the originals to keep game logic completely 2D. I'll put the project link below if you wanted to look, otherwise I'll report back as I attempt to figure out what I messed up.

https://github.com/DTFlip/Mode7_Godot

### BThacker commented Jan 23, 2022 • edited

Sharing here before I forgot. I was just able to get the above code to work in C# with some slight changes. Working on some other touches now but the only way I was able to wrap my head around the entire thing and understand it properly was to use the Systems.Numerics and convert it all to a Matrix4x4.

```// good luck with the below
using Godot;
using System;
using System.Numerics;

public class object_test : Node2D
{
Godot.Node2D parent;
Godot.Sprite spriteToMove;

private Matrix4x4 perspectiveMatrix = new Matrix4x4();
private Matrix4x4 viewMatrix = new Matrix4x4();
private Godot.Vector2 screenSize = new Godot.Vector2(1024,1024);
private float halfHeight = 512;

{
parent = GetParent() as Node2D;
spriteToMove = GetNode<Sprite>("Sprite");

Godot.Vector2 temp = new Godot.Vector2(0, 20);
Create2DPerspectiveMatrix(temp);
}

private void Create2DPerspectiveMatrix(Godot.Vector2 depth)
{
Vector4 _line1 = new Vector4(1f, 0f, 0f, depth.x);
Vector4 _line2 = new Vector4(0f, 1f, 0f, depth.y);
Vector4 _line3 = new Vector4(0f, 0f, 1f, 0f);
Vector4 _line4 = new Vector4(0f, 0f, 0f, 1f);

perspectiveMatrix.M11 = _line1.X;
perspectiveMatrix.M12 = _line1.Y;
perspectiveMatrix.M13 = _line1.Z;
perspectiveMatrix.M14 = _line1.W;
perspectiveMatrix.M21 = _line2.X;
perspectiveMatrix.M22 = _line2.Y;
perspectiveMatrix.M23 = _line2.Z;
perspectiveMatrix.M24 = _line2.W;
perspectiveMatrix.M31 = _line3.X;
perspectiveMatrix.M32 = _line3.Y;
perspectiveMatrix.M33 = _line3.Z;
perspectiveMatrix.M34 = _line3.W;
perspectiveMatrix.M41 = _line4.X;
perspectiveMatrix.M42 = _line4.Y;
perspectiveMatrix.M43 = _line4.Z;
perspectiveMatrix.M44 = _line4.W;

}

private void CreateViewMatrix(Godot.Transform _transform, Godot.Vector2 _depth)
{
Vector4 _line1 = new Vector4(_transform.basis.x.x, _transform.basis.x.y, _transform.basis.x.z, 0f);
Vector4 _line2 = new Vector4(_transform.basis.y.x, _transform.basis.y.y, _transform.basis.y.z, 0f);
Vector4 _line3 = new Vector4(_transform.basis.z.x, _transform.basis.z.y, _transform.basis.z.z, 0f);
Vector4 _line4 = new Vector4(_transform.origin.x, _transform.origin.y, 0f, 1f);

viewMatrix.M11 = _line1.X;
viewMatrix.M12 = _line1.Y;
viewMatrix.M13 = _line1.Z;
viewMatrix.M14 = _line1.W;
viewMatrix.M21 = _line2.X;
viewMatrix.M22 = _line2.Y;
viewMatrix.M23 = _line2.Z;
viewMatrix.M24 = _line2.W;
viewMatrix.M31 = _line3.X;
viewMatrix.M32 = _line3.Y;
viewMatrix.M33 = _line3.Z;
viewMatrix.M34 = _line3.W;
viewMatrix.M41 = _line4.X;
viewMatrix.M42 = _line4.Y;
viewMatrix.M43 = _line4.Z;
viewMatrix.M44 = _line4.W;

}

public Godot.Vector3 MultPositionByVect4(Godot.Vector2 worldPos, Matrix4x4 mat)
{

Vector4 temp = new Vector4(worldPos.x, worldPos.y, 0f, 1f);
Vector4 _transformed = Vector4.Transform(temp, mat);
Godot.Vector3 _newReturn = new Godot.Vector3(_transformed.X / _transformed.W, _transformed.Y / _transformed.W,  _transformed.W);
return _newReturn;

}

public override void _Process(float delta)
{
GD.Print(_transform);
Create2DPerspectiveMatrix(_depth);
CreateViewMatrix(_transform, _depth);
Matrix4x4 _mat = perspectiveMatrix * viewMatrix;
//Matrix4x4 _mat = viewMatrix * perspectiveMatrix;
Matrix4x4 _inverted = new Matrix4x4();
Matrix4x4.Invert(_mat, out _inverted);
//Matrix4x4 _mat = viewMatrix * perspectiveMatrix;
//Godot.Vector2 worldPos = (this.GlobalPosition / (screenSize / 2)) - Godot.Vector2.One;
float wX = this.GlobalPosition.x;
float xY = this.GlobalPosition.y;
Godot.Vector2 worldpos = (this.GlobalPosition / (screenSize / 2)) - Godot.Vector2.One;

Godot.Vector3 newPos = MultPositionByVect4(worldpos, _inverted);
Godot.Vector2 final = new Godot.Vector2(newPos.x, newPos.y);

//Godot.Vector2 finalPos = new Godot.Vector2(newPos.x, newPos.y);
//Godot.Vector2 finalPos1 = new Godot.Vector2(finalPos + Godot.Vector2.One) * (screenSize / 2);
spriteToMove.GlobalPosition = (final + Godot.Vector2.One) * (screenSize / 2);
}
}```