Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
27 changes: 27 additions & 0 deletions render/world_to_screen/assets/materials/unlit.fp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#version 140

// Inputs should match the vertex shader's outputs.
in vec2 var_texcoord0;

// The texture to sample.
uniform lowp sampler2D texture0;

// The final color of the fragment.
out lowp vec4 final_color;

uniform fs_uniforms
{
mediump vec4 tint;
};

void main()
{
// Pre-multiply alpha since all runtime textures already are
vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);

// Sample the texture at the fragment's texture coordinates.
vec4 color = texture(texture0, var_texcoord0.xy) * tint_pm;

// Output the sampled color.
final_color = color;
}
31 changes: 31 additions & 0 deletions render/world_to_screen/assets/materials/unlit.material
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: "unlit"
tags: "model"
vertex_program: "/assets/materials/unlit.vp"
fragment_program: "/assets/materials/unlit.fp"
vertex_space: VERTEX_SPACE_LOCAL
vertex_constants {
name: "mtx_view"
type: CONSTANT_TYPE_VIEW
}
vertex_constants {
name: "mtx_proj"
type: CONSTANT_TYPE_PROJECTION
}
fragment_constants {
name: "tint"
type: CONSTANT_TYPE_USER
value {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
}
samplers {
name: "texture0"
wrap_u: WRAP_MODE_CLAMP_TO_EDGE
wrap_v: WRAP_MODE_CLAMP_TO_EDGE
filter_min: FILTER_MODE_MIN_LINEAR
filter_mag: FILTER_MODE_MAG_LINEAR
max_anisotropy: 0.0
}
28 changes: 28 additions & 0 deletions render/world_to_screen/assets/materials/unlit.vp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#version 140

// The model's vertex position and texture coordinates.
in vec4 position;
in vec2 texcoord0;

// The model's world matrix.
in mat4 mtx_world;

// The projection and view matrices.
uniform general_vp
{
mat4 mtx_view;
mat4 mtx_proj;
};

// The output of a vertex shader are passed to the fragment shader.
// The texture coordinates of the vertex.
out vec2 var_texcoord0;

void main()
{
// Pass the texture coordinates to the fragment shader.
var_texcoord0 = texcoord0;

// Transform the vertex position to clip space.
gl_Position = mtx_proj * mtx_view * mtx_world * vec4(position.xyz, 1.0);
}
28 changes: 28 additions & 0 deletions render/world_to_screen/assets/models/License.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@


Graveyard Kit (5.0)

Created/distributed by Kenney (www.kenney.nl)
Creation date: 17-10-2025 11:00

------------------------------

License: (Creative Commons Zero, CC0)
http://creativecommons.org/publicdomain/zero/1.0/

You can use this content for personal, educational, and commercial purposes.

Support by crediting 'Kenney' or 'www.kenney.nl' (this is not a requirement)

------------------------------

• Website : www.kenney.nl
• Donate : www.kenney.nl/donate

• Patreon : patreon.com/kenney

Follow on social media for updates:

• Twitter: twitter.com/KenneyNL
• BlueSky: kenney.bsky.social
• Instagram: instagram.com/kenney_nl
Binary file not shown.
Binary file added render/world_to_screen/assets/models/colormap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added render/world_to_screen/assets/models/crypt.glb
Binary file not shown.
5 changes: 5 additions & 0 deletions render/world_to_screen/assets/text48.font
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
font: "/assets/SourceSansPro-Semibold.ttf"
material: "/builtins/fonts/font.material"
size: 48
outline_alpha: 0.0
outline_width: 0.0
15 changes: 15 additions & 0 deletions render/world_to_screen/example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
tags: render
title: World to Screen
brief: This example demonstrates how to convert 3D world coordinates to 2D screen coordinates using camera transformations.
author: Artsiom Trubchyk
scripts: player.script
thumbnail: thumbnail.png
---

This example shows how to convert world positions to screen coordinates for UI positioning. It features:

* A `world_to_screen()` function that transforms 3D world positions to 2D screen coordinates using the camera's view and projection matrices.
* A ghost character that rotates around a crypt in 3D space while floating up and down.
* A player name label in the GUI that follows the character's world position by converting it to screen coordinates.
* Demonstrates practical use of world-to-screen conversion for positioning UI elements relative to 3D objects.
22 changes: 22 additions & 0 deletions render/world_to_screen/example/hud.gui
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
script: "/example/hud.gui_script"
fonts {
name: "text48"
font: "/assets/text48.font"
}
nodes {
scale {
x: 0.5
y: 0.5
}
size {
x: 400.0
y: 100.0
}
type: TYPE_TEXT
text: "PLAYER_NAME"
font: "text48"
id: "player_name"
inherit_alpha: true
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT
17 changes: 17 additions & 0 deletions render/world_to_screen/example/hud.gui_script
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function init(self)
self.name_node = gui.get_node("player_name")
end

function final(self)
end

function update(self, dt)
end

function on_message(self, message_id, message, sender)
if message_id == hash("update_data") then
local screen_position = message.screen_position
-- Use screen position to set the position of the player name node
gui.set_screen_position(self.name_node, screen_position)
end
end
60 changes: 60 additions & 0 deletions render/world_to_screen/example/player.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
go.property("camera_url", msg.url("/camera#camera"))
go.property("hud_url", msg.url("/ui#hud"))
go.property("angle", -45) -- we use this property to animate the rotation of the player around the center of the scene

--- Converts a world position to screen coordinates.
-- This function transforms a 3D world position to 2D screen coordinates using the camera's
-- view and projection matrices. The resulting coordinates are in screen space where (0,0)
-- is the bottom-left corner of the screen.
--
-- @param world_position vector3 The world position to convert.
-- @param camera_url url|string The camera component URL to use for the transformation.
-- @return number screen_x The X coordinate in screen space.
-- @return number screen_y The Y coordinate in screen space.
-- @return number screen_z Always returns 0 (depth information is not preserved).
local function world_to_screen(world_position, camera_url)
local proj = camera.get_projection(camera_url)
local view = camera.get_view(camera_url)

local view_proj = proj * view
local scr_coord = view_proj * vmath.vector4(world_position.x, world_position.y, world_position.z, 1)
local w, h = window.get_size()
scr_coord.x = (scr_coord.x / scr_coord.w + 1) * 0.5 * w
scr_coord.y = (scr_coord.y / scr_coord.w + 1) * 0.5 * h

return vmath.vector3(scr_coord.x, scr_coord.y, 0)
end

function init(self)
-- Get the IDs of the player view and UI objects
self.player_view_id = go.get_id("player_view")
self.player_ui_id = go.get_id("player_ui")

-- Animate vertical position of the body
local new_pos_y = go.get(self.player_view_id, "position.y") + 0.2
go.animate(self.player_view_id, "position.y", go.PLAYBACK_LOOP_PINGPONG, new_pos_y, go.EASING_INOUTSINE, 2)

-- Get the base position
self.base_pos = go.get_position()

-- Animate the angle to rotate the player around the center of the scene
go.animate("#", "angle", go.PLAYBACK_LOOP_FORWARD, -3600 + self.angle, go.EASING_LINEAR, 200)
end

function final(self)
end

function update(self, dt)
-- Update the position of the player based on the angle and the base position
local radius = self.base_pos.z
go.set_position(vmath.vector3(radius * math.sin(math.rad(self.angle)), self.base_pos.y, radius * math.cos(math.rad(self.angle))))
-- Update the rotation of the player based on the angle
go.set(".", "euler.y", self.angle + 90)

-- Update the world transform of the player UI object and convert the world position to screen coordinates
go.update_world_transform(self.player_ui_id)
local world_pos = go.get_world_position(self.player_ui_id)
local screen_pos = world_to_screen(world_pos, self.camera_url)
-- Send the screen position to the HUD script
msg.post(self.hud_url, "update_data", { screen_position = screen_pos })
end
146 changes: 146 additions & 0 deletions render/world_to_screen/example/world_to_screen.collection
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
name: "world_to_screen"
scale_along_z: 0
embedded_instances {
id: "camera"
data: "embedded_components {\n"
" id: \"camera\"\n"
" type: \"camera\"\n"
" data: \"aspect_ratio: 1.0\\n"
"fov: 0.7854\\n"
"near_z: 0.01\\n"
"far_z: 100.0\\n"
"orthographic_mode: ORTHO_MODE_AUTO_FIT\\n"
"\"\n"
"}\n"
""
position {
y: 5.0
z: 5.0
}
rotation {
x: -0.37036422
w: 0.9288866
}
}
embedded_instances {
id: "player"
children: "player_shadow"
children: "player_ui"
children: "player_view"
data: "components {\n"
" id: \"player\"\n"
" component: \"/example/player.script\"\n"
"}\n"
""
position {
y: 0.3
z: -2.0
}
}
embedded_instances {
id: "ui"
data: "components {\n"
" id: \"hud\"\n"
" component: \"/example/hud.gui\"\n"
"}\n"
""
position {
z: -361.0
}
}
embedded_instances {
id: "environment"
data: "embedded_components {\n"
" id: \"crypt\"\n"
" type: \"model\"\n"
" data: \"mesh: \\\"/assets/models/crypt.glb\\\"\\n"
"name: \\\"{{NAME}}\\\"\\n"
"materials {\\n"
" name: \\\"colormap\\\"\\n"
" material: \\\"/assets/materials/unlit.material\\\"\\n"
" textures {\\n"
" sampler: \\\"texture0\\\"\\n"
" texture: \\\"/assets/models/colormap.png\\\"\\n"
" }\\n"
"}\\n"
"\"\n"
"}\n"
"embedded_components {\n"
" id: \"crypt_shadow\"\n"
" type: \"sprite\"\n"
" data: \"default_animation: \\\"anim\\\"\\n"
"material: \\\"/builtins/materials/sprite.material\\\"\\n"
"size {\\n"
" x: 2.5\\n"
" y: 3.5\\n"
"}\\n"
"size_mode: SIZE_MODE_MANUAL\\n"
"textures {\\n"
" sampler: \\\"texture_sampler\\\"\\n"
" texture: \\\"/builtins/graphics/particle_blob.tilesource\\\"\\n"
"}\\n"
"\"\n"
" rotation {\n"
" x: 0.70710677\n"
" w: 0.70710677\n"
" }\n"
"}\n"
""
rotation {
y: 0.70710677
w: 0.70710677
}
}
embedded_instances {
id: "player_view"
data: "embedded_components {\n"
" id: \"model\"\n"
" type: \"model\"\n"
" data: \"mesh: \\\"/assets/models/character-ghost.glb\\\"\\n"
"name: \\\"{{NAME}}\\\"\\n"
"materials {\\n"
" name: \\\"colormap\\\"\\n"
" material: \\\"/assets/materials/unlit.material\\\"\\n"
" textures {\\n"
" sampler: \\\"texture0\\\"\\n"
" texture: \\\"/assets/models/colormap.png\\\"\\n"
" }\\n"
"}\\n"
"\"\n"
"}\n"
""
}
embedded_instances {
id: "player_shadow"
data: "embedded_components {\n"
" id: \"sprite\"\n"
" type: \"sprite\"\n"
" data: \"default_animation: \\\"anim\\\"\\n"
"material: \\\"/builtins/materials/sprite.material\\\"\\n"
"size {\\n"
" x: 1.2\\n"
" y: 1.2\\n"
"}\\n"
"size_mode: SIZE_MODE_MANUAL\\n"
"textures {\\n"
" sampler: \\\"texture_sampler\\\"\\n"
" texture: \\\"/builtins/graphics/particle_blob.tilesource\\\"\\n"
"}\\n"
"\"\n"
" rotation {\n"
" x: 0.70710677\n"
" w: 0.70710677\n"
" }\n"
"}\n"
""
position {
y: -0.3
}
}
embedded_instances {
id: "player_ui"
data: ""
position {
y: 1.0
}
}
Loading