Browse Source

Merge pull request #60982 from fire-forge/gradient-mode-3.x

[3.x] Add interpolation modes to Gradient
Rémi Verschelde 3 năm trước cách đây
mục cha
commit
4fabc2b6a4

+ 12 - 0
doc/classes/Gradient.xml

@@ -72,10 +72,22 @@
 		<member name="colors" type="PoolColorArray" setter="set_colors" getter="get_colors" default="PoolColorArray( 0, 0, 0, 1, 1, 1, 1, 1 )">
 			Gradient's colors returned as a [PoolColorArray].
 		</member>
+		<member name="interpolation_mode" type="int" setter="set_interpolation_mode" getter="get_interpolation_mode" enum="Gradient.InterpolationMode" default="0">
+			Defines how the colors between points of the gradient are interpolated. See [enum InterpolationMode] for available modes.
+		</member>
 		<member name="offsets" type="PoolRealArray" setter="set_offsets" getter="get_offsets" default="PoolRealArray( 0, 1 )">
 			Gradient's offsets returned as a [PoolRealArray].
 		</member>
 	</members>
 	<constants>
+		<constant name="GRADIENT_INTERPOLATE_LINEAR" value="0" enum="InterpolationMode">
+			Linear interpolation.
+		</constant>
+		<constant name="GRADIENT_INTERPOLATE_CONSTANT" value="1" enum="InterpolationMode">
+			Constant interpolation, color changes abruptly at each point and stays uniform between. This might cause visible aliasing when used for a gradient texture in some cases.
+		</constant>
+		<constant name="GRADIENT_INTERPOLATE_CUBIC" value="2" enum="InterpolationMode">
+			Cubic interpolation.
+		</constant>
 	</constants>
 </class>

+ 5 - 0
editor/plugins/gradient_editor_plugin.cpp

@@ -45,6 +45,8 @@ void GradientEditor::_gradient_changed() {
 	editing = true;
 	Vector<Gradient::Point> points = gradient->get_points();
 	set_points(points);
+	set_interpolation_mode(gradient->get_interpolation_mode());
+	update();
 	editing = false;
 }
 
@@ -54,8 +56,10 @@ void GradientEditor::_ramp_changed() {
 	undo_redo->create_action(TTR("Gradient Edited"), UndoRedo::MERGE_ENDS);
 	undo_redo->add_do_method(gradient.ptr(), "set_offsets", get_offsets());
 	undo_redo->add_do_method(gradient.ptr(), "set_colors", get_colors());
+	undo_redo->add_do_method(gradient.ptr(), "set_interpolation_mode", get_interpolation_mode());
 	undo_redo->add_undo_method(gradient.ptr(), "set_offsets", gradient->get_offsets());
 	undo_redo->add_undo_method(gradient.ptr(), "set_colors", gradient->get_colors());
+	undo_redo->add_undo_method(gradient.ptr(), "set_interpolation_mode", gradient->get_interpolation_mode());
 	undo_redo->commit_action();
 	editing = false;
 }
