-
-
Save Jellonator/0686c6e74d06745957de5a96fa00ec6c to your computer and use it in GitHub Desktop.
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); | |
} | |
} |
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 matrixTRANSFORM
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
onready var node_sprite = $Sprite
onready var node_mode7 = get_parent()
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
var transform = material.get_shader_param("TRANSFORM")
var depth = material.get_shader_param("DEPTH")
# 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.
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.
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.ShaderMaterial mode7;
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;
public override void _Ready()
{
parent = GetParent() as Node2D;
mode7 = parent.Material as ShaderMaterial;
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)
{
Godot.Transform _transform = (Godot.Transform)mode7.GetShaderParam("TRANSFORM");
GD.Print(_transform);
Godot.Vector2 _depth = (Godot.Vector2)mode7.GetShaderParam("DEPTH");
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);
}
}
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.