UV and Normals Unwrap (Mesh Projection as UV) World Aligned

This shader unwraps the mesh on the fly, aligned to World Position (it projects the mesh in UV space, colored by world coordinates / world position).

By setting is_getting_normals = true it projects the normals, this way you can capture both the UVs and the Normal UVs.

The shader is lightweight, as it just displaces the vertices.


  • Add the shader to any Mesh/Mesh Instance and the unwrap projection happens on the fly (the mesh vertices are displaced as UV projection). If you can see it, try increasing capture_size or rotating the viewport, due to backface culling.
  • Capture the projected mesh with a Viewport and then use it as a texture for painting the mesh during runtime. It can be used to draw bullet hit masks in a character, etc – but this has a big BUT, check “Challenges” below.
  • In order to use this UV projection, you need to know which UV coordinate you need. If you want to convert a World Location to UV position, you can see my solution here: https://godotforums.org/d/30491-how-to-translate-a-world-coordinate-to-uv-coordinate/11

ALTERNATIVES: An alternative is dynamically loading all of the mesh’s vertices, normals and UVs with Godot’s MeshDataTool – but then it’s 3D model format dependant (obj, dae, etc). With the shader you can do the unwrapping for any mesh.

INSPIRATION: Unreal Engine has a very simple way of doing this to project any model during runtime, to use the projection with a Render Target and then paint any kind of mask to show damages, dirt, etc. So I transpiled it from UE to Godot, checking UE’s source code.

Shader code
shader_type spatial;
render_mode unshaded;

varying vec3 color_vertex_world_position;
varying vec3 color_normal;

uniform bool is_getting_normals = false;

uniform float capture_size = 1;
uniform vec3 unwrap_location = vec3(0, 0, 0);
uniform float unwrap_scale = 1.0;

void vertex()
	color_vertex_world_position =  (WORLD_MATRIX * vec4(VERTEX, 1.0)).xyz;
	color_normal = (WORLD_MATRIX * vec4(NORMAL, 1.0)).xyz;
	// Placed in separate steps for comprehension,
	// it can be simplified in a single row.
	vec2 step1 = (UV - 0.5) * capture_size;
	vec3 step2 = vec3(step1.x, step1.y, 0);
	vec3 step3 = step2 + unwrap_location; 
	vec3 final_position = step3 * unwrap_scale;
	VERTEX = final_position;

void fragment()
	if(!is_getting_normals) {
		ALBEDO = color_vertex_world_position;
	} else {
		ALBEDO = color_normal;
mesh projection, normals, projection, texture projection, unwrap, unwrapping, uv, uv unwrap, uv unwrapping, uvunwrap
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

mesh-terrain blending

Vertex Mesh Rotator

Terrain Mesh Blending with Dithering


Inline Feedbacks
View all comments