2D holographic card Shader
My Best Attempt at a card shader in godot. There is much to be desired from this, but I decided to leave this here in case anyone would gain from it.
This is basically a mish mash of two shaders:
The Shader is indended to work with a simple script like this in order to change the cards rotation.
@tool
extends Sprite2D
func _process(delta):
material.set_shader_parameter("mouse_position",get_global_mouse_position())
material.set_shader_parameter("sprite_position",global_position)
How to use this shader:
1. apply the shader to a sprite.
1.5(optional). apply the above script to the sprite
2. set the uniform 2Dsamplers
2a. Foil Mask is a mask for the shader, set it to all white if you want every part of the sprite to be foil, otherwise you must create a specific mask for what you want(I belive godot currently returns a white value if the 2d sampler isn’t there, so you don’t nessesarily need to change it).
2b.The gradiant is the gradiant you wish to see. a rainbow gradiant is used in the example. (this is the most important one to set)
2c. Set the noise to what the underlying holo pattern should be,this makes the shader have a metallic/sparkling look to it.
2d. the normal map (used loosely here, dont expect it to match what it really should look like) allows you to create “grooves”/”textures” in the cards (im imagining pokemon cards here with their textured look).
3.set the other values
3a.Effect alpha mutliply is the strength of the foil mixing, 1 makes it all you see and 0 you will only see the sprite
3b.foil color and foil threshold effects which colors are considered foil, setting the foilcolor to white, and the threshold to 1 makes all colors effected.
3c. scroll effects how much the foilgradiant scrolls, period is how flattened or wide the gradient is.
Please let me know thoughts or improvements, If there is another shader that does this better please let me know. Thanks for checking out my shader.
Shader code
shader_type canvas_item;
group_uniforms code_set_parameters;
uniform vec2 mouse_position;
uniform vec2 sprite_position;
group_uniforms _3D_Perspective;
uniform float fov : hint_range(1, 179) = 90;
uniform bool cull_back = true;
//uniform float y_rot : hint_range(-180, 180) = 0.0; From original shader -- https://godotshaders.com/shader/2d-perspective/
//uniform float x_rot : hint_range(-180, 180) = 0.0;
//Amount to inset the image by, a higher value means the image is smaller but less likely to go outside of the "bounds" of the original sprite size.
uniform float inset : hint_range(0, 1) = 0.0;
//Max tilt. 2 means the sprite goes all the way to one side, and the plane is parrelel to the 2d camera.
uniform float max_tilt : hint_range(0,2.0) = 1.0;
//the Max distance from the center of the sprite that results in the max tilt.
uniform float max_distance = 500.0;
varying flat vec2 o;
varying vec3 p;
group_uniforms foil_uniforms;
//The color of the foil.
uniform vec3 foilcolor : source_color;
uniform float threshold : hint_range(0.0, 2.0, 0.1) = 0.1 ;
uniform float fuzziness : hint_range(0.0,1.0,.01) = 0.1;
uniform float period = 1;
uniform float scroll = 1;
uniform float normal_strength = .1;
uniform float effect_alpha_mult : hint_range(0,1) = 1.0;
uniform float direction : hint_range(0,1.0) = 0.5;
uniform sampler2D foil_mask;
uniform sampler2D gradient;
uniform sampler2D noise;
uniform sampler2D normal_map;
varying smooth vec2 direction_to;
varying smooth vec2 passthrough;
float inverse_lerp(float v, float a, float b){
return (v - a) / (b - a);
}
float color_mask(vec3 mask, vec3 color, float mask_threshold, float mask_fuzziness){
float d = distance(mask,color);
return clamp(1.0 - smoothstep(mask_threshold,mask_threshold + mask_fuzziness, d),0.0,1.0);
}
mat2 rotate2d(float _angle){
return mat2(vec2(cos(_angle),-sin(_angle)),vec2(sin(_angle),cos(_angle)));
}
vec3 rotate_vector(vec3 v, float angleX, float angleY, float magnitude) {
// Create rotation matrices for X and Y axes.
mat3 rotX = mat3(
vec3(1.0, 0.0, 0.0),
vec3(0.0, cos(angleX), -sin(angleX)),
vec3(0.0, sin(angleX), cos(angleX))
);
mat3 rotY = mat3(
vec3(cos(angleY), 0.0, sin(angleY)),
vec3(0.0, 1.0, 0.0),
vec3(-sin(angleY), 0.0, cos(angleY))
);
// Combine the rotations. Order matters! Y then X is common.
mat3 combinedRotation = rotX * rotY; // Apply Y rotation first, then X.
// Rotate the vector.
vec3 rotatedVector = combinedRotation * v;
// Apply magnitude
rotatedVector = normalize(rotatedVector) * magnitude;
return rotatedVector;
}
void vertex(){
direction_to = mouse_position - sprite_position;
float d = length( direction_to );
float magnitude = min( max_tilt , d / max_distance);
float angle = atan(direction_to.x,direction_to.y);
float x_rota = abs(angle) / PI;
float y_rota = abs(atan(direction_to.y,direction_to.x)) / PI;
float sin_b = sin( (-y_rota + .5) * magnitude * (PI / 2.0) );
float cos_b = cos( (-y_rota + .5) * magnitude * (PI / 2.0) );
float sin_c = sin( (x_rota - .5) * magnitude * (PI / 2.0) );
float cos_c = cos( (x_rota - .5) * magnitude * (PI / 2.0) );
mat3 inv_rot_mat;
inv_rot_mat[0][0] = cos_b;
inv_rot_mat[0][1] = 0.0;
inv_rot_mat[0][2] = -sin_b;
inv_rot_mat[1][0] = sin_b * sin_c;
inv_rot_mat[1][1] = cos_c;
inv_rot_mat[1][2] = cos_b * sin_c;
inv_rot_mat[2][0] = sin_b * cos_c;
inv_rot_mat[2][1] = -sin_c;
inv_rot_mat[2][2] = cos_b * cos_c;
float t = tan(fov / 360.0 * PI);
p = inv_rot_mat * vec3((UV - 0.5), 0.5 / t);
float v = (0.5 / t) + 0.5;
p.xy *= v * inv_rot_mat[2].z;
o = v * inv_rot_mat[2].xy;
VERTEX += (UV - 0.5) / TEXTURE_PIXEL_SIZE * t * (1.0 - inset);
}
void fragment() {
//culls the back somehow
if (cull_back && p.z <= 0.0) discard;
//get the UV from based on the tilt from the vertex function
vec2 uv = (p.xy / p.z).xy - o;
vec2 adjusted_uv = uv + 0.5;
//get the color texture
vec4 albedo_tex = texture(TEXTURE, adjusted_uv);
COLOR = albedo_tex;
vec4 mask = texture(foil_mask,adjusted_uv);
float texture_similarity = color_mask(foilcolor,albedo_tex.rgb,threshold,fuzziness);
float d = length( direction_to );
float magnitude = min( max_tilt , d / max_distance);
float angle = atan(direction_to.x,direction_to.y);
float x_rota = abs(angle) / PI;
float y_rota = abs(atan(direction_to.y,direction_to.x)) / PI;
vec3 normal_map_tex = texture(normal_map,adjusted_uv).rgb * 2.0 - 1.0;
vec3 normal = rotate_vector(vec3(1.0,1.0,1.0),x_rota,y_rota,magnitude * magnitude);
normal = rotate_vector(normal,normal_map_tex.x, normal_map_tex.y, length(normal_map_tex)) * normal_strength;
vec4 noise_tex = texture(noise,adjusted_uv);
vec2 gradiant_sample = vec2((0.25+(normal.y*direction*2.0-normal.x*(1.0-direction)*2.0)/2.0+(uv.y*direction+uv.x*(1.0-direction))/2.0), 0.0);
gradiant_sample += vec2(magnitude,0.0) * scroll;
gradiant_sample = mod((gradiant_sample+adjusted_uv * period),1.0);
vec4 gradient_tex = texture(gradient, gradiant_sample);
float strength = effect_alpha_mult * mask.r * texture_similarity;
COLOR.rgb = mix(albedo_tex.rgb, gradient_tex.rgb*(noise_tex.rgb*2.0), strength);//(effect_alpha_mult-length(albedo_tex))*gradient_tex.a*effect_alpha_mult);
COLOR.a *= step(max(abs(uv.x), abs(uv.y)), 0.5);
}
Amazing
Could you should the different settings for the different effects in your video? That would be helpful to understand the uniforms better.
Im still trying to modify this shader to work better, I can help if you need it, however im not really sure what you are confused by, could you tell me? the video and description above describes most of the uniforms.
This is amazing, thanks for your work! Is there any way we can download the demo project? I’d like to use some of those presets and masks, and also see the settings you used to achieve each of those looks
I will try to create an example project. The video uses a project with all my shader references so its a bit big to share, I will try to cut it down and share it some time this week.
Hey, nice work ! Can it work with any control node ? Control? Texture Rect ?
It wont work on everything, although this is more of a generalized shader question. It will work on texture rect, control. The way that it works on the nodes children breaks the illusion if you select the option to inherit parents shader, the workaround is to place everthing under a subvieportcontainer node (see this video at round 30 seconds https://www.youtube.com/watch?v=Alwy-TH0WzE)