@@ -70,6 +74,7 @@ void GradientEditor::set_gradient(const Ref<Gradient> &p_gradient) {
 	connect("ramp_changed", this, "_ramp_changed");
 	gradient->connect("changed", this, "_gradient_changed");
 	set_points(gradient->get_points());
+	set_interpolation_mode(gradient->get_interpolation_mode());
 }
 
 GradientEditor::GradientEditor() {

+ 17 - 40
scene/gui/gradient_edit.cpp

@@ -52,6 +52,11 @@ GradientEdit::GradientEdit() {
 
 	add_child(popup);
 
+	gradient_cache.instance();
+	preview_texture.instance();
+
+	preview_texture->set_width(1024);
+
 	checker = Ref<ImageTexture>(memnew(ImageTexture));
 	Ref<Image> img = memnew(Image(checker_bg_png));
 	checker->create_from_image(img, ImageTexture::FLAG_REPEAT);
@@ -315,46 +320,10 @@ void GradientEdit::_notification(int p_what) {
 		_draw_checker(0, 0, total_w, h);
 
 		//Draw color ramp
-		Gradient::Point prev;
-		prev.offset = 0;
-		if (points.size() == 0) {
-			prev.color = Color(0, 0, 0); //Draw black rectangle if we have no points
-		} else {
-			prev.color = points[0].color; //Extend color of first point to the beginning.
-		}
-
-		for (int i = -1; i < points.size(); i++) {
-			Gradient::Point next;
-			//If there is no next point
-			if (i + 1 == points.size()) {
-				if (points.size() == 0) {
-					next.color = Color(0, 0, 0); //Draw black rectangle if we have no points
-				} else {
-					next.color = points[i].color; //Extend color of last point to the end.
-				}
-				next.offset = 1;
-			} else {
-				next = points[i + 1];
-			}
-
-			if (prev.offset == next.offset) {
-				prev = next;
-				continue;
-			}
-
-			Vector<Vector2> points;
-			Vector<Color> colors;
-			points.push_back(Vector2(prev.offset * total_w, h));
-			points.push_back(Vector2(prev.offset * total_w, 0));
-			points.push_back(Vector2(next.offset * total_w, 0));
-			points.push_back(Vector2(next.offset * total_w, h));
-			colors.push_back(prev.color);
-			colors.push_back(prev.color);
-			colors.push_back(next.color);
-			colors.push_back(next.color);
-			draw_primitive(points, colors, Vector<Point2>());
-			prev = next;
-		}
+		gradient_cache->set_points(points);
+		gradient_cache->set_interpolation_mode(interpolation_mode);
+		preview_texture->set_gradient(gradient_cache);
+		draw_texture_rect(preview_texture, Rect2(0, 0, total_w, h));
 
 		//Draw point markers
 		for (int i = 0; i < points.size(); i++) {
@@ -482,6 +451,14 @@ Vector<Gradient::Point> &GradientEdit::get_points() {
 	return points;
 }
 
+void GradientEdit::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) {
+	interpolation_mode = p_interp_mode;
+}
+
+Gradient::InterpolationMode GradientEdit::get_interpolation_mode() {
+	return interpolation_mode;
+}
+
 void GradientEdit::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("_gui_input"), &GradientEdit::_gui_input);
 	ClassDB::bind_method(D_METHOD("_color_changed"), &GradientEdit::_color_changed);

+ 7 - 0
scene/gui/gradient_edit.h

@@ -47,6 +47,10 @@ class GradientEdit : public Control {
 	bool grabbing;
 	int grabbed;
 	Vector<Gradient::Point> points;
+	Gradient::InterpolationMode interpolation_mode = Gradient::GRADIENT_INTERPOLATE_LINEAR;
+
+	Ref<Gradient> gradient_cache;
+	Ref<GradientTexture> preview_texture;
 
 	void _draw_checker(int x, int y, int w, int h);
 	void _color_changed(const Color &p_color);
@@ -64,6 +68,9 @@ public:
 	Vector<Color> get_colors() const;
 	void set_points(Vector<Gradient::Point> &p_points);
 	Vector<Gradient::Point> &get_points();
+	void set_interpolation_mode(Gradient::InterpolationMode p_interp_mode);
+	Gradient::InterpolationMode get_interpolation_mode();
+
 	virtual Size2 get_minimum_size() const;
 
 	GradientEdit();

+ 19 - 0
scene/resources/gradient.cpp

@@ -71,8 +71,18 @@ void Gradient::_bind_methods() {
 	ClassDB::bind_method(D_METHOD(COLOR_RAMP_SET_COLORS, "colors"), &Gradient::set_colors);
 	ClassDB::bind_method(D_METHOD(COLOR_RAMP_GET_COLORS), &Gradient::get_colors);
 
+	ClassDB::bind_method(D_METHOD("set_interpolation_mode", "interpolation_mode"), &Gradient::set_interpolation_mode);
+	ClassDB::bind_method(D_METHOD("get_interpolation_mode"), &Gradient::get_interpolation_mode);
+
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "interpolation_mode", PROPERTY_HINT_ENUM, "Linear,Constant,Cubic"), "set_interpolation_mode", "get_interpolation_mode");
+
+	ADD_GROUP("Raw Data", "");
 	ADD_PROPERTY(PropertyInfo(Variant::POOL_REAL_ARRAY, "offsets"), COLOR_RAMP_SET_OFFSETS, COLOR_RAMP_GET_OFFSETS);
 	ADD_PROPERTY(PropertyInfo(Variant::POOL_COLOR_ARRAY, "colors"), COLOR_RAMP_SET_COLORS, COLOR_RAMP_GET_COLORS);
+
+	BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_LINEAR);
+	BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_CONSTANT);
+	BIND_ENUM_CONSTANT(GRADIENT_INTERPOLATE_CUBIC);
 }
 
 Vector<float> Gradient::get_offsets() const {
@@ -93,6 +103,15 @@ Vector<Color> Gradient::get_colors() const {
 	return colors;
 }
 
+void Gradient::set_interpolation_mode(Gradient::InterpolationMode p_interp_mode) {
+	interpolation_mode = p_interp_mode;
+	emit_signal(CoreStringNames::get_singleton()->changed);
+}
+
+Gradient::InterpolationMode Gradient::get_interpolation_mode() {
+	return interpolation_mode;
+}
+
 void Gradient::set_offsets(const Vector<float> &p_offsets) {
 	points.resize(p_offsets.size());
 	for (int i = 0; i < points.size(); i++) {

+ 50 - 1
scene/resources/gradient.h

@@ -38,6 +38,12 @@ class Gradient : public Resource {
 	OBJ_SAVE_TYPE(Gradient);
 
 public:
+	enum InterpolationMode {
+		GRADIENT_INTERPOLATE_LINEAR,
+		GRADIENT_INTERPOLATE_CONSTANT,
+		GRADIENT_INTERPOLATE_CUBIC,
+	};
+
 	struct Point {
 		float offset;
 		Color color;
@@ -49,6 +55,8 @@ public:
 private:
 	Vector<Point> points;
 	bool is_sorted;
+	InterpolationMode interpolation_mode = GRADIENT_INTERPOLATE_LINEAR;
+
 	_FORCE_INLINE_ void _update_sorting() {
 		if (!is_sorted) {
 			points.sort();
@@ -81,6 +89,13 @@ public:
 	void set_colors(const Vector<Color> &p_colors);
 	Vector<Color> get_colors() const;
 
+	void set_interpolation_mode(InterpolationMode p_interp_mode);
+	InterpolationMode get_interpolation_mode();
+
+	_FORCE_INLINE_ float cubic_interpolate(float p0, float p1, float p2, float p3, float x) {
+		return p1 + 0.5 * x * (p2 - p0 + x * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + x * (3.0 * (p1 - p2) + p3 - p0)));
+	}
+
 	_FORCE_INLINE_ Color get_color_at_offset(float p_offset) {
 		if (points.empty()) {
 			return Color(0, 0, 0, 1);
@@ -124,10 +139,44 @@ public:
 		}
 		const Point &pointFirst = points[first];
 		const Point &pointSecond = points[second];
-		return pointFirst.color.linear_interpolate(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset));
+
+		switch (interpolation_mode) {
+			case GRADIENT_INTERPOLATE_LINEAR: {
+				return pointFirst.color.linear_interpolate(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset));
+			} break;
+			case GRADIENT_INTERPOLATE_CONSTANT: {
+				return pointFirst.color;
+			} break;
+			case GRADIENT_INTERPOLATE_CUBIC: {
+				int p0 = first - 1;
+				int p3 = second + 1;
+				if (p3 >= points.size()) {
+					p3 = second;
+				}
+				if (p0 < 0) {
+					p0 = first;
+				}
+				const Point &pointP0 = points[p0];
+				const Point &pointP3 = points[p3];
+
+				float x = (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset);
+				float r = cubic_interpolate(pointP0.color.r, pointFirst.color.r, pointSecond.color.r, pointP3.color.r, x);
+				float g = cubic_interpolate(pointP0.color.g, pointFirst.color.g, pointSecond.color.g, pointP3.color.g, x);
+				float b = cubic_interpolate(pointP0.color.b, pointFirst.color.b, pointSecond.color.b, pointP3.color.b, x);
+				float a = cubic_interpolate(pointP0.color.a, pointFirst.color.a, pointSecond.color.a, pointP3.color.a, x);
+
+				return Color(r, g, b, a);
+			} break;
+			default: {
+				// Fallback to linear interpolation.
+				return pointFirst.color.linear_interpolate(pointSecond.color, (p_offset - pointFirst.offset) / (pointSecond.offset - pointFirst.offset));
+			}
+		}
 	}
 
 	int get_points_count() const;
 };
 
+VARIANT_ENUM_CAST(Gradient::InterpolationMode);
+
 #endif // GRADIENT_H