diff --git a/intern/cycles/blender/shader.cpp b/intern/cycles/blender/shader.cpp index 7e131ea9749..c7e327731df 100644 --- a/intern/cycles/blender/shader.cpp +++ b/intern/cycles/blender/shader.cpp @@ -941,6 +941,18 @@ static ShaderNode *add_node(Scene *scene, get_tex_mapping(noise, b_texture_mapping); node = noise; } + else if (b_node.is_a(&RNA_ShaderNodeTexGabor)) { + BL::ShaderNodeTexGabor b_gabor_node(b_node); + GaborTextureNode *gabor = graph->create_node(); + gabor->set_mode((NodeGaborMode)b_gabor_node.mode()); + gabor->set_dimensions(b_gabor_node.gabor_dimensions()); + gabor->set_periodic(b_gabor_node.periodic()); + gabor->set_use_normalize(b_gabor_node.normalize()); + gabor->set_use_origin_offset(b_gabor_node.use_origin_offset()); + BL::TexMapping b_texture_mapping(b_gabor_node.texture_mapping()); + get_tex_mapping(gabor, b_texture_mapping); + node = gabor; + } else if (b_node.is_a(&RNA_ShaderNodeTexMusgrave)) { BL::ShaderNodeTexMusgrave b_musgrave_node(b_node); MusgraveTextureNode *musgrave_node = graph->create_node(); diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt index 8fa7343dfb0..b00b56a4ca5 100644 --- a/intern/cycles/kernel/CMakeLists.txt +++ b/intern/cycles/kernel/CMakeLists.txt @@ -162,6 +162,7 @@ set(SRC_KERNEL_SVM_HEADERS svm/fresnel.h svm/wireframe.h svm/wavelength.h + svm/gabor.h svm/gamma.h svm/brightness.h svm/geometry.h diff --git a/intern/cycles/kernel/osl/shaders/CMakeLists.txt b/intern/cycles/kernel/osl/shaders/CMakeLists.txt index 766277d248c..a6eebc349f0 100644 --- a/intern/cycles/kernel/osl/shaders/CMakeLists.txt +++ b/intern/cycles/kernel/osl/shaders/CMakeLists.txt @@ -33,6 +33,7 @@ set(SRC_OSL node_environment_texture.osl node_float_curve.osl node_fresnel.osl + node_gabor_texture.osl node_gamma.osl node_geometry.osl node_glass_bsdf.osl diff --git a/intern/cycles/kernel/osl/shaders/node_gabor_texture.osl b/intern/cycles/kernel/osl/shaders/node_gabor_texture.osl new file mode 100644 index 00000000000..47256abf999 --- /dev/null +++ b/intern/cycles/kernel/osl/shaders/node_gabor_texture.osl @@ -0,0 +1,463 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +/* Gabor Noise + * + * Based on: Blender patch D287 + * + * Adapted from Open Shading Language + * Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al. + * All Rights Reserved. + * + * Gabor noise originally based on: + * Lagae, A. and Drettakis, G. 2011. Filtering Solid Gabor Noise. + */ + +/* See GLSL implementation for code comments. */ + +#include "node_hash.h" +#include "node_math.h" +#include "node_noise.h" +#include "stdcycles.h" +#include "vector2.h" +#include "vector4.h" + +#define vector3 point + +#define GABOR_SEED 1259 + +struct GaborParams { + float frequency; + float radius; + float impulses; + float phase; + float phase_variance; + float rotation; + float init_rotation; + float rot_variance; + float tilt_randomness; + float cell_randomness; + float anisotropy; + string mode; + vector direction; +}; + +struct FractalParams { + float octaves; + float roughness; + float scl_lacunarity; + float fre_lacunarity; + float rot_lacunarity; +}; + +int impulses_per_cell(vector3 cell, float impulses, int seed) +{ + int n = int(impulses); + float rmd = impulses - floor(impulses); + if (rmd > 0.0) { + float t = hash_vector4_to_float(vector4(cell[0], cell[1], cell[2], float(seed - GABOR_SEED))); + if (t <= rmd) { + return n + 1; + } + return n; + } + return n; +} + +vector3 gabor_kernel(GaborParams gp, point omega, float phi, point position, float dv) +{ + float g = cos(M_PI * sqrt(dv)) * 0.5 + 0.5; + vector3 r = vector3(0.0); + float h; + + if (gp.mode == "gabor") { /* SHD_GABOR_MODE_GABOR */ + h = gp.frequency * dot(omega, position) + phi; + r = vector3(cos(h), 0.0, 0.0); + } + else if (gp.mode == "phasor") { /* SHD_GABOR_MODE_PHASOR */ + h = gp.frequency * dot(omega, position) + phi; + r = vector3(cos(h), sin(h), 0.0); + } + else if (gp.mode == "gabor_cross") { /* SHD_GABOR_MODE_CROSS */ + h = gp.frequency * length(omega * position) + phi; + r = vector3(cos(h), 0.0, 0.0); + } + else if (gp.mode == "phasor_cross") { /* SHD_GABOR_MODE_PHASOR_CROSS */ + h = gp.frequency * length(omega * position) + phi; + r = vector3(cos(h), sin(h), 0.0); + } + else if (gp.mode == "gabor_ring") { /* SHD_GABOR_MODE_RING */ + h = cos(gp.frequency * dot(omega, position) + phi) + + cos(gp.frequency * length(position) + phi); + r = vector3(h, 0.0, 0.0) * 0.5; + ; + } + else if (gp.mode == "phasor_ring") { /* SHD_GABOR_MODE_PHASOR_RING */ + h = cos(gp.frequency * dot(omega, position) + phi) + + cos(gp.frequency * length(position) + phi); + float h2 = sin(gp.frequency * dot(omega, position) + phi) + + sin(gp.frequency * length(position) + phi); + r = vector3(h, h2, 0.0) * 0.5; + } + else if (gp.mode == "gabor_square") { /* SHD_GABOR_MODE_SQUARE */ + vector3 positionyxz = vector3(position.y, position.x, position.z); + h = cos(gp.frequency * dot(omega, position) + phi) + + cos(gp.frequency * dot(omega, positionyxz) + phi); + r = vector3(h, 0.0, 0.0) * 0.5; + } + else if (gp.mode == "phasor_square") { + vector3 positionyxz = vector3(position.y, position.x, position.z); + h = cos(gp.frequency * dot(omega, position) + phi) + + cos(gp.frequency * dot(omega, positionyxz) + phi); + float h2 = sin(gp.frequency * dot(omega, position) + phi) + + sin(gp.frequency * dot(omega, positionyxz) + phi); + r = vector3(h, h2, 0.0) * 0.5; + } + + return r * g; +} + +vector gabor_sample(GaborParams gp, vector3 cell, int seed, output float phi) +{ + vector3 rand_values = hash_vector4_to_color(vector4(cell.x, cell.y, cell.z, float(seed))) * 2.0 - + 1.0; + float pvar = mix(0.0, rand_values.z, gp.phase_variance); + phi = M_2PI * pvar + gp.phase; + + float omega_t = M_PI * (rand_values.x) * gp.rot_variance - gp.rotation - gp.init_rotation; + float cos_omega_p = clamp(rand_values.y * gp.tilt_randomness, -1.0, 1.0); + float sin_omega_p = sqrt(1.0 - cos_omega_p * cos_omega_p); + float sin_omega_t = sin(omega_t); + float cos_omega_t = cos(omega_t); + + return mix(normalize(vector(cos_omega_t * sin_omega_p, sin_omega_t * sin_omega_p, cos_omega_p)), + normalize(transform(euler_to_mat(vector3(0.0, 0.0, -gp.rotation)), gp.direction)), + gp.anisotropy); +} + +vector3 gabor_cell_3d(output GaborParams gp, point cell, point cell_position, int seed) +{ + int num_impulses = impulses_per_cell(cell, gp.impulses, seed); + vector3 sum = vector3(0.0); + for (int i = 0; i < num_impulses; ++i) { + vector3 rand_position = mix( + vector3(0.0), + hash_vector4_to_color(vector4(cell[0], cell[1], cell[2], float(seed + i * GABOR_SEED))), + gp.cell_randomness); + + point kernel_position = (cell_position - rand_position); + + float dv = dot(kernel_position, kernel_position) / gp.radius; + + if (dv <= 1.0) { + float phi; + vector3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, phi); + sum += gabor_kernel(gp, omega, phi, kernel_position, dv); + } + } + return sum; +} + +vector3 gabor_cell_2d(output GaborParams gp, point cell, point cell_position, int seed) +{ + int num_impulses = impulses_per_cell(cell, gp.impulses, seed); + vector3 sum = vector3(0.0); + for (int i = 0; i < num_impulses; ++i) { + vector3 rand_position = mix( + vector3(0.0), + hash_vector4_to_color(vector4(cell[0], cell[1], cell[2], float(seed + i * GABOR_SEED))), + gp.cell_randomness); + rand_position.z = 0.0; + + point kernel_position = (cell_position - rand_position); + + float dv = dot(kernel_position, kernel_position) / gp.radius; + + if (dv <= 1.0) { + float phi; + vector3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, phi); + sum += gabor_kernel(gp, omega, phi, kernel_position, dv); + } + } + return sum; +} + +float gabor_coord_wrap(float a, float b) +{ + return (b != 0.0) ? a - b * floor(a / b) : 0.0; +} + +vector3 gabor_grid_3d(output GaborParams gp, point p, float scale, int periodic, int seed) +{ + point coords = p * scale; + point position = floor(coords); + point local_position = coords - position; + + vector3 sum = 0.0; + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + point cell_offset = point(i, j, k); + point cell = position + cell_offset; + point cell_position = local_position - cell_offset; + + /* Skip this cell if it's too far away to contribute - Bruemmer.osl */ + point Pr = (point(i > 0, j > 0, k > 0) - local_position) * cell_offset; + if (dot(Pr, Pr) >= 1.0) { + continue; + } + + if (periodic == 1) { + cell[0] = gabor_coord_wrap(cell[0], scale); + cell[1] = gabor_coord_wrap(cell[1], scale); + cell[2] = gabor_coord_wrap(cell[2], scale); + } + + sum += gabor_cell_3d(gp, cell, cell_position, seed); + } + } + } + return sum; +} + +vector3 gabor_grid_2d(output GaborParams gp, point p, float scale, int periodic, int seed) +{ + point coords = point(p.x, p.y, 0.0) * scale; + point position = floor(coords); + point local_position = coords - position; + + vector3 sum = 0.0; + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + point cell_offset = point(i, j, 0.0); + point cell = position + cell_offset; + point cell_position = local_position - cell_offset; + + /* Skip this cell if it's too far away to contribute - Bruemmer.osl */ + point Pr = (point(i > 0, j > 0, 0) - local_position) * cell_offset; + if (dot(Pr, Pr) >= 1.0) { + continue; + } + + if (periodic == 1) { + cell[0] = gabor_coord_wrap(cell[0], scale); + cell[1] = gabor_coord_wrap(cell[1], scale); + } + + sum += gabor_cell_2d(gp, cell, cell_position, seed); + } + } + return sum; +} + +float gabor_fractal_noise(FractalParams fp, + output GaborParams gp, + vector3 p, + float scale, + string dimensions, + int periodic, + int use_origin_offset) +{ + float fscale = 1.0; + float amp = 1.0; + float maxamp = 0.0; + vector3 sum = 0.0; + float octaves = clamp(fp.octaves, 0.0, 15.0); + if (fp.roughness == 0.0) { + octaves = 0.0; + } + int n = int(octaves); + for (int i = 0; i <= n; i++) { + int seed = use_origin_offset * i * GABOR_SEED; + vector3 t = (dimensions == "3D") ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) : + gabor_grid_2d(gp, fscale * p, scale, periodic, seed); + gp.frequency *= fp.fre_lacunarity; + gp.rotation -= fp.rot_lacunarity; + sum += t * amp; + maxamp += amp; + amp *= fp.roughness; + fscale *= fp.scl_lacunarity; + } + float rmd = octaves - floor(octaves); + if (rmd != 0.0) { + int seed = use_origin_offset * (n + 1) * GABOR_SEED; + vector3 t = (dimensions == "3D") ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) : + gabor_grid_2d(gp, fscale * p, scale, periodic, seed); + vector3 sum2 = sum + t * amp; + sum = mix(sum, sum2, rmd); + } + sum /= maxamp; + + if (gp.mode == "phasor" || gp.mode == "phasor_ring" || gp.mode == "phasor_cross" || + gp.mode == "phasor_square") + { + float pn = atan2(sum.y, sum.x) / M_PI; + return pn; + } + else { + return sum.x; + } +} + +GaborParams gabor_parameters(vector direction, + float frequency, + float radius, + float impulses, + float phase, + float phase_variance, + float rotation, + float rot_variance, + float tilt_randomness, + float cell_randomness, + float anisotropy, + string mode) +{ + GaborParams gp; + gp.impulses = clamp(impulses, 0.0001, 32.0); + gp.rot_variance = rot_variance; + gp.anisotropy = anisotropy; + gp.mode = mode; + gp.direction = direction; + gp.phase = phase; + gp.rotation = 0.0; + gp.init_rotation = rotation; + gp.phase_variance = phase_variance; + gp.tilt_randomness = tilt_randomness; + gp.cell_randomness = cell_randomness; + gp.radius = radius; + gp.frequency = frequency * M_PI; + return gp; +} + +/* Shader */ + +float gabor_noise(point p, + vector direction, + float scale, + float frequency, + float detail, + float roughness, + float scl_lacunarity, + float fre_lacunarity, + float rot_lacunarity, + float gain, + float radius, + float impulses, + float phase, + float phase_variance, + float rotation, + float rot_variance, + float tilt_randomness, + float cell_randomness, + float anisotropy, + string dimensions, + string mode, + int use_normalize, + int periodic, + int use_origin_offset) +{ + if (impulses == 0.0 || gain == 0.0 || radius <= 0.0 || scale == 0.0) { + if (use_normalize == 1) { + return 0.5; + } + return 0.0; + } + + FractalParams fp; + fp.roughness = roughness; + fp.octaves = detail; + fp.scl_lacunarity = scl_lacunarity; + fp.fre_lacunarity = fre_lacunarity; + fp.rot_lacunarity = rot_lacunarity; + + GaborParams gp = gabor_parameters(direction, + frequency, + radius, + impulses, + phase, + phase_variance, + rotation, + rot_variance, + tilt_randomness, + cell_randomness, + anisotropy, + mode); + + float g = gabor_fractal_noise(fp, gp, p, scale, dimensions, periodic, use_origin_offset) * gain; + + if (gp.mode == "gabor" || gp.mode == "gabor_ring" || gp.mode == "gabor_cross" || + gp.mode == "gabor_square") + { + float impulse_scale = 1.2613446229; + if (impulses > 1.0) { + impulse_scale = 1.2613446229 * sqrt(gp.impulses); + } + g = g / impulse_scale; + } + + if (use_normalize == 1) { + return clamp(0.5 * g + 0.5, 0.0, 1.0); + } + return g; +} + +/* Gabor */ + +shader node_gabor_texture(int use_mapping = 0, + matrix mapping = matrix(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + string dimensions = "2D", + string mode = "gabor", + int use_normalize = 1, + int periodic = 0, + int use_origin_offset = 1, + vector3 Vector = vector(0, 0, 0), + float Scale = 5.0, + float Detail = 0.0, + float Roughness = 0.5, + float ScaleLacunarity = 2.0, + float FrequencyLacunarity = 2.0, + float RotationLacunarity = 0.0, + float Gain = 1.0, + float Frequency = 4.0, + float Radius = 1.0, + float Impulses = 2.0, + float PhaseOffset = 0.0, + float PhaseVariance = 1.0, + float CellRandomness = 1.0, + float Rotation = 0.0, + float RotationVariance = 0.0, + float TiltRandomness = 1.0, + vector3 Direction = vector(0, 0, 1), + float AnisotropicFactor = 0.0, + output float Value = 0.0) +{ + vector3 p = Vector; + + if (use_mapping) + p = transform(mapping, p); + + Value = gabor_noise(p, + Direction, + Scale, + Frequency, + Detail, + Roughness, + ScaleLacunarity, + FrequencyLacunarity, + RotationLacunarity, + Gain, + Radius, + Impulses, + PhaseOffset, + PhaseVariance, + Rotation, + RotationVariance, + TiltRandomness, + CellRandomness, + AnisotropicFactor, + dimensions, + mode, + use_normalize, + periodic, + use_origin_offset); +} diff --git a/intern/cycles/kernel/svm/gabor.h b/intern/cycles/kernel/svm/gabor.h new file mode 100644 index 00000000000..dbd1a3f2239 --- /dev/null +++ b/intern/cycles/kernel/svm/gabor.h @@ -0,0 +1,484 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright 2011-2022 Blender Foundation */ + +CCL_NAMESPACE_BEGIN + +/* Gabor Noise + * + * Based on: Blender patch D287 + * + * Adapted from Open Shading Language + * Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al. + * All Rights Reserved. + * + * Gabor noise originally based on: + * Lagae, A. and Drettakis, G. 2011. Filtering Solid Gabor Noise. + */ + +/* See GLSL implementation for code comments. */ + +#define GABOR_SEED 1259 + +typedef struct GaborParams { + float frequency; + float radius; + float impulses; + float phase; + float phase_variance; + float rotation; + float init_rotation; + float rot_variance; + float tilt_randomness; + float cell_randomness; + float anisotropy; + int mode; + float3 direction; +} GaborParams; + +typedef struct FractalParams { + float octaves; + float roughness; + float scl_lacunarity; + float fre_lacunarity; + float rot_lacunarity; +} FractalParams; + +ccl_device int impulses_per_cell(float3 cell, float impulses, int seed) +{ + int n = int(impulses); + float rmd = impulses - floorf(impulses); + if (rmd > 0.0f) { + float t = hash_float4_to_float(make_float4(cell.x, cell.y, cell.z, float(seed - GABOR_SEED))); + return (t <= rmd) ? n + 1 : n; + } + return n; +} + +ccl_device float3 gabor_kernel(GaborParams gp, float3 omega, float phi, float3 position, float dv) +{ + float g = cos(M_PI_F * sqrtf(dv)) * 0.5f + 0.5f; + float3 r = zero_float3(); + float h; + + if (gp.mode == SHD_GABOR_MODE_GABOR) { + h = gp.frequency * dot(omega, position) + phi; + r = make_float3(cos(h), 0.0f, 0.0f); + } + else if (gp.mode == SHD_GABOR_MODE_PHASOR) { + h = gp.frequency * dot(omega, position) + phi; + r = make_float3(cos(h), sin(h), 0.0f); + } + else if (gp.mode == SHD_GABOR_MODE_CROSS) { + h = gp.frequency * len(omega * position) + phi; + r = make_float3(cos(h), 0.0f, 0.0f); + } + else if (gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) { + h = gp.frequency * len(omega * position) + phi; + r = make_float3(cos(h), sin(h), 0.0f); + } + else if (gp.mode == SHD_GABOR_MODE_RING) { + h = cos(gp.frequency * dot(omega, position) + phi) + cos(gp.frequency * len(position) + phi); + r = make_float3(h, 0.0f, 0.0f) * 0.5f; + } + else if (gp.mode == SHD_GABOR_MODE_PHASOR_RING) { + h = cos(gp.frequency * dot(omega, position) + phi) + cos(gp.frequency * len(position) + phi); + const float h2 = sin(gp.frequency * dot(omega, position) + phi) + + sin(gp.frequency * len(position) + phi); + r = make_float3(h, h2, 0.0f) * 0.5f; + } + else if (gp.mode == SHD_GABOR_MODE_SQUARE) { + const float3 positionyxz = make_float3(position.y, position.x, position.z); + h = cos(gp.frequency * dot(omega, position) + phi) + + cos(gp.frequency * dot(omega, positionyxz) + phi); + r = make_float3(h, 0.0f, 0.0f) * 0.5f; + } + else if (gp.mode == SHD_GABOR_MODE_PHASOR_SQUARE) { + const float3 positionyxz = make_float3(position.y, position.x, position.z); + h = cos(gp.frequency * dot(omega, position) + phi) + + cos(gp.frequency * dot(omega, positionyxz) + phi); + const float h2 = sin(gp.frequency * dot(omega, position) + phi) + + sin(gp.frequency * dot(omega, positionyxz) + phi); + r = make_float3(h, h2, 0.0f) * 0.5f; + } + + return r * g; +} + +ccl_device float3 gabor_sample(GaborParams gp, float3 cell, int seed, ccl_private float *phi) +{ + float3 rand_values = hash_float4_to_float3(make_float4(cell.x, cell.y, cell.z, float(seed))) * + 2.0f - + 1.0f; + float pvar = mix(0.0f, rand_values.z, gp.phase_variance); + *phi = M_2PI_F * pvar + gp.phase; + + float omega_t = M_PI_F * (rand_values.x) * gp.rot_variance - gp.rotation - gp.init_rotation; + float cos_omega_p = clamp(rand_values.y * gp.tilt_randomness, -1.0f, 1.0f); + float sin_omega_p = sqrtf(1.0f - cos_omega_p * cos_omega_p); + float sin_omega_t = sin(omega_t); + float cos_omega_t = cos(omega_t); + + Transform rotationTransform = euler_to_transform(make_float3(0.0f, 0.0f, -gp.rotation)); + return mix( + normalize(make_float3(cos_omega_t * sin_omega_p, sin_omega_t * sin_omega_p, cos_omega_p)), + normalize(transform_direction(&rotationTransform, gp.direction)), + gp.anisotropy); +} + +ccl_device float3 gabor_cell_3d(GaborParams gp, float3 cell, float3 cell_position, int seed) +{ + int num_impulses = impulses_per_cell(cell, gp.impulses, seed); + float3 sum = zero_float3(); + for (int i = 0; i < num_impulses; ++i) { + float3 rand_position = mix( + zero_float3(), + hash_float4_to_float3(make_float4(cell.x, cell.y, cell.z, float(seed + i * GABOR_SEED))), + gp.cell_randomness); + + float3 kernel_position = (cell_position - rand_position); + + float dv = dot(kernel_position, kernel_position) / gp.radius; + + if (dv <= 1.0f) { + float phi; + float3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, &phi); + sum += gabor_kernel(gp, omega, phi, kernel_position, dv); + } + } + return sum; +} + +ccl_device float3 gabor_cell_2d(GaborParams gp, float3 cell, float3 cell_position, int seed) +{ + int num_impulses = impulses_per_cell(cell, gp.impulses, seed); + float3 sum = zero_float3(); + for (int i = 0; i < num_impulses; ++i) { + float3 rand_position = mix( + zero_float3(), + hash_float4_to_float3(make_float4(cell.x, cell.y, cell.z, float(seed + i * GABOR_SEED))), + gp.cell_randomness); + rand_position.z = 0.0f; + + float3 kernel_position = (cell_position - rand_position); + + float dv = dot(kernel_position, kernel_position) / gp.radius; + + if (dv <= 1.0f) { + float phi; + float3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, &phi); + sum += gabor_kernel(gp, omega, phi, kernel_position, dv); + } + } + return sum; +} + +ccl_device float gabor_coord_wrap(float a, float b) +{ + return (b != 0.0f) ? a - b * floorf(a / b) : 0.0f; +} + +ccl_device float3 gabor_grid_3d(GaborParams gp, float3 p, float scale, int periodic, int seed) +{ + float3 coords = p * scale; + float3 position = floor(coords); + float3 local_position = coords - position; + + float3 sum = zero_float3(); + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + float3 cell_offset = make_float3(i, j, k); + float3 cell = position + cell_offset; + float3 cell_position = local_position - cell_offset; + + /* Skip this cell if it's too far away to contribute - Bruemmer.osl */ + float3 Pr = (make_float3(i > 0, j > 0, k > 0) - local_position) * cell_offset; + if (dot(Pr, Pr) >= 1.0f) { + continue; + } + + if (periodic) { + cell.x = gabor_coord_wrap(cell.x, scale); + cell.y = gabor_coord_wrap(cell.y, scale); + cell.z = gabor_coord_wrap(cell.z, scale); + } + + sum += gabor_cell_3d(gp, cell, cell_position, seed); + } + } + } + return sum; +} + +ccl_device float3 gabor_grid_2d(GaborParams gp, float3 p, float scale, int periodic, int seed) +{ + float3 coords = make_float3(p.x, p.y, 0.0f) * scale; + float3 position = floor(coords); + float3 local_position = coords - position; + + float3 sum = zero_float3(); + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + float3 cell_offset = make_float3(i, j, 0.0f); + float3 cell = position + cell_offset; + float3 cell_position = local_position - cell_offset; + + /* Skip this cell if it's too far away to contribute - Bruemmer.osl */ + float3 Pr = (make_float3(i > 0, j > 0, 0) - local_position) * cell_offset; + if (dot(Pr, Pr) >= 1.0f) { + continue; + } + + if (periodic) { + cell.x = gabor_coord_wrap(cell.x, scale); + cell.y = gabor_coord_wrap(cell.y, scale); + } + + sum += gabor_cell_2d(gp, cell, cell_position, seed); + } + } + return sum; +} + +ccl_device float gabor_fractal_noise(FractalParams fp, + GaborParams gp, + float3 p, + float scale, + int dimensions, + int periodic, + int use_origin_offset) +{ + float fscale = 1.0f; + float amp = 1.0f; + float maxamp = 0.0f; + float3 sum = zero_float3(); + float octaves = clamp(fp.octaves, 0.0f, 15.0f); + if (fp.roughness == 0.0f) { + octaves = 0.0f; + } + int n = int(octaves); + for (int i = 0; i <= n; i++) { + int seed = use_origin_offset * i * GABOR_SEED; + float3 t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) : + gabor_grid_2d(gp, fscale * p, scale, periodic, seed); + gp.frequency *= fp.fre_lacunarity; + gp.rotation -= fp.rot_lacunarity; + sum += t * amp; + maxamp += amp; + amp *= fp.roughness; + fscale *= fp.scl_lacunarity; + } + float rmd = octaves - floorf(octaves); + if (rmd != 0.0f) { + int seed = use_origin_offset * (n + 1) * GABOR_SEED; + float3 t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) : + gabor_grid_2d(gp, fscale * p, scale, periodic, seed); + float3 sum2 = sum + t * amp; + sum = mix(sum, sum2, rmd); + } + sum /= maxamp; + + if (gp.mode == SHD_GABOR_MODE_PHASOR || gp.mode == SHD_GABOR_MODE_PHASOR_RING || + gp.mode == SHD_GABOR_MODE_PHASOR_CROSS || gp.mode == SHD_GABOR_MODE_PHASOR_SQUARE) + { + float pn = atan2f(sum.y, sum.x) / M_PI_F; + return pn; + } + else { + return sum.x; + } +} + +ccl_device GaborParams gabor_parameters(float3 direction, + float frequency, + float radius, + float impulses, + float phase, + float phase_variance, + float rotation, + float rot_variance, + float tilt_randomness, + float cell_randomness, + float anisotropy, + int mode) +{ + GaborParams gp; + gp.impulses = clamp(impulses, 0.0001f, 32.0f); + gp.rot_variance = rot_variance; + gp.anisotropy = anisotropy; + gp.mode = mode; + gp.direction = direction; + gp.phase = phase; + gp.rotation = 0.0f; + gp.init_rotation = rotation; + gp.phase_variance = phase_variance; + gp.tilt_randomness = tilt_randomness; + gp.cell_randomness = cell_randomness; + gp.radius = radius; + gp.frequency = frequency * M_PI_F; + return gp; +} + +/* Gabor shader */ + +ccl_device float gabor_noise(float3 p, + float3 direction, + float scale, + float frequency, + float detail, + float roughness, + float scl_lacunarity, + float fre_lacunarity, + float rot_lacunarity, + float gain, + float radius, + float impulses, + float phase, + float phase_variance, + float rotation, + float rot_variance, + float tilt_randomness, + float cell_randomness, + float anisotropy, + int dimensions, + int mode, + int normalize, + int periodic, + int use_origin_offset) +{ + if (impulses == 0.0f || gain == 0.0f || radius <= 0.0f || scale == 0.0f) { + return (normalize == 1) ? 0.5f : 0.0f; + } + + FractalParams fp; + fp.roughness = roughness; + fp.octaves = detail; + fp.scl_lacunarity = scl_lacunarity; + fp.fre_lacunarity = fre_lacunarity; + fp.rot_lacunarity = rot_lacunarity; + + GaborParams gp = gabor_parameters(direction, + frequency, + radius, + impulses, + phase, + phase_variance, + rotation, + rot_variance, + tilt_randomness, + cell_randomness, + anisotropy, + mode); + + float g = gabor_fractal_noise(fp, gp, p, scale, dimensions, periodic, use_origin_offset) * gain; + + if (gp.mode == SHD_GABOR_MODE_GABOR || gp.mode == SHD_GABOR_MODE_RING || + gp.mode == SHD_GABOR_MODE_CROSS || gp.mode == SHD_GABOR_MODE_SQUARE) + { + float impulse_scale = impulses > 1.0f ? 1.2613446229f * sqrt(gp.impulses) : 1.2613446229f; + g = g / impulse_scale; + } + + if (normalize == 1) { + return clamp(0.5f * g + 0.5f, 0.0f, 1.0f); + } + return g; +} + +ccl_device_noinline int svm_node_tex_gabor( + KernelGlobals kg, ccl_private ShaderData *sd, ccl_private float *stack, uint4 node, int offset) +{ + uint4 node2 = read_node(kg, &offset); + uint4 defaults_node3 = read_node(kg, &offset); + uint4 defaults_node4 = read_node(kg, &offset); + uint4 defaults_node5 = read_node(kg, &offset); + uint4 defaults_node6 = read_node(kg, &offset); + uint4 defaults_node7 = read_node(kg, &offset); + + /* Input and Output Sockets */ + uint vector_in_offset, scale_offset, detail_offset, phase_offset, impulse_offset; + uint direction_offset, value_out_offset, fre_lacunarity_offset; + uint mode_offset, aniso_offset, periodic_offset, roughness_offset; + uint rot_variance_offset, phase_variance_offset, rotation_offset, gain_offset, + tilt_randomness_offset, cell_randomness_offset; + uint scl_lacunarity_offset, use_normalize_offset, dimension_offset, rot_lacunarity_offset; + uint frequency_offset, radius_offset; + + svm_unpack_node_uchar4( + node.y, &vector_in_offset, &scale_offset, &frequency_offset, &detail_offset); + svm_unpack_node_uchar4(node.z, + &roughness_offset, + &scl_lacunarity_offset, + &fre_lacunarity_offset, + &rot_lacunarity_offset); + svm_unpack_node_uchar4(node.w, &gain_offset, &radius_offset, &impulse_offset, &phase_offset); + + svm_unpack_node_uchar4(node2.x, + &phase_variance_offset, + &cell_randomness_offset, + &rotation_offset, + &tilt_randomness_offset); + svm_unpack_node_uchar4( + node2.y, &rot_variance_offset, &direction_offset, &value_out_offset, &dimension_offset); + svm_unpack_node_uchar4( + node2.z, &mode_offset, &aniso_offset, &periodic_offset, &use_normalize_offset); + + float3 vector_in = stack_load_float3(stack, vector_in_offset); + + float scale = stack_load_float_default(stack, scale_offset, defaults_node3.x); + float frequency = stack_load_float_default(stack, frequency_offset, defaults_node3.y); + float detail = stack_load_float_default(stack, detail_offset, defaults_node3.z); + float roughness = stack_load_float_default(stack, roughness_offset, defaults_node3.w); + + float scl_lacunarity = stack_load_float_default(stack, scl_lacunarity_offset, defaults_node4.x); + float fre_lacunarity = stack_load_float_default(stack, fre_lacunarity_offset, defaults_node4.y); + float rot_lacunarity = stack_load_float_default(stack, rot_lacunarity_offset, defaults_node4.z); + float gain = stack_load_float_default(stack, gain_offset, defaults_node4.w); + + float radius = stack_load_float_default(stack, radius_offset, defaults_node5.x); + float impulses = stack_load_float_default(stack, impulse_offset, defaults_node5.y); + float phase = stack_load_float_default(stack, phase_offset, defaults_node5.z); + float phase_variance = stack_load_float_default(stack, phase_variance_offset, defaults_node5.w); + + float cell_randomness = stack_load_float_default( + stack, cell_randomness_offset, defaults_node6.x); + float rotation = stack_load_float_default(stack, rotation_offset, defaults_node6.y); + float rot_variance = stack_load_float_default(stack, rot_variance_offset, defaults_node6.z); + float tilt_randomness = stack_load_float_default( + stack, tilt_randomness_offset, defaults_node6.w); + float anisotropy = stack_load_float_default(stack, aniso_offset, defaults_node7.x); + + float3 direction = stack_load_float3(stack, direction_offset); + + if (stack_valid(value_out_offset)) { + float value = gabor_noise(vector_in, + direction, + scale, + frequency, + detail, + roughness, + scl_lacunarity, + fre_lacunarity, + rot_lacunarity, + gain, + radius, + impulses, + phase, + phase_variance, + rotation, + rot_variance, + tilt_randomness, + cell_randomness, + anisotropy, + dimension_offset, + mode_offset, + use_normalize_offset, + periodic_offset, + int(node2.w)); + stack_store_float(stack, value_out_offset, value); + } + + return offset; +} + +CCL_NAMESPACE_END diff --git a/intern/cycles/kernel/svm/node_types_template.h b/intern/cycles/kernel/svm/node_types_template.h index afda2a7a019..1c47ef93b2b 100644 --- a/intern/cycles/kernel/svm/node_types_template.h +++ b/intern/cycles/kernel/svm/node_types_template.h @@ -108,8 +108,9 @@ SHADER_NODE_TYPE(NODE_MIX_COLOR) SHADER_NODE_TYPE(NODE_MIX_FLOAT) SHADER_NODE_TYPE(NODE_MIX_VECTOR) SHADER_NODE_TYPE(NODE_MIX_VECTOR_NON_UNIFORM) +SHADER_NODE_TYPE(NODE_TEX_GABOR) /* Padding for struct alignment. */ -SHADER_NODE_TYPE(NODE_PAD1) +// SHADER_NODE_TYPE(NODE_PAD1) #undef SHADER_NODE_TYPE diff --git a/intern/cycles/kernel/svm/svm.h b/intern/cycles/kernel/svm/svm.h index 45f7099f069..7ef64b13743 100644 --- a/intern/cycles/kernel/svm/svm.h +++ b/intern/cycles/kernel/svm/svm.h @@ -165,6 +165,7 @@ CCL_NAMESPACE_END #include "kernel/svm/convert.h" #include "kernel/svm/displace.h" #include "kernel/svm/fresnel.h" +#include "kernel/svm/gabor.h" #include "kernel/svm/gamma.h" #include "kernel/svm/geometry.h" #include "kernel/svm/gradient.h" @@ -603,6 +604,9 @@ ccl_device void svm_eval_nodes(KernelGlobals kg, SVM_CASE(NODE_MIX_VECTOR_NON_UNIFORM) svm_node_mix_vector_non_uniform(sd, stack, node.y, node.z); break; + SVM_CASE(NODE_TEX_GABOR) + offset = svm_node_tex_gabor(kg, sd, stack, node, offset); + break; default: kernel_assert(!"Unknown node type was passed to the SVM machine"); return; diff --git a/intern/cycles/kernel/svm/types.h b/intern/cycles/kernel/svm/types.h index 1cf96a3f7e3..0e747c281de 100644 --- a/intern/cycles/kernel/svm/types.h +++ b/intern/cycles/kernel/svm/types.h @@ -409,6 +409,17 @@ typedef enum NodeCombSepColorType { NODE_COMBSEP_COLOR_HSL, } NodeCombSepColorType; +typedef enum NodeGaborMode { + SHD_GABOR_MODE_GABOR, + SHD_GABOR_MODE_RING, + SHD_GABOR_MODE_CROSS, + SHD_GABOR_MODE_SQUARE, + SHD_GABOR_MODE_PHASOR, + SHD_GABOR_MODE_PHASOR_RING, + SHD_GABOR_MODE_PHASOR_CROSS, + SHD_GABOR_MODE_PHASOR_SQUARE, +} NodeGaborMode; + /* Closure */ typedef enum ClosureType { diff --git a/intern/cycles/scene/shader_nodes.cpp b/intern/cycles/scene/shader_nodes.cpp index a86b1e71da8..40a1c3097ad 100644 --- a/intern/cycles/scene/shader_nodes.cpp +++ b/intern/cycles/scene/shader_nodes.cpp @@ -1206,6 +1206,179 @@ void NoiseTextureNode::compile(OSLCompiler &compiler) compiler.add(this, "node_noise_texture"); } +/* Gabor Texture */ + +NODE_DEFINE(GaborTextureNode) +{ + NodeType *type = NodeType::add("gabor_texture", create, NodeType::SHADER); + + TEXTURE_MAPPING_DEFINE(GaborTextureNode); + + static NodeEnum dimensions_enum; + dimensions_enum.insert("2D", 2); + dimensions_enum.insert("3D", 3); + SOCKET_ENUM(dimensions, "Dimensions", dimensions_enum, 2); + + static NodeEnum mode_enum; + mode_enum.insert("gabor", SHD_GABOR_MODE_GABOR); + mode_enum.insert("gabor_ring", SHD_GABOR_MODE_RING); + mode_enum.insert("gabor_cross", SHD_GABOR_MODE_CROSS); + mode_enum.insert("gabor_square", SHD_GABOR_MODE_SQUARE); + mode_enum.insert("phasor", SHD_GABOR_MODE_PHASOR); + mode_enum.insert("phasor_ring", SHD_GABOR_MODE_PHASOR_RING); + mode_enum.insert("phasor_cross", SHD_GABOR_MODE_PHASOR_CROSS); + mode_enum.insert("phasor_square", SHD_GABOR_MODE_PHASOR_SQUARE); + SOCKET_ENUM(mode, "Mode", mode_enum, SHD_GABOR_MODE_GABOR); + + SOCKET_BOOLEAN(periodic, "Periodic", false); + SOCKET_BOOLEAN(use_normalize, "Normalize", true); + SOCKET_BOOLEAN(use_origin_offset, "Origin Offset", true); + + SOCKET_IN_POINT(vector, "Vector", zero_float3(), SocketType::LINK_TEXTURE_GENERATED); + SOCKET_IN_FLOAT(scale, "Scale", 5.0f); + SOCKET_IN_FLOAT(frequency, "Frequency", 4.0f); + SOCKET_IN_FLOAT(gain, "Gain", 1.0f); + SOCKET_IN_FLOAT(detail, "Detail", 0.0f); + SOCKET_IN_FLOAT(roughness, "Roughness", 0.5f); + SOCKET_IN_FLOAT(scl_lacunarity, "Scale Lacunarity", 2.0f); + SOCKET_IN_FLOAT(fre_lacunarity, "Frequency Lacunarity", 2.0f); + SOCKET_IN_FLOAT(rot_lacunarity, "Rotation Lacunarity", 0.0f); + SOCKET_IN_FLOAT(radius, "Radius", 1.0f); + SOCKET_IN_FLOAT(impulses, "Impulses", 2.0f); + SOCKET_IN_FLOAT(phase, "Phase Offset", 0.0f); + SOCKET_IN_FLOAT(phase_variance, "Phase Variance", 1.0f); + SOCKET_IN_FLOAT(rotation, "Rotation", 0.0f); + SOCKET_IN_FLOAT(rot_variance, "Rotation Variance", 0.0f); + SOCKET_IN_FLOAT(tilt_randomness, "Tilt Randomness", 1.0f); + SOCKET_IN_POINT(direction, "Direction", make_float3(0.0f, 0.0f, 1.0f)); + SOCKET_IN_FLOAT(cell_randomness, "Cell Randomness", 1.0f); + SOCKET_IN_FLOAT(anisotropy, "Anisotropic Factor", 1.0f); + + SOCKET_OUT_FLOAT(value, "Value"); + + return type; +} + +GaborTextureNode::GaborTextureNode() : TextureNode(node_type) {} + +void GaborTextureNode::constant_fold(const ConstantFolder &folder) +{ + ShaderInput *scale_in = input("Scale"); + ShaderInput *gain_in = input("Gain"); + ShaderInput *radius_in = input("Radius"); + ShaderInput *impulses_in = input("Impulses"); + + if ((!scale_in->link && scale == 0.0f) || (!gain_in->link && gain == 0.0f) || + (!radius_in->link && radius == 0.0f) || (!impulses_in->link && impulses == 0.0f)) + { + folder.make_constant(use_normalize ? 0.5f : 0.0f); + } +} + +void GaborTextureNode::compile(SVMCompiler &compiler) +{ + ShaderInput *vector_in = input("Vector"); + ShaderInput *scale_in = input("Scale"); + ShaderInput *frequency_in = input("Frequency"); + ShaderInput *detail_in = input("Detail"); + ShaderInput *roughness_in = input("Roughness"); + ShaderInput *scl_lacunarity_in = input("Scale Lacunarity"); + ShaderInput *fre_lacunarity_in = input("Frequency Lacunarity"); + ShaderInput *rot_lacunarity_in = input("Rotation Lacunarity"); + ShaderInput *gain_in = input("Gain"); + ShaderInput *radius_in = input("Radius"); + ShaderInput *impulses_in = input("Impulses"); + ShaderInput *phase_in = input("Phase Offset"); + ShaderInput *phase_variance_in = input("Phase Variance"); + ShaderInput *rotation_in = input("Rotation"); + ShaderInput *rot_variance_in = input("Rotation Variance"); + ShaderInput *tilt_randomness_in = input("Tilt Randomness"); + ShaderInput *direction_in = input("Direction"); + ShaderInput *cell_randomness_in = input("Cell Randomness"); + ShaderInput *anisotropy_in = input("Anisotropic Factor"); + + ShaderOutput *value_out = output("Value"); + + int vector_stack_offset = tex_mapping.compile_begin(compiler, vector_in); + + int scale_in_stack_offset = compiler.stack_assign(scale_in); + int frequency_in_stack_offset = compiler.stack_assign(frequency_in); + int detail_in_stack_offset = compiler.stack_assign(detail_in); + int roughness_in_stack_offset = compiler.stack_assign(roughness_in); + int scl_lacunarity_in_stack_offset = compiler.stack_assign(scl_lacunarity_in); + int fre_lacunarity_in_stack_offset = compiler.stack_assign(fre_lacunarity_in); + int rot_lacunarity_in_stack_offset = compiler.stack_assign(rot_lacunarity_in); + int gain_in_stack_offset = compiler.stack_assign(gain_in); + int radius_in_stack_offset = compiler.stack_assign(radius_in); + int impulses_in_stack_offset = compiler.stack_assign(impulses_in); + int phase_in_stack_offset = compiler.stack_assign(phase_in); + int phase_variance_in_stack_offset = compiler.stack_assign(phase_variance_in); + int rotation_in_stack_offset = compiler.stack_assign(rotation_in); + int rot_variance_in_stack_offset = compiler.stack_assign(rot_variance_in); + int tilt_randomness_in_stack_offset = compiler.stack_assign(tilt_randomness_in); + int cell_randomness_in_stack_offset = compiler.stack_assign(cell_randomness_in); + int anisotropy_in_stack_offset = compiler.stack_assign(anisotropy_in); + int direction_in_stack_offset = compiler.stack_assign(direction_in); + + int value_out_stack_offset = compiler.stack_assign_if_linked(value_out); + + compiler.add_node(NODE_TEX_GABOR, + compiler.encode_uchar4(vector_stack_offset, + scale_in_stack_offset, + frequency_in_stack_offset, + detail_in_stack_offset), + compiler.encode_uchar4(roughness_in_stack_offset, + scl_lacunarity_in_stack_offset, + fre_lacunarity_in_stack_offset, + rot_lacunarity_in_stack_offset), + compiler.encode_uchar4(gain_in_stack_offset, + radius_in_stack_offset, + impulses_in_stack_offset, + phase_in_stack_offset)); + compiler.add_node( + compiler.encode_uchar4(phase_variance_in_stack_offset, + cell_randomness_in_stack_offset, + rotation_in_stack_offset, + tilt_randomness_in_stack_offset), + compiler.encode_uchar4(rot_variance_in_stack_offset, + direction_in_stack_offset, + value_out_stack_offset, + dimensions), + compiler.encode_uchar4(mode, anisotropy_in_stack_offset, periodic, use_normalize), + use_origin_offset); + + compiler.add_node(__float_as_int(scale), + __float_as_int(frequency), + __float_as_int(detail), + __float_as_int(roughness)); + compiler.add_node(__float_as_int(scl_lacunarity), + __float_as_int(fre_lacunarity), + __float_as_int(rot_lacunarity), + __float_as_int(gain)); + compiler.add_node(__float_as_int(radius), + __float_as_int(impulses), + __float_as_int(phase), + __float_as_int(phase_variance)); + compiler.add_node(__float_as_int(cell_randomness), + __float_as_int(rotation), + __float_as_int(rot_variance), + __float_as_int(tilt_randomness)); + compiler.add_node(__float_as_int(anisotropy)); + + tex_mapping.compile_end(compiler, vector_in, vector_stack_offset); +} + +void GaborTextureNode::compile(OSLCompiler &compiler) +{ + tex_mapping.compile(compiler); + compiler.parameter(this, "dimensions"); + compiler.parameter(this, "mode"); + compiler.parameter(this, "periodic"); + compiler.parameter(this, "use_normalize"); + compiler.parameter(this, "use_origin_offset"); + compiler.add(this, "node_gabor_texture"); +} + /* Voronoi Texture */ NODE_DEFINE(VoronoiTextureNode) diff --git a/intern/cycles/scene/shader_nodes.h b/intern/cycles/scene/shader_nodes.h index 5e349ade9f4..5f0193c0b94 100644 --- a/intern/cycles/scene/shader_nodes.h +++ b/intern/cycles/scene/shader_nodes.h @@ -238,6 +238,38 @@ class NoiseTextureNode : public TextureNode { NODE_SOCKET_API(float3, vector) }; +class GaborTextureNode : public TextureNode { + public: + SHADER_NODE_CLASS(GaborTextureNode) + void constant_fold(const ConstantFolder &folder); + + NODE_SOCKET_API(int, dimensions) + NODE_SOCKET_API(NodeGaborMode, mode); + NODE_SOCKET_API(bool, periodic) + NODE_SOCKET_API(bool, use_normalize) + NODE_SOCKET_API(bool, use_origin_offset) + + NODE_SOCKET_API(float3, vector) + NODE_SOCKET_API(float, scale) + NODE_SOCKET_API(float, frequency) + NODE_SOCKET_API(float, detail) + NODE_SOCKET_API(float, roughness) + NODE_SOCKET_API(float, scl_lacunarity) + NODE_SOCKET_API(float, fre_lacunarity) + NODE_SOCKET_API(float, rot_lacunarity) + NODE_SOCKET_API(float, gain) + NODE_SOCKET_API(float, radius) + NODE_SOCKET_API(float, impulses) + NODE_SOCKET_API(float, phase) + NODE_SOCKET_API(float, phase_variance) + NODE_SOCKET_API(float, rotation) + NODE_SOCKET_API(float, rot_variance) + NODE_SOCKET_API(float, tilt_randomness) + NODE_SOCKET_API(float, cell_randomness) + NODE_SOCKET_API(float, anisotropy) + NODE_SOCKET_API(float3, direction) +}; + class VoronoiTextureNode : public TextureNode { public: SHADER_NODE_CLASS(VoronoiTextureNode) diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 10a4f4ce169..909837030a6 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -519,6 +519,7 @@ class NODE_MT_category_GEO_TEXTURE(Menu): layout = self.layout node_add_menu.add_node_type(layout, "ShaderNodeTexBrick") node_add_menu.add_node_type(layout, "ShaderNodeTexChecker") + node_add_menu.add_node_type(layout, "ShaderNodeTexGabor") node_add_menu.add_node_type(layout, "ShaderNodeTexGradient") node_add_menu.add_node_type(layout, "GeometryNodeImageTexture") node_add_menu.add_node_type(layout, "ShaderNodeTexMagic") diff --git a/scripts/startup/bl_ui/node_add_menu_shader.py b/scripts/startup/bl_ui/node_add_menu_shader.py index b181dc50b19..ce51a0bcef4 100644 --- a/scripts/startup/bl_ui/node_add_menu_shader.py +++ b/scripts/startup/bl_ui/node_add_menu_shader.py @@ -288,6 +288,7 @@ class NODE_MT_category_shader_texture(Menu): node_add_menu.add_node_type(layout, "ShaderNodeTexBrick") node_add_menu.add_node_type(layout, "ShaderNodeTexChecker") node_add_menu.add_node_type(layout, "ShaderNodeTexEnvironment") + node_add_menu.add_node_type(layout, "ShaderNodeTexGabor") node_add_menu.add_node_type(layout, "ShaderNodeTexGradient") node_add_menu.add_node_type(layout, "ShaderNodeTexIES") node_add_menu.add_node_type(layout, "ShaderNodeTexImage") diff --git a/scripts/startup/nodeitems_builtins.py b/scripts/startup/nodeitems_builtins.py index c59efab5a8a..55e0004503d 100644 --- a/scripts/startup/nodeitems_builtins.py +++ b/scripts/startup/nodeitems_builtins.py @@ -36,6 +36,13 @@ class ShaderNodeCategory(SortedNodeCategory): context.space_data.tree_type == 'ShaderNodeTree') +class TextureNodeCategory(SortedNodeCategory): + @classmethod + def poll(cls, context): + return (context.space_data.type == 'NODE_EDITOR' and + context.space_data.tree_type == 'TextureNodeTree') + + # Menu entry for node group tools. def group_tools_draw(_self, layout, _context): layout.operator("node.group_make") @@ -75,10 +82,10 @@ def node_group_items(context): for group in context.blend_data.node_groups: if group.bl_idname != ntree.bl_idname: continue - # Filter out recursive groups. + # filter out recursive groups if group.contains_tree(ntree): continue - # Filter out hidden node-trees. + # filter out hidden nodetrees if group.name.startswith('.'): continue yield NodeItem(node_tree_group_type[group.bl_idname], @@ -94,12 +101,359 @@ def group_input_output_item_poll(context): return False +# only show input/output nodes when editing line style node trees +def line_style_shader_nodes_poll(context): + snode = context.space_data + return (snode.tree_type == 'ShaderNodeTree' and + snode.shader_type == 'LINESTYLE') + + +# only show nodes working in world node trees +def world_shader_nodes_poll(context): + snode = context.space_data + return (snode.tree_type == 'ShaderNodeTree' and + snode.shader_type == 'WORLD') + + +# only show nodes working in object node trees +def object_shader_nodes_poll(context): + snode = context.space_data + return (snode.tree_type == 'ShaderNodeTree' and + snode.shader_type == 'OBJECT') + + +def cycles_shader_nodes_poll(context): + return context.engine == 'CYCLES' + + +def eevee_shader_nodes_poll(context): + return context.engine == 'BLENDER_EEVEE' + + +def eevee_cycles_shader_nodes_poll(context): + return (cycles_shader_nodes_poll(context) or + eevee_shader_nodes_poll(context)) + + +def object_cycles_shader_nodes_poll(context): + return (object_shader_nodes_poll(context) and + cycles_shader_nodes_poll(context)) + + +def object_eevee_shader_nodes_poll(context): + return (object_shader_nodes_poll(context) and + eevee_shader_nodes_poll(context)) + + +def object_eevee_cycles_shader_nodes_poll(context): + return (object_shader_nodes_poll(context) and + eevee_cycles_shader_nodes_poll(context)) + + +# All standard node categories currently used in nodes. + +shader_node_categories = [ + # Shader Nodes (Cycles and Eevee) + ShaderNodeCategory("SH_NEW_INPUT", "Input", items=[ + NodeItem("ShaderNodeTexCoord"), + NodeItem("ShaderNodeAttribute"), + NodeItem("ShaderNodeLightPath"), + NodeItem("ShaderNodeFresnel"), + NodeItem("ShaderNodeLayerWeight"), + NodeItem("ShaderNodeRGB"), + NodeItem("ShaderNodeValue"), + NodeItem("ShaderNodeTangent"), + NodeItem("ShaderNodeNewGeometry"), + NodeItem("ShaderNodeWireframe"), + NodeItem("ShaderNodeBevel"), + NodeItem("ShaderNodeAmbientOcclusion"), + NodeItem("ShaderNodeObjectInfo"), + NodeItem("ShaderNodeHairInfo"), + NodeItem("ShaderNodePointInfo"), + NodeItem("ShaderNodeVolumeInfo"), + NodeItem("ShaderNodeParticleInfo"), + NodeItem("ShaderNodeCameraData"), + NodeItem("ShaderNodeUVMap"), + NodeItem("ShaderNodeVertexColor"), + NodeItem("ShaderNodeUVAlongStroke", poll=line_style_shader_nodes_poll), + ]), + ShaderNodeCategory("SH_NEW_OUTPUT", "Output", items=[ + NodeItem("ShaderNodeOutputMaterial", poll=object_eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeOutputLight", poll=object_cycles_shader_nodes_poll), + NodeItem("ShaderNodeOutputAOV"), + NodeItem("ShaderNodeOutputWorld", poll=world_shader_nodes_poll), + NodeItem("ShaderNodeOutputLineStyle", poll=line_style_shader_nodes_poll), + ]), + ShaderNodeCategory("SH_NEW_SHADER", "Shader", items=[ + NodeItem("ShaderNodeMixShader", poll=eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeAddShader", poll=eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeBsdfDiffuse", poll=object_eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeBsdfPrincipled", poll=object_eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeBsdfGlossy", poll=object_eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeBsdfTransparent", poll=object_eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeBsdfRefraction", poll=object_eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeBsdfGlass", poll=object_eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeBsdfTranslucent", poll=object_eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeBsdfSheen", poll=object_cycles_shader_nodes_poll), + NodeItem("ShaderNodeBsdfToon", poll=object_cycles_shader_nodes_poll), + NodeItem("ShaderNodeSubsurfaceScattering", poll=object_eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeEmission", poll=eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeBsdfHair", poll=object_cycles_shader_nodes_poll), + NodeItem("ShaderNodeBackground", poll=world_shader_nodes_poll), + NodeItem("ShaderNodeHoldout", poll=object_eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeVolumeAbsorption", poll=eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeVolumeScatter", poll=eevee_cycles_shader_nodes_poll), + NodeItem("ShaderNodeVolumePrincipled"), + NodeItem("ShaderNodeEeveeSpecular", poll=object_eevee_shader_nodes_poll), + NodeItem("ShaderNodeBsdfHairPrincipled", poll=object_cycles_shader_nodes_poll), + ]), + ShaderNodeCategory("SH_NEW_TEXTURE", "Texture", items=[ + NodeItem("ShaderNodeTexImage"), + NodeItem("ShaderNodeTexEnvironment"), + NodeItem("ShaderNodeTexSky"), + NodeItem("ShaderNodeTexNoise"), + NodeItem("ShaderNodeTexWave"), + NodeItem("ShaderNodeTexVoronoi"), + NodeItem("ShaderNodeTexGabor"), + NodeItem("ShaderNodeTexMusgrave"), + NodeItem("ShaderNodeTexGradient"), + NodeItem("ShaderNodeTexMagic"), + NodeItem("ShaderNodeTexChecker"), + NodeItem("ShaderNodeTexBrick"), + NodeItem("ShaderNodeTexPointDensity"), + NodeItem("ShaderNodeTexIES"), + NodeItem("ShaderNodeTexWhiteNoise"), + ]), + ShaderNodeCategory("SH_NEW_OP_COLOR", "Color", items=[ + NodeItem("ShaderNodeMix", label="Mix Color", settings={"data_type": "'RGBA'"}), + NodeItem("ShaderNodeRGBCurve"), + NodeItem("ShaderNodeInvert"), + NodeItem("ShaderNodeLightFalloff"), + NodeItem("ShaderNodeHueSaturation"), + NodeItem("ShaderNodeGamma"), + NodeItem("ShaderNodeBrightContrast"), + ]), + ShaderNodeCategory("SH_NEW_OP_VECTOR", "Vector", items=[ + NodeItem("ShaderNodeMapping"), + NodeItem("ShaderNodeBump"), + NodeItem("ShaderNodeDisplacement"), + NodeItem("ShaderNodeVectorDisplacement"), + NodeItem("ShaderNodeNormalMap"), + NodeItem("ShaderNodeNormal"), + NodeItem("ShaderNodeVectorCurve"), + NodeItem("ShaderNodeVectorRotate"), + NodeItem("ShaderNodeVectorTransform"), + ]), + ShaderNodeCategory("SH_NEW_CONVERTOR", "Converter", items=[ + NodeItem("ShaderNodeMapRange"), + NodeItem("ShaderNodeFloatCurve"), + NodeItem("ShaderNodeClamp"), + NodeItem("ShaderNodeMath"), + NodeItem("ShaderNodeMix"), + NodeItem("ShaderNodeValToRGB"), + NodeItem("ShaderNodeRGBToBW"), + NodeItem("ShaderNodeShaderToRGB", poll=object_eevee_shader_nodes_poll), + NodeItem("ShaderNodeVectorMath"), + NodeItem("ShaderNodeSeparateColor"), + NodeItem("ShaderNodeCombineColor"), + NodeItem("ShaderNodeSeparateXYZ"), + NodeItem("ShaderNodeCombineXYZ"), + NodeItem("ShaderNodeWavelength"), + NodeItem("ShaderNodeBlackbody"), + ]), + ShaderNodeCategory("SH_NEW_SCRIPT", "Script", items=[ + NodeItem("ShaderNodeScript"), + ]), + ShaderNodeCategory("SH_NEW_GROUP", "Group", items=node_group_items), + ShaderNodeCategory("SH_NEW_LAYOUT", "Layout", items=[ + NodeItem("NodeFrame"), + NodeItem("NodeReroute"), + ]), +] + +compositor_node_categories = [ + # Compositor Nodes + CompositorNodeCategory("CMP_INPUT", "Input", items=[ + NodeItem("CompositorNodeRLayers"), + NodeItem("CompositorNodeImage"), + NodeItem("CompositorNodeMovieClip"), + NodeItem("CompositorNodeMask"), + NodeItem("CompositorNodeRGB"), + NodeItem("CompositorNodeValue"), + NodeItem("CompositorNodeTexture"), + NodeItem("CompositorNodeBokehImage"), + NodeItem("CompositorNodeTime"), + NodeItem("CompositorNodeSceneTime"), + NodeItem("CompositorNodeTrackPos"), + ]), + CompositorNodeCategory("CMP_OUTPUT", "Output", items=[ + NodeItem("CompositorNodeComposite"), + NodeItem("CompositorNodeViewer"), + NodeItem("CompositorNodeSplitViewer"), + NodeItem("CompositorNodeOutputFile"), + NodeItem("CompositorNodeLevels"), + ]), + CompositorNodeCategory("CMP_OP_COLOR", "Color", items=[ + NodeItem("CompositorNodeMixRGB"), + NodeItem("CompositorNodeAlphaOver"), + NodeItem("CompositorNodeInvert"), + NodeItem("CompositorNodeCurveRGB"), + NodeItem("CompositorNodeHueSat"), + NodeItem("CompositorNodeColorBalance"), + NodeItem("CompositorNodeHueCorrect"), + NodeItem("CompositorNodeBrightContrast"), + NodeItem("CompositorNodeGamma"), + NodeItem("CompositorNodeExposure"), + NodeItem("CompositorNodeColorCorrection"), + NodeItem("CompositorNodePosterize"), + NodeItem("CompositorNodeTonemap"), + NodeItem("CompositorNodeZcombine"), + ]), + CompositorNodeCategory("CMP_CONVERTOR", "Converter", items=[ + NodeItem("CompositorNodeMath"), + NodeItem("CompositorNodeValToRGB"), + NodeItem("CompositorNodeSetAlpha"), + NodeItem("CompositorNodePremulKey"), + NodeItem("CompositorNodeIDMask"), + NodeItem("CompositorNodeRGBToBW"), + NodeItem("CompositorNodeSeparateColor"), + NodeItem("CompositorNodeCombineColor"), + NodeItem("CompositorNodeSeparateXYZ"), + NodeItem("CompositorNodeCombineXYZ"), + NodeItem("CompositorNodeSwitchView"), + NodeItem("CompositorNodeConvertColorSpace"), + ]), + CompositorNodeCategory("CMP_OP_FILTER", "Filter", items=[ + NodeItem("CompositorNodeBlur"), + NodeItem("CompositorNodeBilateralblur"), + NodeItem("CompositorNodeDilateErode"), + NodeItem("CompositorNodeDespeckle"), + NodeItem("CompositorNodeFilter"), + NodeItem("CompositorNodeBokehBlur"), + NodeItem("CompositorNodeVecBlur"), + NodeItem("CompositorNodeDefocus"), + NodeItem("CompositorNodeGlare"), + NodeItem("CompositorNodeInpaint"), + NodeItem("CompositorNodeDBlur"), + NodeItem("CompositorNodePixelate"), + NodeItem("CompositorNodeSunBeams"), + NodeItem("CompositorNodeDenoise"), + NodeItem("CompositorNodeAntiAliasing"), + NodeItem("CompositorNodeKuwahara"), + ]), + CompositorNodeCategory("CMP_OP_VECTOR", "Vector", items=[ + NodeItem("CompositorNodeNormal"), + NodeItem("CompositorNodeMapValue"), + NodeItem("CompositorNodeMapRange"), + NodeItem("CompositorNodeNormalize"), + NodeItem("CompositorNodeCurveVec"), + ]), + CompositorNodeCategory("CMP_MATTE", "Matte", items=[ + NodeItem("CompositorNodeKeying"), + NodeItem("CompositorNodeKeyingScreen"), + NodeItem("CompositorNodeChannelMatte"), + NodeItem("CompositorNodeColorSpill"), + NodeItem("CompositorNodeBoxMask"), + NodeItem("CompositorNodeEllipseMask"), + NodeItem("CompositorNodeLumaMatte"), + NodeItem("CompositorNodeDiffMatte"), + NodeItem("CompositorNodeDistanceMatte"), + NodeItem("CompositorNodeChromaMatte"), + NodeItem("CompositorNodeColorMatte"), + NodeItem("CompositorNodeDoubleEdgeMask"), + NodeItem("CompositorNodeCryptomatte"), + NodeItem("CompositorNodeCryptomatteV2"), + ]), + CompositorNodeCategory("CMP_DISTORT", "Distort", items=[ + NodeItem("CompositorNodeScale"), + NodeItem("CompositorNodeLensdist"), + NodeItem("CompositorNodeMovieDistortion"), + NodeItem("CompositorNodeTranslate"), + NodeItem("CompositorNodeRotate"), + NodeItem("CompositorNodeFlip"), + NodeItem("CompositorNodeCrop"), + NodeItem("CompositorNodeDisplace"), + NodeItem("CompositorNodeMapUV"), + NodeItem("CompositorNodeTransform"), + NodeItem("CompositorNodeStabilize"), + NodeItem("CompositorNodePlaneTrackDeform"), + NodeItem("CompositorNodeCornerPin"), + ]), + CompositorNodeCategory("CMP_GROUP", "Group", items=node_group_items), + CompositorNodeCategory("CMP_LAYOUT", "Layout", items=[ + NodeItem("NodeFrame"), + NodeItem("NodeReroute"), + NodeItem("CompositorNodeSwitch"), + ]), +] + +texture_node_categories = [ + # Texture Nodes + TextureNodeCategory("TEX_INPUT", "Input", items=[ + NodeItem("TextureNodeCurveTime"), + NodeItem("TextureNodeCoordinates"), + NodeItem("TextureNodeTexture"), + NodeItem("TextureNodeImage"), + ]), + TextureNodeCategory("TEX_OUTPUT", "Output", items=[ + NodeItem("TextureNodeOutput"), + NodeItem("TextureNodeViewer"), + ]), + TextureNodeCategory("TEX_OP_COLOR", "Color", items=[ + NodeItem("TextureNodeMixRGB"), + NodeItem("TextureNodeCurveRGB"), + NodeItem("TextureNodeInvert"), + NodeItem("TextureNodeHueSaturation"), + NodeItem("TextureNodeCombineColor"), + NodeItem("TextureNodeSeparateColor"), + ]), + TextureNodeCategory("TEX_PATTERN", "Pattern", items=[ + NodeItem("TextureNodeChecker"), + NodeItem("TextureNodeBricks"), + ]), + TextureNodeCategory("TEX_TEXTURE", "Textures", items=[ + NodeItem("TextureNodeTexNoise"), + NodeItem("TextureNodeTexDistNoise"), + NodeItem("TextureNodeTexClouds"), + NodeItem("TextureNodeTexBlend"), + NodeItem("TextureNodeTexVoronoi"), + NodeItem("TextureNodeTexMagic"), + NodeItem("TextureNodeTexMarble"), + NodeItem("TextureNodeTexWood"), + NodeItem("TextureNodeTexMusgrave"), + NodeItem("TextureNodeTexStucci"), + ]), + TextureNodeCategory("TEX_CONVERTOR", "Converter", items=[ + NodeItem("TextureNodeMath"), + NodeItem("TextureNodeValToRGB"), + NodeItem("TextureNodeRGBToBW"), + NodeItem("TextureNodeValToNor"), + NodeItem("TextureNodeDistance"), + ]), + TextureNodeCategory("TEX_DISTORT", "Distort", items=[ + NodeItem("TextureNodeScale"), + NodeItem("TextureNodeTranslate"), + NodeItem("TextureNodeRotate"), + NodeItem("TextureNodeAt"), + ]), + TextureNodeCategory("TEX_GROUP", "Group", items=node_group_items), + TextureNodeCategory("TEX_LAYOUT", "Layout", items=[ + NodeItem("NodeFrame"), + NodeItem("NodeReroute"), + ]), +] + + def register(): - pass + nodeitems_utils.register_node_categories('SHADER', shader_node_categories) + nodeitems_utils.register_node_categories('COMPOSITING', compositor_node_categories) + nodeitems_utils.register_node_categories('TEXTURE', texture_node_categories) def unregister(): - pass + nodeitems_utils.unregister_node_categories('SHADER') + nodeitems_utils.unregister_node_categories('COMPOSITING') + nodeitems_utils.unregister_node_categories('TEXTURE') if __name__ == "__main__": diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 574bcc60eba..a7b71bf2bee 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -966,6 +966,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i #define SH_NODE_COMBINE_COLOR 711 #define SH_NODE_SEPARATE_COLOR 712 #define SH_NODE_MIX 713 +#define SH_NODE_TEX_GABOR 714 /** \} */ diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 9c9511f1fd9..ef7811301a6 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -546,6 +546,7 @@ set(GLSL_SRC shaders/material/gpu_shader_material_tex_brick.glsl shaders/material/gpu_shader_material_tex_checker.glsl shaders/material/gpu_shader_material_tex_environment.glsl + shaders/material/gpu_shader_material_tex_gabor.glsl shaders/material/gpu_shader_material_tex_gradient.glsl shaders/material/gpu_shader_material_tex_image.glsl shaders/material/gpu_shader_material_tex_magic.glsl diff --git a/source/blender/gpu/shaders/material/gpu_shader_material_tex_gabor.glsl b/source/blender/gpu/shaders/material/gpu_shader_material_tex_gabor.glsl new file mode 100644 index 00000000000..f046a8461f8 --- /dev/null +++ b/source/blender/gpu/shaders/material/gpu_shader_material_tex_gabor.glsl @@ -0,0 +1,467 @@ +#pragma BLENDER_REQUIRE(gpu_shader_common_hash.glsl) +#pragma BLENDER_REQUIRE(gpu_shader_common_math_utils.glsl) + +/* Gabor Noise + * + * Based on: Blender patch D287 & D3495 + * With additional controls for kernel variance. + * + * This implementation is adapted from the original built-in OSL implementation based on the 2009 + * paper "Procedural noise using sparse Gabor convolution". Some parts are also adapted from the + * 2011 paper "Filtering Solid Gabor Noise" but this does not include the filtering and slicing. + * References to the papers are for copyright and reference. + * + * Notes and changes from the original OSL implementation and reference papers: + * - For 2D noise, as with Voronoi the calculation uses a 3x3x1 grid rather than slicing 3D noise. + * This is more performant when only 2D texture is required. + * - For artistic control, calculations for Bandwidth have been simplified and replaced with + * separate controls for Frequency, Gain and Radius. This provides finer control where + * before frequency and bandwidth were bound to the same parameter. Radius values over 1 may result + * in artefacts and discontinuities. This is not clamped as pushing the radius can create some + * potentially useful noise. + * - Phasor noise has been added. Since this is based on Gabor and sums the changes in phase it is + * trivial to add. + * - Additional sincos based kernels have been added which provide different texture control in a + * similar way to distance metrics in Voronoi. + * - Added Roughness, Scale Lacunarity, Frequency Lacunarity and Rotation Lacunarity to control + * additive fractal noise. + * - Uses built-in Blender hashes instead of adding a separate gabor rng. + * + * Adapted from Open Shading Language implementation. + * Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al. + * All Rights Reserved. + * + * OSL Gabor noise references: + * Lagae, Lefebvre, Drettakis and Dutré, 2009. Procedural noise using sparse Gabor convolution + * Lagae, A. and Drettakis, G. 2011. Filtering Solid Gabor Noise + * Tavernier et al. 2018, Gabor Noise Revisited + * + * Phasor noise reference: + * Tricard et al. 2019. Procedural Phasor Noise + */ + +#define SHD_GABOR_MODE_GABOR 0 +#define SHD_GABOR_MODE_RING 1 +#define SHD_GABOR_MODE_CROSS 2 +#define SHD_GABOR_MODE_SQUARE 3 +#define SHD_GABOR_MODE_PHASOR 4 +#define SHD_GABOR_MODE_PHASOR_RING 5 +#define SHD_GABOR_MODE_PHASOR_CROSS 6 +#define SHD_GABOR_MODE_PHASOR_SQUARE 7 + +/* Large prime number for grid offset and impulse seed. */ +#define GABOR_SEED 1259 + +/* Struct to hold Gabor parameters. */ +struct GaborParams { + float frequency; + float radius; + float impulses; + float phase; + float phase_variance; + float rotation; + float init_rotation; + float rot_variance; + float tilt_randomness; + float cell_randomness; + float anisotropy; + int mode; + vec3 direction; +}; + +/* Struct to hold Fractal parameters. */ +struct FractalParams { + float octaves; + float roughness; + float scl_lacunarity; + float fre_lacunarity; + float rot_lacunarity; +}; + +/* Calculate impulses per cell. Performance is optimised when impulses are set to whole numbers. + * It's useful to allow impulses less than 1 to create spotted textures which is why int(impulses) + * is not directly used. Above 1 this provides a linear increase in impulses as the input value + * increases. */ +int impulses_per_cell(vec3 cell, float impulses, int seed) +{ + int n = int(impulses); + float rmd = impulses - floor(impulses); + if (rmd > 0.0) { + float t = hash_vec4_to_float(vec4(cell, float(seed - GABOR_SEED))); + return (t <= rmd) ? n + 1 : n; + } + return n; +} + +/* Calculates the kernel shape that is multiplied by the gaussian envelope. For Phasor a sum of two + * values is required. For Gabor a sum of one value is required. These are passed as a vector in + * all cases. */ +vec3 gabor_kernel(GaborParams gp, vec3 omega, float phi, vec3 position, float dv) +{ + float g = cos(M_PI * sqrt(dv)) * 0.5 + 0.5; + vec3 r = vec3(0.0); + float h; + + if (gp.mode == SHD_GABOR_MODE_GABOR) { + h = gp.frequency * dot(omega, position) + phi; + r = vec3(cos(h), 0.0, 0.0); + } + else if (gp.mode == SHD_GABOR_MODE_PHASOR) { + h = gp.frequency * dot(omega, position) + phi; + r = vec3(cos(h), sin(h), 0.0); + } + else if (gp.mode == SHD_GABOR_MODE_CROSS) { + h = gp.frequency * length(omega * position) + phi; + r = vec3(cos(h), 0.0, 0.0); + } + else if (gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) { + h = gp.frequency * length(omega * position) + phi; + r = vec3(cos(h), sin(h), 0.0); + } + else if (gp.mode == SHD_GABOR_MODE_RING) { + h = cos(gp.frequency * dot(omega, position) + phi) + + cos(gp.frequency * length(position) + phi); + r = vec3(h, 0.0, 0.0) * 0.5; + } + else if (gp.mode == SHD_GABOR_MODE_PHASOR_RING) { + h = cos(gp.frequency * dot(omega, position) + phi) + + cos(gp.frequency * length(position) + phi); + float h2 = sin(gp.frequency * dot(omega, position) + phi) + + sin(gp.frequency * length(position) + phi); + r = vec3(h, h2, 0.0) * 0.5; + } + else if (gp.mode == SHD_GABOR_MODE_SQUARE) { + h = cos(gp.frequency * dot(omega, position) + phi) + + cos(gp.frequency * dot(omega, position.yxz) + phi); + r = vec3(h, 0.0, 0.0) * 0.5; + } + else if (gp.mode == SHD_GABOR_MODE_PHASOR_SQUARE) { + h = cos(gp.frequency * dot(omega, position) + phi) + + cos(gp.frequency * dot(omega, position.yxz) + phi); + float h2 = sin(gp.frequency * dot(omega, position) + phi) + + sin(gp.frequency * dot(omega, position.yxz) + phi); + r = vec3(h, h2, 0.0) * 0.5; + } + + return r * g; +} + +/* Set omega (angular frequency/direction) and phi (phase) for the impulse based on the anisotropy + * parameters. Isotropic and Anisotropic modes have been combined using a factor to mix between + * these modes. */ +vec3 gabor_sample(GaborParams gp, vec3 cell, int seed, out float phi) +{ + vec3 rand_values = hash_vec4_to_vec3(vec4(cell, float(seed))) * 2.0 - 1.0; + + /* Phase. */ + float pvar = mix(0.0, rand_values.z, gp.phase_variance); + phi = M_2PI * pvar + gp.phase; + + /* Isotropic direction. */ + float omega_t = M_PI * (rand_values.x) * gp.rot_variance - gp.rotation - gp.init_rotation; + float cos_omega_p = clamp(rand_values.y * gp.tilt_randomness, -1.0, 1.0); + float sin_omega_p = sqrt(1.0 - cos_omega_p * cos_omega_p); + float sin_omega_t = sin(omega_t); + float cos_omega_t = cos(omega_t); + + /* Mix between Isotropic and Anisotropic direction. */ + return mix(normalize(vec3(cos_omega_t * sin_omega_p, sin_omega_t * sin_omega_p, cos_omega_p)), + normalize(euler_to_mat3(vec3(0.0, 0.0, -gp.rotation)) * gp.direction), + gp.anisotropy); +} + +/* Generate noise based on the cell position and number of impulses. */ +vec3 gabor_cell_3d(GaborParams gp, vec3 cell, vec3 cell_position, int seed) +{ + int num_impulses = impulses_per_cell(cell, gp.impulses, seed); + vec3 sum = vec3(0.0); + for (int i = 0; i < num_impulses; ++i) { + vec3 rand_position = mix(vec3(0.0), + hash_vec4_to_vec3(vec4(cell, float(seed + i * GABOR_SEED))), + gp.cell_randomness); + + vec3 kernel_position = (cell_position - rand_position); + + float dv = dot(kernel_position, kernel_position) / gp.radius; + + if (dv <= 1.0) { + float phi; + vec3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, phi); + sum += gabor_kernel(gp, omega, phi, kernel_position, dv); + } + } + return sum; +} + +/* Generate noise based on the cell position and number of impulses. Z position is zeroed for 2D + * cell as the kernel is still processed in 3D. */ +vec3 gabor_cell_2d(GaborParams gp, vec3 cell, vec3 cell_position, int seed) +{ + int num_impulses = impulses_per_cell(cell, gp.impulses, seed); + vec3 sum = vec3(0.0); + for (int i = 0; i < num_impulses; ++i) { + vec3 rand_position = mix(vec3(0.0), + hash_vec4_to_vec3(vec4(cell, float(seed + i * GABOR_SEED))), + gp.cell_randomness); + rand_position.z = 0.0; + + vec3 kernel_position = (cell_position - rand_position); + + float dv = dot(kernel_position, kernel_position) / gp.radius; + + if (dv <= 1.0) { + float phi; + vec3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, phi); + sum += gabor_kernel(gp, omega, phi, kernel_position, dv); + } + } + return sum; +} + +/* Utility function to wrap coords for periodic mode. */ +float gabor_coord_wrap(float s, float p) +{ + return (p != 0.0) ? s - p * floor(s / p) : 0.0; +} + +/* Calculate 3D noise using 3x3x3 cell grid. */ +vec3 gabor_grid_3d(GaborParams gp, vec3 p, float scale, int periodic, int seed) +{ + vec3 coords = p * scale; + vec3 position = floor(coords); + vec3 local_position = coords - position; + + vec3 sum = vec3(0.0); + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec3 cell_offset = vec3(i, j, k); + vec3 cell = position + cell_offset; + vec3 cell_position = local_position - cell_offset; + + /* Skip this cell if it's too far away to contribute - Lee Bruemmer.osl */ + vec3 Pr = (vec3(i > 0, j > 0, k > 0) - local_position) * cell_offset; + if (dot(Pr, Pr) >= 1.0) { + continue; + } + + /* Wrap cells for periodic noise. */ + if (periodic == 1) { + cell.x = gabor_coord_wrap(cell.x, scale); + cell.y = gabor_coord_wrap(cell.y, scale); + cell.z = gabor_coord_wrap(cell.z, scale); + } + + sum += gabor_cell_3d(gp, cell, cell_position, seed); + } + } + } + return sum; +} + +/* Calculate 2D noise using 3x3x1 cell grid. Less computational than 3x3x3 grid. */ +vec3 gabor_grid_2d(GaborParams gp, vec3 p, float scale, int periodic, int seed) +{ + vec3 coords = vec3(p.xy, 0.0) * scale; + vec3 position = floor(coords); + vec3 local_position = coords - position; + + vec3 sum = vec3(0.0); + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + vec3 cell_offset = vec3(i, j, 0.0); + vec3 cell = position + cell_offset; + vec3 cell_position = local_position - cell_offset; + + /* Skip this cell if it's too far away to contribute - Lee Bruemmer.osl */ + vec3 Pr = (vec3(i > 0, j > 0, 0) - local_position) * cell_offset; + if (dot(Pr, Pr) >= 1.0) { + continue; + } + + /* Wrap cells for periodic noise. */ + if (periodic == 1) { + cell.x = gabor_coord_wrap(cell.x, scale); + cell.y = gabor_coord_wrap(cell.y, scale); + } + + sum += gabor_cell_2d(gp, cell, cell_position, seed); + } + } + return sum; +} + +/* Layered fractal noise. Optionally, for each layer, the offset is changed. This ensures that + * the impulses occur in different places if scale lacunarity is set to 1. Gabor has more variables + * to consider when layering the noise compared to Perlin noise. Kernel frequency and rotation have + * lacunarity parameters in addition to scale lacunarity and roughness that is found in Noise and + * Voronoi textures. */ +float gabor_fractal_noise(FractalParams fp, + GaborParams gp, + vec3 p, + float scale, + int dimensions, + int periodic, + int use_origin_offset) +{ + float fscale = 1.0; + float amp = 1.0; + float maxamp = 0.0; + float3 sum = vec3(0.0); + float octaves = clamp(fp.octaves, 0.0, 15.0); + if (fp.roughness == 0.0) { + octaves = 0.0; + } + int n = int(octaves); + for (int i = 0; i <= n; i++) { + int seed = use_origin_offset * i * GABOR_SEED; + float3 t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) : + gabor_grid_2d(gp, fscale * p, scale, periodic, seed); + gp.frequency *= fp.fre_lacunarity; + gp.rotation -= fp.rot_lacunarity; + sum += t * amp; + maxamp += amp; + amp *= fp.roughness; + fscale *= fp.scl_lacunarity; + } + float rmd = octaves - floor(octaves); + if (rmd != 0.0) { + int seed = use_origin_offset * (n + 1) * GABOR_SEED; + float3 t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) : + gabor_grid_2d(gp, fscale * p, scale, periodic, seed); + float3 sum2 = sum + t * amp; + sum = mix(sum, sum2, rmd); + } + sum /= maxamp; + + /* Extract summed values for Phasor mode. */ + if (gp.mode == SHD_GABOR_MODE_PHASOR || gp.mode == SHD_GABOR_MODE_PHASOR_RING || + gp.mode == SHD_GABOR_MODE_PHASOR_CROSS || gp.mode == SHD_GABOR_MODE_PHASOR_SQUARE) + { + float pn = atan(sum.y, sum.x) / M_PI; + return pn; + } + else { + return sum.x; + } +} + +/* Gabor parameters. Impulses are clamped as these directly impact performance. */ +GaborParams gabor_parameters(vec3 direction, + float frequency, + float radius, + float impulses, + float phase, + float phase_variance, + float rotation, + float rot_variance, + float tilt_randomness, + float cell_randomness, + float anisotropy, + int mode) +{ + GaborParams gp; + gp.impulses = clamp(impulses, 0.0001, 32.0); + gp.rot_variance = rot_variance; + gp.anisotropy = anisotropy; + gp.mode = mode; + gp.direction = direction; + gp.phase = phase; + gp.rotation = 0.0; + gp.init_rotation = rotation; + gp.phase_variance = phase_variance; + gp.tilt_randomness = tilt_randomness; + gp.cell_randomness = cell_randomness; + gp.radius = radius; + gp.frequency = frequency * M_PI; + return gp; +} + +/* Gabor texture node. */ + +void node_tex_gabor(vec3 co, + float scale, + float gain, + float detail, + float roughness, + float scl_lacunarity, + float fre_lacunarity, + float rot_lacunarity, + float frequency, + float radius, + float impulses, + float phase, + float phase_variance, + float cell_randomness, + float rotation, + float rot_variance, + float tilt_randomness, + float anisotropy, + vec3 direction, + float dimensions, + float mode, + float use_normalize, + float periodic, + float use_origin_offset, + out float value) +{ + /* Return early with mid level value. */ + if (impulses == 0.0 || gain == 0.0 || radius <= 0.0 || scale == 0.0) { + value = (int(use_normalize) == 1) ? 0.5 : 0.0; + return; + } + + /* Set Fractal params. */ + FractalParams fp; + fp.roughness = roughness; + fp.octaves = detail; + fp.scl_lacunarity = scl_lacunarity; + fp.fre_lacunarity = fre_lacunarity; + fp.rot_lacunarity = rot_lacunarity; + + /* Set Gabor params. */ + GaborParams gp = gabor_parameters(direction, + frequency, + radius, + impulses, + phase, + phase_variance, + rotation, + rot_variance, + tilt_randomness, + cell_randomness, + anisotropy, + int(mode)); + + float g = gabor_fractal_noise( + fp, gp, co, scale, int(dimensions), int(periodic), int(use_origin_offset)) * + gain; + + /* For Gabor modes, scale height of noise by the number of impulses. This is empircal as there is + * no easy way to analytically determine the scaling factor for the sum of impulses. The + * following calculation is taken from Lee Bruemmer's OSL script implementation. + * + * Scale the noise so the range [-1,1] covers 6 standard deviations, or 3*sqrt(variance) Since + * the radius is set to 1/a the scale simplifies to 3/4*sqrt(3*n/(pi*sqrt(2))) float scale = + * 0.6162961511 * sqrt( Impulses ); but with a fixed number of impulses also divide by + * sqrt(0.75/pi) (0.4886025119) which give 1.2613446229 * sqrt(gp.impulses). + * + * In tests I've found that sqrt(2) 1.41 clips less but the previous value is better overall. + * Artists can overcome this limitation by using the gain control to tweak the texture as + * required. + * + * Phasor does not require this because it always returns [-1,1]. + */ + if (gp.mode == SHD_GABOR_MODE_GABOR || gp.mode == SHD_GABOR_MODE_RING || + gp.mode == SHD_GABOR_MODE_CROSS || gp.mode == SHD_GABOR_MODE_SQUARE) + { + float impulse_scale = impulses > 1.0 ? 1.2613446229 * sqrt(gp.impulses) : 1.2613446229; + g = g / impulse_scale; + } + + /* Normalise and clamp noise to [0.1] range. */ + if (int(use_normalize) == 1) { + g = clamp(0.5 * g + 0.5, 0.0, 1.0); + } + value = g; +} diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 3df53a58dd0..a5021947a53 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1275,6 +1275,16 @@ typedef struct NodeTexNoise { char _pad[3]; } NodeTexNoise; +typedef struct NodeTexGabor { + NodeTexBase base; + char mode; + char periodic; + char normalize; + char dimensions; + char use_origin_offset; + char _pad[3]; +} NodeTexGabor; + typedef struct NodeTexVoronoi { NodeTexBase base; int dimensions; @@ -2028,6 +2038,17 @@ enum { SHD_VORONOI_N_SPHERE_RADIUS = 4, }; +typedef enum NodeGaborMode { + SHD_GABOR_MODE_GABOR, + SHD_GABOR_MODE_RING, + SHD_GABOR_MODE_CROSS, + SHD_GABOR_MODE_SQUARE, + SHD_GABOR_MODE_PHASOR, + SHD_GABOR_MODE_PHASOR_RING, + SHD_GABOR_MODE_PHASOR_CROSS, + SHD_GABOR_MODE_PHASOR_SQUARE, +} NodeGaborMode; + /* musgrave texture */ enum { SHD_MUSGRAVE_MULTIFRACTAL = 0, diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 1b7a22dbcee..f563986f831 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -4964,6 +4964,58 @@ static void def_sh_tex_musgrave(StructRNA *srna) RNA_def_property_update(prop, 0, "rna_ShaderNode_socket_update"); } +static void def_sh_tex_gabor(StructRNA *srna) +{ + static const EnumPropertyItem prop_gabor_mode[] = { + {SHD_GABOR_MODE_GABOR, "GABOR", 0, "Gabor", "Gabor default kernel"}, + {SHD_GABOR_MODE_RING, "GABOR_RING", 0, "Gabor Ring", "Gabor ring kernel"}, + {SHD_GABOR_MODE_CROSS, "GABOR_CROSS", 0, "Gabor Cross", "Gabor cross kernel"}, + {SHD_GABOR_MODE_SQUARE, "GABOR_SQUARE", 0, "Gabor Square", "Gabor square kernel"}, + {SHD_GABOR_MODE_PHASOR, "PHASOR", 0, "Phasor", "Phasor default kernel"}, + {SHD_GABOR_MODE_PHASOR_RING, "PHASOR_RING", 0, "Phasor Ring", "Phasor ring kernel"}, + {SHD_GABOR_MODE_PHASOR_CROSS, "PHASOR_CROSS", 0, "Phasor Cross", "Phasor cross kernel"}, + {SHD_GABOR_MODE_PHASOR_SQUARE, "PHASOR_SQUARE", 0, "Phasor Square", "Phasor square kernel"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + static const EnumPropertyItem prop_gabor_dimensions[] = { + {2, "2D", 0, "2D", "Use the 2D vector (X, Y) as input. The Z component is ignored"}, + {3, "3D", 0, "3D", "Use the 3D vector (X, Y, Z) as input"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + PropertyRNA *prop; + RNA_def_struct_sdna_from(srna, "NodeTexGabor", "storage"); + def_sh_tex(srna); + + prop = RNA_def_property(srna, "gabor_dimensions", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "dimensions"); + RNA_def_property_enum_items(prop, prop_gabor_dimensions); + RNA_def_property_ui_text(prop, "Dimensions", "Number of dimensions to output noise for"); + RNA_def_property_update(prop, 0, "rna_ShaderNode_socket_update"); + + prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "mode"); + RNA_def_property_enum_items(prop, prop_gabor_mode); + RNA_def_property_ui_text(prop, "Mode", "Mode"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_ShaderNode_socket_update"); + + prop = RNA_def_property(srna, "periodic", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "periodic", 0); + RNA_def_property_ui_text(prop, "Periodic", "Periodic noise"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "normalize", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "normalize", 0); + RNA_def_property_ui_text(prop, "Normalize", "Normalize output to 0.0 to 1.0 range"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "use_origin_offset", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "use_origin_offset", 0); + RNA_def_property_ui_text(prop, "Offset Origin", "Offset origin for each octave"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_ShaderNode_socket_update"); +} + static void def_sh_tex_voronoi(StructRNA *srna) { static EnumPropertyItem prop_distance_items[] = { diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 924796a866c..1a0b4609471 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -124,6 +124,7 @@ DefNode(ShaderNode, SH_NODE_CURVE_FLOAT, def_float_curve, "CUR DefNode(ShaderNode, SH_NODE_COMBINE_COLOR, def_sh_combsep_color, "COMBINE_COLOR", CombineColor, "Combine Color", "Create a color from individual components using multiple models") DefNode(ShaderNode, SH_NODE_SEPARATE_COLOR, def_sh_combsep_color, "SEPARATE_COLOR", SeparateColor, "Separate Color", "Split a color into its individual components using multiple models") DefNode(ShaderNode, SH_NODE_MIX, def_sh_mix, "MIX", Mix, "Mix", "Mix values by a factor") +DefNode(ShaderNode, SH_NODE_TEX_GABOR, def_sh_tex_gabor, "TEX_GABOR", TexGabor, "Gabor Texture", "Generate Gabor wavelet noise" ) DefNode(CompositorNode, CMP_NODE_VIEWER, def_cmp_viewer, "VIEWER", Viewer, "Viewer", "" ) DefNode(CompositorNode, CMP_NODE_RGB, 0, "RGB", RGB, "RGB", "" ) diff --git a/source/blender/nodes/shader/CMakeLists.txt b/source/blender/nodes/shader/CMakeLists.txt index 86e73ae527c..759bc2b0658 100644 --- a/source/blender/nodes/shader/CMakeLists.txt +++ b/source/blender/nodes/shader/CMakeLists.txt @@ -95,6 +95,7 @@ set(SRC nodes/node_shader_tex_checker.cc nodes/node_shader_tex_coord.cc nodes/node_shader_tex_environment.cc + nodes/node_shader_tex_gabor.cc nodes/node_shader_tex_gradient.cc nodes/node_shader_tex_image.cc nodes/node_shader_tex_magic.cc diff --git a/source/blender/nodes/shader/node_shader_register.cc b/source/blender/nodes/shader/node_shader_register.cc index d2f7214e51b..b814033e54d 100644 --- a/source/blender/nodes/shader/node_shader_register.cc +++ b/source/blender/nodes/shader/node_shader_register.cc @@ -84,6 +84,7 @@ void register_shader_nodes() register_node_type_sh_tex_checker(); register_node_type_sh_tex_coord(); register_node_type_sh_tex_environment(); + register_node_type_sh_tex_gabor(); register_node_type_sh_tex_gradient(); register_node_type_sh_tex_ies(); register_node_type_sh_tex_image(); diff --git a/source/blender/nodes/shader/node_shader_register.hh b/source/blender/nodes/shader/node_shader_register.hh index 4d75586983f..215d2f8a813 100644 --- a/source/blender/nodes/shader/node_shader_register.hh +++ b/source/blender/nodes/shader/node_shader_register.hh @@ -83,6 +83,7 @@ void register_node_type_sh_tex_brick(); void register_node_type_sh_tex_checker(); void register_node_type_sh_tex_coord(); void register_node_type_sh_tex_environment(); +void register_node_type_sh_tex_gabor(); void register_node_type_sh_tex_gradient(); void register_node_type_sh_tex_ies(); void register_node_type_sh_tex_image(); diff --git a/source/blender/nodes/shader/nodes/node_shader_tex_gabor.cc b/source/blender/nodes/shader/nodes/node_shader_tex_gabor.cc new file mode 100644 index 00000000000..34228bce3d3 --- /dev/null +++ b/source/blender/nodes/shader/nodes/node_shader_tex_gabor.cc @@ -0,0 +1,732 @@ +/* SPDX-FileCopyrightText: 2005 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Gabor Noise + * + * Based on: Blender patch D287 & D3495 + * With additional controls for kernel variance. + * + * Adapted from Open Shading Language implementation. + * Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al. + * All Rights Reserved. + * + * OSL Gabor noise originally based on: + * Lagae, Lefebvre, Drettakis and Dutré, 2009. Procedural noise using sparse Gabor convolution + * Lagae, A. and Drettakis, G. 2011. Filtering Solid Gabor Noise + * + * Phasor noise option is based on: + * Tricard et al. 2019. Procedural Phasor Noise + */ + +/* See GLSL implementation for code comments. */ + +#include "node_shader_util.hh" +#include "node_util.hh" + +#include "BKE_texture.h" + +#include "BLI_hash.hh" +#include "BLI_math_matrix.h" +#include "BLI_math_rotation.h" +#include "BLI_math_vector.hh" +#include "BLI_noise.hh" + +#include "NOD_multi_function.hh" + +#include "UI_interface.hh" +#include "UI_resources.hh" + +#include "node_shader_util.hh" + +namespace blender::nodes::node_shader_tex_gabor_cc { + +NODE_STORAGE_FUNCS(NodeTexGabor) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.is_function_node(); + b.use_custom_socket_order(); + b.add_output("Value").no_muted_links(); + b.add_input("Vector").implicit_field(implicit_field_inputs::position); + b.add_input("Scale").min(-1000.0f).max(1000.0f).default_value(5.0f); + b.add_input("Gain").min(-10.0f).max(10.0f).default_value(1.0f); + PanelDeclarationBuilder &fractal = b.add_panel("Fractal").default_closed(true).draw_buttons( + [](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { + uiItemR(layout, ptr, "use_origin_offset", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); + }); + fractal.add_input("Detail").min(0.0f).max(15.0f).default_value(0.0f).description( + "Number of noise octaves, high values are slower to compute"); + fractal.add_input("Roughness") + .min(0.0f) + .max(1.0f) + .default_value(0.5f) + .subtype(PROP_FACTOR); + fractal.add_input("Scale Lacunarity") + .min(-10.0f) + .max(10.0f) + .default_value(2.0f) + .description("The difference between the scale of each consecutive octave"); + fractal.add_input("Frequency Lacunarity") + .min(-10.0f) + .max(10.0f) + .default_value(2.0f) + .description("The difference between the kernel frequency of each consecutive octave"); + fractal.add_input("Rotation Lacunarity") + .subtype(PROP_ANGLE) + .description( + "The difference between the kernel rotation of each consecutive octave, does not work " + "when Anisotropic is set to 1"); + PanelDeclarationBuilder &kernel = b.add_panel("Kernel").default_closed(true); + kernel.add_input("Frequency") + .min(-100.0f) + .max(100.0f) + .default_value(4.0f) + .description("Frequency for the kernel shape, higher values provides more detail"); + kernel.add_input("Radius") + .min(0.0f) + .max(1.0f) + .default_value(1.0f) + .subtype(PROP_FACTOR) + .description("Controls the radius of the kernel, values over 1 may produce artefacts"); + kernel.add_input("Impulses") + .min(0.0f) + .max(16.0f) + .default_value(2.0f) + .description("Controls the amount of kernel impulses, high values are slower to compute"); + kernel.add_input("Phase Offset") + .subtype(PROP_ANGLE) + .description("Kernel shape phase offset"); + kernel.add_input("Phase Variance") + .min(0.0f) + .max(1.0f) + .default_value(1.0f) + .subtype(PROP_FACTOR); + kernel.add_input("Cell Randomness") + .min(0.0f) + .max(1.0f) + .default_value(1.0f) + .subtype(PROP_FACTOR); + PanelDeclarationBuilder &aniso = b.add_panel("Anisotropy").default_closed(false); + aniso.add_input("Rotation") + .subtype(PROP_ANGLE) + .description("Kernel shape rotation"); + aniso.add_input("Rotation Variance") + .subtype(PROP_ANGLE) + .description("Rotation randomness"); + aniso.add_input("Tilt Randomness") + .min(0.0f) + .max(1.0f) + .default_value(1.0f) + .subtype(PROP_FACTOR); + aniso.add_input("Anisotropic Factor") + .min(0.0f) + .max(1.0f) + .default_value(0.0f) + .subtype(PROP_FACTOR) + .description("Mix between Isotropic and fixed Anisotropic control"); + aniso.add_input("Direction") + .description("Direction and magnitude of the anisotropy") + .default_value({0.0f, 0.0f, 1.0f}) + .min(-1.0f) + .max(1.0f) + .subtype(PROP_DIRECTION); +} + +static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) +{ + uiLayout *col; + uiLayout *split; + col = uiLayoutColumn(layout, true); + split = uiLayoutSplit(col, 0.33f, true); + uiItemR(split, ptr, "gabor_dimensions", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); + uiItemR(split, ptr, "mode", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); + + col = uiLayoutColumn(layout, true); + uiItemR(col, ptr, "periodic", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); + uiItemR(col, ptr, "normalize", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE); +} + +static void node_init(bNodeTree * /*ntree*/, bNode *node) +{ + NodeTexGabor *tex = MEM_cnew(__func__); + BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT); + BKE_texture_colormapping_default(&tex->base.color_mapping); + + tex->mode = SHD_GABOR_MODE_GABOR; + tex->periodic = 0; + tex->normalize = 1; + tex->dimensions = 2; + tex->use_origin_offset = 1; + node->storage = tex; +} + +static int node_shader_gpu_tex_gabor(GPUMaterial *mat, + bNode *node, + bNodeExecData * /*execdata*/, + GPUNodeStack *in, + GPUNodeStack *out) +{ + node_shader_gpu_default_tex_coord(mat, node, &in[0].link); + node_shader_gpu_tex_mapping(mat, node, in, out); + + const NodeTexGabor &storage = node_storage(*node); + const float dimensions = storage.dimensions; + const float mode = storage.mode; + const float periodic = storage.periodic; + const float use_normalize = storage.normalize; + const float use_origin_offset = storage.use_origin_offset; + + return GPU_stack_link(mat, + node, + "node_tex_gabor", + in, + out, + GPU_constant(&dimensions), + GPU_constant(&mode), + GPU_constant(&use_normalize), + GPU_constant(&periodic), + GPU_constant(&use_origin_offset)); +} + +#define GABOR_SEED 1259 + +typedef struct GaborParams { + float frequency; + float radius; + float impulses; + float phase; + float phase_variance; + float rotation; + float init_rotation; + float rot_variance; + float tilt_randomness; + float cell_randomness; + float anisotropy; + int mode; + float3 direction; +} GaborParams; + +typedef struct FractalParams { + float octaves; + float roughness; + float scl_lacunarity; + float fre_lacunarity; + float rot_lacunarity; +} FractalParams; + +int impulses_per_cell(const float3 cell, const float impulses, const int seed) +{ + const int n = int(impulses); + const float rmd = impulses - math::floor(impulses); + if (rmd > 0.0f) { + const float t = noise::hash_float_to_float(float4(cell, float(seed - GABOR_SEED))); + return (t <= rmd) ? n + 1 : n; + } + return n; +} + +static float3 gabor_kernel(const GaborParams gp, + const float3 omega, + const float phi, + const float3 position, + const float dv) +{ + const float g = math::cos(float(M_PI) * math::sqrt(dv)) * 0.5f + 0.5f; + float3 r = float3(0.0f); + float h; + + if (gp.mode == SHD_GABOR_MODE_GABOR) { + h = gp.frequency * math::dot(omega, position) + phi; + r = float3(math::cos(h), 0.0f, 0.0f); + } + else if (gp.mode == SHD_GABOR_MODE_PHASOR) { + h = gp.frequency * math::dot(omega, position) + phi; + r = float3(math::cos(h), math::sin(h), 0.0f); + } + else if (gp.mode == SHD_GABOR_MODE_CROSS) { + h = gp.frequency * math::length(omega * position) + phi; + r = float3(math::cos(h), 0.0f, 0.0f); + } + else if (gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) { + h = gp.frequency * math::length(omega * position) + phi; + r = float3(math::cos(h), math::sin(h), 0.0f); + } + else if (gp.mode == SHD_GABOR_MODE_RING) { + h = math::cos(gp.frequency * math::dot(omega, position) + phi) + + math::cos(gp.frequency * math::length(position) + phi); + r = float3(h, 0.0f, 0.0f) * 0.5f; + } + else if (gp.mode == SHD_GABOR_MODE_PHASOR_RING) { + h = math::cos(gp.frequency * math::dot(omega, position) + phi) + + math::cos(gp.frequency * math::length(position) + phi); + const float h2 = math::sin(gp.frequency * math::dot(omega, position) + phi) + + math::sin(gp.frequency * math::length(position) + phi); + r = float3(h, h2, 0.0f) * 0.5f; + } + else if (gp.mode == SHD_GABOR_MODE_SQUARE) { + const float3 positionyxz = float3(position.y, position.x, position.z); + h = math::cos(gp.frequency * math::dot(omega, position) + phi) + + math::cos(gp.frequency * math::dot(omega, positionyxz) + phi); + r = float3(h, 0.0f, 0.0f) * 0.5f; + } + else if (gp.mode == SHD_GABOR_MODE_PHASOR_SQUARE) { + const float3 positionyxz = float3(position.y, position.x, position.z); + h = math::cos(gp.frequency * math::dot(omega, position) + phi) + + math::cos(gp.frequency * math::dot(omega, positionyxz) + phi); + const float h2 = math::sin(gp.frequency * math::dot(omega, position) + phi) + + math::sin(gp.frequency * math::dot(omega, positionyxz) + phi); + r = float3(h, h2, 0.0f) * 0.5f; + } + + return r * g; +} + +static float3 rotate_z(const float3 &vector, float angle) +{ + float mat[3][3]; + float3 result = vector; + eul_to_mat3(mat, float3(0.0f, 0.0f, angle)); + mul_m3_v3(mat, result); + return result; +} + +static float3 gabor_sample(const GaborParams gp, const float3 cell, const int seed, float &phi) +{ + const float3 rand_values = noise::hash_float_to_float3(float4(cell, float(seed))) * 2.0f - 1.0f; + const float pvar = math::interpolate(0.0f, rand_values.z, gp.phase_variance); + phi = 2.0f * float(M_PI) * pvar + gp.phase; + + const float omega_t = float(M_PI) * (rand_values.x) * gp.rot_variance - gp.rotation - + gp.init_rotation; + const float cos_omega_p = math::clamp(rand_values.y * gp.tilt_randomness, -1.0f, 1.0f); + const float sin_omega_p = math::sqrt(1.0f - cos_omega_p * cos_omega_p); + const float sin_omega_t = math::sin(omega_t); + const float cos_omega_t = math::cos(omega_t); + + return math::interpolate( + math::normalize(float3(cos_omega_t * sin_omega_p, sin_omega_t * sin_omega_p, cos_omega_p)), + math::normalize(rotate_z(gp.direction, -gp.rotation)), + gp.anisotropy); +} + +/* Generate noise based on the cell position. */ +static float3 gabor_cell_3d(GaborParams &gp, float3 cell, float3 cell_position, int seed) +{ + int num_impulses = impulses_per_cell(cell, gp.impulses, seed); + float3 sum = float3(0.0f); + for (int i = 0; i < num_impulses; ++i) { + float3 rand_position = math::interpolate( + float3(0.0f), + noise::hash_float_to_float3(float4(cell, float(seed + i * GABOR_SEED))), + gp.cell_randomness); + + const float3 kernel_position = (cell_position - rand_position); + + const float dv = math::dot(kernel_position, kernel_position) / gp.radius; + + if (dv <= 1.0f) { + float phi; + float3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, phi); + sum += gabor_kernel(gp, omega, phi, kernel_position, dv); + } + } + return sum; +} + +static float3 gabor_cell_2d(GaborParams &gp, float3 cell, float3 cell_position, int seed) +{ + int num_impulses = impulses_per_cell(cell, gp.impulses, seed); + float3 sum = float3(0.0f); + for (int i = 0; i < num_impulses; ++i) { + float3 rand_position = math::interpolate( + float3(0.0f), + noise::hash_float_to_float3(float4(cell, float(seed + i * GABOR_SEED))), + gp.cell_randomness); + rand_position.z = 0.0f; + + float3 kernel_position = (cell_position - rand_position); + + const float dv = math::dot(kernel_position, kernel_position) / gp.radius; + + if (dv <= 1.0f) { + float phi; + float3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, phi); + sum += gabor_kernel(gp, omega, phi, kernel_position, dv); + } + } + return sum; +} + +/* Wrap value. */ +static float gabor_coord_wrap(const float a, const float b) +{ + return (b != 0.0f) ? a - b * math::floor(a / b) : 0.0f; +} + +/* 3*3 cell grid */ +static float3 gabor_grid_3d( + GaborParams &gp, const float3 p, const float scale, const int periodic, const int seed) +{ + const float3 coords = p * scale; + const float3 position = math::floor(coords); + const float3 local_position = coords - position; + + float3 sum = float3(0.0f); + for (int k = -1; k <= 1; k++) { + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + const float3 cell_offset = float3(i, j, k); + + /* Skip this cell if it's too far away to contribute - Bruemmer.osl */ + const float3 Pr = (float3(i > 0, j > 0, k > 0) - local_position) * cell_offset; + if (math::dot(Pr, Pr) >= 1.0f) { + continue; + } + + float3 cell = position + cell_offset; + const float3 cell_position = local_position - cell_offset; + + if (periodic) { + cell[0] = gabor_coord_wrap(cell[0], scale); + cell[1] = gabor_coord_wrap(cell[1], scale); + cell[2] = gabor_coord_wrap(cell[2], scale); + } + + sum += gabor_cell_3d(gp, cell, cell_position, seed); + } + } + } + return sum; +} + +/* 2*2 cell grid */ +static float3 gabor_grid_2d( + GaborParams &gp, const float3 p, const float scale, const int periodic, const int seed) +{ + const float3 coords = float3(p.x, p.y, 0.0f) * scale; + const float3 position = math::floor(coords); + const float3 local_position = coords - position; + + float3 sum = float3(0.0f); + for (int j = -1; j <= 1; j++) { + for (int i = -1; i <= 1; i++) { + const float3 cell_offset = float3(i, j, 0.0f); + + /* Skip this cell if it's too far away to contribute - Bruemmer.osl */ + const float3 Pr = (float3(i > 0, j > 0, 0) - local_position) * cell_offset; + if (math::dot(Pr, Pr) >= 1.0f) { + continue; + } + + float3 cell = position + cell_offset; + const float3 cell_position = local_position - cell_offset; + + if (periodic) { + cell[0] = gabor_coord_wrap(cell[0], scale); + cell[1] = gabor_coord_wrap(cell[1], scale); + } + + sum += gabor_cell_2d(gp, cell, cell_position, seed); + } + } + return sum; +} + +/* Fractal gabor noise. Layered noise with octaves, lacunarity, frequency and roughness control. */ +static float gabor_fractal_noise(FractalParams fp, + GaborParams &gp, + const float3 p, + const float scale, + const int dimensions, + const int periodic, + const int use_origin_offset) +{ + float fscale = 1.0f; + float amp = 1.0f; + float maxamp = 0.0f; + float3 sum = float3(0.0f); + float octaves = math::clamp(fp.octaves, 0.0f, 15.0f); + if (fp.roughness == 0.0f) { + octaves = 0.0f; + } + const int n = int(octaves); + for (int i = 0; i <= n; i++) { + const int seed = use_origin_offset * i * GABOR_SEED; + const float3 t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) : + gabor_grid_2d(gp, fscale * p, scale, periodic, seed); + gp.frequency *= fp.fre_lacunarity; + gp.rotation -= fp.rot_lacunarity; + sum += t * amp; + maxamp += amp; + amp *= fp.roughness; + fscale *= fp.scl_lacunarity; + } + float rmd = octaves - math::floor(octaves); + if (rmd != 0.0f) { + const int seed = use_origin_offset * (n + 1) * GABOR_SEED; + const float3 t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) : + gabor_grid_2d(gp, fscale * p, scale, periodic, seed); + const float3 sum2 = sum + t * amp; + sum = ((1.0f - rmd) * sum + rmd * sum2); + } + sum /= maxamp; + + if (gp.mode == SHD_GABOR_MODE_PHASOR || gp.mode == SHD_GABOR_MODE_PHASOR_RING || + gp.mode == SHD_GABOR_MODE_PHASOR_CROSS || gp.mode == SHD_GABOR_MODE_PHASOR_SQUARE) + { + const float pn = math::atan2(sum.y, sum.x) / float(M_PI); + return pn; + } + else { + return sum.x; + } +} + +/* Set parameters used by Gabor noise. */ +static GaborParams gabor_parameters(float3 direction, + float frequency, + float radius, + float impulses, + float phase, + float phase_variance, + float rotation, + float rot_variance, + float tilt_randomness, + float cell_randomness, + float anisotropy, + int mode) +{ + GaborParams gp; + gp.impulses = math::clamp(impulses, 0.0001f, 32.0f); + gp.rot_variance = rot_variance; + gp.anisotropy = anisotropy; + gp.mode = mode; + gp.direction = direction; + gp.phase = phase; + gp.rotation = 0.0f; + gp.init_rotation = rotation; + gp.phase_variance = phase_variance; + gp.tilt_randomness = tilt_randomness; + gp.cell_randomness = cell_randomness; + gp.radius = radius; + gp.frequency = frequency * float(M_PI); + return gp; +} + +static float gabor_noise(const float3 p, + const float3 direction, + const float scale, + const float frequency, + const float detail, + const float roughness, + const float scl_lacunarity, + const float fre_lacunarity, + const float rot_lacunarity, + const float gain, + const float radius, + const float impulses, + const float phase, + const float phase_variance, + const float rotation, + const float rot_variance, + const float tilt_randomness, + const float cell_randomness, + const float anisotropy, + const int dimensions, + const int mode, + const int use_normalize, + const int periodic, + const int use_origin_offset) +{ + if (impulses == 0.0f || gain == 0.0f || radius <= 0.0f || scale == 0.0f) { + return (use_normalize) ? 0.5f : 0.0f; + } + + FractalParams fp; + fp.roughness = roughness; + fp.octaves = detail; + fp.scl_lacunarity = scl_lacunarity; + fp.fre_lacunarity = fre_lacunarity; + fp.rot_lacunarity = rot_lacunarity; + + GaborParams gp = gabor_parameters(direction, + frequency, + radius, + impulses, + phase, + phase_variance, + rotation, + rot_variance, + tilt_randomness, + cell_randomness, + anisotropy, + mode); + + float g = gabor_fractal_noise(fp, gp, p, scale, dimensions, periodic, use_origin_offset) * gain; + + if (gp.mode == SHD_GABOR_MODE_GABOR || gp.mode == SHD_GABOR_MODE_RING || + gp.mode == SHD_GABOR_MODE_CROSS || gp.mode == SHD_GABOR_MODE_SQUARE) + { + const float impulse_scale = impulses > 1.0 ? 1.2613446229f * math::sqrt(gp.impulses) : + 1.2613446229f; + g = g / impulse_scale; + } + + if (use_normalize) { + return math::clamp(0.5f * g + 0.5f, 0.0f, 1.0f); + } + return g; +} + +static mf::Signature gabor_signature(const char *name) +{ + mf::Signature signature; + mf::SignatureBuilder builder{name, signature}; + builder.single_input("Vector"); + builder.single_input("Scale"); + builder.single_input("Gain"); + builder.single_input("Detail"); + builder.single_input("Roughness"); + builder.single_input("Scale Lacunarity"); + builder.single_input("Frequency Lacunarity"); + builder.single_input("Rotation Lacunarity"); + builder.single_input("Frequency"); + builder.single_input("Radius"); + builder.single_input("Impulses"); + builder.single_input("Phase Offset"); + builder.single_input("Phase Variance"); + builder.single_input("Cell Randomness"); + builder.single_input("Rotation"); + builder.single_input("Rotation Variance"); + builder.single_input("Direction"); + builder.single_input("Tilt Randomness"); + builder.single_input("Anisotropic Factor"); + builder.single_output("Value"); + return signature; +} + +class GaborNoiseFunction : public mf::MultiFunction { + private: + int dimensions_; + int mode_; + int periodic_; + int normalize_; + int use_origin_offset_; + + public: + GaborNoiseFunction( + int dimensions, int kernel, int periodic, int normalize, int use_origin_offset) + : dimensions_(dimensions), + mode_(kernel), + periodic_(periodic), + normalize_(normalize), + use_origin_offset_(use_origin_offset) + + { + static mf::Signature signature = gabor_signature("GaborNoise"); + this->set_signature(&signature); + } + + void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override + { + int param = 0; + const VArray &vector = params.readonly_single_input(param++, "Vector"); + const VArray &scale = params.readonly_single_input(param++, "Scale"); + const VArray &gain = params.readonly_single_input(param++, "Gain"); + const VArray &detail = params.readonly_single_input(param++, "Detail"); + const VArray &roughness = params.readonly_single_input(param++, "Roughness"); + const VArray &scale_lacunarity = params.readonly_single_input( + param++, "Scale Lacunarity"); + const VArray &freq_lacunarity = params.readonly_single_input( + param++, "Frequency Lacunarity"); + const VArray &rot_lacunarity = params.readonly_single_input( + param++, "Rotation Lacunarity"); + const VArray &frequency = params.readonly_single_input(param++, "Frequency"); + const VArray &radius = params.readonly_single_input(param++, "Radius"); + const VArray &impulses = params.readonly_single_input(param++, "Impulses"); + + const VArray &phase = params.readonly_single_input(param++, "Phase Offset"); + const VArray &phase_variance = params.readonly_single_input(param++, + "Phase Variance"); + const VArray &cell_randomness = params.readonly_single_input(param++, + "Cell Randomness"); + const VArray &rotation = params.readonly_single_input(param++, "Rotation"); + const VArray &rot_variance = params.readonly_single_input(param++, + "Rotation Variance"); + const VArray &tilt_randomness = params.readonly_single_input(param++, + "Tilt Randomness"); + const VArray &anisotropy = params.readonly_single_input(param++, + "Anisotropic Factor"); + const VArray &direction = params.readonly_single_input(param++, "Direction"); + + MutableSpan r_value = params.uninitialized_single_output_if_required(param++, + "Value"); + + mask.foreach_index([&](const int64_t i) { + r_value[i] = gabor_noise(vector[i], + direction[i], + scale[i], + frequency[i], + detail[i], + roughness[i], + scale_lacunarity[i], + freq_lacunarity[i], + rot_lacunarity[i], + gain[i], + radius[i], + impulses[i], + phase[i], + phase_variance[i], + rotation[i], + rot_variance[i], + tilt_randomness[i], + cell_randomness[i], + anisotropy[i], + dimensions_, + mode_, + normalize_, + periodic_, + use_origin_offset_); + }); + } + + ExecutionHints get_execution_hints() const override + { + ExecutionHints hints; + hints.allocates_array = false; + hints.min_grain_size = 100; + return hints; + } +}; + +static void build_multi_function(NodeMultiFunctionBuilder &builder) +{ + const NodeTexGabor &storage = node_storage(builder.node()); + builder.construct_and_set_matching_fn(storage.dimensions, + storage.mode, + storage.periodic, + storage.normalize, + storage.use_origin_offset); +} + +} // namespace blender::nodes::node_shader_tex_gabor_cc + +void register_node_type_sh_tex_gabor() +{ + namespace file_ns = blender::nodes::node_shader_tex_gabor_cc; + + static bNodeType ntype; + + sh_fn_node_type_base(&ntype, SH_NODE_TEX_GABOR, "Gabor Texture", NODE_CLASS_TEXTURE); + ntype.declare = file_ns::node_declare; + ntype.draw_buttons = file_ns::node_layout; + ntype.initfunc = file_ns::node_init; + ntype.gpu_fn = file_ns::node_shader_gpu_tex_gabor; + node_type_storage( + &ntype, "NodeTexGabor", node_free_standard_storage, node_copy_standard_storage); + blender::bke::node_type_size_preset(&ntype, blender::bke::eNodeSizePreset::MIDDLE); + ntype.build_multi_function = file_ns::build_multi_function; + + nodeRegisterType(&ntype); +}