Projected texture

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Q

How is it possible to project an image via a spotlight? I read, that the color attribute is a spatial material, but couldn’t find a way to assign a shader to a light. Thanks for your help! (:

:bust_in_silhouette: Reply From: SIsilicon

It could about an hour to come up with the answer. So if there’s a bug, let me know.

The Script

tool
extends Node
var mat
var light

func _ready():
    mat = $MeshInstance.get_surface_material(0)
    light = $SpotLight

func _process(delta):
    var trans = light.global_transform
    var fov = light.spot_angle
    var far = light.spot_range
    var proj = Vector3(1/tan(fov/2 * PI/180), 0.01, far)

    mat.set_shader_param("lightMatrix", trans)
    mat.set_shader_param("projvals", proj)

Put this in as a GDscript in some node. Preferably the scene’s root node.

The Shader

shader_type spatial;
render_mode world_vertex_coords;

uniform sampler2D tex;
uniform mat4 lightMatrix;
uniform vec3 projvals;
uniform float scale = 2.0;

varying vec4 UVtex;
varying float facing;

void vertex() {
    float n = projvals.y;
    float f = projvals.z;

    mat4 projection = mat4(vec4(projvals.x, 0, 0, 0),
		       vec4(0, projvals.x, 0, 0),
	   	       vec4(0, 0, -f/(f-n), -1),
		       vec4(0, 0, -(f*n)/(f-n), 0));

    UVtex = projection * inverse(lightMatrix) * vec4(VERTEX,1.0);
    vec3 lightpos = lightMatrix[3].xyz;
    facing = max(dot(normalize(lightpos - VERTEX), NORMAL), 0.0);
}

void light() {
    vec4 proj = UVtex.xyzw / UVtex.w;
    bool outofbounds = proj.z <= -1.0 || proj.z >= 1.0;

    if( !outofbounds ) {
	   proj.xy = proj.xy * 0.25 + 0.5;
	   proj.y = 1.0 - proj.y;
	   proj.xy *= scale;
               vec4 texproj = textureProj(tex, proj);
	   DIFFUSE_LIGHT += LIGHT_COLOR * ATTENUATION * ALBEDO * texproj.rgb*texproj.a * facing;
    }
}

Put this in as a shader in a shader material. The shader material shall be the material of your mesh that’ll be illuminated.

How to use

You will now need to change some stuff so that these will work for your project.
In the script, change the node reference of the light variable to that of yours. Has to be a spotlight. Also change the material reference of the mat variable. Just like the light var, but you not only must assign the node reference, but you must also retrieve the material from that mesh instance. Eg: $MeshInstance.get_surface_material(0)

Save your scene then revert it so that the script runs in the editor.
Then you can change the tex parameter in the shader material to the texture of your choice. And the scale parameter for how much of the texture you see.

WARNING!!! THE THE SYSTEM WILL BREAK WITH MORE THAN ONE LIGHT SOURCE!