diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 1b329c7a3..8e5fe61d9 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -47,6 +47,7 @@ core.features = { particle_blend_clip = true, remove_item_match_meta = true, httpfetch_additional_methods = true, + color_transform_matrix = true, } function core.has_feature(arg) diff --git a/builtin/game/misc_s.lua b/builtin/game/misc_s.lua index 9433f74bb..983bd7096 100644 --- a/builtin/game/misc_s.lua +++ b/builtin/game/misc_s.lua @@ -130,6 +130,7 @@ core.protocol_versions = { ["5.10.0"] = 46, ["5.11.0"] = 47, ["5.12.0"] = 48, + ["5.13.0"] = 49, } setmetatable(core.protocol_versions, {__newindex = function() diff --git a/client/shaders/second_stage/opengl_fragment.glsl b/client/shaders/second_stage/opengl_fragment.glsl index 6053884bc..838a380f5 100644 --- a/client/shaders/second_stage/opengl_fragment.glsl +++ b/client/shaders/second_stage/opengl_fragment.glsl @@ -31,6 +31,8 @@ centroid varying vec2 varTexCoord; varying float exposure; // linear exposure factor, see vertex shader #endif +uniform mat3 colorTransformMatrix; + #ifdef ENABLE_BLOOM vec4 applyBloom(vec4 color, vec2 uv) @@ -103,6 +105,12 @@ vec3 screen_space_dither(highp vec2 frag_coord) { } #endif +vec4 applyColorVision(vec4 color) +{ + vec3 transformedColor = colorTransformMatrix * color.rgb; + return vec4(transformedColor, color.a); +} + void main(void) { vec2 uv = varTexCoord.st; @@ -133,6 +141,7 @@ void main(void) color = applyBloom(color, uv); #endif + color = applyColorVision(color); color.rgb = clamp(color.rgb, vec3(0.), vec3(1.)); diff --git a/doc/lua_api.md b/doc/lua_api.md index 438769085..ee0c88ef9 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -5810,6 +5810,8 @@ Utilities remove_item_match_meta = true, -- The HTTP API supports the HEAD and PATCH methods (5.12.0) httpfetch_additional_methods = true, + -- Scene color can be transformed by transform matrix (5.13.0) + color_transform_matrix = true, } ``` @@ -9061,6 +9063,23 @@ child will follow movement and rotation of that bone. * Currently, bloom `intensity` and `strength_factor` affect volumetric lighting `strength` and vice versa. This behavior is to be changed in the future, do not rely on it. + * `color_transform_matrix`: is a matrix with default value (identity matrix): + ```lua + { {1.0, 0.0, 0.0}, -- r + {0.0, 1.0, 0.0}, -- g + {0.0, 0.0, 1.0}} -- b + ``` + + * Work as `transformed_color_RGB = color_transform_matrix * color_RGB` + * Can be used for creation color blind effect, base for night vision effect etc. + * Request client with protocol version 49 or higger. + + ```lua + -- example of night vision like transform + { {0.0, 0.0, 0.0}, + {1.0, 9.0, 1.0}, + {0.0, 0.0, 0.0}} + ``` * `get_lighting()`: returns the current state of lighting for the player. * Result is a table with the same fields as `light_definition` in `set_lighting`. diff --git a/src/client/game.cpp b/src/client/game.cpp index ebfc3f1c8..77a3cb480 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -231,6 +231,9 @@ class GameGlobalShaderUniformSetter : public IShaderUniformSetter CachedPixelShaderSetting m_volumetric_light_strength_pixel{"volumetricLightStrength"}; + CachedPixelShaderSetting + m_color_transform_matrix{"colorTransformMatrix"}; + static constexpr std::array SETTING_CALLBACKS = { "exposure_compensation", }; @@ -321,6 +324,8 @@ public: m_bloom_radius_pixel.set(&radius, services); } + m_color_transform_matrix.set(lighting.vision_effects.color_transform_matrix.data(), services); + float saturation = lighting.saturation; m_saturation_pixel.set(&saturation, services); diff --git a/src/lighting.h b/src/lighting.h index 4ba1b37ef..26fc1d8b7 100644 --- a/src/lighting.h +++ b/src/lighting.h @@ -40,6 +40,18 @@ struct AutoExposure {} }; +struct VisionEffects +{ + std::array color_transform_matrix; + + constexpr VisionEffects() + : color_transform_matrix{ + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f} + {} +}; + /** Describes ambient light settings for a player */ struct Lighting @@ -52,4 +64,5 @@ struct Lighting float bloom_intensity {0.05f}; float bloom_strength_factor {1.0f}; float bloom_radius {1.0f}; + VisionEffects vision_effects; }; diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 6cd7150c6..4364ca81a 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1795,4 +1795,9 @@ void Client::handleCommand_SetLighting(NetworkPacket *pkt) >> lighting.bloom_strength_factor >> lighting.bloom_radius; } + if (pkt->getRemainingBytes() >= 36) { + for (int i = 0; i < 9; ++i) { + *pkt >> lighting.vision_effects.color_transform_matrix[i]; + } + } } diff --git a/src/network/networkprotocol.cpp b/src/network/networkprotocol.cpp index a88b5b091..22ce3c998 100644 --- a/src/network/networkprotocol.cpp +++ b/src/network/networkprotocol.cpp @@ -65,10 +65,13 @@ PROTOCOL VERSION 48 Add compression to some existing packets [scheduled bump for 5.12.0] + PROTOCOL VERSION 49 + Add "vision_effects" to TOCLIENT_SET_LIGHTING packet + [scheduled bump for 5.13.0] */ // Note: Also update core.protocol_versions in builtin when bumping -const u16 LATEST_PROTOCOL_VERSION = 48; +const u16 LATEST_PROTOCOL_VERSION = 49; // See also formspec [Version History] in doc/lua_api.md const u16 FORMSPEC_API_VERSION = 9; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index eed2215ac..6cf53cad5 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -2696,7 +2696,32 @@ int ObjectRef::l_set_lighting(lua_State *L) lighting.bloom_radius = getfloatfield_default(L, -1, "radius", lighting.bloom_radius); } lua_pop(L, 1); // bloom -} + + lua_getfield(L, 2, "color_transform_matrix"); + + // if none or nil, keep color transform matrix unchanged + if (!lua_isnoneornil(L, -1)) { + if (!lua_istable(L, -1)) + throw LuaError("vision_effects.color_transform_matrix is not a table"); + + for (int row = 1; row <= 3; ++row) { + lua_rawgeti(L, -1, row); + if (!lua_istable(L, -1)) + throw LuaError("color_transform_matrix should be in format {{a, b, c},{d, e, f},{g, a, h}}"); + + for (int col = 1; col <= 3; ++col) { + lua_rawgeti(L, -1, col); + if (!lua_isnumber(L, -1)) + throw LuaError("color_transform_matrix should be in format {{a, b, c},{d, e, f},{g, a, h}}"); + + lighting.vision_effects.color_transform_matrix[(row - 1) * 3 + (col - 1)] = (float)lua_tonumber(L, -1); + lua_pop(L, 1); // Pop the value at [row][col] + } + lua_pop(L, 1); // Pop the row table + } + } + lua_pop(L, 1); // color_transform_matrix + } getServer(L)->setLighting(player, lighting); return 0; @@ -2748,6 +2773,17 @@ int ObjectRef::l_get_lighting(lua_State *L) lua_pushnumber(L, lighting.bloom_radius); lua_setfield(L, -2, "radius"); lua_setfield(L, -2, "bloom"); + lua_newtable(L); // "color_transform_matrix" + // Create the nested table structure {{a, b, c}, {d, e, f}, {g, h, i}} + for (int row = 0; row < 3; row++) { + lua_newtable(L); // Create inner row table + for (int col = 0; col < 3; col++) { + lua_pushnumber(L, lighting.vision_effects.color_transform_matrix[row * 3 + col]); // Push the value + lua_rawseti(L, -2, col + 1); // Set value in inner table with 1-based indexing + } + lua_rawseti(L, -2, row + 1); // Set inner table in the outer matrix table + } + lua_setfield(L, -2, "color_transform_matrix"); return 1; } diff --git a/src/server.cpp b/src/server.cpp index 16434f447..febf0488c 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1952,6 +1952,10 @@ void Server::SendSetLighting(session_t peer_id, const Lighting &lighting) pkt << lighting.bloom_intensity << lighting.bloom_strength_factor << lighting.bloom_radius; + for (int i = 0; i < 9; ++i) { + pkt << lighting.vision_effects.color_transform_matrix[i]; + } + Send(&pkt); }