From ea2b2c457c8d2b12cf6a9eb774d0a5264279e586 Mon Sep 17 00:00:00 2001 From: hydrogendeuteride Date: Sun, 28 Dec 2025 00:01:44 +0900 Subject: [PATCH] ADD: planet prototype --- shaders/deferred_lighting.frag | 44 ++++++-- src/CMakeLists.txt | 2 + src/core/engine_ui.cpp | 117 ++++++++++++++++++++ src/main.cpp | 10 ++ src/scene/planet/planet_system.cpp | 168 +++++++++++++++++++++++++++++ src/scene/planet/planet_system.h | 54 ++++++++++ src/scene/vk_scene.cpp | 11 ++ src/scene/vk_scene.h | 6 ++ 8 files changed, 405 insertions(+), 7 deletions(-) create mode 100644 src/scene/planet/planet_system.cpp create mode 100644 src/scene/planet/planet_system.h diff --git a/shaders/deferred_lighting.frag b/shaders/deferred_lighting.frag index 14fafa2..52ac6f2 100644 --- a/shaders/deferred_lighting.frag +++ b/shaders/deferred_lighting.frag @@ -35,6 +35,26 @@ const float SHADOW_MIN_BIAS = 1e-5; const float SHADOW_RAY_TMIN = 0.02;// start a bit away from the surface const float SHADOW_RAY_ORIGIN_BIAS = 0.01;// world units +// Estimate the float ULP scale at this world position magnitude, used to keep +// ray bias and tMin effective even when world coordinates are very large. +float world_pos_ulp(vec3 p) +{ + float m = max(max(abs(p.x), abs(p.y)), abs(p.z)); + // For IEEE-754 float, relative precision is ~2^-23 (~1.192e-7). Clamp to a + // small baseline to avoid tiny values near the origin. + return max(1e-4, m * 1.1920929e-7); +} + +float shadow_ray_origin_bias(vec3 p) +{ + return max(SHADOW_RAY_ORIGIN_BIAS, world_pos_ulp(p) * 8.0); +} + +float shadow_ray_tmin(vec3 p) +{ + return max(SHADOW_RAY_TMIN, world_pos_ulp(p) * 16.0); +} + vec3 getCameraWorldPosition() { // view = [ R^T -R^T*C ] @@ -223,9 +243,11 @@ float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L) #ifdef GL_EXT_ray_query float farR = max(max(sceneData.cascadeSplitsView.x, sceneData.cascadeSplitsView.y), max(sceneData.cascadeSplitsView.z, sceneData.cascadeSplitsView.w)); + float originBias = shadow_ray_origin_bias(wp); + float tmin = shadow_ray_tmin(wp); rayQueryEXT rq; rayQueryInitializeEXT(rq, topLevelAS, gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT, - 0xFF, wp + N * SHADOW_RAY_ORIGIN_BIAS, SHADOW_RAY_TMIN, L, farR); + 0xFF, wp + N * originBias, tmin, L, farR); while (rayQueryProceedEXT(rq)) { } bool hit = (rayQueryGetIntersectionTypeEXT(rq, true) != gl_RayQueryCommittedIntersectionNoneEXT); return hit ? 0.0 : 1.0; @@ -249,10 +271,12 @@ float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L) if (cascadeEnabled && NoL < sceneData.rtParams.x) { float maxT = sceneData.cascadeSplitsView[cm.i0]; + float originBias = shadow_ray_origin_bias(wp); + float tmin = shadow_ray_tmin(wp); rayQueryEXT rq; // tmin: small offset to avoid self-hits rayQueryInitializeEXT(rq, topLevelAS, gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT, - 0xFF, wp + N * SHADOW_RAY_ORIGIN_BIAS, SHADOW_RAY_TMIN, L, maxT); + 0xFF, wp + N * originBias, tmin, L, maxT); bool hit = false; while (rayQueryProceedEXT(rq)) { } hit = (rayQueryGetIntersectionTypeEXT(rq, true) != gl_RayQueryCommittedIntersectionNoneEXT); @@ -278,9 +302,11 @@ float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L) float maxT0 = sceneData.cascadeSplitsView[cm.i0]; float maxT1 = sceneData.cascadeSplitsView[cm.i1]; float maxT = max(maxT0, maxT1); + float originBias = shadow_ray_origin_bias(wp); + float tmin = shadow_ray_tmin(wp); rayQueryEXT rq; rayQueryInitializeEXT(rq, topLevelAS, gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT, - 0xFF, wp + N * SHADOW_RAY_ORIGIN_BIAS, SHADOW_RAY_TMIN, L, maxT); + 0xFF, wp + N * originBias, tmin, L, maxT); while (rayQueryProceedEXT(rq)) { } bool hit = (rayQueryGetIntersectionTypeEXT(rq, true) != gl_RayQueryCommittedIntersectionNoneEXT); if (hit) vis = min(vis, 0.0); @@ -335,7 +361,9 @@ void main(){ if (maxT > 0.01) { vec3 dir = toL / maxT; - vec3 origin = pos + N * SHADOW_RAY_ORIGIN_BIAS; + float originBias = shadow_ray_origin_bias(pos); + float tmin = shadow_ray_tmin(pos); + vec3 origin = pos + N * originBias; rayQueryEXT rq; rayQueryInitializeEXT( @@ -344,7 +372,7 @@ void main(){ gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT, 0xFF, origin, - SHADOW_RAY_TMIN, + tmin, dir, maxT ); @@ -380,7 +408,9 @@ void main(){ float cosTheta = dot(-L, dir); if (cosTheta > sceneData.spotLights[i].direction_cos_outer.w) { - vec3 origin = pos + N * SHADOW_RAY_ORIGIN_BIAS; + float originBias = shadow_ray_origin_bias(pos); + float tmin = shadow_ray_tmin(pos); + vec3 origin = pos + N * originBias; rayQueryEXT rq; rayQueryInitializeEXT( @@ -389,7 +419,7 @@ void main(){ gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT, 0xFF, origin, - SHADOW_RAY_TMIN, + tmin, L, maxT ); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2b16f5e..60e5da8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -130,6 +130,8 @@ add_executable (vulkan_engine scene/camera/mode_chase.cpp scene/camera/mode_fixed.h scene/camera/mode_fixed.cpp + scene/planet/planet_system.h + scene/planet/planet_system.cpp # compute compute/vk_compute.h compute/vk_compute.cpp diff --git a/src/core/engine_ui.cpp b/src/core/engine_ui.cpp index 805cd29..fb6afb9 100644 --- a/src/core/engine_ui.cpp +++ b/src/core/engine_ui.cpp @@ -37,6 +37,7 @@ #include #include "mesh_bvh.h" +#include "scene/planet/planet_system.h" namespace { @@ -2242,6 +2243,111 @@ namespace pick->worldPos = local_to_world(glm::vec3(targetTransform[3]), sceneMgr->get_world_origin()); } } + + static void ui_planets(VulkanEngine *eng) + { + if (!eng || !eng->_sceneManager) + { + return; + } + + SceneManager *scene = eng->_sceneManager.get(); + PlanetSystem *planets = scene->get_planet_system(); + if (!planets) + { + ImGui::TextUnformatted("Planet system not available"); + return; + } + + bool enabled = planets->enabled(); + if (ImGui::Checkbox("Enable planet rendering", &enabled)) + { + planets->set_enabled(enabled); + } + + const WorldVec3 origin_world = scene->get_world_origin(); + const WorldVec3 cam_world = scene->getMainCamera().position_world; + const glm::vec3 cam_local = scene->get_camera_local_position(); + + ImGui::Separator(); + ImGui::Text("Camera world (m): %.3f, %.3f, %.3f", cam_world.x, cam_world.y, cam_world.z); + ImGui::Text("Camera local (m): %.3f, %.3f, %.3f", cam_local.x, cam_local.y, cam_local.z); + ImGui::Text("World origin (m): %.3f, %.3f, %.3f", origin_world.x, origin_world.y, origin_world.z); + + auto look_at_world = [](Camera &cam, const WorldVec3 &target_world) + { + glm::dvec3 dirD = glm::normalize(target_world - cam.position_world); + glm::vec3 dir = glm::normalize(glm::vec3(dirD)); + + glm::vec3 up(0.0f, 1.0f, 0.0f); + if (glm::length2(glm::cross(dir, up)) < 1e-6f) + { + up = glm::vec3(0.0f, 0.0f, 1.0f); + } + + glm::vec3 f = dir; + glm::vec3 r = glm::normalize(glm::cross(up, f)); + glm::vec3 u = glm::cross(f, r); + + glm::mat3 rot; + rot[0] = r; + rot[1] = u; + rot[2] = -f; // -Z forward + cam.orientation = glm::quat_cast(rot); + }; + + PlanetSystem::PlanetBody *earth = planets->get_body(PlanetSystem::BodyID::Earth); + PlanetSystem::PlanetBody *moon = planets->get_body(PlanetSystem::BodyID::Moon); + + if (earth) + { + ImGui::Separator(); + + bool vis = earth->visible; + if (ImGui::Checkbox("Render Earth", &vis)) + { + earth->visible = vis; + } + ImGui::SameLine(); + ImGui::Text("(R=%.1f km)", earth->radius_m / 1000.0); + + const double dist = glm::length(cam_world - earth->center_world); + const double alt_m = dist - earth->radius_m; + ImGui::Text("Altitude above Earth: %.3f km", alt_m / 1000.0); + + if (ImGui::Button("Teleport: 10000 km above surface")) + { + scene->getMainCamera().position_world = + earth->center_world + WorldVec3(0.0, 0.0, earth->radius_m + 1.0e7); + look_at_world(scene->getMainCamera(), earth->center_world); + } + + if (ImGui::Button("Teleport: 1000 km orbit")) + { + scene->getMainCamera().position_world = + earth->center_world + WorldVec3(0.0, 0.0, earth->radius_m + 1.0e6); + look_at_world(scene->getMainCamera(), earth->center_world); + } + ImGui::SameLine(); + if (ImGui::Button("Teleport: 10 km above surface")) + { + scene->getMainCamera().position_world = + earth->center_world + WorldVec3(0.0, 0.0, earth->radius_m + 1.0e4); + look_at_world(scene->getMainCamera(), earth->center_world); + } + } + + if (moon) + { + bool vis = moon->visible; + if (ImGui::Checkbox("Render Moon", &vis)) + { + moon->visible = vis; + } + ImGui::SameLine(); + ImGui::Text("(R=%.1f km)", moon->radius_m / 1000.0); + } + } } // namespace // Window visibility states for menu-bar toggles @@ -2260,6 +2366,7 @@ namespace bool show_postfx{false}; bool show_scene{false}; bool show_camera{false}; + bool show_planets{false}; bool show_async_assets{false}; bool show_textures{false}; }; @@ -2282,6 +2389,7 @@ void vk_engine_draw_debug_ui(VulkanEngine *eng) ImGui::Separator(); ImGui::MenuItem("Scene", nullptr, &g_debug_windows.show_scene); ImGui::MenuItem("Camera", nullptr, &g_debug_windows.show_camera); + ImGui::MenuItem("Planets", nullptr, &g_debug_windows.show_planets); ImGui::MenuItem("Render Graph", nullptr, &g_debug_windows.show_render_graph); ImGui::MenuItem("Pipelines", nullptr, &g_debug_windows.show_pipelines); ImGui::Separator(); @@ -2406,6 +2514,15 @@ void vk_engine_draw_debug_ui(VulkanEngine *eng) ImGui::End(); } + if (g_debug_windows.show_planets) + { + if (ImGui::Begin("Planets", &g_debug_windows.show_planets)) + { + ui_planets(eng); + } + ImGui::End(); + } + if (g_debug_windows.show_async_assets) { if (ImGui::Begin("Async Assets", &g_debug_windows.show_async_assets)) diff --git a/src/main.cpp b/src/main.cpp index 74b0484..2932b0d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,6 +33,16 @@ public: api.load_global_ibl(ibl); } + // Planet demo defaults (Milestone A): start outside Earth and speed up the free camera. + { + constexpr double kEarthRadiusM = 6378137.0; + GameAPI::FreeCameraSettings free = api.get_free_camera_settings(); + free.moveSpeed = 20000.0f; + api.set_free_camera_settings(free); + + api.set_camera_position(glm::dvec3(0.0, 0.0, kEarthRadiusM + 1.0e6)); + api.camera_look_at(glm::dvec3(0.0, 0.0, 0.0)); + } // Load a glTF model asynchronously // api.add_gltf_instance_async("example_model", "models/example.gltf", diff --git a/src/scene/planet/planet_system.cpp b/src/scene/planet/planet_system.cpp new file mode 100644 index 0000000..7a648cc --- /dev/null +++ b/src/scene/planet/planet_system.cpp @@ -0,0 +1,168 @@ +#include "planet_system.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ + constexpr double kEarthRadiusM = 6378137.0; // WGS84 equatorial radius + constexpr double kMoonRadiusM = 1737400.0; // mean radius + constexpr double kMoonDistanceM = 384400000.0; // mean Earth-Moon distance + + GLTFMetallic_Roughness::MaterialConstants make_planet_constants() + { + GLTFMetallic_Roughness::MaterialConstants c{}; + c.colorFactors = glm::vec4(1.0f); + // metal_rough_factors.x = metallic, .y = roughness + c.metal_rough_factors = glm::vec4(0.0f, 1.0f, 0.0f, 0.0f); + return c; + } +} + +void PlanetSystem::init(EngineContext *context) +{ + _context = context; +} + +const PlanetSystem::PlanetBody *PlanetSystem::get_body(BodyID id) const +{ + size_t i = static_cast(id); + if (i >= _bodies.size()) + { + return nullptr; + } + return &_bodies[i]; +} + +PlanetSystem::PlanetBody *PlanetSystem::get_body(BodyID id) +{ + size_t i = static_cast(id); + if (i >= _bodies.size()) + { + return nullptr; + } + return &_bodies[i]; +} + +void PlanetSystem::ensure_bodies_created() +{ + if (!_bodies.empty()) + { + return; + } + + PlanetBody earth{}; + earth.name = "Earth"; + earth.center_world = WorldVec3(0.0, 0.0, 0.0); + earth.radius_m = kEarthRadiusM; + + PlanetBody moon{}; + moon.name = "Moon"; + moon.center_world = WorldVec3(kMoonDistanceM, 0.0, 0.0); + moon.radius_m = kMoonRadiusM; + + if (_context && _context->assets) + { + AssetManager *assets = _context->assets; + + // Earth: textured sphere (albedo only for now). + { + AssetManager::MeshCreateInfo ci{}; + ci.name = "Planet_EarthSphere"; + ci.geometry.type = AssetManager::MeshGeometryDesc::Type::Sphere; + ci.geometry.sectors = 64; + ci.geometry.stacks = 32; + + ci.material.kind = AssetManager::MeshMaterialDesc::Kind::Textured; + ci.material.options.albedoPath = "earth/earth_8k.jpg"; + ci.material.options.albedoSRGB = true; + ci.material.options.constants = make_planet_constants(); + ci.material.options.pass = MaterialPass::MainColor; + + earth.mesh = assets->createMesh(ci); + if (earth.mesh && !earth.mesh->surfaces.empty()) + { + earth.material = earth.mesh->surfaces[0].material; + } + } + + // Moon: constant albedo (no texture yet). + { + GLTFMetallic_Roughness::MaterialConstants mc = make_planet_constants(); + mc.colorFactors = glm::vec4(0.72f, 0.72f, 0.75f, 1.0f); + + moon.material = assets->createMaterialFromConstants("Planet_MoonMaterial", mc, MaterialPass::MainColor); + + std::vector verts; + std::vector inds; + primitives::buildSphere(verts, inds, 48, 24); + geom::generate_tangents(verts, inds); + + moon.mesh = assets->createMesh("Planet_MoonSphere", verts, inds, moon.material); + } + } + + _bodies.push_back(std::move(earth)); + _bodies.push_back(std::move(moon)); +} + +void PlanetSystem::update_and_emit(const SceneManager &scene, DrawContext &draw_context) +{ + if (!_enabled) + { + return; + } + + ensure_bodies_created(); + + const WorldVec3 origin_world = scene.get_world_origin(); + + for (PlanetBody &b : _bodies) + { + if (!b.visible || !b.mesh || b.mesh->surfaces.empty()) + { + continue; + } + + const glm::vec3 t_local = world_to_local(b.center_world, origin_world); + const float r = static_cast(b.radius_m); + const glm::vec3 s = glm::vec3(r * 2.0f); // primitive sphere radius is 0.5 + const glm::quat q = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); + const glm::mat4 transform = make_trs_matrix(t_local, q, s); + + uint32_t surface_index = 0; + for (const GeoSurface &surf : b.mesh->surfaces) + { + RenderObject obj{}; + obj.indexCount = surf.count; + obj.firstIndex = surf.startIndex; + obj.indexBuffer = b.mesh->meshBuffers.indexBuffer.buffer; + obj.vertexBuffer = b.mesh->meshBuffers.vertexBuffer.buffer; + obj.vertexBufferAddress = b.mesh->meshBuffers.vertexBufferAddress; + obj.material = surf.material ? &surf.material->data : nullptr; + obj.bounds = surf.bounds; + obj.transform = transform; + obj.sourceMesh = b.mesh.get(); + obj.surfaceIndex = surface_index++; + obj.objectID = draw_context.nextID++; + obj.ownerType = RenderObject::OwnerType::MeshInstance; + obj.ownerName = b.name; + + if (obj.material && obj.material->passType == MaterialPass::Transparent) + { + draw_context.TransparentSurfaces.push_back(obj); + } + else + { + draw_context.OpaqueSurfaces.push_back(obj); + } + } + } +} diff --git a/src/scene/planet/planet_system.h b/src/scene/planet/planet_system.h new file mode 100644 index 0000000..3715270 --- /dev/null +++ b/src/scene/planet/planet_system.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include +#include +#include +#include + +class EngineContext; +class SceneManager; +struct DrawContext; +class MeshAsset; +struct GLTFMaterial; + +class PlanetSystem +{ +public: + enum class BodyID : uint8_t + { + Earth = 0, + Moon = 1, + }; + + struct PlanetBody + { + std::string name; + WorldVec3 center_world{0.0, 0.0, 0.0}; + double radius_m = 1.0; + bool visible = true; + + std::shared_ptr mesh; + std::shared_ptr material; + }; + + void init(EngineContext *context); + + void update_and_emit(const SceneManager &scene, DrawContext &draw_context); + + bool enabled() const { return _enabled; } + void set_enabled(bool enabled) { _enabled = enabled; } + + const PlanetBody *get_body(BodyID id) const; + PlanetBody *get_body(BodyID id); + const std::vector &bodies() const { return _bodies; } + +private: + void ensure_bodies_created(); + + EngineContext *_context = nullptr; + bool _enabled = true; + std::vector _bodies; +}; + diff --git a/src/scene/vk_scene.cpp b/src/scene/vk_scene.cpp index 5f89d72..6241870 100644 --- a/src/scene/vk_scene.cpp +++ b/src/scene/vk_scene.cpp @@ -4,6 +4,7 @@ #include #include +#include "scene/planet/planet_system.h" #include "core/device/swapchain.h" #include "core/context.h" #include "core/config.h" @@ -19,6 +20,8 @@ #include "core/config.h" #include +SceneManager::SceneManager() = default; + SceneManager::~SceneManager() { fmt::println("[SceneManager] dtor: loadedScenes={} dynamicGLTFInstances={} pendingGLTFRelease={}", @@ -121,6 +124,9 @@ void SceneManager::init(EngineContext *context) cameraRig.init(*this, mainCamera); _camera_position_local = world_to_local(mainCamera.position_world, _origin_world); + + _planetSystem = std::make_unique(); + _planetSystem->init(_context); } void SceneManager::update_scene() @@ -306,6 +312,11 @@ void SceneManager::update_scene() } } + if (_planetSystem) + { + _planetSystem->update_and_emit(*this, mainDrawContext); + } + glm::mat4 view = mainCamera.getViewMatrix(_camera_position_local); // Use reversed infinite-Z projection (right-handed, -Z forward) to avoid far-plane clipping // on very large scenes. Vulkan clip space is 0..1 (GLM_FORCE_DEPTH_ZERO_TO_ONE) and requires Y flip. diff --git a/src/scene/vk_scene.h b/src/scene/vk_scene.h index 8ef42d5..bc6284a 100644 --- a/src/scene/vk_scene.h +++ b/src/scene/vk_scene.h @@ -12,6 +12,7 @@ #include "scene/vk_loader.h" class EngineContext; +class PlanetSystem; struct RenderObject { @@ -61,6 +62,7 @@ struct DrawContext class SceneManager { public: + SceneManager(); ~SceneManager(); void init(EngineContext *context); @@ -223,6 +225,8 @@ public: const PickingDebug &getPickingDebug() const { return pickingDebug; } + PlanetSystem *get_planet_system() const { return _planetSystem.get(); } + // Returns the LoadedGLTF scene for a named GLTF instance, or nullptr if not found. std::shared_ptr getGLTFInstanceScene(const std::string &instanceName) const; @@ -252,5 +256,7 @@ private: // GPU resources that might still be in-flight. std::vector> pendingGLTFRelease; + std::unique_ptr _planetSystem; + PickingDebug pickingDebug{}; };