Browse Source

Add world-to-screen example (#138)

Artsiom Trubchyk 1 month ago
parent
commit
06a5e933a6

BIN
render/world_to_screen/assets/SourceSansPro-Semibold.ttf


+ 27 - 0
render/world_to_screen/assets/materials/unlit.fp

@@ -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 - 0
render/world_to_screen/assets/materials/unlit.material

@@ -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 - 0
render/world_to_screen/assets/materials/unlit.vp

@@ -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 - 0
render/world_to_screen/assets/models/License.txt

@@ -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

BIN
render/world_to_screen/assets/models/character-ghost.glb


BIN
render/world_to_screen/assets/models/colormap.png


BIN
render/world_to_screen/assets/models/crypt.glb


+ 5 - 0
render/world_to_screen/assets/text48.font

@@ -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 - 0
render/world_to_screen/example.md

@@ -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 - 0
render/world_to_screen/example/hud.gui

@@ -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 - 0
render/world_to_screen/example/hud.gui_script

@@ -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 - 0
render/world_to_screen/example/player.script

@@ -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 - 0
render/world_to_screen/example/world_to_screen.collection

@@ -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
+  }
+}

+ 62 - 0
render/world_to_screen/game.project

@@ -0,0 +1,62 @@
+[project]
+title = render-world_to_screen
+version = 0.1
+
+[bootstrap]
+main_collection = /example/world_to_screen.collectionc
+
+[input]
+game_binding = /builtins/input/all.input_bindingc
+repeat_interval = 0.05
+
+[display]
+width = 720
+height = 720
+high_dpi = 1
+
+[physics]
+scale = 0.02
+gravity_y = -500.0
+
+[script]
+shared_state = 1
+
+[collection_proxy]
+max_count = 256
+
+[label]
+subpixels = 1
+
+[sprite]
+subpixels = 1
+max_count = 32765
+
+[windows]
+iap_provider = 
+
+[android]
+package = com.defold.examples
+
+[ios]
+bundle_identifier = com.defold.examples
+
+[osx]
+bundle_identifier = com.defold.examples
+
+[html5]
+show_fullscreen_button = 0
+show_made_with_defold = 0
+scale_mode = no_scale
+heap_size = 64
+
+[collection]
+max_instances = 32765
+
+[particle_fx]
+max_emitter_count = 1024
+
+[render]
+clear_color_blue = 0.1
+clear_color_green = 0.1
+clear_color_red = 0.1
+

BIN
render/world_to_screen/thumbnail.png