-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
…le texture This can be used for advanced procedural textures or VFX. The texture is generated once on the CPU, so while there is an added generation time, there is no additional cost once the texture is done generating (unlike shader-based blending). Also, unlike shader-based blending, the resulting CompositeTexture2D can be used anywhere, including in locations that can't use custom shaders. TODO: - Add editor icon. - Fix the resulting texture not updating when a GradientTexture is modified (since its update is deferred). - Fix nested CompositeTexture2D behavior.
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<class name="CompositeTexture2D" inherits="Texture2D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> | ||
<brief_description> | ||
Blends two textures together using a predetermined blend mode. | ||
</brief_description> | ||
<description> | ||
A [CompositeTexture2D] blends two textures together using a predetermined blend mode. This operation occurs on the CPU so it's slower than blending in a shader, but unlike a shader, it can be used anywhere a [Texture2D] (and subsequently an [Image]) can be displayed. This can be paired with [NoiseTexture2D] and [GradientTexture2D] to create more advanced procedural texture or VFX effects. | ||
</description> | ||
<tutorials> | ||
</tutorials> | ||
<members> | ||
<member name="blend_factor" type="float" setter="set_blend_factor" getter="get_blend_factor" default="0.5"> | ||
The blend factor to use when overlaying [member texture_overlay] on top of [member texture_base]. A blend factor of [code]0.0[/code] is fully transparent while [code]1.0[/code] is fully opaque. Only effective if [member texture_base] and [member texture_overlay] are both specified. | ||
</member> | ||
<member name="blend_mode" type="int" setter="set_blend_mode" getter="get_blend_mode" enum="CompositeTexture2D.BlendMode" default="0"> | ||
The blend factor to use when overlaying [member texture_overlay] on top of [member texture_base]. Only effective if [member texture_base] and [member texture_overlay] are both specified. | ||
</member> | ||
<member name="generate_mipmaps" type="int" setter="set_generate_mipmaps" getter="get_generate_mipmaps" enum="CompositeTexture2D.GenerateMipmapsMode" default="0"> | ||
Whether to generate mipmaps for the resulting texture. Mipmaps prevent the texture from looking grainy when viewed at a distance and can improve performance by reducing texture sampling requirements, but they increase memory usage by roughly 33%. Generating mipmaps also slows down texture generation speed, so consider using [constant GENERATE_MIPMAPS_MODE_NEVER] instead if the viewing distance is fixed (e.g. in 2D). | ||
</member> | ||
<member name="resource_local_to_scene" type="bool" setter="set_local_to_scene" getter="is_local_to_scene" overrides="Resource" default="false" /> | ||
<member name="texture_base" type="Texture2D" setter="set_texture_base" getter="get_texture_base"> | ||
The texture to use as a base for blending. If this is the only texture specified, then it's rendered as-is with no blending. | ||
</member> | ||
<member name="texture_overlay" type="Texture2D" setter="set_texture_overlay" getter="get_texture_overlay"> | ||
The texture to apply as an overlay to [member texture_base]. If this is the only texture specified, then it's rendered as-is with no blending. | ||
</member> | ||
</members> | ||
<constants> | ||
<constant name="BLEND_MODE_MIX" value="0" enum="BlendMode"> | ||
Standard mix blending (straight alpha blending). | ||
</constant> | ||
<constant name="BLEND_MODE_ADD" value="1" enum="BlendMode"> | ||
Additive blending ([member texture_base] and [member texture_overlay]'s colors are added together). | ||
</constant> | ||
<constant name="BLEND_MODE_SUB" value="2" enum="BlendMode"> | ||
Subtractive blending ([member texture_overlay]'s colors are subtracted from [member texture_base]). | ||
</constant> | ||
<constant name="BLEND_MODE_MUL" value="3" enum="BlendMode"> | ||
Multiplicative blending ([member texture_overlay]'s colors are multiplied with [member texture_base]). | ||
</constant> | ||
<constant name="BLEND_MODE_PREMUL_ALPHA" value="4" enum="BlendMode"> | ||
Premultiplied alpha blending. The blend mode is equivalent to [constant BLEND_MODE_ADD] for [member texture_overlay]'s fully transparent pixels and equivalent to [constant BLEND_MODE_MIX] for [member texture_overlay]'s fully opaque pixels. Values in between will result in a mix of both. | ||
</constant> | ||
<constant name="GENERATE_MIPMAPS_MODE_AUTOMATIC" value="0" enum="GenerateMipmapsMode"> | ||
Generate mipmaps if either [member texture_base] or [member texture_overlay] have mipmaps available. | ||
</constant> | ||
<constant name="GENERATE_MIPMAPS_MODE_NEVER" value="1" enum="GenerateMipmapsMode"> | ||
Never generate mipmaps. | ||
</constant> | ||
<constant name="GENERATE_MIPMAPS_MODE_ALWAYS" value="2" enum="GenerateMipmapsMode"> | ||
Always generate mipmaps | ||
</constant> | ||
</constants> | ||
</class> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,327 @@ | ||
/**************************************************************************/ | ||
/* composite_texture.cpp */ | ||
/**************************************************************************/ | ||
/* This file is part of: */ | ||
/* GODOT ENGINE */ | ||
/* https://godotengine.org */ | ||
/**************************************************************************/ | ||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ | ||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ | ||
/* */ | ||
/* Permission is hereby granted, free of charge, to any person obtaining */ | ||
/* a copy of this software and associated documentation files (the */ | ||
/* "Software"), to deal in the Software without restriction, including */ | ||
/* without limitation the rights to use, copy, modify, merge, publish, */ | ||
/* distribute, sublicense, and/or sell copies of the Software, and to */ | ||
/* permit persons to whom the Software is furnished to do so, subject to */ | ||
/* the following conditions: */ | ||
/* */ | ||
/* The above copyright notice and this permission notice shall be */ | ||
/* included in all copies or substantial portions of the Software. */ | ||
/* */ | ||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ | ||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ | ||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ | ||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ | ||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ | ||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ | ||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ | ||
/**************************************************************************/ | ||
|
||
#include "composite_texture.h" | ||
|
||
CompositeTexture2D::CompositeTexture2D() { | ||
_queue_update(); | ||
} | ||
|
||
CompositeTexture2D::~CompositeTexture2D() { | ||
if (texture.is_valid()) { | ||
ERR_FAIL_NULL(RenderingServer::get_singleton()); | ||
RS::get_singleton()->free(texture); | ||
} | ||
} | ||
|
||
void CompositeTexture2D::set_texture_overlay(const Ref<Texture2D> &p_texture_overlay) { | ||
if (texture_overlay == p_texture_overlay) { | ||
return; | ||
} | ||
|
||
if (texture_overlay.is_valid()) { | ||
texture_overlay->disconnect_changed(callable_mp(this, &CompositeTexture2D::_queue_update)); | ||
} | ||
|
||
texture_overlay = p_texture_overlay; | ||
|
||
if (texture_overlay.is_valid()) { | ||
texture_overlay->connect_changed(callable_mp(this, &CompositeTexture2D::_queue_update)); | ||
} | ||
|
||
_update_size(); | ||
|
||
_queue_update(); | ||
emit_changed(); | ||
} | ||
|
||
Ref<Texture2D> CompositeTexture2D::get_texture_overlay() const { | ||
return texture_overlay; | ||
} | ||
|
||
void CompositeTexture2D::set_texture_base(const Ref<Texture2D> &p_texture_base) { | ||
if (texture_base == p_texture_base) { | ||
return; | ||
} | ||
|
||
if (texture_base.is_valid()) { | ||
texture_base->disconnect_changed(callable_mp(this, &CompositeTexture2D::_queue_update)); | ||
} | ||
|
||
texture_base = p_texture_base; | ||
|
||
if (texture_base.is_valid()) { | ||
texture_base->connect_changed(callable_mp(this, &CompositeTexture2D::_queue_update)); | ||
} | ||
|
||
_update_size(); | ||
|
||
_queue_update(); | ||
emit_changed(); | ||
} | ||
|
||
void CompositeTexture2D::_update_size() { | ||
if (texture_overlay.is_valid() && texture_base.is_valid()) { | ||
width = MAX(texture_overlay->get_size().width, texture_base->get_size().width); | ||
height = MAX(texture_overlay->get_size().height, texture_base->get_size().height); | ||
} else if (texture_overlay.is_valid()) { | ||
width = texture_overlay->get_size().width; | ||
height = texture_overlay->get_size().height; | ||
} else if (texture_base.is_valid()) { | ||
width = texture_base->get_size().width; | ||
height = texture_base->get_size().height; | ||
} else { | ||
width = 1; | ||
height = 1; | ||
} | ||
} | ||
|
||
Ref<Texture2D> CompositeTexture2D::get_texture_base() const { | ||
return texture_base; | ||
} | ||
|
||
void CompositeTexture2D::set_blend_mode(BlendMode p_blend_mode) { | ||
if (blend_mode == p_blend_mode) { | ||
return; | ||
} | ||
|
||
blend_mode = p_blend_mode; | ||
|
||
_queue_update(); | ||
emit_changed(); | ||
} | ||
|
||
CompositeTexture2D::BlendMode CompositeTexture2D::get_blend_mode() const { | ||
return blend_mode; | ||
} | ||
|
||
void CompositeTexture2D::set_blend_factor(float p_factor) { | ||
if (blend_factor == p_factor) { | ||
return; | ||
} | ||
|
||
blend_factor = CLAMP(p_factor, 0.0, 1.0); | ||
|
||
_queue_update(); | ||
emit_changed(); | ||
} | ||
|
||
float CompositeTexture2D::get_blend_factor() const { | ||
return blend_factor; | ||
} | ||
|
||
void CompositeTexture2D::set_generate_mipmaps(GenerateMipmapsMode p_mode) { | ||
if (generate_mipmaps == p_mode) { | ||
return; | ||
} | ||
|
||
generate_mipmaps = p_mode; | ||
|
||
_queue_update(); | ||
emit_changed(); | ||
} | ||
|
||
CompositeTexture2D::GenerateMipmapsMode CompositeTexture2D::get_generate_mipmaps() const { | ||
return generate_mipmaps; | ||
} | ||
|
||
void CompositeTexture2D::_queue_update() { | ||
if (update_pending) { | ||
return; | ||
} | ||
update_pending = true; | ||
callable_mp(this, &CompositeTexture2D::update_now).call_deferred(); | ||
} | ||
|
||
void CompositeTexture2D::_update() const { | ||
update_pending = false; | ||
|
||
if (texture_base.is_null() && texture_overlay.is_null()) { | ||
return; | ||
} | ||
|
||
// Guard against situations where a texture's is updated late, such as with GradientTexture. | ||
if ((texture_base.is_valid() && texture_base->get_image().is_null()) || (texture_overlay.is_valid() && texture_overlay->get_image().is_null())) { | ||
return; | ||
} | ||
|
||
Ref<Image> image; | ||
image.instantiate(); | ||
|
||
if (texture_base.is_valid() && texture_overlay.is_null()) { | ||
// No need to blend two images together. | ||
image = texture_base->get_image(); | ||
} else if (texture_overlay.is_valid() && texture_base.is_null()) { | ||
// No need to blend two images together. | ||
image = texture_overlay->get_image(); | ||
} else { | ||
const Ref<Image> image_overlay = texture_overlay->get_image(); | ||
const Ref<Image> image_base = texture_base->get_image(); | ||
if (image_overlay->is_compressed()) { | ||
image_overlay->decompress(); | ||
} | ||
if (image_base->is_compressed()) { | ||
image_base->decompress(); | ||
} | ||
|
||
image->copy_from(texture_base); | ||
|
||
if (true) { | ||
image->copy_from(image_base); | ||
if (generate_mipmaps == GENERATE_MIPMAPS_MODE_NEVER) { | ||
image->clear_mipmaps(); | ||
} | ||
// Resize images to match the largest image of the two. | ||
// FIXME: This doesn't work in nested CompositeTexture scenarios if there are textures of different sizes. | ||
if (image_overlay->get_size().width < width || image_overlay->get_size().height < height) { | ||
image_overlay->resize(width, height); | ||
} else if (image_base->get_size().width < width || image_base->get_size().height < height) { | ||
image_base->resize(width, height); | ||
} | ||
for (int y = 0; y < height; y++) { | ||
for (int x = 0; x < width; x++) { | ||
image->set_pixel(x, y, _blend_pixel(image->get_pixel(x, y), image_overlay->get_pixel(x, y))); | ||
} | ||
} | ||
|
||
if (generate_mipmaps == GENERATE_MIPMAPS_MODE_ALWAYS || (generate_mipmaps == GENERATE_MIPMAPS_MODE_AUTOMATIC && (image_base->has_mipmaps() || image_overlay->has_mipmaps()))) { | ||
image->generate_mipmaps(); | ||
} | ||
} else { | ||
// // Use an optimized codepath. | ||
// Vector<uint8_t> data; | ||
// data.resize(width * height * 4); | ||
// { | ||
// uint8_t *wd8 = data.ptrw(); | ||
// Gradient &g = **gradient; | ||
// for (int y = 0; y < height; y++) { | ||
// for (int x = 0; x < width; x++) { | ||
// float ofs = _get_blend_mode_offset_at(x, y); | ||
// const Color &c = g.get_color_at_offset(ofs); | ||
|
||
// wd8[(x + (y * width)) * 4 + 0] = uint8_t(CLAMP(c.r * 255.0, 0, 255)); | ||
// wd8[(x + (y * width)) * 4 + 1] = uint8_t(CLAMP(c.g * 255.0, 0, 255)); | ||
// wd8[(x + (y * width)) * 4 + 2] = uint8_t(CLAMP(c.b * 255.0, 0, 255)); | ||
// wd8[(x + (y * width)) * 4 + 3] = uint8_t(CLAMP(c.a * 255.0, 0, 255)); | ||
// } | ||
// } | ||
// } | ||
// image->set_data(width, height, false, Image::FORMAT_RGBA8, data); | ||
// } | ||
} | ||
} | ||
|
||
if (texture.is_valid()) { | ||
RID new_texture = RS::get_singleton()->texture_2d_create(image); | ||
RS::get_singleton()->texture_replace(texture, new_texture); | ||
} else { | ||
texture = RS::get_singleton()->texture_2d_create(image); | ||
} | ||
RS::get_singleton()->texture_set_path(texture, get_path()); | ||
} | ||
|
||
Color CompositeTexture2D::_blend_pixel(const Color &p_base, const Color &p_overlay) const { | ||
switch (blend_mode) { | ||
case BLEND_MODE_MIX: | ||
return p_base.lerp(p_overlay, blend_factor); | ||
case BLEND_MODE_ADD: | ||
return p_base + p_overlay * blend_factor; | ||
case BLEND_MODE_SUB: | ||
return p_base - p_overlay * blend_factor; | ||
case BLEND_MODE_MUL: | ||
return p_base.lerp(p_base * p_overlay, blend_factor); | ||
case BLEND_MODE_PREMUL_ALPHA: | ||
return Color(p_base + p_overlay * blend_factor).lerp(p_base.lerp(p_overlay, blend_factor), p_overlay.a); | ||
} | ||
} | ||
Check failure on line 263 in scene/resources/composite_texture.cpp GitHub Actions / 🐧 Linux / Editor w/ Mono (target=editor)
Check failure on line 263 in scene/resources/composite_texture.cpp GitHub Actions / 🐧 Linux / Editor with doubles and GCC sanitizers (target=editor, tests=yes, dev_build=yes, scu_build=yes, precision=double, use_asan=yes, use_ubsan=yes, linker=gold)
Check failure on line 263 in scene/resources/composite_texture.cpp GitHub Actions / 🐧 Linux / Template w/ Mono (target=template_release, tests=yes)
Check failure on line 263 in scene/resources/composite_texture.cpp GitHub Actions / 🐧 Linux / Minimal template (target=template_release, tests=yes, everything disabled)
Check failure on line 263 in scene/resources/composite_texture.cpp GitHub Actions / 🏁 Windows / Editor (target=editor, tests=yes)
Check warning on line 263 in scene/resources/composite_texture.cpp GitHub Actions / 🏁 Windows / Editor (target=editor, tests=yes)
Check failure on line 263 in scene/resources/composite_texture.cpp GitHub Actions / 🏁 Windows / Template (target=template_release, tests=yes)
|
||
|
||
int CompositeTexture2D::get_width() const { | ||
return width; | ||
} | ||
|
||
int CompositeTexture2D::get_height() const { | ||
return height; | ||
} | ||
|
||
RID CompositeTexture2D::get_rid() const { | ||
if (!texture.is_valid()) { | ||
texture = RS::get_singleton()->texture_2d_placeholder_create(); | ||
} | ||
return texture; | ||
} | ||
|
||
Ref<Image> CompositeTexture2D::get_image() const { | ||
update_now(); | ||
if (!texture.is_valid()) { | ||
return Ref<Image>(); | ||
} | ||
return RenderingServer::get_singleton()->texture_2d_get(texture); | ||
} | ||
|
||
void CompositeTexture2D::update_now() const { | ||
if (update_pending) { | ||
_update(); | ||
} | ||
} | ||
|
||
void CompositeTexture2D::_bind_methods() { | ||
ClassDB::bind_method(D_METHOD("set_texture_overlay", "texture"), &CompositeTexture2D::set_texture_overlay); | ||
ClassDB::bind_method(D_METHOD("get_texture_overlay"), &CompositeTexture2D::get_texture_overlay); | ||
|
||
ClassDB::bind_method(D_METHOD("set_texture_base", "texture"), &CompositeTexture2D::set_texture_base); | ||
ClassDB::bind_method(D_METHOD("get_texture_base"), &CompositeTexture2D::get_texture_base); | ||
|
||
ClassDB::bind_method(D_METHOD("set_blend_mode", "blend_mode"), &CompositeTexture2D::set_blend_mode); | ||
ClassDB::bind_method(D_METHOD("get_blend_mode"), &CompositeTexture2D::get_blend_mode); | ||
|
||
ClassDB::bind_method(D_METHOD("set_blend_factor", "blend_factor"), &CompositeTexture2D::set_blend_factor); | ||
ClassDB::bind_method(D_METHOD("get_blend_factor"), &CompositeTexture2D::get_blend_factor); | ||
|
||
ClassDB::bind_method(D_METHOD("set_generate_mipmaps", "mode"), &CompositeTexture2D::set_generate_mipmaps); | ||
ClassDB::bind_method(D_METHOD("get_generate_mipmaps"), &CompositeTexture2D::get_generate_mipmaps); | ||
|
||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_overlay", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_overlay", "get_texture_overlay"); | ||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_base", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture_base", "get_texture_base"); | ||
|
||
ADD_PROPERTY(PropertyInfo(Variant::INT, "blend_mode", PROPERTY_HINT_ENUM, "Mix,Add,Subtract,Multiply,Premultiplied Alpha"), "set_blend_mode", "get_blend_mode"); | ||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "blend_factor", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_blend_factor", "get_blend_factor"); | ||
|
||
ADD_PROPERTY(PropertyInfo(Variant::INT, "generate_mipmaps", PROPERTY_HINT_ENUM, "Automatic,Never,Always"), "set_generate_mipmaps", "get_generate_mipmaps"); | ||
|
||
BIND_ENUM_CONSTANT(BLEND_MODE_MIX); | ||
BIND_ENUM_CONSTANT(BLEND_MODE_ADD); | ||
BIND_ENUM_CONSTANT(BLEND_MODE_SUB); | ||
BIND_ENUM_CONSTANT(BLEND_MODE_MUL); | ||
BIND_ENUM_CONSTANT(BLEND_MODE_PREMUL_ALPHA); | ||
|
||
BIND_ENUM_CONSTANT(GENERATE_MIPMAPS_MODE_AUTOMATIC); | ||
BIND_ENUM_CONSTANT(GENERATE_MIPMAPS_MODE_NEVER); | ||
BIND_ENUM_CONSTANT(GENERATE_MIPMAPS_MODE_ALWAYS); | ||
} |