Skip to main content

kvas_engine/
material_nodes_v4.rs

1//! The fourth part of the node library: general "Math" / "Vector math" nodes with an operation
2//! selector, Blender-style texture generators with several outputs, an image texture with its
3//! own file, curves, mapping and colour tools. It also holds the table of extra outputs that any
4//! node may have and the conversions a linked input can apply. See [`crate::material_graph`].
5
6use crate::material_graph::NodeSpec;
7
8const COORD: &str = "vec3<f32>(in.uv, 0.0)";
9const ZERO: &str = "vec3<f32>(0.0)";
10const ONE: &str = "vec3<f32>(1.0)";
11const HALF: &str = "vec3<f32>(0.5)";
12const WORLD: &str = "in.world_pos";
13
14macro_rules! node {
15    ($kind:expr, $cat:expr, [$($i:expr),*], [$($p:expr),*], $expr:expr) => {
16        NodeSpec { kind: $kind, category: $cat, inputs: &[$($i),*], params: &[$($p),*], expr: $expr, color: false }
17    };
18}
19
20/// Operations of the "Math" node, in the order of its selector.
21pub const MATH_OPS: &[&str] = &[
22    "Add", "Subtract", "Multiply", "Divide", "Power", "Logarithm", "Square root", "Absolute", "Minimum", "Maximum", "Less than", "Greater than", "Round", "Floor",
23    "Ceil", "Fraction", "Modulo", "Sine", "Cosine", "Tangent", "Arctan2", "Smooth minimum", "Smooth maximum", "Snap", "Ping-pong", "Multiply add",
24];
25/// Operations of the "Vector math" node.
26pub const VECTOR_OPS: &[&str] = &[
27    "Add", "Subtract", "Multiply", "Divide", "Cross product", "Project", "Reflect", "Dot product", "Distance", "Length", "Scale", "Normalize", "Absolute", "Minimum",
28    "Maximum", "Floor", "Fraction",
29];
30/// Blend modes of "Mix colour" (same order as `lib_blend`).
31pub const BLEND_MODES: &[&str] = &["Mix", "Multiply", "Screen", "Overlay", "Add", "Subtract", "Difference", "Darken", "Lighten", "Soft light", "Colour dodge", "Colour burn", "Hard light"];
32
33/// Named choices of selector parameters (`node kind`, `parameter index`) shown as drop-downs.
34pub fn choices(kind: &str, param: usize) -> Option<&'static [&'static str]> {
35    Some(match (kind, param) {
36        ("Math", 0) => MATH_OPS,
37        ("Vector math", 0) => VECTOR_OPS,
38        ("Mix colour", 0) => BLEND_MODES,
39        ("Noise texture", 0) => &["fBm", "Ridged", "Billow"],
40        ("Voronoi texture", 0) => &["Distance (F1)", "Second (F2)", "Edge (F2-F1)", "Cell value"],
41        ("Wave texture", 0) => &["Bands", "Rings"],
42        ("Wave texture", 1) => &["Sine", "Saw", "Triangle"],
43        ("Gradient texture", 0) => &["Linear", "Quadratic", "Eased", "Diagonal", "Radial", "Spherical", "Quadratic sphere"],
44        ("Image texture" | "Texture" | "Triplanar texture", 0) => &["Slot 0 (albedo)", "Slot 1 (normal)", "Slot 2 (ORM)"],
45        ("Color ramp", 4) => &["Linear", "Constant", "Ease"],
46        ("Math", 1) | ("Mix colour", 1) => &["Off", "Clamp 0..1"],
47        _ => return None,
48    })
49}
50
51/// Nodes added in the fourth wave.
52pub const V4_SPECS: &[NodeSpec] = &[
53    // ---- input
54    node!("Image texture", "Input", [("Vector", COORD)], [("slot", 0.0, 0.0, 2.0), ("tiling x", 1.0, 0.01, 100.0), ("tiling y", 1.0, 0.01, 100.0), ("offset x", 0.0, -10.0, 10.0), ("offset y", 0.0, -10.0, 10.0), ("rotation", 0.0, -1.0, 1.0)], "textureSample({tex}, albedo_smp, lib_rot2(({0}).xy * vec2<f32>({p1}, {p2}), {p5} * 6.2831853) + vec2<f32>({p3}, {p4})).rgb"),
55    node!("Texture coordinate", "Geometry", [], [], "vec3<f32>(in.uv, 0.0)"),
56    node!("Geometry info", "Geometry", [], [], "in.world_pos"),
57    node!("Layer weight", "Light", [("Blend", HALF)], [], "vec3<f32>(pow(1.0 - max(dot(n, view), 0.0), 1.0 / max(1.0 - clamp(({0}).x, 0.0, 0.99), 0.01)))"),
58    node!("RGB", "Input", [("Colour", "vec3<f32>(0.8, 0.5, 0.2)")], [], "{0}"),
59    // ---- maths
60    node!("Math", "Math", [("A", HALF), ("B", HALF), ("C", HALF)], [("operation", 2.0, 0.0, 25.0), ("clamp", 0.0, 0.0, 1.0)], "select(lib_math({0}, {1}, {2}, {p0}), clamp(lib_math({0}, {1}, {2}, {p0}), vec3<f32>(0.0), vec3<f32>(1.0)), {p1} > 0.5)"),
61    node!("Vector math", "Vector", [("A", ZERO), ("B", ONE)], [("operation", 0.0, 0.0, 16.0)], "lib_vmath({0}, {1}, {p0})"),
62    node!("Separate RGB", "Vector", [("Colour", "vec3<f32>(0.8, 0.5, 0.2)")], [], "{0}"),
63    node!("Separate XYZ", "Vector", [("Vector", WORLD)], [], "{0}"),
64    node!("Combine XYZ", "Vector", [("X", ZERO), ("Y", ZERO), ("Z", ZERO)], [], "vec3<f32>(({0}).x, ({1}).x, ({2}).x)"),
65    node!("Mapping", "Vector", [("Vector", COORD)], [("location x", 0.0, -100.0, 100.0), ("location y", 0.0, -100.0, 100.0), ("location z", 0.0, -100.0, 100.0), ("rotation", 0.0, -1.0, 1.0), ("scale x", 1.0, -100.0, 100.0), ("scale y", 1.0, -100.0, 100.0), ("scale z", 1.0, -100.0, 100.0)], "lib_mapping({0}, vec3<f32>({p0}, {p1}, {p2}), {p3}, vec3<f32>({p4}, {p5}, {p6}))"),
66    node!("Jitter coords", "Vector", [("Vector", WORLD), ("Amount", "vec3<f32>(0.2)")], [("scale", 4.0, 0.01, 100.0)], "lib_jitter({0}, ({1}).x, {p0})"),
67    node!("Float curve", "Math", [("Value", HALF)], [("y at 0", 0.0, -1.0, 2.0), ("y at 0.25", 0.25, -1.0, 2.0), ("y at 0.5", 0.5, -1.0, 2.0), ("y at 0.75", 0.75, -1.0, 2.0), ("y at 1", 1.0, -1.0, 2.0)], "lib_curve_rgb({0}, {p0}, {p1}, {p2}, {p3}, {p4})"),
68    node!("Clamp range", "Math", [("Value", HALF), ("Min", ZERO), ("Max", ONE)], [], "clamp({0}, min({1}, {2}), max({1}, {2}))"),
69    // ---- texture generators
70    node!("Noise texture", "Noise", [("Vector", WORLD), ("Scale", "vec3<f32>(4.0)"), ("Detail", "vec3<f32>(5.0)"), ("Roughness", HALF), ("Distortion", ZERO)], [("type", 0.0, 0.0, 2.0), ("lacunarity", 2.0, 1.2, 4.0)], "vec3<f32>(lib_fractal(({0}) * ({1}).x + ({4}).x * (vec3<f32>(lib_vnoise3(({0}) * ({1}).x * 0.5 + vec3<f32>(3.1)), lib_vnoise3(({0}) * ({1}).x * 0.5 + vec3<f32>(7.4)), lib_vnoise3(({0}) * ({1}).x * 0.5 + vec3<f32>(1.7))) - vec3<f32>(0.5)), clamp(({2}).x, 1.0, 8.0), {p1}, clamp(({3}).x, 0.0, 1.0), {p0}))"),
71    node!("Voronoi texture", "Noise", [("Vector", WORLD), ("Scale", "vec3<f32>(5.0)"), ("Randomness", ONE)], [("feature", 0.0, 0.0, 3.0)], "lib_voronoi3(({0}) * ({1}).x, clamp(({2}).x, 0.0, 1.0))"),
72    node!("Wave texture", "Procedural", [("Vector", WORLD), ("Scale", "vec3<f32>(3.0)"), ("Distortion", "vec3<f32>(2.0)"), ("Detail", "vec3<f32>(3.0)")], [("type", 0.0, 0.0, 1.0), ("profile", 0.0, 0.0, 2.0)], "vec3<f32>(lib_wave(({0}) * ({1}).x, {p0}, {p1}, ({2}).x, clamp(({3}).x, 1.0, 8.0)))"),
73    node!("Gradient texture", "Procedural", [("Vector", COORD)], [("type", 0.0, 0.0, 6.0)], "vec3<f32>(lib_gradient({0}, {p0}))"),
74    node!("Brick texture", "Procedural", [("Vector", COORD), ("Colour 1", "vec3<f32>(0.62, 0.24, 0.16)"), ("Colour 2", "vec3<f32>(0.45, 0.17, 0.12)"), ("Mortar", "vec3<f32>(0.75, 0.72, 0.66)"), ("Scale", "vec3<f32>(6.0)")], [("brick ratio", 2.0, 0.2, 6.0), ("row offset", 0.5, 0.0, 1.0), ("mortar size", 0.05, 0.0, 0.3), ("mortar smooth", 0.02, 0.0, 0.3), ("colour variation", 0.6, 0.0, 1.0)], "lib_brick(({0}).xy, ({4}).x, {p0}, {p1}, {p2}, {p3})"),
75    // ---- colour
76    node!("Mix colour", "Colour", [("Factor", HALF), ("A", "vec3<f32>(0.2)"), ("B", "vec3<f32>(0.8)")], [("blend mode", 0.0, 0.0, 12.0), ("clamp result", 0.0, 0.0, 1.0)], "select(mix({1}, lib_blend({1}, {2}, {p0}), clamp(({0}).x, 0.0, 1.0)), clamp(mix({1}, lib_blend({1}, {2}, {p0}), clamp(({0}).x, 0.0, 1.0)), vec3<f32>(0.0), vec3<f32>(1.0)), {p1} > 0.5)"),
77    node!("Hue saturation value", "Colour", [("Colour", "vec3<f32>(0.8, 0.4, 0.2)"), ("Hue", HALF), ("Saturation", ONE), ("Value", ONE), ("Factor", ONE)], [], "mix({0}, lib_hsv_adjust({0}, ({1}).x - 0.5, ({2}).x, ({3}).x), clamp(({4}).x, 0.0, 1.0))"),
78    node!("Invert", "Colour", [("Colour", "vec3<f32>(0.8)"), ("Factor", ONE)], [], "mix({0}, vec3<f32>(1.0) - {0}, clamp(({1}).x, 0.0, 1.0))"),
79    node!("RGB curves", "Colour", [("Colour", HALF), ("Factor", ONE)], [("y at 0", 0.0, -1.0, 2.0), ("y at 0.25", 0.22, -1.0, 2.0), ("y at 0.5", 0.5, -1.0, 2.0), ("y at 0.75", 0.78, -1.0, 2.0), ("y at 1", 1.0, -1.0, 2.0)], "mix({0}, lib_curve_rgb({0}, {p0}, {p1}, {p2}, {p3}, {p4}), clamp(({1}).x, 0.0, 1.0))"),
80    node!("Bright contrast", "Colour", [("Colour", HALF), ("Bright", ZERO), ("Contrast", ZERO)], [], "max((({0}) - vec3<f32>(0.5)) * (1.0 + ({2}).x) + vec3<f32>(0.5) + ({1}).x, vec3<f32>(0.0))"),
81    // ---- normal
82    node!("Bump", "Normal", [("Height", ZERO), ("Strength", ONE), ("Distance", "vec3<f32>(0.1)")], [("invert", 0.0, 0.0, 1.0)], "lib_bump(({0}).x * select(1.0, -1.0, {p0} > 0.5) * ({2}).x * 10.0, in.world_pos, n, clamp(({1}).x, 0.0, 4.0))"),
83];
84
85/// Nodes of this file that sample textures or use derivatives (not in camera effects / vertex stage).
86pub const MESH_ONLY: &[&str] = &["Image texture", "Layer weight", "Bump", "Geometry info", "Texture coordinate"];
87/// Nodes that cannot feed a vertex offset.
88pub const FRAGMENT_ONLY: &[&str] = &["Image texture", "Bump"];
89
90/// Extra outputs of a node: name and a WGSL expression where `{v}` is the node's main result and
91/// `{0}`.., `{p0}`.. are its inputs and parameters, like in the main expression. Output 0 is always
92/// the main result; nodes missing here have only that one.
93pub fn outputs(kind: &str) -> &'static [(&'static str, &'static str)] {
94    const RGB: &[(&str, &str)] = &[("Colour", "{v}"), ("R", "vec3<f32>(({v}).x)"), ("G", "vec3<f32>(({v}).y)"), ("B", "vec3<f32>(({v}).z)"), ("Luma", "vec3<f32>(lib_luma({v}))")];
95    const XYZ: &[(&str, &str)] = &[("Vector", "{v}"), ("X", "vec3<f32>(({v}).x)"), ("Y", "vec3<f32>(({v}).y)"), ("Z", "vec3<f32>(({v}).z)")];
96    match kind {
97        "Image texture" | "Texture" | "Triplanar texture" | "Color" | "RGB" | "Separate RGB" | "Cell colour" => RGB,
98        "Separate XYZ" | "World position" | "Local position" | "Object position" | "Camera position" | "Mapping" | "Jitter coords" | "Sun direction" | "View direction" | "Reflection" => XYZ,
99        "UV" => &[("UV", "{v}"), ("U", "vec3<f32>(({v}).x)"), ("V", "vec3<f32>(({v}).y)")],
100        "Texture coordinate" => &[("UV", "{v}"), ("Object", "(in.world_pos - object.model[3].xyz)"), ("World", "in.world_pos"), ("Normal", "n"), ("Reflection", "reflect(-view, n)"), ("Screen", "vec3<f32>(in.clip.xy * globals.viewport.zw, 0.0)")],
101        "Geometry info" => &[("Position", "{v}"), ("Normal", "n"), ("View", "view"), ("Height", "vec3<f32>(in.world_pos.y)"), ("Backfacing", "vec3<f32>(select(0.0, 1.0, dot(n, view) < 0.0))"), ("Slope", "vec3<f32>(1.0 - abs(n.y))")],
102        "Normal" => &[("Normal", "{v}"), ("Up facing", "vec3<f32>(max(({v}).y, 0.0))"), ("Down facing", "vec3<f32>(max(-({v}).y, 0.0))")],
103        "Time" => &[("Seconds", "{v}"), ("Sine", "vec3<f32>(sin(({v}).x) * 0.5 + 0.5)"), ("Fraction", "fract({v})")],
104        "Fresnel" => &[("Fresnel", "{v}"), ("Facing", "(vec3<f32>(1.0) - {v})")],
105        "Layer weight" => &[("Fresnel", "{v}"), ("Facing", "vec3<f32>(1.0 - pow(max(dot(n, view), 0.0), 1.0 / max(1.0 - clamp(({0}).x, 0.0, 0.99), 0.01)))")],
106        "Noise texture" => &[("Factor", "{v}"), ("Colour", "vec3<f32>(({v}).x, lib_fractal(({0}) * ({1}).x + vec3<f32>(19.1, 3.7, 8.2), clamp(({2}).x, 1.0, 8.0), {p1}, clamp(({3}).x, 0.0, 1.0), {p0}), lib_fractal(({0}) * ({1}).x + vec3<f32>(4.3, 21.9, 13.4), clamp(({2}).x, 1.0, 8.0), {p1}, clamp(({3}).x, 0.0, 1.0), {p0}))")],
107        "Voronoi texture" => &[("Factor", "vec3<f32>(lib_voro_out({v}, {p0}))"), ("Distance", "vec3<f32>(({v}).x)"), ("Edge", "vec3<f32>(({v}).y - ({v}).x)"), ("Cell value", "vec3<f32>(({v}).z)"), ("Cell colour", "lib_h33(vec3<f32>(({v}).z * 97.0, ({v}).z * 31.0, ({v}).z * 57.0))")],
108        "Brick texture" => &[("Colour", "mix({3}, mix({1}, {2}, clamp(({v}).y * {p4} + (1.0 - {p4}) * 0.5, 0.0, 1.0)), ({v}).x)"), ("Mask", "vec3<f32>(({v}).x)"), ("Brick value", "vec3<f32>(({v}).y)"), ("Edge distance", "vec3<f32>(({v}).z)")],
109        "Bricks" => &[("Mask", "{v}"), ("Mortar", "(vec3<f32>(1.0) - {v})")],
110        "Checker" | "Stripes" | "Dots" | "Wave texture" | "Gradient texture" | "Noise" | "Voronoi" | "Fractal noise" | "Perlin noise" | "Marble veins" | "Clouds" | "Grunge" | "Cracks" | "Wood rings" =>
111            &[("Factor", "{v}"), ("Inverted", "(vec3<f32>(1.0) - {v})"), ("Sharp", "smoothstep(vec3<f32>(0.45), vec3<f32>(0.55), {v})")],
112        "Color ramp" => &[("Colour", "{v}"), ("Alpha", "vec3<f32>(lib_luma({v}))")],
113        "Combine XYZ" | "Combine" => &[("Vector", "{v}"), ("Length", "vec3<f32>(length({v}))"), ("Normalized", "normalize({v} + vec3<f32>(0.00001))")],
114        "Mix colour" | "Hue saturation value" | "RGB curves" | "Invert" | "Bright contrast" | "Blend colours" | "HSV adjust" => RGB,
115        _ => &[],
116    }
117}
118
119/// How a linked input converts the incoming value before use. Every socket carries a `vec3`, so
120/// the conversion decides what a number input reads from a colour or a vector (and the other way).
121pub const CONVERSIONS: &[(&str, &str)] = &[
122    ("Auto", "{v}"),
123    ("Red / X", "vec3<f32>(({v}).x)"),
124    ("Green / Y", "vec3<f32>(({v}).y)"),
125    ("Blue / Z", "vec3<f32>(({v}).z)"),
126    ("Luminance", "vec3<f32>(lib_luma({v}))"),
127    ("Average", "vec3<f32>(dot({v}, vec3<f32>(0.33333)))"),
128    ("Length", "vec3<f32>(length({v}))"),
129    ("Largest", "vec3<f32>(max(max(({v}).x, ({v}).y), ({v}).z))"),
130    ("Invert (1 - x)", "(vec3<f32>(1.0) - {v})"),
131    ("Negate", "(-{v})"),
132    ("Clamp 0..1", "clamp({v}, vec3<f32>(0.0), vec3<f32>(1.0))"),
133    ("To 0..1 (x * 0.5 + 0.5)", "({v} * 0.5 + vec3<f32>(0.5))"),
134    ("To -1..1 (x * 2 - 1)", "({v} * 2.0 - vec3<f32>(1.0))"),
135];
136
137/// Short badge drawn next to a converted input.
138pub fn conversion_badge(index: u8) -> &'static str {
139    ["", "R", "G", "B", "L", "Avg", "Len", "Max", "1-x", "-x", "0..1", "½", "±"].get(index as usize).copied().unwrap_or("?")
140}
141
142/// What kind of data a socket naturally holds; used for colouring sockets and picking the
143/// conversion of a new link.
144#[derive(Clone, Copy, PartialEq, Eq, Debug)]
145pub enum SocketType {
146    /// One number (stored in all three channels).
147    Float,
148    /// An RGB colour.
149    Colour,
150    /// A direction or position.
151    Vector,
152}
153
154/// The type of input `index` of a node kind.
155pub fn input_type(name: &str, default: &str) -> SocketType {
156    let colour = matches!(
157        name,
158        "Base color" | "Emission" | "Colour" | "From" | "To" | "Color" | "Tint" | "Low" | "Middle" | "High" | "Stop 1" | "Stop 2" | "Stop 3" | "Stop 4" | "Colour 1" | "Colour 2" | "Mortar" | "Map"
159    ) || (name == "A" || name == "B") && default.contains(',');
160    if colour {
161        return SocketType::Colour;
162    }
163    if matches!(name, "Vector" | "Coords" | "UV" | "Normal" | "Direction" | "Axis" | "Offset" | "Position") || default.contains("in.") || default == "n" {
164        return SocketType::Vector;
165    }
166    SocketType::Float
167}
168
169/// The type of output `index` of a node kind.
170pub fn output_type(kind: &str, category: &str, index: usize) -> SocketType {
171    let outs = outputs(kind);
172    let name = outs.get(index).map_or("", |o| o.0);
173    match name {
174        "Colour" | "Cell colour" => return SocketType::Colour,
175        "Vector" | "UV" | "Object" | "World" | "Normal" | "Reflection" | "Screen" | "Position" | "View" | "Normalized" => return SocketType::Vector,
176        "" => {}
177        _ => return SocketType::Float,
178    }
179    match category {
180        "Colour" | "Light" if !matches!(kind, "Fresnel" | "Facing ratio" | "Light angle" | "Half lambert" | "Backlight" | "Sun with shadow") => SocketType::Colour,
181        "Input" if matches!(kind, "Color" | "Object colour" | "Texture" | "Image texture" | "RGB") => SocketType::Colour,
182        "Geometry" | "Vector" if !matches!(kind, "Height" | "Slope mask" | "Camera distance" | "Length" | "Distance" | "Dot") => SocketType::Vector,
183        _ => SocketType::Float,
184    }
185}
186
187/// The conversion a fresh link from `from` to `to` should use: a colour or a vector plugged into
188/// a number reads its brightness or length; everything else passes as it is.
189pub fn default_conversion(from: SocketType, to: SocketType) -> u8 {
190    match (from, to) {
191        (SocketType::Colour, SocketType::Float) => 4,
192        _ => 0,
193    }
194}
195
196/// English and Russian help of the nodes in this file.
197pub const HELP: &[(&str, &str, &str)] = &[
198    ("Image texture", "An image file of the project. Pick it in the node; up to three images per material (slots 0..2). Outputs colour, channels and brightness.", "Картинка из проекта. Выберите файл прямо в ноде; до трёх картинок на материал (слоты 0..2). Выходы: цвет, каналы и яркость."),
199    ("Texture coordinate", "Every coordinate system at once: UV, object, world, normal, reflection and screen.", "Все системы координат сразу: UV, объект, мир, нормаль, отражение и экран."),
200    ("Geometry info", "Position, normal, view direction, height, back side and slope of the surface point.", "Позиция, нормаль, направление взгляда, высота, обратная сторона и наклон точки поверхности."),
201    ("Layer weight", "Fresnel and facing masks with a blend control; good for coatings and rims.", "Маски Френеля и «лицом к камере» с регулировкой; для лаков и окантовок."),
202    ("RGB", "A colour with R, G, B and brightness outputs.", "Цвет с выходами R, G, B и яркости."),
203    ("Math", "One node for 26 operations on numbers (add, power, smooth min, snap, ping-pong...). Pick the operation in the list.", "Одна нода на 26 операций с числами (сложение, степень, плавный минимум, шаг, пинг-понг...). Операция выбирается в списке."),
204    ("Vector math", "Operations on vectors: cross, dot, project, reflect, distance, normalize...", "Операции с векторами: векторное и скалярное произведение, проекция, отражение, расстояние, нормализация..."),
205    ("Separate RGB", "Splits a colour into R, G and B.", "Разделяет цвет на R, G и B."),
206    ("Separate XYZ", "Splits a vector into X, Y and Z.", "Разделяет вектор на X, Y и Z."),
207    ("Combine XYZ", "Builds a vector from three numbers.", "Собирает вектор из трёх чисел."),
208    ("Mapping", "Moves, turns and scales texture coordinates.", "Сдвигает, поворачивает и масштабирует координаты текстуры."),
209    ("Jitter coords", "Adds smooth noise to coordinates to break visible repetition.", "Добавляет к координатам плавный шум, чтобы убрать заметные повторы."),
210    ("Float curve", "Reshapes a value by a smooth curve through five points.", "Меняет значение по плавной кривой через пять точек."),
211    ("Clamp range", "Keeps a value between two inputs.", "Ограничивает значение двумя входами."),
212    ("Noise texture", "Fractal noise with scale, detail, roughness and distortion inputs; outputs a factor and a colour.", "Фрактальный шум с входами масштаба, детализации, шероховатости и искажения; выходы: коэффициент и цвет."),
213    ("Voronoi texture", "Cells: distance, edges, a value and a colour per cell.", "Ячейки: расстояние, края, значение и цвет каждой ячейки."),
214    ("Wave texture", "Bands or rings with sine, saw or triangle profile, bent by noise.", "Полосы или кольца с синусом, пилой или треугольником, изогнутые шумом."),
215    ("Gradient texture", "Linear, quadratic, eased, diagonal, radial or spherical gradient.", "Линейный, квадратичный, плавный, диагональный, радиальный или сферический градиент."),
216    ("Brick texture", "Bricks with two colours, mortar, row offset and a value per brick.", "Кирпичи двух цветов с раствором, сдвигом рядов и значением каждого кирпича."),
217    ("Mix colour", "Blends A and B with a mode (mix, multiply, overlay, screen...) by a factor.", "Смешивает A и B в режиме (обычный, умножение, перекрытие, экран...) по коэффициенту."),
218    ("Hue saturation value", "Shifts hue and scales saturation and brightness.", "Сдвигает оттенок и меняет насыщенность и яркость."),
219    ("Invert", "Inverts a colour by a factor.", "Инвертирует цвет на заданную долю."),
220    ("RGB curves", "Tone curve for colours through five points.", "Тоновая кривая цвета через пять точек."),
221    ("Bright contrast", "Brightness and contrast as inputs.", "Яркость и контраст как входы."),
222    ("Bump", "Surface relief from a height value; connect to Output > Normal.", "Рельеф поверхности из высоты; подключайте к Output > Normal."),
223];