From ca480ed54593c25fc5796751dbae45d6f804eed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Harabie=C5=84?= Date: Sun, 25 Aug 2024 23:25:43 +0200 Subject: [PATCH] d3d11: use far clip plane and refactor projections RF mainly uses one far clip plane from fog, but there are exceptions: * skyroom is rendered without far clip plane * glares and volumetric lights are using doubled far clip plane Because it is not possible to change projection matrix between objects (it would break Z-ordering) a different approach was taken. Disable depth clipping in rasterizer if game wants far clip plane that is farther than fog clip plane. Frustum culling should deal with glares and volumetric lights good enough. Add abstractions for projection to simplify changing it in the future. Also get rid of zm variable (set it to 1) to simplify the code. --- game_patch/CMakeLists.txt | 1 - game_patch/graphics/d3d11/gr_d3d11.cpp | 52 +++++++++++++++- game_patch/graphics/d3d11/gr_d3d11.h | 6 +- .../graphics/d3d11/gr_d3d11_context.cpp | 4 +- game_patch/graphics/d3d11/gr_d3d11_context.h | 28 +++++++-- .../d3d11/gr_d3d11_dynamic_geometry.cpp | 41 +++++++----- .../d3d11/gr_d3d11_dynamic_geometry.h | 2 + game_patch/graphics/d3d11/gr_d3d11_hooks.cpp | 38 +++++++++++- game_patch/graphics/d3d11/gr_d3d11_state.cpp | 3 +- game_patch/graphics/d3d11/gr_d3d11_state.h | 12 ++-- .../graphics/d3d11/gr_d3d11_transform.cpp | 45 -------------- .../graphics/d3d11/gr_d3d11_transform.h | 62 ++++++++++++++++++- game_patch/rf/gr/gr.h | 5 ++ game_patch/rf/math/vector.h | 30 +++++++++ 14 files changed, 245 insertions(+), 84 deletions(-) delete mode 100644 game_patch/graphics/d3d11/gr_d3d11_transform.cpp diff --git a/game_patch/CMakeLists.txt b/game_patch/CMakeLists.txt index 892fef24..42b4ec8e 100644 --- a/game_patch/CMakeLists.txt +++ b/game_patch/CMakeLists.txt @@ -85,7 +85,6 @@ set(SRCS graphics/d3d11/gr_d3d11_shader.cpp graphics/d3d11/gr_d3d11_shader.h graphics/d3d11/gr_d3d11_error.cpp - graphics/d3d11/gr_d3d11_transform.cpp graphics/d3d11/gr_d3d11_transform.h graphics/d3d11/gr_d3d11_solid.cpp graphics/d3d11/gr_d3d11_solid.h diff --git a/game_patch/graphics/d3d11/gr_d3d11.cpp b/game_patch/graphics/d3d11/gr_d3d11.cpp index 02acfcbf..90e69a99 100644 --- a/game_patch/graphics/d3d11/gr_d3d11.cpp +++ b/game_patch/graphics/d3d11/gr_d3d11.cpp @@ -387,6 +387,45 @@ namespace df::gr::d3d11 dyn_geo_renderer_->line_2d(x1, y1, x2, y2, mode); } + bool Renderer::poly(int nv, rf::gr::Vertex** vertices, int vertex_attributes, rf::gr::Mode mode, bool constant_sw, float sw) + { + rf::ubyte and_code = 0xFF; + for (int i = 0; i < nv; ++i) { + and_code &= vertices[i]->codes; + } + if (and_code) { + return false; + } + for (int i = 0; i < nv; ++i) { + auto v = vertices[i]; + if (!(v->flags & rf::gr::VF_PROJECTED)) { + project_vertex(v); + } + if (constant_sw) { + float unscaled_z = sw / matrix_scale.z; + v->sw = render_context_->projection().project_z(unscaled_z); + } + } + tmapper(nv, const_cast(vertices), vertex_attributes, mode); + return true; + } + + void Renderer::project_vertex(rf::gr::Vertex* v) + { + if (v->flags & rf::gr::VF_PROJECTED) { + return; + } + + rf::Vector3 unscaled_world_pos = v->world_pos / matrix_scale; + auto& proj = render_context_->projection(); + auto proj_pos = proj.project(unscaled_world_pos); + v->sx = screen.clip_width / 2.0f * (proj_pos.x + 1.0f) + screen.offset_x; + v->sy = screen.clip_height / 2.0f * (1.0f - proj_pos.y) + screen.offset_y; + v->sw = proj_pos.z; + + v->flags |= rf::gr::VF_PROJECTED; + } + bool Renderer::set_render_target(int bm_handle) { dyn_geo_renderer_->flush(); @@ -416,9 +455,14 @@ namespace df::gr::d3d11 return texture_manager_->read_back_buffer(back_buffer_, x, y, w, h, data); } - void Renderer::setup_3d() + void Renderer::setup_3d(Projection proj) { - render_context_->update_view_proj_transform(); + render_context_->update_view_proj_transform(proj); + } + + void Renderer::set_far_clip(bool enabled) + { + render_context_->set_depth_clip_enabled(enabled); } void Renderer::render_solid(rf::GSolid* solid, rf::GRoom** rooms, int num_rooms) @@ -504,4 +548,8 @@ namespace df::gr::d3d11 mesh_renderer_->flush_caches(); } + float Renderer::z_far() const + { + return render_context_->projection().z_far(); + } } diff --git a/game_patch/graphics/d3d11/gr_d3d11.h b/game_patch/graphics/d3d11/gr_d3d11.h index 2924a7df..b90b8a57 100644 --- a/game_patch/graphics/d3d11/gr_d3d11.h +++ b/game_patch/graphics/d3d11/gr_d3d11.h @@ -93,6 +93,7 @@ namespace df::gr::d3d11 void clear(); void zbuffer_clear(); void set_clip(); + void set_far_clip(bool enabled); void flip(); void texture_save_cache(); void texture_flush_cache(bool force); @@ -107,7 +108,9 @@ namespace df::gr::d3d11 void tmapper(int nv, const rf::gr::Vertex **vertices, int vertex_attributes, rf::gr::Mode mode); void line_3d(const rf::gr::Vertex& v0, const rf::gr::Vertex& v1, rf::gr::Mode mode); void line_2d(float x1, float y1, float x2, float y2, rf::gr::Mode mode); - void setup_3d(); + bool poly(int nv, rf::gr::Vertex** vertices, int vertex_attributes, rf::gr::Mode mode, bool constant_sw, float sw); + void project_vertex(rf::gr::Vertex* v); + void setup_3d(Projection proj); void render_solid(rf::GSolid* solid, rf::GRoom** rooms, int num_rooms); void render_movable_solid(rf::GSolid* solid, const rf::Vector3& pos, const rf::Matrix3& orient); void render_alpha_detail_room(rf::GRoom *room, rf::GSolid *solid); @@ -123,6 +126,7 @@ namespace df::gr::d3d11 void page_in_solid(rf::GSolid* solid); void page_in_movable_solid(rf::GSolid* solid); void flush_caches(); + float z_far() const; private: void init_device(); diff --git a/game_patch/graphics/d3d11/gr_d3d11_context.cpp b/game_patch/graphics/d3d11/gr_d3d11_context.cpp index b1d8d73d..d023584b 100644 --- a/game_patch/graphics/d3d11/gr_d3d11_context.cpp +++ b/game_patch/graphics/d3d11/gr_d3d11_context.cpp @@ -131,11 +131,11 @@ namespace df::gr::d3d11 DF_GR_D3D11_CHECK_HR(device->CreateBuffer(&desc, nullptr, &buffer_)); } - void ViewProjTransformBuffer::update(ID3D11DeviceContext* device_context) + void ViewProjTransformBuffer::update(const Projection& proj, ID3D11DeviceContext* device_context) { ViewProjTransformBufferData data; data.view_mat = build_view_matrix(rf::gr::eye_pos, rf::gr::eye_matrix); - data.proj_mat = build_proj_matrix(); + data.proj_mat = proj.matrix(); D3D11_MAPPED_SUBRESOURCE mapped_subres; DF_GR_D3D11_CHECK_HR( diff --git a/game_patch/graphics/d3d11/gr_d3d11_context.h b/game_patch/graphics/d3d11/gr_d3d11_context.h index 3d7b3088..b8fe5f90 100644 --- a/game_patch/graphics/d3d11/gr_d3d11_context.h +++ b/game_patch/graphics/d3d11/gr_d3d11_context.h @@ -48,7 +48,7 @@ namespace df::gr::d3d11 public: ViewProjTransformBuffer(ID3D11Device* device); - void update(ID3D11DeviceContext* device_context); + void update(const Projection& proj, ID3D11DeviceContext* device_context); operator ID3D11Buffer*() const { @@ -238,9 +238,10 @@ namespace df::gr::d3d11 void zbuffer_clear(); void set_clip(); - void update_view_proj_transform() + void update_view_proj_transform(Projection proj) { - view_proj_transform_cbuffer_.update(device_context_); + projection_ = proj; + view_proj_transform_cbuffer_.update(projection_, device_context_); } void update_per_frame_constants() @@ -312,10 +313,11 @@ namespace df::gr::d3d11 void set_cull_mode(D3D11_CULL_MODE cull_mode) { - if (current_cull_mode_ != cull_mode || zbias_changed_) { + if (current_cull_mode_ != cull_mode || zbias_changed_ || depth_clip_enabled_changed_) { current_cull_mode_ = cull_mode; zbias_changed_ = false; - set_rasterizer_state(state_manager_.lookup_rasterizer_state(current_cull_mode_, zbias_)); + depth_clip_enabled_changed_ = false; + set_rasterizer_state(state_manager_.lookup_rasterizer_state(current_cull_mode_, zbias_, depth_clip_enabled_)); } } @@ -332,6 +334,14 @@ namespace df::gr::d3d11 } } + void set_depth_clip_enabled(bool depth_clip_enabled) + { + if (depth_clip_enabled_ != depth_clip_enabled) { + depth_clip_enabled_ = depth_clip_enabled; + depth_clip_enabled_changed_ = true; + } + } + void update_lights() { lights_buffer_.update(device_context_); @@ -342,6 +352,11 @@ namespace df::gr::d3d11 device_context_->DrawIndexed(index_count, index_start_location, base_vertex_location); } + const Projection& projection() const + { + return projection_; + } + private: void bind_cbuffers(); @@ -391,5 +406,8 @@ namespace df::gr::d3d11 ID3D11RasterizerState* current_rasterizer_state_ = nullptr; int zbias_ = 0; bool zbias_changed_ = true; + bool depth_clip_enabled_ = true; + bool depth_clip_enabled_changed_ = true; + Projection projection_; }; } diff --git a/game_patch/graphics/d3d11/gr_d3d11_dynamic_geometry.cpp b/game_patch/graphics/d3d11/gr_d3d11_dynamic_geometry.cpp index 5cab6e6a..22776046 100644 --- a/game_patch/graphics/d3d11/gr_d3d11_dynamic_geometry.cpp +++ b/game_patch/graphics/d3d11/gr_d3d11_dynamic_geometry.cpp @@ -4,8 +4,6 @@ #include "gr_d3d11_shader.h" #include "gr_d3d11_context.h" #include "../../rf/os/frametime.h" -#define NO_D3D8 -#include "../../rf/gr/gr_direct3d.h" using namespace rf; @@ -77,6 +75,23 @@ namespace df::gr::d3d11 return color; } + inline std::array DynamicGeometryRenderer::convert_pos(const gr::Vertex& v, bool is_3d) + { + Vector3 ndc{ + ((v.sx - gr::screen.offset_x) / gr::screen.clip_width * 2.0f - 1.0f), + ((v.sy - gr::screen.offset_y) / gr::screen.clip_height * -2.0f + 1.0f), + v.sw, + }; + // Set w to depth in camera space (needed for 3D rendering) + float w = is_3d ? render_context_.projection().unproject_z(v.sw) : 1.0f; + return { + ndc.x * w, + ndc.y * w, + ndc.z * w, + w, + }; + } + void DynamicGeometryRenderer::add_poly(int nv, const gr::Vertex **vertices, int vertex_attributes, const std::array& tex_handles, gr::Mode mode) { int num_index = (nv - 2) * 3; @@ -106,16 +121,13 @@ namespace df::gr::d3d11 if (use_vert_alpha && !(vertex_attributes & gr::TMAP_FLAG_ALPHA)) { color.alpha = gr::screen.current_color.alpha; } - // Note: gr_matrix_scale is zero before first gr_setup_3d call - float matrix_scale_z = gr::matrix_scale.z ? gr::matrix_scale.z : 1.0f; for (int i = 0; i < nv; ++i) { const gr::Vertex& in_vert = *vertices[i]; GpuTransformedVertex& out_vert = gpu_verts[i]; - // Set w to depth in camera space (needed for 3D rendering) - float w = 1.0f / in_vert.sw / matrix_scale_z; - out_vert.x = ((in_vert.sx - gr::screen.offset_x) / gr::screen.clip_width * 2.0f - 1.0f) * w; - out_vert.y = ((in_vert.sy - gr::screen.offset_y) / gr::screen.clip_height * -2.0f + 1.0f) * w; - out_vert.z = in_vert.sw * gr::d3d::zm * w; + auto [x, y, z, w] = convert_pos(in_vert, true); + out_vert.x = x; + out_vert.y = y; + out_vert.z = z; out_vert.w = w; if (use_vert_color && (vertex_attributes & gr::TMAP_FLAG_RGB)) { @@ -154,16 +166,13 @@ namespace df::gr::d3d11 rf::Color color = get_vertex_color_from_screen(mode); int diffuse = pack_color(color); - // Note: gr_matrix_scale is zero before first gr_setup_3d call - float matrix_scale_z = gr::matrix_scale.z ? gr::matrix_scale.z : 1.0f; for (int i = 0; i < num_verts; ++i) { const gr::Vertex& in_vert = *vertices[i]; GpuTransformedVertex& out_vert = gpu_verts[i]; - out_vert.x = (in_vert.sx - gr::screen.offset_x) / gr::screen.clip_width * 2.0f - 1.0f; - out_vert.y = (in_vert.sy - gr::screen.offset_y) / gr::screen.clip_height * -2.0f + 1.0f; - float w = is_3d ? 1.0f / in_vert.sw / matrix_scale_z : 1.0f; - out_vert.z = in_vert.sw * gr::d3d::zm * w; - // Set w to depth in camera space (needed for 3D rendering) + auto [x, y, z, w] = convert_pos(in_vert, is_3d); + out_vert.x = x; + out_vert.y = y; + out_vert.z = z; out_vert.w = w; out_vert.diffuse = diffuse; } diff --git a/game_patch/graphics/d3d11/gr_d3d11_dynamic_geometry.h b/game_patch/graphics/d3d11/gr_d3d11_dynamic_geometry.h index 41c922bf..29195e46 100644 --- a/game_patch/graphics/d3d11/gr_d3d11_dynamic_geometry.h +++ b/game_patch/graphics/d3d11/gr_d3d11_dynamic_geometry.h @@ -65,6 +65,8 @@ namespace df::gr::d3d11 return {gpu_verts, gpu_inds, base_vertex}; } + std::array convert_pos(const rf::gr::Vertex& v, bool is_3d); + ComPtr device_; RenderContext& render_context_; RingBuffer vertex_ring_buffer_; diff --git a/game_patch/graphics/d3d11/gr_d3d11_hooks.cpp b/game_patch/graphics/d3d11/gr_d3d11_hooks.cpp index 78584323..dce16d37 100644 --- a/game_patch/graphics/d3d11/gr_d3d11_hooks.cpp +++ b/game_patch/graphics/d3d11/gr_d3d11_hooks.cpp @@ -198,6 +198,17 @@ namespace df::gr::d3d11 renderer->set_fullscreen_state(rf::gr::screen.window_mode == rf::gr::FULLSCREEN); } + rf::ubyte project_vertex_new(Vertex* v) + { + renderer->project_vertex(v); + return v->flags; + } + + bool poly(int nv, rf::gr::Vertex** vertices, int vertex_attributes, rf::gr::Mode mode, bool constant_sw, float sw) + { + return renderer->poly(nv, vertices, vertex_attributes, mode, constant_sw, sw); + } + static CodeInjection g_render_room_objects_render_liquid_injection{ 0x004D4106, [](auto& regs) { @@ -211,7 +222,27 @@ namespace df::gr::d3d11 static CodeInjection gr_d3d_setup_3d_injection{ 0x005473E4, []() { - renderer->setup_3d(); + float sx = matrix_scale.x / matrix_scale.z; + float sy = matrix_scale.y / matrix_scale.z; + static auto& zm = addr_as_ref(0x005A7DD8); + float zn = 0.1f; // static near plane (RF uses: zm / matrix_scale.z) + zm = 1.0f; // let's not use zm at all to simplify software projections + float zf = rf::level.distance_fog_far_clip > 0.0f ? rf::level.distance_fog_far_clip : 1700.0f; + renderer->setup_3d(Projection{sx, sy, zn, zf}); + }, + }; + + static CodeInjection gr_d3d_setup_fustrum_injection{ + 0x00546A40, + []() { + // Glares and volumetric lights use doubled far clip plane + // To avoid using "user clip planes" disable depth clipping + // Frustum culling should be enough + static auto& use_far_clip = addr_as_ref(0x01818B65); + static auto& far_clip_dist = addr_as_ref(0x01818B68); + float z_far = renderer->z_far(); + bool depth_clip_enable = use_far_clip && far_clip_dist < z_far * 1.1f; + renderer->set_far_clip(depth_clip_enable); }, }; @@ -281,6 +312,7 @@ void gr_d3d11_apply_patch() g_render_room_objects_render_liquid_injection.install(); gr_d3d_setup_3d_injection.install(); + gr_d3d_setup_fustrum_injection.install(); vif_lod_mesh_ctor_injection.install(); vif_lod_mesh_dtor_injection.install(); v3d_page_in_injection.install(); @@ -304,7 +336,7 @@ void gr_d3d11_apply_patch() //AsmWriter{0x00547150}.ret(); // gr_d3d_setup_3d //AsmWriter{0x005473F0}.ret(); // gr_d3d_start_instance //AsmWriter{0x00547540}.ret(); // gr_d3d_stop_instance - //AsmWriter{0x005477A0}.ret(); // gr_d3d_project_vertex + AsmWriter{0x005477A0}.jmp(project_vertex_new); // gr_d3d_project_vertex //AsmWriter{0x005478F0}.ret(); // gr_d3d_is_normal_facing //AsmWriter{0x00547960}.ret(); // gr_d3d_is_normal_facing_plane //AsmWriter{0x005479B0}.ret(); // gr_d3d_get_apparent_distance_from_camera @@ -344,7 +376,7 @@ void gr_d3d11_apply_patch() //AsmWriter{0x00558450}.ret(); // gr_d3d_render_glass_shard - uses gr_poly AsmWriter{0x00558550}.ret(); // gr_d3d_render_face_wireframe //AsmWriter{0x005585F0}.ret(); // gr_d3d_render_weapon_tracer - uses gr_poly - //AsmWriter{0x005587C0}.ret(); // gr_d3d_poly - uses gr_d3d_tmapper + AsmWriter{0x005587C0}.jmp(poly); // gr_d3d_poly AsmWriter{0x00558920}.ret(); // gr_d3d_render_geometry_wireframe AsmWriter{0x00558960}.ret(); // gr_d3d_render_geometry_in_editor AsmWriter{0x00558C40}.ret(); // gr_d3d_render_sel_face_in_editor diff --git a/game_patch/graphics/d3d11/gr_d3d11_state.cpp b/game_patch/graphics/d3d11/gr_d3d11_state.cpp index fbfb9925..2bf0a835 100644 --- a/game_patch/graphics/d3d11/gr_d3d11_state.cpp +++ b/game_patch/graphics/d3d11/gr_d3d11_state.cpp @@ -11,7 +11,7 @@ namespace df::gr::d3d11 { } - ComPtr StateManager::create_rasterizer_state(D3D11_CULL_MODE cull_mode, int depth_bias) + ComPtr StateManager::create_rasterizer_state(D3D11_CULL_MODE cull_mode, int depth_bias, bool depth_clip_enable) { CD3D11_RASTERIZER_DESC desc{CD3D11_DEFAULT{}}; desc.CullMode = cull_mode; @@ -19,6 +19,7 @@ namespace df::gr::d3d11 if (g_game_config.msaa) { desc.MultisampleEnable = TRUE; } + desc.DepthClipEnable = depth_clip_enable; ComPtr rasterizer_state; check_hr( diff --git a/game_patch/graphics/d3d11/gr_d3d11_state.h b/game_patch/graphics/d3d11/gr_d3d11_state.h index acdf3b9f..ec5ba7e4 100644 --- a/game_patch/graphics/d3d11/gr_d3d11_state.h +++ b/game_patch/graphics/d3d11/gr_d3d11_state.h @@ -12,15 +12,15 @@ namespace df::gr::d3d11 public: StateManager(ComPtr device); - ID3D11RasterizerState* lookup_rasterizer_state(D3D11_CULL_MODE cull_mode, int depth_bias = 0) + ID3D11RasterizerState* lookup_rasterizer_state(D3D11_CULL_MODE cull_mode, int depth_bias = 0, bool depth_clip_enable = true) { - auto key = std::make_tuple(cull_mode, depth_bias); + auto key = std::make_tuple(cull_mode, depth_bias, depth_clip_enable); auto it = rasterizer_state_cache_.find(key); if (it != rasterizer_state_cache_.end()) { return it->second; } - auto rasterizer_state = create_rasterizer_state(cull_mode, depth_bias); - auto p = rasterizer_state_cache_.emplace(key, create_rasterizer_state(cull_mode, depth_bias)); + auto rasterizer_state = create_rasterizer_state(cull_mode, depth_bias, depth_clip_enable); + auto p = rasterizer_state_cache_.emplace(key, create_rasterizer_state(cull_mode, depth_bias, depth_clip_enable)); return p.first->second; } @@ -70,7 +70,7 @@ namespace df::gr::d3d11 } private: - ComPtr create_rasterizer_state(D3D11_CULL_MODE cull_mode, int depth_bias); + ComPtr create_rasterizer_state(D3D11_CULL_MODE cull_mode, int depth_bias, bool depth_clip_enable); ComPtr create_sampler_state(rf::gr::TextureSource ts); ComPtr create_blend_state(rf::gr::AlphaBlend ab); ComPtr create_depth_stencil_state(gr::ZbufferType zbt); @@ -79,6 +79,6 @@ namespace df::gr::d3d11 std::unordered_map> sampler_state_cache_; std::unordered_map> blend_state_cache_; std::unordered_map> depth_stencil_state_cache_; - std::map, ComPtr> rasterizer_state_cache_; + std::map, ComPtr> rasterizer_state_cache_; }; } diff --git a/game_patch/graphics/d3d11/gr_d3d11_transform.cpp b/game_patch/graphics/d3d11/gr_d3d11_transform.cpp deleted file mode 100644 index b8833180..00000000 --- a/game_patch/graphics/d3d11/gr_d3d11_transform.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include -#include -#include "../../rf/gr/gr.h" -#define NO_D3D8 -#include "../../rf/gr/gr_direct3d.h" -#include "gr_d3d11_transform.h" - -using namespace rf; - -namespace df::gr::d3d11 -{ - GpuMatrix4x4 build_proj_matrix() - { - // TODO: use variable fov and far plane - #if 0 - // Standard D3D projection matrix, 1/z, maps near plane to 0 and far plane to 1. See: - // https://docs.microsoft.com/en-us/windows/win32/dxtecharts/the-direct3d-transformation-pipeline - // https://docs.microsoft.com/en-us/windows/win32/direct3d9/projection-transform - float z_near = 0.15f; - float z_far = 275.0f; - float q = z_far / (z_far - z_near); - float fov_w = 3.14592 / 180.0f * 120.0f; - float fov_h = fov_w * 3.0f / 4.0f; - float w = 1 / tanf(fov_w / 2.0f); - float h = 1 / tanf(fov_h / 2.0f); - return {{ - {w, 0.0f, 0.0f, 0.0f}, - {0.0f, h, 0.0f, 0.0f}, - {0.0f, 0.0f, q, -q * z_near}, - {0.0f, 0.0f, 1.0f, 0.0f}, - }}; - #else - // RF projection, 1/z, maps near plane to 1 and far plane to 0 - // It has some benefits, see "reversed-Z trick" in https://developer.nvidia.com/content/depth-precision-visualized - using rf::gr::matrix_scale; - using rf::gr::d3d::zm; - return {{ - {matrix_scale.x / matrix_scale.z, 0.0f, 0.0f, 0.0f}, - {0.0f, matrix_scale.y / matrix_scale.z, 0.0f, 0.0f}, - {0.0f, 0.0f, 0.0f, zm / matrix_scale.z}, - {0.0f, 0.0f, 1.0f, 0.0f}, - }}; - #endif - } -} diff --git a/game_patch/graphics/d3d11/gr_d3d11_transform.h b/game_patch/graphics/d3d11/gr_d3d11_transform.h index 78901745..453d44b1 100644 --- a/game_patch/graphics/d3d11/gr_d3d11_transform.h +++ b/game_patch/graphics/d3d11/gr_d3d11_transform.h @@ -11,6 +11,66 @@ namespace df::gr::d3d11 // 4 rows, 3 cols (column-major) using GpuMatrix4x3 = std::array, 3>; + class Projection + { + float sx_ = 1.0f; + float sy_ = 1.0f; + float sz_ = -0.1f; + float tz_ = 1.0f; + float zf_ = 1.0f; + + public: + Projection() = default; + Projection(float sx, float sy, float zn, float zf) : + sx_{sx}, sy_{sy}, + sz_{-zn / (zf - zn)}, + tz_{zf * zn / (zf - zn)}, + zf_{zf} + {} + + float project_z(float z) const + { + return (z * sz_ + tz_) / z; + } + + rf::Vector3 project(rf::Vector3 p) const + { + return { + p.x * sx_ / p.z, + p.y * sy_ / p.z, + project_z(p.z), + }; + } + + float unproject_z(float pz) const { + // (z * sz_ + tz_) / z == pz + // pz * z = z * sz_ + tz_ + // pz * z - sz_ * z = tz_ + // z * (pz - sz_) = tz_ + // z = tz_ / (pz - sz_) + return tz_ / (pz - sz_); + } + + GpuMatrix4x4 matrix() const + { + // RF projection (1/z) uses reversed-Z trick, maps near plane to 1 and infinity to 0 + // It has some benefits, see "reversed-Z trick" in https://developer.nvidia.com/content/depth-precision-visualized + // To support far plane clipping change it a bit and map far plane to 0 instead of infinity + // Matrix construction: https://iolite-engine.com/blog_posts/reverse_z_cheatsheet + return {{ + {sx_, 0.0f, 0.0f, 0.0f}, + {0.0f, sy_, 0.0f, 0.0f}, + {0.0f, 0.0f, sz_, tz_}, + {0.0f, 0.0f, 1.0f, 0.0f}, + }}; + } + + float z_far() const + { + return zf_; + } + }; + inline GpuMatrix4x4 build_identity_matrix() { return {{ @@ -52,6 +112,4 @@ namespace df::gr::d3d11 {orient.fvec.x, orient.fvec.y, orient.fvec.z, translation.z}, }}; } - - GpuMatrix4x4 build_proj_matrix(); } diff --git a/game_patch/rf/gr/gr.h b/game_patch/rf/gr/gr.h index b49275a2..7a855d4e 100644 --- a/game_patch/rf/gr/gr.h +++ b/game_patch/rf/gr/gr.h @@ -306,6 +306,11 @@ namespace rf::gr TMAP_FLAG_ALPHA = 8, }; + enum VertexFlags + { + VF_PROJECTED = 1, + }; + static auto& screen = addr_as_ref(0x017C7BC0); static auto& gamma_ramp = addr_as_ref(0x017C7C68); static auto& default_wfar = addr_as_ref(0x00596140); diff --git a/game_patch/rf/math/vector.h b/game_patch/rf/math/vector.h index a680cd76..ea81cb67 100644 --- a/game_patch/rf/math/vector.h +++ b/game_patch/rf/math/vector.h @@ -32,6 +32,22 @@ namespace rf return *this; } + Vector3& operator*=(const Vector3& other) + { + x *= other.x; + y *= other.y; + z *= other.z; + return *this; + } + + Vector3& operator/=(const Vector3& other) + { + x /= other.x; + y /= other.y; + z /= other.z; + return *this; + } + Vector3& operator+=(float scalar) { x += scalar; @@ -83,6 +99,20 @@ namespace rf return tmp; } + [[nodiscard]] Vector3 operator*(const Vector3& other) const + { + Vector3 tmp = *this; + tmp *= other; + return tmp; + } + + [[nodiscard]] Vector3 operator/(const Vector3& other) const + { + Vector3 tmp = *this; + tmp /= other; + return tmp; + } + [[nodiscard]] Vector3 operator+(float scalar) const { Vector3 tmp = *this;