完善客户端功能并同步差距文档
This commit is contained in:
@@ -1,8 +1,16 @@
|
||||
#include "environment_builder.h"
|
||||
|
||||
#include "asset_io.h"
|
||||
#include "dxt.h"
|
||||
|
||||
#include <godot_cpp/classes/environment.hpp>
|
||||
#include <godot_cpp/classes/image.hpp>
|
||||
#include <godot_cpp/classes/image_texture.hpp>
|
||||
#include <godot_cpp/classes/procedural_sky_material.hpp>
|
||||
#include <godot_cpp/classes/shader.hpp>
|
||||
#include <godot_cpp/classes/shader_material.hpp>
|
||||
#include <godot_cpp/classes/sky.hpp>
|
||||
#include <godot_cpp/classes/texture2d.hpp>
|
||||
#include <godot_cpp/core/object.hpp>
|
||||
|
||||
#include <m2_coord.h>
|
||||
@@ -18,9 +26,158 @@ namespace {
|
||||
Color rgba(const fmt::Rgba &c) { return Color(c[0], c[1], c[2], c[3]); }
|
||||
float luma(const fmt::Rgba &c) { return 0.2126f * c[0] + 0.7152f * c[1] + 0.0722f * c[2]; }
|
||||
|
||||
const char *SRC_SKYBOX = R"(shader_type sky;
|
||||
render_mode use_debanding;
|
||||
|
||||
uniform sampler2D front_tex : source_color, filter_linear_mipmap_anisotropic;
|
||||
uniform sampler2D back_tex : source_color, filter_linear_mipmap_anisotropic;
|
||||
uniform sampler2D left_tex : source_color, filter_linear_mipmap_anisotropic;
|
||||
uniform sampler2D right_tex : source_color, filter_linear_mipmap_anisotropic;
|
||||
uniform sampler2D top_tex : source_color, filter_linear_mipmap_anisotropic;
|
||||
uniform sampler2D bottom_tex : source_color, filter_linear_mipmap_anisotropic;
|
||||
uniform bool has_front = false;
|
||||
uniform bool has_back = false;
|
||||
uniform bool has_left = false;
|
||||
uniform bool has_right = false;
|
||||
uniform bool has_top = false;
|
||||
uniform bool has_bottom = false;
|
||||
uniform vec4 gradient_top = vec4(0.2, 0.3, 0.6, 1.0);
|
||||
uniform vec4 gradient_horizon = vec4(0.5, 0.6, 0.8, 1.0);
|
||||
uniform vec4 gradient_bottom = vec4(0.2, 0.2, 0.25, 1.0);
|
||||
uniform sampler2D cloud_tex : source_color, repeat_enable, filter_linear_mipmap_anisotropic;
|
||||
uniform bool has_cloud = false;
|
||||
uniform vec2 cloud_scale = vec2(2000.0, 2000.0);
|
||||
uniform float cloud_height = 300.0;
|
||||
uniform vec2 cloud_texture_scale = vec2(4.0, 4.0);
|
||||
uniform vec2 cloud_speed = vec2(0.001, 0.001);
|
||||
uniform vec4 cloud_tint = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
vec3 gradient_color(vec3 direction) {
|
||||
float height = clamp(direction.y * 0.5 + 0.5, 0.0, 1.0);
|
||||
if (height < 0.5)
|
||||
return mix(gradient_bottom.rgb, gradient_horizon.rgb, height * 2.0);
|
||||
return mix(gradient_horizon.rgb, gradient_top.rgb, (height - 0.5) * 2.0);
|
||||
}
|
||||
|
||||
vec3 apply_cloud(vec3 color, vec3 direction) {
|
||||
if (!has_cloud || direction.y <= 0.001 || cloud_height <= 0.0 ||
|
||||
cloud_scale.x <= 0.001 || cloud_scale.y <= 0.001)
|
||||
return color;
|
||||
|
||||
// ClientVS22 renders a finite horizontal quad at camera.z + CloudHeight.
|
||||
// After the Metin2 -> Godot conversion this is camera.y + height. The
|
||||
// source quad maps source +Y -> U=0 and source +X -> V=1; source Y is
|
||||
// Godot -Z, so keep that orientation here.
|
||||
float distance_to_cloud = cloud_height / direction.y;
|
||||
vec2 plane = direction.xz * distance_to_cloud;
|
||||
if (abs(plane.x) > cloud_scale.x || abs(plane.y) > cloud_scale.y)
|
||||
return color;
|
||||
vec2 uv = vec2(
|
||||
0.5 + 0.5 * plane.y / cloud_scale.y,
|
||||
0.5 + 0.5 * plane.x / cloud_scale.x);
|
||||
uv = fract(uv * cloud_texture_scale + TIME * cloud_speed);
|
||||
|
||||
// SkyBox.cpp uses MODULATEINVALPHA_ADDCOLOR followed by ONE /
|
||||
// INVSRCCOLOR blending: cloud.rgb + sky * (1 - cloud.rgb).
|
||||
vec4 texel = texture(cloud_tex, uv);
|
||||
vec3 cloud_rgb = clamp(texel.rgb * (1.0 - cloud_tint.a) + cloud_tint.rgb,
|
||||
0.0, 1.0);
|
||||
return cloud_rgb + color * (1.0 - cloud_rgb);
|
||||
}
|
||||
|
||||
void sky() {
|
||||
vec3 direction = normalize(EYEDIR);
|
||||
vec3 color = gradient_color(direction);
|
||||
float ax = abs(direction.x);
|
||||
float ay = abs(direction.y);
|
||||
float az = abs(direction.z);
|
||||
vec2 uv;
|
||||
|
||||
// ClientVS22 uses Metin2 Z-up faces. After x,z,-y conversion:
|
||||
// front=+Z, back=-Z, left=+X, right=-X, top=+Y, bottom=-Y.
|
||||
if (az >= ax && az >= ay) {
|
||||
if (direction.z > 0.0) {
|
||||
uv = vec2(0.5 - 0.5 * direction.x / az,
|
||||
0.5 - 0.5 * direction.y / az);
|
||||
if (has_front)
|
||||
color = texture(front_tex, uv).rgb;
|
||||
} else {
|
||||
uv = vec2(0.5 + 0.5 * direction.x / az,
|
||||
0.5 - 0.5 * direction.y / az);
|
||||
if (has_back)
|
||||
color = texture(back_tex, uv).rgb;
|
||||
}
|
||||
} else if (ax >= ay) {
|
||||
if (direction.x > 0.0) {
|
||||
uv = vec2(0.5 + 0.5 * direction.z / ax,
|
||||
0.5 - 0.5 * direction.y / ax);
|
||||
if (has_left)
|
||||
color = texture(left_tex, uv).rgb;
|
||||
} else {
|
||||
uv = vec2(0.5 - 0.5 * direction.z / ax,
|
||||
0.5 - 0.5 * direction.y / ax);
|
||||
if (has_right)
|
||||
color = texture(right_tex, uv).rgb;
|
||||
}
|
||||
} else if (direction.y > 0.0) {
|
||||
uv = vec2(0.5 - 0.5 * direction.x / ay,
|
||||
0.5 - 0.5 * direction.z / ay);
|
||||
if (has_top)
|
||||
color = texture(top_tex, uv).rgb;
|
||||
} else {
|
||||
uv = vec2(0.5 - 0.5 * direction.x / ay,
|
||||
0.5 - 0.5 * direction.z / ay);
|
||||
if (has_bottom)
|
||||
color = texture(bottom_tex, uv).rgb;
|
||||
}
|
||||
COLOR = apply_cloud(color, direction);
|
||||
}
|
||||
)";
|
||||
|
||||
Ref<ImageTexture> solid_texture(const Color &color) {
|
||||
PackedByteArray pixels;
|
||||
pixels.resize(4);
|
||||
uint8_t *p = pixels.ptrw();
|
||||
p[0] = (uint8_t)std::round(std::clamp(color.r, 0.0f, 1.0f) * 255.0f);
|
||||
p[1] = (uint8_t)std::round(std::clamp(color.g, 0.0f, 1.0f) * 255.0f);
|
||||
p[2] = (uint8_t)std::round(std::clamp(color.b, 0.0f, 1.0f) * 255.0f);
|
||||
p[3] = (uint8_t)std::round(std::clamp(color.a, 0.0f, 1.0f) * 255.0f);
|
||||
Ref<godot::Image> image = godot::Image::create_from_data(
|
||||
1, 1, false, godot::Image::FORMAT_RGBA8, pixels);
|
||||
image->generate_mipmaps();
|
||||
return ImageTexture::create_from_image(image);
|
||||
}
|
||||
|
||||
Ref<ImageTexture> load_texture(const std::string &name, const fmt::AssetResolver *resolver) {
|
||||
if (name.empty() || resolver == nullptr)
|
||||
return Ref<ImageTexture>();
|
||||
const std::string path = resolver->resolve(name, nullptr);
|
||||
if (path.empty())
|
||||
return Ref<ImageTexture>();
|
||||
Ref<godot::Image> image;
|
||||
const String godot_path(path.c_str());
|
||||
if (godot_path.get_extension().to_lower() == "dds") {
|
||||
const mtgodot::Image decoded = mtgodot::dds_from_file(godot_path);
|
||||
if (!decoded.ok())
|
||||
return Ref<ImageTexture>();
|
||||
PackedByteArray pixels;
|
||||
pixels.resize((int64_t)decoded.rgba.size());
|
||||
std::copy(decoded.rgba.begin(), decoded.rgba.end(), pixels.ptrw());
|
||||
image = godot::Image::create_from_data(decoded.w, decoded.h, false,
|
||||
godot::Image::FORMAT_RGBA8, pixels);
|
||||
} else {
|
||||
image = godot::Image::load_from_file(godot_path);
|
||||
}
|
||||
if (image.is_null() || image->is_empty())
|
||||
return Ref<ImageTexture>();
|
||||
image->generate_mipmaps();
|
||||
return ImageTexture::create_from_image(image);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EnvNodes apply_environment(const fmt::Environment &env, Node *parent) {
|
||||
EnvNodes apply_environment(const fmt::Environment &env, Node *parent,
|
||||
const fmt::AssetResolver *resolver) {
|
||||
EnvNodes out;
|
||||
|
||||
// --- DirectionalLight (Background) ---
|
||||
@@ -30,6 +187,8 @@ EnvNodes apply_environment(const fmt::Environment &env, Node *parent) {
|
||||
out.sun->set_name("Sun");
|
||||
parent->add_child(out.sun);
|
||||
}
|
||||
out.sun->set_cull_mask(1u << 0); // background / terrain layer
|
||||
out.sun->set_visible(env.dir_light.bg_enable);
|
||||
// .msenv Direction 是 Metin2 Z-up 向量(光传播方向)。
|
||||
fmt::m2coord::Vec3 d = fmt::m2coord::direction_to_godot(
|
||||
env.dir_light.direction[0], env.dir_light.direction[1], env.dir_light.direction[2]);
|
||||
@@ -56,6 +215,26 @@ EnvNodes apply_environment(const fmt::Environment &env, Node *parent) {
|
||||
// 角色/物体的间接补光:暖色,能量取自 Material.Ambient
|
||||
out.sun->set_param(Light3D::PARAM_SPECULAR, 0.4f);
|
||||
|
||||
// ClientVS22 keeps the character light separate from the background light
|
||||
// (MapOutdoor::OnBeginEnvironment -> SpeedTree::SetLight). Godot's light
|
||||
// cull mask is the closest exact scene-level equivalent: static world
|
||||
// geometry is layer 1, Metin2Model geometry is layer 2.
|
||||
out.character_light = Object::cast_to<DirectionalLight3D>(
|
||||
parent->get_node_or_null(NodePath("CharacterLight")));
|
||||
if (!out.character_light) {
|
||||
out.character_light = memnew(DirectionalLight3D);
|
||||
out.character_light->set_name("CharacterLight");
|
||||
parent->add_child(out.character_light);
|
||||
}
|
||||
out.character_light->set_cull_mask(1u << 1);
|
||||
out.character_light->set_visible(env.dir_light.ch_enable);
|
||||
out.character_light->set_transform(Transform3D(b, Vector3(0, 0, 0)));
|
||||
out.character_light->set_color(rgba(env.dir_light.ch_diffuse));
|
||||
out.character_light->set_param(Light3D::PARAM_ENERGY,
|
||||
std::clamp(0.9f + luma(env.dir_light.ch_diffuse) * 0.4f, 0.7f, 1.6f));
|
||||
out.character_light->set_shadow(false);
|
||||
out.character_light->set_param(Light3D::PARAM_SPECULAR, 0.4f);
|
||||
|
||||
// --- WorldEnvironment ---
|
||||
out.world_env =
|
||||
Object::cast_to<WorldEnvironment>(parent->get_node_or_null(NodePath("WorldEnv")));
|
||||
@@ -68,7 +247,12 @@ EnvNodes apply_environment(const fmt::Environment &env, Node *parent) {
|
||||
if (e.is_null())
|
||||
e.instantiate();
|
||||
|
||||
// 天空:SkyBox Gradient -> ProceduralSky 的三段色(zenith / horizon / ground)
|
||||
// 天空:SkyBox Gradient -> ProceduralSky 的三段色(zenith / horizon / ground)。
|
||||
// 参考端的 texture mode 是独立六面 quad;Godot Sky shader 用 EYEDIR 采样
|
||||
// 同一套六面图,坐标和 UV 依照 EterLib/SkyBox.cpp 的面顶点顺序转换。
|
||||
int sky_face_count = 0;
|
||||
bool sky_texture_applied = false;
|
||||
bool sky_cloud_applied = false;
|
||||
{
|
||||
Ref<ProceduralSkyMaterial> psm;
|
||||
psm.instantiate();
|
||||
@@ -83,7 +267,79 @@ EnvNodes apply_environment(const fmt::Environment &env, Node *parent) {
|
||||
psm->set_sun_angle_max(6.0f);
|
||||
Ref<Sky> sky;
|
||||
sky.instantiate();
|
||||
sky->set_material(psm);
|
||||
std::array<Ref<ImageTexture>, 6> faces;
|
||||
std::array<bool, 6> face_loaded{};
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
if (env.sky.face_textures[i].empty())
|
||||
continue;
|
||||
faces[i] = load_texture(env.sky.face_textures[i], resolver);
|
||||
face_loaded[i] = faces[i].is_valid();
|
||||
if (face_loaded[i])
|
||||
++sky_face_count;
|
||||
}
|
||||
Ref<ImageTexture> cloud_texture;
|
||||
if (!env.sky.cloud_texture.empty())
|
||||
cloud_texture = load_texture(env.sky.cloud_texture, resolver);
|
||||
const bool use_texture_sky = env.sky.texture_render_mode && sky_face_count > 0;
|
||||
sky_texture_applied = use_texture_sky;
|
||||
if (use_texture_sky) {
|
||||
Ref<Shader> shader;
|
||||
shader.instantiate();
|
||||
shader->set_code(String(SRC_SKYBOX));
|
||||
Ref<ShaderMaterial> material;
|
||||
material.instantiate();
|
||||
material->set_shader(shader);
|
||||
Color fallback = g.empty() ? Color(0.35f, 0.45f, 0.7f, 1.0f) : rgba(g.front());
|
||||
Color horizon = g.size() < 2 ? fallback : rgba(g[g.size() / 2]);
|
||||
Color ground = g.empty() ? Color(0.2f, 0.2f, 0.25f, 1.0f) : rgba(g.back());
|
||||
material->set_shader_parameter("gradient_top", fallback);
|
||||
material->set_shader_parameter("gradient_horizon", horizon);
|
||||
material->set_shader_parameter("gradient_bottom", ground);
|
||||
static constexpr const char *kFaceParams[6] = {
|
||||
"front_tex", "back_tex", "left_tex", "right_tex", "top_tex", "bottom_tex"};
|
||||
static constexpr const char *kFaceFlags[6] = {
|
||||
"has_front", "has_back", "has_left", "has_right", "has_top", "has_bottom"};
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
if (!faces[i].is_valid())
|
||||
faces[i] = solid_texture(i == 5 ? ground : horizon);
|
||||
material->set_shader_parameter(kFaceParams[i], faces[i]);
|
||||
material->set_shader_parameter(kFaceFlags[i], face_loaded[i]);
|
||||
}
|
||||
if (cloud_texture.is_valid()) {
|
||||
material->set_shader_parameter("cloud_tex", cloud_texture);
|
||||
material->set_shader_parameter("has_cloud", true);
|
||||
material->set_shader_parameter("cloud_scale", Vector2(
|
||||
std::max(env.sky.cloud_scale[0] * (float)fmt::m2coord::CM_TO_M, 0.01f),
|
||||
std::max(env.sky.cloud_scale[1] * (float)fmt::m2coord::CM_TO_M, 0.01f)));
|
||||
material->set_shader_parameter("cloud_height",
|
||||
std::max(env.sky.cloud_height * (float)fmt::m2coord::CM_TO_M, 0.01f));
|
||||
material->set_shader_parameter("cloud_texture_scale", Vector2(
|
||||
env.sky.cloud_texture_scale[0], env.sky.cloud_texture_scale[1]));
|
||||
material->set_shader_parameter("cloud_speed", Vector2(
|
||||
env.sky.cloud_speed[0], env.sky.cloud_speed[1]));
|
||||
Color tint = Color(0, 0, 0, 0);
|
||||
if (!env.sky.cloud_color.empty())
|
||||
tint = rgba(env.sky.cloud_color.front());
|
||||
material->set_shader_parameter("cloud_tint", tint);
|
||||
sky_cloud_applied = true;
|
||||
}
|
||||
sky->set_material(material);
|
||||
} else {
|
||||
sky->set_material(psm);
|
||||
}
|
||||
// Reference SkyBox scrolls clouds in its own renderer. Godot's procedural
|
||||
// sky has no equivalent UV-speed control, but sky_cover preserves the real
|
||||
// cloud asset and alpha/color instead of silently dropping CloudTextureFileName.
|
||||
if (!env.sky.cloud_texture.empty() && !use_texture_sky) {
|
||||
if (cloud_texture.is_valid()) {
|
||||
psm->set_sky_cover(cloud_texture);
|
||||
Color tint = Color(1, 1, 1, 1);
|
||||
if (!env.sky.cloud_color.empty())
|
||||
tint = rgba(env.sky.cloud_color.front());
|
||||
psm->set_sky_cover_modulate(tint);
|
||||
sky_cloud_applied = true;
|
||||
}
|
||||
}
|
||||
e->set_sky(sky);
|
||||
e->set_background(godot::Environment::BG_SKY);
|
||||
}
|
||||
@@ -145,6 +401,37 @@ EnvNodes apply_environment(const fmt::Environment &env, Node *parent) {
|
||||
e->set_ssao_radius(1.2f);
|
||||
e->set_ssao_intensity(0.9f);
|
||||
|
||||
// Keep parsed reference values visible to the runtime/debug probe even where
|
||||
// Godot has no 1:1 mapping (six cube faces, D3D blend factors, cloud motion,
|
||||
// and lens flare). This also prevents a future builder from silently losing
|
||||
// the fields while the renderer equivalent is being implemented.
|
||||
out.world_env->set_meta("msenv_sky_texture_mode", env.sky.texture_render_mode);
|
||||
out.world_env->set_meta("msenv_sky_face_count", sky_face_count);
|
||||
out.world_env->set_meta("msenv_sky_texture_mode_applied", sky_texture_applied);
|
||||
out.world_env->set_meta("msenv_sky_cloud_applied", sky_cloud_applied);
|
||||
out.world_env->set_meta("msenv_sky_scale",
|
||||
Vector3(env.sky.scale[0], env.sky.scale[1], env.sky.scale[2]));
|
||||
out.world_env->set_meta("msenv_cloud_scale",
|
||||
Vector2(env.sky.cloud_scale[0], env.sky.cloud_scale[1]));
|
||||
out.world_env->set_meta("msenv_cloud_height", env.sky.cloud_height);
|
||||
out.world_env->set_meta("msenv_cloud_texture_scale",
|
||||
Vector2(env.sky.cloud_texture_scale[0], env.sky.cloud_texture_scale[1]));
|
||||
out.world_env->set_meta("msenv_cloud_speed",
|
||||
Vector2(env.sky.cloud_speed[0], env.sky.cloud_speed[1]));
|
||||
out.world_env->set_meta("msenv_filter_enabled", env.filter.enable);
|
||||
out.world_env->set_meta("msenv_filter_color", rgba(env.filter.color));
|
||||
out.world_env->set_meta("msenv_filter_alpha_src", env.filter.alpha_src);
|
||||
out.world_env->set_meta("msenv_filter_alpha_dest", env.filter.alpha_dest);
|
||||
out.world_env->set_meta("msenv_lens_flare_enabled", env.lens_flare.enable);
|
||||
out.world_env->set_meta("msenv_lens_flare_texture", env.lens_flare.main_flare_texture.c_str());
|
||||
out.world_env->set_meta("msenv_background_light_enabled", env.dir_light.bg_enable);
|
||||
out.world_env->set_meta("msenv_character_light_enabled", env.dir_light.ch_enable);
|
||||
out.world_env->set_meta("msenv_background_light_ambient", rgba(env.dir_light.bg_ambient));
|
||||
out.world_env->set_meta("msenv_character_light_ambient", rgba(env.dir_light.ch_ambient));
|
||||
// MapUtil.cpp defaults; Environment_Load has no wind token to override these.
|
||||
out.world_env->set_meta("msenv_wind_strength", 0.2f);
|
||||
out.world_env->set_meta("msenv_wind_random", 0.0f);
|
||||
|
||||
out.world_env->set_environment(e);
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user