Node: Gabor Noise Texture #110802

Open
Charlie Jolly wants to merge 68 commits from CharlieJolly/blender:gabor into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
11 changed files with 1305 additions and 894 deletions
Showing only changes of commit a243de001a - Show all commits

View File

@ -940,7 +940,9 @@ static ShaderNode *add_node(Scene *scene,
GaborTextureNode *gabor = graph->create_node<GaborTextureNode>();
gabor->set_kernel_shape((NodeGaborKernel)b_gabor_node.kernel());
gabor->set_anisotropic((NodeGaborAnisotropic)b_gabor_node.anisotropic());
gabor->set_dimensions(b_gabor_node.gabor_dimensions());
gabor->set_periodic(b_gabor_node.periodic());
gabor->set_use_normalize(b_gabor_node.normalize());
BL::TexMapping b_texture_mapping(b_gabor_node.texture_mapping());
get_tex_mapping(gabor, b_texture_mapping);
node = gabor;

View File

@ -13,6 +13,7 @@
* Lagae, A. and Drettakis, G. 2011. Filtering Solid Gabor Noise.
*/
#include "node_hash.h"
#include "node_noise.h"
#include "stdcycles.h"
#include "vector2.h"
@ -24,70 +25,55 @@
# define M_EPI 0.0432139182637722
#endif
int gabor_rng_seed(point cell, int seed)
{
int chash = hash(floor(cell), float(seed));
if (chash == 0)
chash = 1;
return chash * 1519588931;
}
float gabor_rng_uniform(output int rng)
{
float int_max_inv = 1.0 / 2147483647.0;
float res = rng * int_max_inv * 0.5 + 0.5;
rng *= 1519588931;
return res;
}
int gabor_rng_poisson(output int rng, float mean)
{
if (mean >= 1.0) {
return int(mean);
}
float g = exp(-mean);
int em = 0;
float t = gabor_rng_uniform(rng);
while (t > g) {
++em;
t *= gabor_rng_uniform(rng);
}
return em;
}
struct GaborParams {
float detail;
float variance;
float weight;
float randomness;
float impulses;
float frequency;
float lacunarity;
float phase;
float rotation;
float radius;
float base_frequency;
float octaves;
float roughness;
float scl_lacunarity;
float fre_lacunarity;
float rot_lacunarity;
float bandwidth;
int periodic;
string kernel;
float radius;
float impulses;
float phase;
float phase_variance;
float cell_randomness;
float rotation;
float rot_variance;
string kernel_shape;
string anisotropic;
vector direction;
};
int impulses_per_cell(int seed, vector3 cell, float impulses)
{
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 - 1259)));
if (t <= rmd) {
return n + 1;
}
return n;
}
return n;
}
vector3 gabor_kernel(GaborParams gp, float freq, point omega, float phi, point position, float g)
{
float h;
if (gp.kernel == "phasor") { /* SHD_GABOR_KERNEL_PHASOR */
if (gp.kernel_shape == "phasor") { /* SHD_GABOR_KERNEL_PHASOR */
float phase = freq * dot(omega, position) + phi;
return vector3(cos(phase), sin(phase), 0.0) * g;
}
else if (gp.kernel == "square") { /* SHD_GABOR_KERNEL_SQUARE */
else if (gp.kernel_shape == "square") { /* SHD_GABOR_KERNEL_SQUARE */
vector3 positionyx = vector3(position.y, position.x, position.z);
h = cos(freq * dot(omega, position) + phi) + cos(freq * dot(omega, positionyx) + phi);
h = (cos(freq * dot(omega, position) + phi) + cos(freq * dot(omega, positionyx) + phi)) * 0.5;
}
else if (gp.kernel == "cross") { /* SHD_GABOR_KERNEL_CROSS */
else if (gp.kernel_shape == "cross") { /* SHD_GABOR_KERNEL_CROSS */
h = cos(freq * length(position * omega) + phi);
}
else if (gp.kernel == "ring" /* SHD_GABOR_KERNEL_RING */) {
else if (gp.kernel_shape == "ring" /* SHD_GABOR_KERNEL_RING */) {
h = cos(freq * dot(position, omega) + phi) - cos(freq * length(position) + phi);
}
else {
@ -104,21 +90,15 @@ void gabor_sample(GaborParams gp,
output vector omega,
output float phi)
{
float pvar = mix(0.0, prand * 2.0 - 1.0, gp.weight);
float pvar = mix(0.0, prand * 2.0 - 1.0, gp.phase_variance);
phi = M_2PI * pvar + gp.phase;
float ovar = M_PI * (orand * 2.0 - 1.0) * gp.variance;
float omega_t = ovar - gp.rotation;
if (gp.anisotropic == "aniso") { /* ANISO */
omega = normalize(gp.direction);
}
else if (gp.anisotropic == "hybrid") { /* HYBRID */
float sin_omega_t = sin(omega_t);
float cos_omega_t = cos(omega_t);
omega = length(gp.direction) * point(cos_omega_t, sin_omega_t, 0.0);
omega = gp.direction;
}
else { /* ISO */
float ovar = M_PI * (orand * 2.0 - 1.0) * gp.rot_variance;
float omega_t = ovar - gp.rotation;
float cos_omega_p = 1.0 - 2.0 * orand2;
float sin_omega_p = sqrt(1.0 - cos_omega_p * cos_omega_p);
float sin_omega_t = sin(omega_t);
@ -127,61 +107,64 @@ void gabor_sample(GaborParams gp,
}
}
vector3 gabor_fractal(GaborParams gp, float dv, point omega, float phi, point kernel_position)
vector3 gabor_cell_3d(output GaborParams gp, point cell, point cell_position, int seed)
{
float freq = gp.frequency;
vector3 sum = vector3(0.0);
float g = (1.0 + M_EPI) * (exp(-M_PI * gp.bandwidth * gp.bandwidth * dv) - M_EPI);
float amp = 1.0;
float maxamp = 0.0;
float octaves = clamp(gp.detail, 0.0, 15.0);
int n = int(octaves);
for (int i = 0; i <= n; i++) {
vector3 t = gabor_kernel(gp, freq, omega, phi, kernel_position, g);
sum += t * amp;
maxamp += amp;
freq *= gp.lacunarity;
}
float rmd = octaves - floor(octaves);
if (rmd > 0.0) {
vector3 t = gabor_kernel(gp, freq, omega, phi, kernel_position, g);
vector3 sum2 = sum + t * amp;
sum /= maxamp;
sum2 /= maxamp + amp;
return (1.0 - rmd) * sum + rmd * sum2;
}
else {
return sum / maxamp;
}
}
vector3 gabor_cell_3d(output GaborParams gp, point cell, point cell_position)
{
int rng = gabor_rng_seed(cell, 0);
int num_impulses = gabor_rng_poisson(rng, gp.impulses);
int num_impulses = impulses_per_cell(seed, cell, gp.impulses);
vector3 sum = vector3(0.0);
for (int i = 0; i < num_impulses; ++i) {
float xrand = gabor_rng_uniform(rng);
float yrand = gabor_rng_uniform(rng);
float zrand = gabor_rng_uniform(rng);
xrand = mix(0.0, xrand, gp.randomness);
yrand = mix(0.0, yrand, gp.randomness);
zrand = mix(0.0, zrand, gp.randomness);
vector3 rand_position = vector3(0.0);
if (gp.cell_randomness > 0.0) {
rand_position = mix(
rand_position,
hash_vector4_to_color(vector4(cell[0], cell[1], cell[2], float(seed + i * 1259))),
gp.cell_randomness);
}
point kernel_position = (cell_position - point(xrand, yrand, zrand)) * gp.radius;
point kernel_position = (cell_position - rand_position);
float dv = dot(kernel_position, kernel_position);
float orand = gabor_rng_uniform(rng);
float prand = gabor_rng_uniform(rng);
float orand2 = gabor_rng_uniform(rng);
float dv = dot(kernel_position, kernel_position) / gp.radius;
if (dv < gp.radius * gp.radius) {
if (dv <= 1.0) {
vector3 omega;
float phi;
vector3 rand_values = hash_vector4_to_color(
vector4(cell[0], cell[1], cell[2], float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, orand, orand2, prand, omega, phi);
gabor_sample(gp, rand_values[0], rand_values[1], rand_values[2], omega, phi);
float g = (1.0 + M_EPI) * (exp(-M_PI * dv) - M_EPI);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g) * gp.bandwidth;
}
}
return sum;
}
sum += gabor_fractal(gp, dv, omega, phi, kernel_position);
vector3 gabor_cell_2d(output GaborParams gp, point cell, point cell_position, int seed)
{
int num_impulses = impulses_per_cell(seed, cell, gp.impulses);
vector3 sum = vector3(0.0);
for (int i = 0; i < num_impulses; ++i) {
vector3 rand_position = vector3(0.0);
if (gp.cell_randomness > 0.0) {
rand_position = mix(
rand_position,
hash_vector4_to_color(vector4(cell[0], cell[1], cell[2], float(seed + i * 1259))),
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) {
vector3 omega;
float phi;
vector3 rand_values = hash_vector4_to_color(
vector4(cell[0], cell[1], cell[2], float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, rand_values[0], rand_values[1], rand_values[2], omega, phi);
float g = (1.0 + M_EPI) * (exp(-M_PI * dv) - M_EPI);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g) * gp.bandwidth;
}
}
return sum;
@ -192,7 +175,7 @@ float gabor_wrap(float a, float b)
return (b != 0.0) ? a - b * floor(a / b) : 0.0;
}
float gabor_grid_3d(output GaborParams gp, point p, float scale)
float gabor_grid_3d(output GaborParams gp, point p, float scale, int seed, int periodic)
{
point coords = p * scale;
point position = floor(coords);
@ -206,126 +189,220 @@ float gabor_grid_3d(output GaborParams gp, point p, float scale)
point cell = position + cell_offset;
point cell_position = local_position - cell_offset;
if (gp.periodic == 1) {
if (periodic == 1) {
cell[0] = gabor_wrap(cell[0], scale);
cell[1] = gabor_wrap(cell[1], scale);
cell[2] = gabor_wrap(cell[2], scale);
}
sum += gabor_cell_3d(gp, cell, cell_position);
sum += gabor_cell_3d(gp, cell, cell_position, seed);
}
}
}
if (gp.kernel == "phasor") {
float pn = atan2(sum.y, sum.x);
return pn * gp.radius;
if (gp.kernel_shape == "phasor") {
float pn = atan2(sum.y, sum.x) / M_PI;
return pn;
}
else {
return sum.x * gp.radius;
return sum.x;
}
}
float gabor_grid_2d(output GaborParams gp, point p, float scale, int seed, int periodic)
{
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;
if (periodic == 1) {
cell[0] = gabor_wrap(cell[0], scale);
cell[1] = gabor_wrap(cell[1], scale);
}
sum += gabor_cell_2d(gp, cell, cell_position, seed);
}
}
if (gp.kernel_shape == "phasor") {
float pn = atan2(sum.y, sum.x) / M_PI;
return pn;
}
else {
return sum.x;
}
}
GaborParams gabor_parameters(vector direction,
float base_frequency,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float bandwidth,
float radius,
float impulses,
float frequency,
float lacunarity,
float phase,
float weight,
float randomness,
float phase_variance,
float cell_randomness,
float rotation,
float variance,
string kernel,
float rot_variance,
string kernel_shape,
string anisotropic,
int periodic)
{
GaborParams gp;
gp.periodic = periodic;
gp.variance = variance;
gp.lacunarity = lacunarity;
gp.roughness = clamp(roughness, 0.0, 1.0);
gp.octaves = clamp(detail, 0.0, 15.0);
gp.impulses = clamp(impulses, 0.0001, 32.0);
gp.rot_variance = rot_variance;
gp.scl_lacunarity = scl_lacunarity;
gp.fre_lacunarity = fre_lacunarity;
gp.rot_lacunarity = rot_lacunarity;
gp.anisotropic = anisotropic;
gp.kernel = kernel;
gp.kernel_shape = kernel_shape;
gp.direction = direction;
gp.phase = phase;
gp.rotation = rotation;
gp.detail = detail;
gp.impulses = clamp(impulses, 0.0001, 32.0);
gp.weight = weight;
gp.randomness = randomness;
float sqrt_pi_over_ln2 = sqrt(M_PI / M_LN2);
float bandwidth_pow = exp2(bandwidth);
gp.bandwidth = ((bandwidth_pow - 1.0) / (bandwidth_pow + 1.0)) * sqrt_pi_over_ln2;
gp.radius = 1.0 / gp.bandwidth;
gp.frequency = frequency * gp.bandwidth;
gp.phase_variance = phase_variance;
gp.cell_randomness = cell_randomness;
gp.bandwidth = bandwidth;
gp.radius = radius;
gp.base_frequency = base_frequency;
return gp;
}
float gabor_fractal_noise(
output GaborParams gp, vector3 p, float scale, string dimensions, int periodic)
{
float fscale = 1.0;
float amp = 1.0;
float maxamp = 0.0;
float sum = 0.0;
float octaves = gp.octaves;
if (gp.roughness == 0.0 || gp.scl_lacunarity == 0.0) {
octaves = 0.0;
}
int n = int(octaves);
for (int i = 0; i <= n; i++) {
float t = (dimensions == "3D") ? gabor_grid_3d(gp, fscale * p, scale, i, periodic) :
gabor_grid_2d(gp, fscale * p, scale, i, periodic);
gp.base_frequency *= gp.fre_lacunarity;
gp.rotation -= gp.rot_lacunarity;
sum += t * amp;
maxamp += amp;
amp *= gp.roughness;
fscale *= gp.scl_lacunarity;
}
float rmd = octaves - floor(octaves);
if (rmd != 0.0) {
float t = (dimensions == "3D") ? gabor_grid_3d(gp, fscale * p, scale, n + 1, periodic) :
gabor_grid_2d(gp, fscale * p, scale, n + 1, periodic);
float sum2 = sum + t * amp;
return mix(sum, sum2, rmd) / maxamp;
}
else {
return sum / maxamp;
}
}
/* Shader */
float gabor_noise(point p,
vector direction,
float scale,
float base_frequency,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float bandwidth,
float radius,
float impulses,
float frequency,
float lacunarity,
float phase,
float weight,
float randomness,
float phase_variance,
float cell_randomness,
float rotation,
float variance,
string kernel,
float rot_variance,
string dimensions,
string kernel_shape,
string anisotropic,
int use_normalize,
int periodic)
{
if (impulses == 0.0) {
return 0.5;
if (impulses == 0.0 || bandwidth == 0.0 || radius == 0.0 || scale == 0.0) {
if (use_normalize == 1) {
return 0.5;
}
return 0.0;
}
GaborParams gp;
gp = gabor_parameters(direction,
base_frequency,
detail,
roughness,
scl_lacunarity,
fre_lacunarity,
rot_lacunarity,
bandwidth,
radius,
impulses,
frequency,
lacunarity,
phase,
weight,
randomness,
phase_variance,
cell_randomness,
rotation,
variance,
kernel,
rot_variance,
kernel_shape,
anisotropic,
periodic);
use_normalize);
return min(max(gabor_grid_3d(gp, p, scale) * 0.5 + 0.5, 0.0), 1.0);
float g = gabor_fractal_noise(gp, p, scale, dimensions, periodic);
float impulse_scale = 1.0; // sqrt(0.75 / pi) = 1.26
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 kernel_shape = "gabor",
string anisotropic = "iso",
int use_normalize = 1,
int periodic = 0,
vector Vector = vector(0, 0, 0),
vector3 Vector = vector(0, 0, 0),
float Scale = 5.0,
float BaseFrequency = 2.0,
float Detail = 0.0,
float Roughness = 0.5,
float ScaleLacunarity = 2.0,
float FrequencyLacunarity = 2.0,
float RotationLacunarity = 0.0,
float Bandwidth = 2.0,
float Impulses = 1.0,
float Frequency = 2.0,
float Lacunarity = 2.0,
float Radius = 1.0,
float Impulses = 2.0,
float Phase = 0.0,
float PhaseRandomness = 1.0,
float PhaseVariance = 1.0,
float CellRandomness = 1.0,
float Rotation = 0.0,
float RotationVariance = 0.0,
vector Direction = vector(0, 0, 0),
vector3 Direction = vector(0, 0, 1),
output float Value = 0.0)
{
vector3 p = Vector;
@ -336,17 +413,23 @@ shader node_gabor_texture(int use_mapping = 0,
Value = gabor_noise(p,
Direction,
Scale,
BaseFrequency,
Detail,
Roughness,
ScaleLacunarity,
FrequencyLacunarity,
RotationLacunarity,
Bandwidth,
Radius,
Impulses,
Frequency,
Lacunarity,
Phase,
PhaseRandomness,
PhaseVariance,
CellRandomness,
Rotation,
RotationVariance,
dimensions,
kernel_shape,
anisotropic,
use_normalize,
periodic);
}

View File

@ -19,59 +19,39 @@ CCL_NAMESPACE_BEGIN
# define M_EPI_F (0.0432139182637722f)
#endif
ccl_device int gabor_rng_seed(float3 cell, int seed)
{
int chash = hash_uint4(__float_as_uint(floorf(cell.x)),
__float_as_uint(floorf(cell.y)),
__float_as_uint(floorf(cell.z)),
__float_as_uint(float(seed)));
if (chash == 0)
chash = 1;
return chash * 1519588931;
}
ccl_device float gabor_rng_uniform(ccl_private int *rng)
{
const float int_max_inv = 1.0f / 2147483647.0f;
float res = (float)*rng * int_max_inv * 0.5f + 0.5f;
*rng *= 1519588931;
return res;
}
ccl_device int gabor_rng_poisson(ccl_private int *rng, float mean)
{
if (mean >= 1.0f) {
return int(mean);
}
float g = expf(-mean);
int em = 0;
float t = gabor_rng_uniform(rng);
while (t > g) {
++em;
t *= gabor_rng_uniform(rng);
}
return em;
}
typedef struct GaborParams {
float detail;
float variance;
float weight;
float randomness;
float impulses;
float frequency;
float lacunarity;
float phase;
float rotation;
float radius;
float base_frequency;
float octaves;
float roughness;
float scl_lacunarity;
float fre_lacunarity;
float rot_lacunarity;
float bandwidth;
int periodic;
float radius;
float impulses;
float phase;
float phase_variance;
float cell_randomness;
float rotation;
float rot_variance;
int kernel_shape;
int anisotropic;
float3 direction;
} GaborParams;
ccl_device float3 gabor_kernel(GaborParams gp, float freq, float3 omega, float phi, float3 position, float g)
ccl_device int impulses_per_cell(int seed, float3 cell, float impulses)
{
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 - 1259)));
return (t <= rmd) ? n + 1 : n;
}
return n;
}
ccl_device float3
gabor_kernel(GaborParams gp, float freq, float3 omega, float phi, float3 position, float g)
{
float h;
@ -81,7 +61,7 @@ ccl_device float3 gabor_kernel(GaborParams gp, float freq, float3 omega, float p
}
else if (gp.kernel_shape == SHD_GABOR_KERNEL_SQUARE) {
float3 positionyx = make_float3(position.y, position.x, position.z);
h = cos(freq * dot(omega, position) + phi) + cos(freq * dot(omega, positionyx) + phi);
h = (cos(freq * dot(omega, position) + phi) + cos(freq * dot(omega, positionyx) + phi)) * 0.5f;
}
else if (gp.kernel_shape == SHD_GABOR_KERNEL_CROSS) {
h = cos(freq * len(position * omega) + phi);
@ -103,21 +83,15 @@ ccl_device void gabor_sample(GaborParams gp,
ccl_private float3 *omega,
ccl_private float *phi)
{
float pvar = mix(0.0f, prand * 2.0f - 1.0f, gp.weight);
float pvar = mix(0.0f, prand * 2.0f - 1.0f, gp.phase_variance);
*phi = M_2PI_F * pvar + gp.phase;
float ovar = M_PI_F * (orand * 2.0f - 1.0f);
float omega_t = ovar * gp.variance - gp.rotation;
if (gp.anisotropic == 1) { /* ANISO */
*omega = normalize(gp.direction);
}
else if (gp.anisotropic == 2) { /* HYBRID */
float sin_omega_t = sin(omega_t);
float cos_omega_t = cos(omega_t);
*omega = len(gp.direction) * make_float3(cos_omega_t, sin_omega_t, 0.0f);
*omega = gp.direction;
}
else { /* ISO */
float ovar = M_PI_F * (orand * 2.0f - 1.0f);
float omega_t = ovar * gp.rot_variance - gp.rotation;
float cos_omega_p = 1.0f - 2.0f * orand2;
float sin_omega_p = sqrtf(1.0f - cos_omega_p * cos_omega_p);
float sin_omega_t = sin(omega_t);
@ -127,62 +101,64 @@ ccl_device void gabor_sample(GaborParams gp,
}
}
ccl_device float3
gabor_fractal(GaborParams gp, float dv, float3 omega, float phi, float3 kernel_position)
ccl_device float3 gabor_cell_3d(GaborParams gp, float3 cell, float3 cell_position, int seed)
{
float freq = gp.frequency;
float3 sum = zero_float3();
float g = (1.0f + M_EPI_F) * (expf(-M_PI_F * gp.bandwidth * gp.bandwidth * dv) - M_EPI_F);
float amp = 1.0f;
float maxamp = 0.0f;
float octaves = clamp(gp.detail, 0.0f, 15.0f);
int n = float_to_int(octaves);
for (int i = 0; i <= n; i++) {
float3 t = gabor_kernel(gp, freq, omega, phi, kernel_position, g);
sum += t * amp;
maxamp += amp;
freq *= gp.lacunarity;
}
float rmd = octaves - floorf(octaves);
if (rmd > 0.0f) {
float3 t = gabor_kernel(gp, freq, omega, phi, kernel_position, g);
float3 sum2 = sum + t * amp;
sum /= maxamp;
sum2 /= maxamp + amp;
return (1.0f - rmd) * sum + rmd * sum2;
}
else {
return sum / maxamp;
}
}
ccl_device float3 gabor_cell_3d(GaborParams gp, float3 cell, float3 cell_position)
{
int rng = gabor_rng_seed(cell, 0);
int num_impulses = gabor_rng_poisson(&rng, gp.impulses);
int num_impulses = impulses_per_cell(seed, cell, gp.impulses);
float3 sum = zero_float3();
for (int i = 0; i < num_impulses; ++i) {
float xrand = gabor_rng_uniform(&rng);
float yrand = gabor_rng_uniform(&rng);
float zrand = gabor_rng_uniform(&rng);
xrand = mix(0.0f, xrand, gp.randomness);
yrand = mix(0.0f, yrand, gp.randomness);
zrand = mix(0.0f, zrand, gp.randomness);
float3 rand_position = zero_float3();
if (gp.cell_randomness > 0.0f) {
rand_position = mix(
rand_position,
hash_float4_to_float3(make_float4(cell.x, cell.y, cell.z, float(seed + i * 1259))),
gp.cell_randomness);
}
float3 kernel_position = (cell_position - make_float3(xrand, yrand, zrand)) * gp.radius;
float3 kernel_position = (cell_position - rand_position);
float dv = dot(kernel_position, kernel_position);
float orand = gabor_rng_uniform(&rng);
float prand = gabor_rng_uniform(&rng);
float orand2 = gabor_rng_uniform(&rng);
float dv = dot(kernel_position, kernel_position) / gp.radius;
if (dv < gp.radius * gp.radius) {
if (dv <= 1.0f) {
float3 omega;
float phi;
float3 rand_values = hash_float4_to_float3(
make_float4(cell.x, cell.y, cell.z, float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, orand, orand2, prand, &omega, &phi);
gabor_sample(gp, rand_values.x, rand_values.y, rand_values.z, &omega, &phi);
float g = (1.0f + M_EPI_F) * (expf(-M_PI * dv) - M_EPI_F);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g) * gp.bandwidth;
}
}
return sum;
}
sum += gabor_fractal(gp, dv, omega, phi, kernel_position);
ccl_device float3 gabor_cell_2d(GaborParams gp, float3 cell, float3 cell_position, int seed)
{
int num_impulses = impulses_per_cell(seed, cell, gp.impulses);
float3 sum = zero_float3();
for (int i = 0; i < num_impulses; ++i) {
float3 rand_position = zero_float3();
if (gp.cell_randomness > 0.0f) {
rand_position = mix(
rand_position,
hash_float4_to_float3(make_float4(cell.x, cell.y, cell.z, float(seed + i * 1259))),
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) {
float3 omega;
float phi;
float3 rand_values = hash_float4_to_float3(
make_float4(cell.x, cell.y, cell.z, float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, rand_values.x, rand_values.y, rand_values.z, &omega, &phi);
float g = (1.0f + M_EPI_F) * (expf(-M_PI * dv) - M_EPI_F);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g) * gp.bandwidth;
}
}
return sum;
@ -193,7 +169,7 @@ ccl_device float gabor_wrap(float a, float b)
return (b != 0.0f) ? a - b * floorf(a / b) : 0.0f;
}
ccl_device float gabor_grid_3d(GaborParams gp, float3 p, float scale)
ccl_device float gabor_grid_3d(GaborParams gp, float3 p, float scale, int seed, int periodic)
{
float3 coords = p * scale;
float3 position = floor(coords);
@ -207,61 +183,127 @@ ccl_device float gabor_grid_3d(GaborParams gp, float3 p, float scale)
float3 cell = position + cell_offset;
float3 cell_position = local_position - cell_offset;
if (gp.periodic) {
if (periodic) {
cell.x = gabor_wrap(cell.x, scale);
cell.y = gabor_wrap(cell.y, scale);
cell.z = gabor_wrap(cell.z, scale);
}
sum += gabor_cell_3d(gp, cell, cell_position);
sum += gabor_cell_3d(gp, cell, cell_position, seed);
}
}
}
if (gp.kernel_shape == SHD_GABOR_KERNEL_PHASOR) {
float pn = atan2f(sum.y, sum.x);
return pn * gp.radius;
float pn = atan2f(sum.y, sum.x) / M_PI_F;
return pn;
}
else {
return sum.x * gp.radius;
return sum.x;
}
}
ccl_device float gabor_grid_2d(GaborParams gp, float3 p, float scale, int seed, int periodic)
{
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;
if (periodic) {
cell.x = gabor_wrap(cell.x, scale);
cell.y = gabor_wrap(cell.y, scale);
}
sum += gabor_cell_2d(gp, cell, cell_position, seed);
}
}
if (gp.kernel_shape == SHD_GABOR_KERNEL_PHASOR) {
float pn = atan2f(sum.y, sum.x) / M_PI_F;
return pn;
}
else {
return sum.x;
}
}
ccl_device float gabor_fractal_noise(
GaborParams gp, float3 p, float scale, int dimensions, int periodic)
{
float fscale = 1.0f;
float amp = 1.0f;
float maxamp = 0.0f;
float sum = 0.0f;
float octaves = gp.octaves;
if (gp.roughness == 0.0f || gp.scl_lacunarity == 0.0f) {
CharlieJolly marked this conversation as resolved Outdated

Is there a need for this if clause? It makes the behavior different to the other noise nodes when scale is 0.0.

Is there a need for this if clause? It makes the behavior different to the other noise nodes when scale is 0.0.

Removed this clause.

Removed this clause.

Sorry I misremembered. if (gp.roughness == 0.0f || gp.scl_lacunarity == 0.0f) was actually correct, see: 39a40d6f84/intern/cycles/kernel/svm/voronoi.h (L892)

Could you please change it back?

Sorry I misremembered. `if (gp.roughness == 0.0f || gp.scl_lacunarity == 0.0f)` was actually correct, see: https://projects.blender.org/blender/blender/src/commit/39a40d6f84a658d07860aef60487660a34aa0d3b/intern/cycles/kernel/svm/voronoi.h#L892 Could you please change it back?

This differs between noise and voronoi, I'm not sure which one is actually preferable. If the input is driven then it may make sense to keep it as is so the transition from 0 to 1 is smoother. The gabor function just returns midlevel value when scale lacunarity is zero.

This differs between noise and voronoi, I'm not sure which one is actually preferable. If the input is driven then it may make sense to keep it as is so the transition from 0 to 1 is smoother. The gabor function just returns midlevel value when scale lacunarity is zero.

This differs between noise and voronoi, I'm not sure which one is actually preferable.
The behavior for both noise and voronoi are the same. This is because when scale lacunarity is 0.0 the coordinates of all octaves higher than the base octaves are also (0.0, 0.0, 0.0). Due to how Perlin noise works this then also results in a constant 0.0 output for all higher octaves, which means it doesn't matter if the higher octaves are being computed or not.
But actually I think your way of doing it is actually better as it makes the output continuous as a function of Lacunarity, so I think you should just keep it as is and I'll change Voronoi to also have the same behavior.

>This differs between noise and voronoi, I'm not sure which one is actually preferable. The behavior for both noise and voronoi are the same. This is because when scale lacunarity is 0.0 the coordinates of all octaves higher than the base octaves are also (0.0, 0.0, 0.0). Due to how Perlin noise works this then also results in a constant 0.0 output for all higher octaves, which means it doesn't matter if the higher octaves are being computed or not. But actually I think your way of doing it is actually better as it makes the output continuous as a function of Lacunarity, so I think you should just keep it as is and I'll change Voronoi to also have the same behavior.
octaves = 0.0f;
}
int n = int(octaves);
for (int i = 0; i <= n; i++) {
float t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, i, periodic) :
gabor_grid_2d(gp, fscale * p, scale, i, periodic);
gp.base_frequency *= gp.fre_lacunarity;
gp.rotation -= gp.rot_lacunarity;
sum += t * amp;
maxamp += amp;
amp *= gp.roughness;
fscale *= gp.scl_lacunarity;
}
float rmd = octaves - floorf(octaves);
if (rmd != 0.0f) {
float t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, n + 1, periodic) :
gabor_grid_2d(gp, fscale * p, scale, n + 1, periodic);
float sum2 = sum + t * amp;
return mix(sum, sum2, rmd) / maxamp;
}
else {
return sum / maxamp;
}
}
ccl_device GaborParams gabor_parameters(float3 direction,
float base_frequency,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float bandwidth,
float radius,
float impulses,
float frequency,
float lacunarity,
float phase,
float weight,
float randomness,
float phase_variance,
float cell_randomness,
CharlieJolly marked this conversation as resolved Outdated

Perhaps a little nitpicky but perhaps return sum * gp.bandwidth / maxamp; makes the intention a little clearer as division isn't commutative.

Perhaps a little nitpicky but perhaps `return sum * gp.bandwidth / maxamp;` makes the intention a little clearer as division isn't commutative.
float rotation,
float variance,
float rot_variance,
int kernel_shape,
int anisotropic,
int periodic)
int anisotropic)
{
GaborParams gp;
gp.periodic = periodic;
gp.variance = variance;
gp.lacunarity = lacunarity;
gp.roughness = clamp(roughness, 0.0f, 1.0f);
gp.octaves = clamp(detail, 0.0f, 15.0f);
gp.impulses = clamp(impulses, 0.0001f, 32.0f);
gp.rot_variance = rot_variance;
gp.scl_lacunarity = scl_lacunarity;
gp.fre_lacunarity = fre_lacunarity;
gp.rot_lacunarity = rot_lacunarity;
gp.anisotropic = anisotropic;
gp.kernel_shape = kernel_shape;
gp.direction = direction;
gp.phase = phase;
gp.rotation = rotation;
gp.detail = detail;
gp.impulses = clamp(impulses, 0.0001f, 32.0f);
gp.weight = weight;
gp.randomness = randomness;
float sqrt_pi_over_ln2 = sqrtf(M_PI_F / M_LN2_F);
float bandwidth_pow = exp2(bandwidth);
gp.bandwidth = ((bandwidth_pow - 1.0f) / (bandwidth_pow + 1.0f)) * sqrt_pi_over_ln2;
gp.radius = 1.0f / gp.bandwidth;
gp.frequency = frequency * gp.bandwidth;
gp.phase_variance = phase_variance;
gp.cell_randomness = cell_randomness;
gp.bandwidth = bandwidth;
gp.radius = radius;
gp.base_frequency = base_frequency;
return gp;
}
@ -270,40 +312,56 @@ ccl_device GaborParams gabor_parameters(float3 direction,
ccl_device float gabor_noise(float3 p,
float3 direction,
float scale,
float base_frequency,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float bandwidth,
float radius,
float impulses,
float frequency,
float lacunarity,
float phase,
float weight,
float randomness,
float phase_variance,
float cell_randomness,
float rotation,
float variance,
float rot_variance,
int dimensions,
int kernel_shape,
int anisotropic,
int normalize,
int periodic)
{
if (impulses == 0.0f) {
return 0.5f;
if (impulses == 0.0f || bandwidth == 0.0f || radius == 0.0f || scale == 0.0f) {
return (normalize == 1) ? 0.5f : 0.0f;
}
GaborParams gp;
gp = gabor_parameters(direction,
base_frequency,
detail,
roughness,
scl_lacunarity,
fre_lacunarity,
rot_lacunarity,
bandwidth,
radius,
impulses,
frequency,
lacunarity,
phase,
weight,
randomness,
phase_variance,
cell_randomness,
rotation,
variance,
rot_variance,
kernel_shape,
anisotropic,
periodic);
anisotropic);
return min(max(gabor_grid_3d(gp, p, scale) * 0.5f + 0.5f, 0.0f), 1.0f);
float g = gabor_fractal_noise(gp, p, scale, dimensions, periodic);
float impulse_scale = impulses < 1.0f ? (1.2613446229f * sqrt(gp.impulses)) :
1.0f; // sqrt(0.75 / pi) = 1.26
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(
@ -313,56 +371,82 @@ ccl_device_noinline int svm_node_tex_gabor(
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);
/* Input and Output Sockets */
uint vector_in_offset, scale_offset, detail_offset, phase_offset, impulse_offset;
uint direction_offset, value_offset, frequency_offset, frequency_var_offset;
uint kernel_shape_offset, aniso_offset, periodic_offset;
uint variance_offset, weight_offset, rotation_offset, bandwidth_offset, randomness_offset;
uint direction_offset, value_out_offset, fre_lacunarity_offset;
uint kernel_shape_offset, aniso_offset, periodic_offset, roughness_offset;
uint rot_variance_offset, phase_variance_offset, rotation_offset, bandwidth_offset,
cell_randomness_offset;
uint scl_lacunarity_offset, use_normalize_offset, dimension_offset, rot_lacunarity_offset;
uint base_frequency_offset, radius_offset;
svm_unpack_node_uchar4(node.y, &vector_in_offset, &scale_offset, &phase_offset, &impulse_offset);
svm_unpack_node_uchar4(
node.z, &detail_offset, &value_offset, &frequency_offset, &direction_offset);
svm_unpack_node_uchar3(node.w, &bandwidth_offset, &rotation_offset, &variance_offset);
node.y, &vector_in_offset, &scale_offset, &base_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, &bandwidth_offset, &radius_offset, &impulse_offset, &phase_offset);
svm_unpack_node_uchar3(node2.x, &frequency_var_offset, &randomness_offset, &weight_offset);
svm_unpack_node_uchar3(node2.y, &kernel_shape_offset, &aniso_offset, &periodic_offset);
svm_unpack_node_uchar3(
node2.x, &phase_variance_offset, &cell_randomness_offset, &rotation_offset);
svm_unpack_node_uchar4(
node2.y, &rot_variance_offset, &direction_offset, &value_out_offset, &dimension_offset);
svm_unpack_node_uchar4(
node2.z, &kernel_shape_offset, &aniso_offset, &periodic_offset, &use_normalize_offset);
float3 vector_in = stack_load_float3(stack, vector_in_offset);
float3 direction = stack_load_float3(stack, direction_offset);
float randomness = stack_load_float_default(stack, randomness_offset, defaults_node3.x);
float detail = stack_load_float_default(stack, detail_offset, defaults_node3.y);
float lacunarity = stack_load_float_default(stack, frequency_var_offset, defaults_node3.z);
float scale = stack_load_float_default(stack, scale_offset, defaults_node3.x);
float base_frequency = stack_load_float_default(stack, base_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 variance = stack_load_float_default(stack, variance_offset, defaults_node4.x);
float weight = stack_load_float_default(stack, weight_offset, defaults_node4.y);
float rotation = stack_load_float_default(stack, rotation_offset, defaults_node4.z);
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 bandwidth = stack_load_float_default(stack, bandwidth_offset, defaults_node4.w);
float scale = stack_load_float_default(stack, scale_offset, defaults_node5.x);
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 frequency = stack_load_float_default(stack, frequency_offset, defaults_node5.z);
float phase = stack_load_float_default(stack, phase_offset, defaults_node5.w);
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);
if (stack_valid(value_offset)) {
float cell_randomness = stack_load_float_default(
stack, cell_randomness_offset, defaults_node6.x);
float rot_variance = stack_load_float_default(stack, rot_variance_offset, defaults_node6.y);
float rotation = stack_load_float_default(stack, rotation_offset, defaults_node6.z);
float3 direction = stack_load_float3(stack, direction_offset);
if (stack_valid(value_out_offset)) {
float value = gabor_noise(vector_in,
direction,
scale,
base_frequency,
detail,
roughness,
scl_lacunarity,
fre_lacunarity,
rot_lacunarity,
bandwidth,
radius,
impulses,
frequency,
lacunarity,
phase,
weight,
randomness,
phase_variance,
cell_randomness,
rotation,
variance,
rot_variance,
dimension_offset,
kernel_shape_offset,
aniso_offset,
use_normalize_offset,
periodic_offset);
stack_store_float(stack, value_offset, value);
stack_store_float(stack, value_out_offset, value);
}
return offset;

View File

@ -414,7 +414,6 @@ typedef enum NodeGaborKernel {
typedef enum NodeGaborAnisotropic {
SHD_GABOR_MODE_ISOTROPIC,
SHD_GABOR_MODE_ANISOTROPIC,
SHD_GABOR_MODE_HYBRID,
} NodeGaborAnisotropic;
/* Closure */

View File

@ -1197,6 +1197,16 @@ NODE_DEFINE(GaborTextureNode)
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("iso", SHD_GABOR_MODE_ISOTROPIC);
mode_enum.insert("aniso", SHD_GABOR_MODE_ANISOTROPIC);
SOCKET_ENUM(anisotropic, "Anisotropic", mode_enum, SHD_GABOR_MODE_ISOTROPIC);
static NodeEnum kernel_enum;
kernel_enum.insert("gabor", SHD_GABOR_KERNEL_GABOR);
kernel_enum.insert("ring", SHD_GABOR_KERNEL_RING);
@ -1205,27 +1215,26 @@ NODE_DEFINE(GaborTextureNode)
kernel_enum.insert("phasor", SHD_GABOR_KERNEL_PHASOR);
SOCKET_ENUM(kernel_shape, "Kernel", kernel_enum, SHD_GABOR_KERNEL_GABOR);
static NodeEnum mode_enum;
mode_enum.insert("iso", SHD_GABOR_MODE_ISOTROPIC);
mode_enum.insert("aniso", SHD_GABOR_MODE_ANISOTROPIC);
mode_enum.insert("hybrid", SHD_GABOR_MODE_HYBRID);
SOCKET_ENUM(anisotropic, "Anisotropic", mode_enum, SHD_GABOR_MODE_ISOTROPIC);
SOCKET_INT(periodic, "Periodic", 0);
SOCKET_BOOLEAN(periodic, "Periodic", false);
SOCKET_BOOLEAN(use_normalize, "Normalize", true);
SOCKET_IN_POINT(vector, "Vector", zero_float3(), SocketType::LINK_TEXTURE_GENERATED);
SOCKET_IN_FLOAT(scale, "Scale", 5.0f);
SOCKET_IN_FLOAT(base_frequency, "Base Frequency", 4.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(bandwidth, "Bandwidth", 2.0f);
SOCKET_IN_FLOAT(impulses, "Impulses", 1.0f);
SOCKET_IN_FLOAT(frequency, "Frequency", 2.0f);
SOCKET_IN_FLOAT(lacunarity, "Lacunarity", 2.0f);
SOCKET_IN_FLOAT(radius, "Radius", 1.0f);
SOCKET_IN_FLOAT(impulses, "Impulses", 2.0f);
SOCKET_IN_FLOAT(phase, "Phase", 0.0f);
SOCKET_IN_FLOAT(weight, "Phase Randomness", 1.0f);
SOCKET_IN_FLOAT(randomness, "Cell Randomness", 1.0f);
SOCKET_IN_FLOAT(phase_variance, "Phase Variance", 1.0f);
SOCKET_IN_FLOAT(cell_randomness, "Cell Randomness", 1.0f);
SOCKET_IN_FLOAT(rotation, "Rotation", 0.0f);
SOCKET_IN_FLOAT(variance, "Rotation Variance", 0.0f);
SOCKET_IN_POINT(direction, "Direction", make_float3(1.0f, 1.0f, 1.0f));
SOCKET_IN_FLOAT(rot_variance, "Rotation Variance", 0.0f);
SOCKET_IN_POINT(direction, "Direction", make_float3(0.0f, 0.0f, 1.0f));
SOCKET_OUT_FLOAT(value, "Value");
@ -1234,36 +1243,59 @@ NODE_DEFINE(GaborTextureNode)
GaborTextureNode::GaborTextureNode() : TextureNode(node_type) {}
void GaborTextureNode::constant_fold(const ConstantFolder &folder)
{
ShaderInput *scale_in = input("Scale");
ShaderInput *bandwidth_in = input("Bandwidth");
ShaderInput *radius_in = input("Radius");
ShaderInput *impulses_in = input("Impulses");
if ((!scale_in->link && scale == 0.0f) || (!bandwidth_in->link && bandwidth == 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 *base_frequency_in = input("Base 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 *bandwidth_in = input("Bandwidth");
ShaderInput *radius_in = input("Radius");
ShaderInput *impulses_in = input("Impulses");
ShaderInput *frequency_in = input("Frequency");
ShaderInput *frequency_var_in = input("Lacunarity");
ShaderInput *phase_in = input("Phase");
ShaderInput *weight_in = input("Phase Randomness");
ShaderInput *randomness_in = input("Cell Randomness");
ShaderInput *phase_variance_in = input("Phase Variance");
ShaderInput *cell_randomness_in = input("Cell Randomness");
ShaderInput *rotation_in = input("Rotation");
ShaderInput *variance_in = input("Rotation Variance");
ShaderInput *rot_variance_in = input("Rotation Variance");
ShaderInput *direction_in = input("Direction");
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 base_frequency_in_stack_offset = compiler.stack_assign(base_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 bandwidth_in_stack_offset = compiler.stack_assign(bandwidth_in);
int radius_in_stack_offset = compiler.stack_assign(radius_in);
int impulses_in_stack_offset = compiler.stack_assign(impulses_in);
int frequency_in_stack_offset = compiler.stack_assign(frequency_in);
int frequency_var_in_stack_offset = compiler.stack_assign(frequency_var_in);
int weight_in_stack_offset = compiler.stack_assign(weight_in);
int randomness_in_stack_offset = compiler.stack_assign(randomness_in);
int phase_in_stack_offset = compiler.stack_assign(phase_in);
int phase_variance_in_stack_offset = compiler.stack_assign(phase_variance_in);
int cell_randomness_in_stack_offset = compiler.stack_assign(cell_randomness_in);
int rotation_in_stack_offset = compiler.stack_assign(rotation_in);
int variance_in_stack_offset = compiler.stack_assign(variance_in);
int rot_variance_in_stack_offset = compiler.stack_assign(rot_variance_in);
int direction_in_stack_offset = compiler.stack_assign(direction_in);
int value_out_stack_offset = compiler.stack_assign_if_linked(value_out);
@ -1271,32 +1303,39 @@ void GaborTextureNode::compile(SVMCompiler &compiler)
compiler.add_node(NODE_TEX_GABOR,
compiler.encode_uchar4(vector_stack_offset,
scale_in_stack_offset,
phase_in_stack_offset,
impulses_in_stack_offset),
compiler.encode_uchar4(detail_in_stack_offset,
value_out_stack_offset,
frequency_in_stack_offset,
direction_in_stack_offset),
base_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(bandwidth_in_stack_offset,
rotation_in_stack_offset,
variance_in_stack_offset));
compiler.add_node(compiler.encode_uchar4(frequency_var_in_stack_offset,
randomness_in_stack_offset,
weight_in_stack_offset),
compiler.encode_uchar4(kernel_shape, anisotropic, periodic),
SVM_STACK_INVALID,
SVM_STACK_INVALID);
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),
compiler.encode_uchar4(rot_variance_in_stack_offset,
direction_in_stack_offset,
value_out_stack_offset,
dimensions),
compiler.encode_uchar4(kernel_shape, anisotropic, periodic, use_normalize));
compiler.add_node(
__float_as_int(randomness), __float_as_int(detail), __float_as_int(lacunarity));
compiler.add_node(__float_as_int(variance),
__float_as_int(weight),
__float_as_int(rotation),
__float_as_int(bandwidth));
compiler.add_node(__float_as_int(scale),
__float_as_int(base_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(bandwidth));
compiler.add_node(__float_as_int(radius),
__float_as_int(impulses),
__float_as_int(frequency),
__float_as_int(phase));
__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));
tex_mapping.compile_end(compiler, vector_in, vector_stack_offset);
}
@ -1304,9 +1343,11 @@ void GaborTextureNode::compile(SVMCompiler &compiler)
void GaborTextureNode::compile(OSLCompiler &compiler)
{
tex_mapping.compile(compiler);
compiler.parameter(this, "dimensions");
compiler.parameter(this, "kernel_shape");
compiler.parameter(this, "anisotropic");
compiler.parameter(this, "periodic");
compiler.parameter(this, "use_normalize");
compiler.add(this, "node_gabor_texture");
}

View File

@ -241,23 +241,31 @@ class NoiseTextureNode : public TextureNode {
class GaborTextureNode : public TextureNode {
public:
SHADER_NODE_CLASS(GaborTextureNode)
void constant_fold(const ConstantFolder &folder);
NODE_SOCKET_API(float3, vector)
NODE_SOCKET_API(float3, direction)
NODE_SOCKET_API(float, scale)
NODE_SOCKET_API(float, detail)
NODE_SOCKET_API(float, bandwidth)
NODE_SOCKET_API(float, impulses)
NODE_SOCKET_API(float, frequency)
NODE_SOCKET_API(float, lacunarity)
NODE_SOCKET_API(float, phase)
NODE_SOCKET_API(float, weight)
NODE_SOCKET_API(float, randomness)
NODE_SOCKET_API(float, rotation)
NODE_SOCKET_API(float, variance)
NODE_SOCKET_API(int, dimensions)
NODE_SOCKET_API(NodeGaborKernel, kernel_shape);
NODE_SOCKET_API(NodeGaborAnisotropic, anisotropic);
NODE_SOCKET_API(int, periodic)
NODE_SOCKET_API(bool, periodic)
NODE_SOCKET_API(bool, use_normalize)
NODE_SOCKET_API(float3, vector)
NODE_SOCKET_API(float, scale)
NODE_SOCKET_API(float, base_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, bandwidth)
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, cell_randomness)
NODE_SOCKET_API(float, rotation)
NODE_SOCKET_API(float, rot_variance)
NODE_SOCKET_API(float3, direction)
};
class VoronoiTextureNode : public TextureNode {

View File

@ -504,6 +504,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")

View File

@ -1,79 +1,101 @@
#pragma BLENDER_REQUIRE(gpu_shader_common_hash.glsl)
#ifndef M_LN2
# define M_LN2 0.6931471805599453
#endif
/* 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
* Procedural noise using sparse Gabor convolution. Some parts are also adapted from 2011 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 2x2 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
CharlieJolly marked this conversation as resolved Outdated

Brackets to follow Blender's coding style.

Brackets to follow Blender's coding style.
* separate controls for Frequency, Bandwidth (Power) and Radius. This provides finer control where
* before frequency, 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
CharlieJolly marked this conversation as resolved Outdated

Can't we use our own random functions? Is there an advantage to using those?

Can't we use our own random functions? Is there an advantage to using those?

The initial hash is using Blender hash functions to seed the RNG. We don't seem to have an RNG implementation in the shading system for so this is why this is used.

The initial hash is using Blender hash functions to seed the RNG. We don't seem to have an RNG implementation in the shading system for so this is why this is used.

But is it necessary to use an RNG at all? Why not use our pseudo-number-generator with the impulse index as an input?

But is it necessary to use an RNG at all? Why not use our pseudo-number-generator with the impulse index as an input?

Updated to use built in hash functions

Updated to use built in hash functions
* trivial to add.
* - Additional sincos based kernels have been added which provide different texture control.
* Similar 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 gabor rng.
* - Anisotropic input direction is not normalised by default for artistic control.
* - Removed Hybrid Anistropic option (can be added back but I find it is non-intuitive)
*
* 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
*/
#ifndef M_EPI
# define M_EPI 0.0432139182637722
CharlieJolly marked this conversation as resolved Outdated

What is M_EPI?

What is `M_EPI`?

Added comment to define: e^(-PI)

Added comment to define: e^(-PI)
#endif
int gabor_rng_seed(vec3 cell, int seed)
{
int chash = int(hash_uint4(floatBitsToUint(floor(cell.x)),
floatBitsToUint(floor(cell.y)),
floatBitsToUint(floor(cell.z)),
floatBitsToUint(float(seed))));
if (chash == 0)
chash = 1;
return chash * 1519588931;
}
float gabor_rng_uniform(inout int rng)
{
float int_max_inv = 1.0 / 2147483647.0;
float res = float(rng) * int_max_inv * 0.5 + 0.5;
rng *= 1519588931;
return res;
}
int gabor_rng_poisson(inout int rng, float mean)
{
if (mean >= 1.0) {
return int(mean);
}
float g = exp(-mean);
int em = 0;
float t = gabor_rng_uniform(rng);
while (t > g) {
++em;
t *= gabor_rng_uniform(rng);
}
return em;
}
struct GaborParams {
float detail;
float variance;
float weight;
float randomness;
float impulses;
float frequency;
float lacunarity;
float phase;
float rotation;
float radius;
float base_frequency;
float octaves;
float roughness;
float scl_lacunarity;
float fre_lacunarity;
float rot_lacunarity;
float bandwidth;
int periodic;
int kernel;
float radius;
float impulses;
float phase;
float phase_variance;
float cell_randomness;
float rotation;
float rot_variance;
int kernel_shape;
int anisotropic;
vec3 direction;
};
/* Calculate impulses per cell. Performance is optimised when impulses are set to whole numbers.
* Uniform distribution is faster than running a poisson for calculating impulses for remaining
* fractional part. */
int impulses_per_cell(int seed, vec3 cell, float impulses)
{
int n = int(impulses);
float rmd = impulses - floor(impulses);
if (rmd > 0.0) {
float t = hash_vec4_to_float(vec4(cell, float(seed - 1259)));
return (t <= rmd) ? n + 1 : n;
}
CharlieJolly marked this conversation as resolved Outdated

Why not simply int(impulses)?

Why not simply `int(impulses)`?

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 smooth increase in impulses as the input value increases. It could return int(impulses) above 1 but this would give jumps as the impulse value increases.

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 smooth increase in impulses as the input value increases. It could return `int(impulses)` above 1 but this would give jumps as the impulse value increases.

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 smooth increase in impulses as the input value increases. It could return int(impulses) above 1 but this would give jumps as the impulse value increases.

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 smooth increase in impulses as the input value increases. It could return `int(impulses)` above 1 but this would give jumps as the impulse value increases.
return n;
}
/* Calculates the kernel shape that is multiplied by the gaussian envelope. */
vec3 gabor_kernel(GaborParams gp, float freq, vec3 omega, float phi, vec3 position, float g)
{
float h;
if (gp.kernel == 4) { /* SHD_GABOR_KERNEL_PHASOR */
if (gp.kernel_shape == 4) { /* SHD_GABOR_KERNEL_PHASOR */
float phase = freq * dot(omega, position) + phi;
return g * vec3(cos(phase), sin(phase), 0.0);
}
CharlieJolly marked this conversation as resolved Outdated

Add a note specifying that for non phasor modes, the result is just a broadcast of a single value in a vec3.

Add a note specifying that for non phasor modes, the result is just a broadcast of a single value in a `vec3`.

Note added.

Note added.
else if (gp.kernel == 3) { /* SHD_GABOR_KERNEL_SQUARE */
h = cos(freq * dot(omega, position) + phi) + cos(freq * dot(omega, position.yxz) + phi);
else if (gp.kernel_shape == 3) { /* SHD_GABOR_KERNEL_SQUARE */
h = (cos(freq * dot(omega, position) + phi) + cos(freq * dot(omega, position.yxz) + phi)) *
0.5;
}
else if (gp.kernel == 2) { /* SHD_GABOR_KERNEL_CROSS */
else if (gp.kernel_shape == 2) { /* SHD_GABOR_KERNEL_CROSS */
CharlieJolly marked this conversation as resolved Outdated

Can we find a way to incorporate all three options into a single factor input that controls the amount of anisotropy?

Can we find a way to incorporate all three options into a single factor input that controls the amount of anisotropy?

This has been slightly refactored but combining to a single factor is not feasible for Gabor noise.

This has been slightly refactored but combining to a single factor is not feasible for Gabor noise.

Latest version now combines all three modes/options into one.
There is now an Anisotropy Factor to mix between Isotropic/Manual direction and Anisotropic/Fixed Direction.

Latest version now combines all three modes/options into one. There is now an Anisotropy Factor to mix between Isotropic/Manual direction and Anisotropic/Fixed Direction.
h = cos(freq * length(position * omega) + phi);
}
else if (gp.kernel == 1) { /* SHD_GABOR_KERNEL_RING */
else if (gp.kernel_shape == 1) { /* SHD_GABOR_KERNEL_RING */
h = cos(freq * dot(position, omega) + phi) - cos(freq * length(position) + phi);
}
else { // SHD_GABOR_KERNEL_GABOR
@ -83,88 +105,97 @@ vec3 gabor_kernel(GaborParams gp, float freq, vec3 omega, float phi, vec3 positi
return vec3(g * h);
}
GaborParams gabor_sample(
GaborParams gp, float orand, float orand2, float prand, inout vec3 omega, out float phi)
/* Set omega (angular frequency) and phi (phase) for the impulse. Unlike reference papers,
* Anisotropic is not normalised to provide additional artistic control. */
GaborParams gabor_sample(GaborParams gp,
vec3 cell,
vec3 rand_position,
float orand,
CharlieJolly marked this conversation as resolved Outdated

Why are we computing the fractcal on the freqiency as opposed to the scale to make it consistent with other noise functions? Is there a good reason for that?

Why are we computing the fractcal on the freqiency as opposed to the scale to make it consistent with other noise functions? Is there a good reason for that?

This was implemented per impulse/splat rather than per texture pass. This was mentioned to me by @Hoshinova so I have this marked as a todo. The results are quite different.

This was implemented per impulse/splat rather than per texture pass. This was mentioned to me by @Hoshinova so I have this marked as a todo. The results are quite different.

Implemented fractal based on other noise functions.

Implemented fractal based on other noise functions.
float orand2,
float prand,
inout vec3 omega,
CharlieJolly marked this conversation as resolved Outdated

Why does this function return a vec3 when the third component is always zero and ignored?

Why does this function return a `vec3` when the third component is always zero and ignored?

Added note to gabor_kernel function, this is due to returning Phasor values.

Added note to gabor_kernel function, this is due to returning Phasor values.
out float phi)
{
float pvar = mix(0.0, prand * 2.0 - 1.0, gp.weight);
float pvar = mix(0.0, prand * 2.0 - 1.0, gp.phase_variance);
phi = M_2PI * pvar + gp.phase;
float ovar = M_PI * (orand * 2.0 - 1.0);
float omega_t = ovar * gp.variance - gp.rotation;
if (gp.anisotropic == 1) { /* ANISO */
omega = normalize(gp.direction);
/* Anisotropic direction. */
if (gp.anisotropic == 1) {
omega = gp.direction;
}
else if (gp.anisotropic == 2) { /* HYBRID */
float sin_omega_t = sin(omega_t);
float cos_omega_t = cos(omega_t);
omega = length(gp.direction) * vec3(cos_omega_t, sin_omega_t, 0.0);
}
else { /* ISO */
else { /* Isotropic. */
float ovar = M_PI * (orand * 2.0 - 1.0);
float omega_t = ovar * gp.rot_variance - gp.rotation;
float cos_omega_p = 1.0 - 2.0 * orand2;
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);
omega = normalize(vec3(cos_omega_t * sin_omega_p, sin_omega_t * sin_omega_p, cos_omega_p));
}
return gp; // workaround gpu_codegen parser
/* Return GaborParams to workaround gpu_codegen parser warning when return type is set to void :
* Unknown parameter type "GaborParams". */
return gp;
}
vec3 gabor_fractal(GaborParams gp, float dv, inout vec3 omega, float phi, vec3 kernel_position)
/* Generate noise based on the cell position and number of impulses. */
vec3 gabor_cell_3d(GaborParams gp, vec3 cell, vec3 cell_position, int seed)
{
float freq = gp.frequency;
vec3 sum = vec3(0.0);
float g = (1.0 + M_EPI) * (exp(-M_PI * gp.bandwidth * gp.bandwidth * dv) - M_EPI);
float amp = 1.0;
float maxamp = 0.0;
float octaves = clamp(gp.detail, 0.0, 15.0);
int n = int(octaves);
for (int i = 0; i <= n; i++) {
vec3 t = gabor_kernel(gp, freq, omega, phi, kernel_position, g);
sum += t * amp;
maxamp += amp;
freq *= gp.lacunarity;
}
float rmd = octaves - floor(octaves);
if (rmd > 0.0) {
vec3 t = gabor_kernel(gp, freq, omega, phi, kernel_position, g);
vec3 sum2 = sum + t * amp;
sum /= maxamp;
sum2 /= maxamp + amp;
return (1.0 - rmd) * sum + rmd * sum2;
}
else {
return sum / maxamp;
}
}
vec3 gabor_cell_3d(GaborParams gp, vec3 cell, vec3 cell_position)
{
int rng = gabor_rng_seed(cell, 0);
int num_impulses = gabor_rng_poisson(rng, gp.impulses);
int num_impulses = impulses_per_cell(seed, cell, gp.impulses);
vec3 sum = vec3(0.0);
for (int i = 0; i < num_impulses; ++i) {
float xrand = gabor_rng_uniform(rng);
float yrand = gabor_rng_uniform(rng);
float zrand = gabor_rng_uniform(rng);
xrand = mix(0.0, xrand, gp.randomness);
yrand = mix(0.0, yrand, gp.randomness);
zrand = mix(0.0, zrand, gp.randomness);
vec3 rand_position = vec3(0.0);
if (gp.cell_randomness > 0.0) {
rand_position = mix(rand_position,
hash_vec4_to_vec3(vec4(cell, float(seed + i * 1259))),
gp.cell_randomness);
}
vec3 kernel_position = (cell_position - vec3(xrand, yrand, zrand)) * gp.radius;
vec3 kernel_position = (cell_position - rand_position);
float dv = dot(kernel_position, kernel_position);
float orand = gabor_rng_uniform(rng);
float prand = gabor_rng_uniform(rng);
float orand2 = gabor_rng_uniform(rng);
float dv = dot(kernel_position, kernel_position) / gp.radius;
if (dv < gp.radius * gp.radius) {
if (dv <= 1.0) {
vec3 omega;
CharlieJolly marked this conversation as resolved Outdated

This if seems redundant and can be removed.

This `if` seems redundant and can be removed.

Is this a redundant optimisation?

Is this a redundant optimisation?

I wouldn't call it an optimization, branches like this can needlessly slow down vectorized code, so it is best avoided.

I wouldn't call it an optimization, branches like this can needlessly slow down vectorized code, so it is best avoided.

I assume this is the same for all codebases or just glsl?

I assume this is the same for all codebases or just glsl?

This is probably for all backends, not just GLSL.

This is probably for all backends, not just GLSL.
float phi;
vec3 rand_values = hash_vec4_to_vec3(vec4(cell, float(seed + (num_impulses + i) * 1259)));
CharlieJolly marked this conversation as resolved Outdated

I am still not sure why the anisotropic and isotropic types can't be joined by defining an annular sector as described in section "3.2 Noise with Controllable Band-Limits" of the paper.

I am still not sure why the anisotropic and isotropic types can't be joined by defining an annular sector as described in section "3.2 Noise with Controllable Band-Limits" of the paper.

I can add back the Hybrid mode.

I can add back the Hybrid mode.

@CharlieJolly So the hybrid mode was based on that section of the paper?

@CharlieJolly So the hybrid mode was based on that section of the paper?

Well TBH I'm not sure, reading it again this looks like a feature of their application which is no longer available it seems. In this case I believe this can be achieved using the Rotation Variance control or by using the fractal noise options.

Well TBH I'm not sure, reading it again this looks like a feature of their application which is no longer available it seems. In this case I believe this can be achieved using the Rotation Variance control or by using the fractal noise options.

Well, the hybrid mode is actually described in section 3.3 of:

Lagae, Ares, and George Drettakis. "Filtering solid Gabor noise." ACM Transactions on Graphics (TOG) 30.4 (2011): 1-6.

But what I am after is removing the mode altogether and replacing it with intutive parameters that describe and annular sector as mentioned before.

Well, the hybrid mode is actually described in section 3.3 of: Lagae, Ares, and George Drettakis. "Filtering solid Gabor noise." ACM Transactions on Graphics (TOG) 30.4 (2011): 1-6. But what I am after is removing the mode altogether and replacing it with intutive parameters that describe and annular sector as mentioned before.

@OmarEmaraDev this is achieved using the Rotation and Rotation Variance controls.

image

@OmarEmaraDev this is achieved using the Rotation and Rotation Variance controls. ![image](/attachments/e4d3e0a6-737b-466c-8c23-2eba581bdebd)

Is isotropic the same as Rotation Variance = 360?

Is isotropic the same as `Rotation Variance = 360`?

Yes, in OSL implementation we have the angular frequency set to a random number

float omega_t = float(M_TWO_PI) * rng();

in this patch this is contolled by Rotation and Rotation Variance.

float ovar = M_PI * (rand_values.x * 2.0 - 1.0);
float omega_t = ovar * gp.rot_variance - gp.rotation;
Yes, in OSL implementation we have the angular frequency set to a random number ``` float omega_t = float(M_TWO_PI) * rng(); ``` in this patch this is contolled by Rotation and Rotation Variance. ``` float ovar = M_PI * (rand_values.x * 2.0 - 1.0); float omega_t = ovar * gp.rot_variance - gp.rotation; ```

Okay, so why not remove the enum, rename Rotation Variance to Anisotropy, and unify the Direction and Rotation inputs?

Okay, so why not remove the enum, rename Rotation Variance to Anisotropy, and unify the Direction and Rotation inputs?

Okay, so why not remove the enum, rename Rotation Variance to Anisotropy, and unify the Direction and Rotation inputs?

I did think about this but found it tricky to do this in a nice way. The Rotation controls are not shown on Anisotropic mode as it is only controlled by the direction vector. Hybrid mixed both these modes so that maybe something to consider adding back.

> Okay, so why not remove the enum, rename Rotation Variance to Anisotropy, and unify the Direction and Rotation inputs? I did think about this but found it tricky to do this in a nice way. The Rotation controls are not shown on Anisotropic mode as it is only controlled by the direction vector. Hybrid mixed both these modes so that maybe something to consider adding back.

I really think we should try to reduce the number of options and inputs in the node as much as we can, because those inputs are not all orthogonal, so the node is hard to use for the average user.

I really think we should try to reduce the number of options and inputs in the node as much as we can, because those inputs are not all orthogonal, so the node is hard to use for the average user.

The number of inputs is definitely increased by adding the fractal controls. Removing those alone would reduce inputs by five but at the loss of that feature. (@Hoshinova)

image

Node panels patch may help here.

Otherwise it is difficult to reduce the params without compromising the features.

In comparison to the Principled BSDF.

image

The number of inputs is definitely increased by adding the fractal controls. Removing those alone would reduce inputs by five but at the loss of that feature. (@Hoshinova) ![image](/attachments/213c08e3-2dfa-4c3b-8d9c-c6b451629ebc) Node panels patch may help here. Otherwise it is difficult to reduce the params without compromising the features. In comparison to the Principled BSDF. ![image](/attachments/d12b218d-3582-496e-9f24-7e0c5bf624a7)

Please stop this project and look into adding loops to shaders and reworking all fractal noise texture types into a node group asset)

Please stop this project and look into adding loops to shaders and reworking all fractal noise texture types into a node group asset)

Please stop this project and look into adding loops to shaders and reworking all fractal noise texture types into a node group asset)

Voronoi and Noise have both recently had fractal noise modes added. That is why this was added to this node.

I can't comment on the feasibility of adding loops to shading nodes. Don't forget that for any shading node, there has to be three implementations for Eevee, Cycles and OSL.

> Please stop this project and look into adding loops to shaders and reworking all fractal noise texture types into a node group asset) Voronoi and Noise have both recently had fractal noise modes added. That is why this was added to this node. I can't comment on the feasibility of adding loops to shading nodes. Don't forget that for any shading node, there has to be three implementations for Eevee, Cycles and OSL.

The UI has now been grouped using the new Panel feature.

The UI has now been grouped using the new Panel feature.
gabor_sample(gp, orand, orand2, prand, omega, phi);
gabor_sample(
gp, cell, rand_position, rand_values.x, rand_values.y, rand_values.z, omega, phi);
const float g = (1.0 + M_EPI) * (exp(-M_PI * dv) - M_EPI);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g) * gp.bandwidth;
}
}
return sum;
}
sum += gabor_fractal(gp, dv, omega, phi, kernel_position);
/* Generate noise based on the cell position and number of impulses. Z position is zeroed for 2x2
CharlieJolly marked this conversation as resolved Outdated

If this is simply only passed to gabor_sample, why not call it in gabor_sample directly?

If this is simply only passed to `gabor_sample`, why not call it in `gabor_sample` directly?
* 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(seed, cell, gp.impulses);
CharlieJolly marked this conversation as resolved Outdated

The same for the Gaussian componenet, just compute it in gabor_sample directly.

The same for the Gaussian componenet, just compute it in `gabor_sample` directly.

Added to gabor_kernel function

Added to `gabor_kernel` function
vec3 sum = vec3(0.0);
for (int i = 0; i < num_impulses; ++i) {
vec3 rand_position = vec3(0.0);
if (gp.cell_randomness > 0.0) {
rand_position = mix(rand_position,
hash_vec4_to_vec3(vec4(cell, float(seed + i * 1259))),
gp.cell_randomness);
rand_position.z = 0.0;
}
CharlieJolly marked this conversation as resolved Outdated

Unnecessary separation between declaration and initialization, just do vec3 rand_position = mix(...).

Unnecessary separation between declaration and initialization, just do `vec3 rand_position = mix(...)`.
vec3 kernel_position = (cell_position - rand_position);
float dv = dot(kernel_position, kernel_position) / gp.radius;
if (dv <= 1.0) {
vec3 omega;
float phi;
vec3 rand_values = hash_vec4_to_vec3(vec4(cell, float(seed + (num_impulses + i) * 1259)));
gabor_sample(
gp, cell, rand_position, rand_values.x, rand_values.y, rand_values.z, omega, phi);
const float g = (1.0 + M_EPI) * (exp(-M_PI * dv) - M_EPI);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g) * gp.bandwidth;
}
}
return sum;
@ -175,7 +206,8 @@ float gabor_wrap(float s, float p)
return (p != 0.0) ? s - p * floor(s / p) : 0.0;
}
float gabor_grid_3d(GaborParams gp, vec3 p, float scale)
/* Calculate noise using 3x3 cell grid. */
float gabor_grid_3d(GaborParams gp, vec3 p, float scale, int seed, int periodic)
{
vec3 coords = p * scale;
vec3 position = floor(coords);
@ -189,138 +221,199 @@ float gabor_grid_3d(GaborParams gp, vec3 p, float scale)
vec3 cell = position + cell_offset;
vec3 cell_position = local_position - cell_offset;
if (gp.periodic == 1) {
/* Wrap cells for periodic noise. */
if (periodic == 1) {
cell.x = gabor_wrap(cell.x, scale);
cell.y = gabor_wrap(cell.y, scale);
cell.z = gabor_wrap(cell.z, scale);
}
sum += gabor_cell_3d(gp, cell, cell_position);
sum += gabor_cell_3d(gp, cell, cell_position, seed);
}
}
}
if (gp.kernel == 4) { /* SHD_GABOR_KERNEL_PHASOR */
float pn = atan(sum.y, sum.x);
return pn * gp.radius;
if (gp.kernel_shape == 4) { /* SHD_GABOR_KERNEL_PHASOR */
float pn = atan(sum.y, sum.x) / M_PI;
return pn;
}
else {
return sum.x * gp.radius;
return sum.x;
}
}
/* Calculate noise using 2x2 cell grid for 2D noise. Less computational than 3x3. */
float gabor_grid_2d(GaborParams gp, vec3 p, float scale, int seed, int periodic)
{
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;
/* Wrap cells for periodic noise. */
if (periodic == 1) {
cell.x = gabor_wrap(cell.x, scale);
cell.y = gabor_wrap(cell.y, scale);
}
sum += gabor_cell_2d(gp, cell, cell_position, seed);
}
}
CharlieJolly marked this conversation as resolved Outdated

This seems like the kind of thing that you can sanitize before passing it to the shader.

This seems like the kind of thing that you can sanitize before passing it to the shader.

This is partly implemented in Cycles using constant folding. Do you have an example for Eevee?

This is partly implemented in Cycles using constant folding. Do you have an example for Eevee?

@CharlieJolly That's probably just a:

if (folding_condition) {
  const float base_value = 0.5f;
  return GPU_link(mat, "set_value", GPU_uniform(&base_value), &out->link);
}
@CharlieJolly That's probably just a: ``` if (folding_condition) { const float base_value = 0.5f; return GPU_link(mat, "set_value", GPU_uniform(&base_value), &out->link); } ```

But I just realized that the impulses are not constant, so you can ignore my comment here.

But I just realized that the impulses are not constant, so you can ignore my comment here.
if (gp.kernel_shape == 4) { /* SHD_GABOR_KERNEL_PHASOR */
float pn = atan(sum.y, sum.x) / M_PI;
return pn;
}
else {
return sum.x;
}
}
/* Gabor parameters. Octaves and impulses are clamped as these directly impact performance. */
GaborParams gabor_parameters(vec3 direction,
float base_frequency,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float bandwidth,
float radius,
float impulses,
float frequency,
float lacunarity,
float phase,
float weight,
float randomness,
float phase_variance,
float cell_randomness,
float rotation,
float variance,
int kernel,
int anisotropic,
int periodic)
float rot_variance,
int kernel_shape,
int anisotropic)
{
GaborParams gp;
gp.periodic = periodic;
gp.variance = variance;
gp.lacunarity = lacunarity;
gp.roughness = clamp(roughness, 0.0, 1.0);
gp.octaves = clamp(detail, 0.0, 15.0);
gp.impulses = clamp(impulses, 0.0001, 32.0);
gp.rot_variance = rot_variance;
gp.scl_lacunarity = scl_lacunarity;
gp.fre_lacunarity = fre_lacunarity;
gp.rot_lacunarity = rot_lacunarity;
gp.anisotropic = anisotropic;
gp.kernel = kernel;
gp.kernel_shape = kernel_shape;
gp.direction = direction;
gp.phase = phase;
gp.rotation = rotation;
gp.detail = detail;
gp.impulses = clamp(impulses, 0.0001, 32.0);
gp.weight = weight;
gp.randomness = randomness;
float sqrt_pi_over_ln2 = sqrt(M_PI / M_LN2);
float bandwidth_pow = exp2(bandwidth);
gp.bandwidth = ((bandwidth_pow - 1.0) / (bandwidth_pow + 1.0)) * sqrt_pi_over_ln2;
gp.radius = 1.0 / gp.bandwidth;
gp.frequency = frequency * gp.bandwidth;
gp.phase_variance = phase_variance;
gp.cell_randomness = cell_randomness;
gp.bandwidth = bandwidth;
gp.radius = radius;
gp.base_frequency = base_frequency;
return gp;
}
float gabor_noise(vec3 p,
vec3 direction,
float scale,
float detail,
float bandwidth,
float impulses,
float frequency,
float lacunarity,
float phase,
float weight,
float randomness,
float rotation,
float variance,
int kernel,
int anisotropic,
int periodic)
/* Layered fractal noise. For each layer, the seed is changed. This ensures that the impulses are
* occur in different places if lacunarity is set to 1. Kernel frequency and rotation have
* lacunarity parameters. Unlike Perlin noise, Gabor has more variables to consider when layering
* the noise. */
float gabor_fractal_noise(GaborParams gp, vec3 p, float scale, int dimensions, int periodic)
{
if (impulses == 0.0) {
return 0.5;
float fscale = 1.0;
float amp = 1.0;
float maxamp = 0.0;
float sum = 0.0;
float octaves = gp.octaves;
if (gp.roughness == 0.0 || gp.scl_lacunarity == 0.0) {
octaves = 0.0;
}
int n = int(octaves);
for (int i = 0; i <= n; i++) {
float t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, i, periodic) :
gabor_grid_2d(gp, fscale * p, scale, i, periodic);
gp.base_frequency *= gp.fre_lacunarity;
gp.rotation -= gp.rot_lacunarity;
sum += t * amp;
maxamp += amp;
amp *= gp.roughness;
fscale *= gp.scl_lacunarity;
}
float rmd = octaves - floor(octaves);
if (rmd != 0.0) {
float t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, n + 1, periodic) :
gabor_grid_2d(gp, fscale * p, scale, n + 1, periodic);
float sum2 = sum + t * amp;
return mix(sum, sum2, rmd) / maxamp;
}
else {
return sum / maxamp;
}
GaborParams gp;
gp = gabor_parameters(direction,
detail,
bandwidth,
impulses,
frequency,
lacunarity,
phase,
weight,
randomness,
rotation,
variance,
kernel,
anisotropic,
periodic);
return min(max(gabor_grid_3d(gp, p, scale) * 0.5 + 0.5, 0.0), 1.0);
}
/* Gabor node. */
void node_tex_gabor(vec3 co,
float scale,
float base_frequency,
float detail,
float lacunarity,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float bandwidth,
float radius,
float impulses,
float frequency,
float phase,
float weight,
float randomness,
float phase_variance,
float cell_randomness,
float rotation,
float variance,
float rot_variance,
vec3 direction,
float kernel,
float dimensions,
float kernel_shape,
float anisotropic,
float use_normalize,
float periodic,
out float value)
{
value = gabor_noise(co,
direction,
scale,
detail,
bandwidth,
impulses,
frequency,
lacunarity,
phase,
weight,
randomness,
rotation,
variance,
int(kernel),
int(anisotropic),
int(periodic));
/* Return early with mid level value. */
if (impulses == 0.0 || bandwidth == 0.0 || radius == 0.0 || scale == 0.0) {
value = (int(use_normalize) == 1) ? 0.5 : 0.0;
return;
}
/* Set Gabor params. */
GaborParams gp;
gp = gabor_parameters(direction,
base_frequency,
detail,
roughness,
scl_lacunarity,
fre_lacunarity,
rot_lacunarity,
bandwidth,
radius,
impulses,
phase,
phase_variance,
cell_randomness,
rotation,
rot_variance,
int(kernel_shape),
int(anisotropic));
/* Normalise 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. */
float g = gabor_fractal_noise(gp, co, scale, int(dimensions), int(periodic));
float impulse_scale = impulses < 1.0 ? (1.2613446229 * sqrt(gp.impulses)) :
1.0; // sqrt(0.75 / pi) = 1.26
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;
}

View File

@ -1226,7 +1226,9 @@ typedef struct NodeTexGabor {
char kernel;
char anisotropic;
char periodic;
char _pad[5];
char normalize;
char dimensions;
char _pad[3];
} NodeTexGabor;
typedef struct NodeTexVoronoi {
@ -1988,7 +1990,6 @@ typedef enum NodeGaborKernel {
typedef enum NodeGaborAnisotropic {
SHD_GABOR_MODE_ISOTROPIC,
SHD_GABOR_MODE_ANISOTROPIC,
SHD_GABOR_MODE_HYBRID,
} NodeGaborAnisotropic;
/* musgrave texture */

View File

@ -5278,11 +5278,12 @@ static void def_sh_tex_gabor(StructRNA *srna)
static const EnumPropertyItem prop_gabor_mode[] = {
{SHD_GABOR_MODE_ISOTROPIC, "ISOTROPIC", 0, "Isotropic", "Isotropic noise"},
{SHD_GABOR_MODE_ANISOTROPIC, "ANISOTROPIC", 0, "Anisotropic", "Anisotropic noise"},
{SHD_GABOR_MODE_HYBRID,
"HYBRID",
0,
"Hybrid",
"Mix between isotropic and anisotropic noise"},
{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},
};
@ -5290,6 +5291,12 @@ static void def_sh_tex_gabor(StructRNA *srna)
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, "kernel", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "kernel");
RNA_def_property_enum_items(prop, prop_gabor_kernel);
@ -5303,9 +5310,14 @@ static void def_sh_tex_gabor(StructRNA *srna)
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, NULL, "periodic", 0);
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");
}
static void def_sh_tex_voronoi(StructRNA *srna)

View File

@ -7,20 +7,25 @@
* Based on: Blender patch D287 & D3495
* With additional controls for kernel variance.
*
* Adapted from Open Shading Language
* Adapted from Open Shading Language implementation.
* 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. */
* 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
*/
#include "node_shader_util.hh"
#include "node_util.hh"
#include "BKE_texture.h"
#include "BLI_math_vector.hh"
#include "BLI_hash.hh"
#include "BLI_math_vector.hh"
#include "BLI_noise.hh"
#include "NOD_multi_function.hh"
@ -39,32 +44,49 @@ static void node_declare(NodeDeclarationBuilder &b)
b.is_function_node();
b.add_input<decl::Vector>("Vector").implicit_field(implicit_field_inputs::position);
b.add_input<decl::Float>("Scale").min(-1000.0f).max(1000.0f).default_value(5.0f);
b.add_input<decl::Float>("Detail").min(0.0f).max(15.0f).default_value(1.0f).description(
"Number of noise octaves");
b.add_input<decl::Float>("Lacunarity")
b.add_input<decl::Float>("Base Frequency")
.min(-32.0f)
.max(32.0f)
.default_value(4.0f)
.description("Frequency for the kernel shape, higher values provides more detail");
b.add_input<decl::Float>("Detail").min(0.0f).max(15.0f).default_value(0.0f).description(
"Number of noise octaves, high values are slower to compute");
b.add_input<decl::Float>("Roughness")
.min(0.0f)
.max(1.0f)
.default_value(0.5f)
.subtype(PROP_FACTOR);
b.add_input<decl::Float>("Scale Lacunarity")
.min(-100.0f)
.max(100.0f)
.default_value(2.0f)
.description(
"The difference between the scale of each two consecutive octaves. High "
"values are slower to compute");
b.add_input<decl::Float>("Bandwidth")
.min(0.00001f)
.max(8.0f)
.description("The difference between the scale of each consecutive octave");
b.add_input<decl::Float>("Frequency Lacunarity")
.min(-16.0f)
.max(16.0f)
.default_value(2.0f)
.description("Controls the amount of kernel detail");
.description("The difference between the kernel frequency of each consecutive octave");
b.add_input<decl::Float>("Rotation Lacunarity")
.subtype(PROP_ANGLE)
.description("The difference between the kernel rotation of each consecutive octave");
b.add_input<decl::Float>("Bandwidth")
.min(0.0f)
.max(32.0f)
.default_value(2.0f)
.description("Controls the power of the kernel");
b.add_input<decl::Float>("Radius")
.min(0.0f)
.max(1.0f)
.default_value(1.0f)
.subtype(PROP_FACTOR)
.description("Controls the radius of the kernel, values over 1 will produce artefacts");
b.add_input<decl::Float>("Impulses")
.min(0.0f)
.max(16.0f)
.default_value(2.0f)
.description("Controls the amount of kernel splats, high values are slower to compute");
b.add_input<decl::Float>("Frequency")
.min(-1000.0f)
.max(1000.0f)
.default_value(2.0f)
.description("Base frequency for the kernel shape, higher values provides more detail");
b.add_input<decl::Float>("Phase").subtype(PROP_ANGLE).description("Kernel shape phase");
b.add_input<decl::Float>("Phase Randomness")
.description("Controls the amount of kernel impulses, high values are slower to compute");
b.add_input<decl::Float>("Phase").subtype(PROP_ANGLE).description("Kernel shape phase offset");
b.add_input<decl::Float>("Phase Variance")
.min(0.0f)
.max(1.0f)
.default_value(1.0f)
@ -81,15 +103,20 @@ static void node_declare(NodeDeclarationBuilder &b)
.description("Rotation randomness");
b.add_input<decl::Vector>("Direction")
.description("Direction and magnitude of the anisotropy")
.hide_value();
.default_value({0.0f, 0.0f, 1.0f})
.min(-1.0f)
.max(1.0f)
.subtype(PROP_DIRECTION);
b.add_output<decl::Float>("Value").no_muted_links();
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiItemR(layout, ptr, "gabor_dimensions", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
uiItemR(layout, ptr, "anisotropic", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
uiItemR(layout, ptr, "kernel", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
uiItemR(layout, ptr, "periodic", UI_ITEM_NONE, IFACE_("Periodic"), ICON_NONE);
uiItemR(layout, ptr, "periodic", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
uiItemR(layout, ptr, "normalize", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
}
static void node_init(bNodeTree * /*ntree*/, bNode *node)
@ -101,6 +128,8 @@ static void node_init(bNodeTree * /*ntree*/, bNode *node)
tex->kernel = SHD_GABOR_KERNEL_GABOR;
tex->anisotropic = SHD_GABOR_MODE_ISOTROPIC;
tex->periodic = 0;
tex->normalize = 0;
tex->dimensions = 2;
node->storage = tex;
}
@ -114,18 +143,22 @@ static int node_shader_gpu_tex_gabor(GPUMaterial *mat,
node_shader_gpu_tex_mapping(mat, node, in, out);
const NodeTexGabor &storage = node_storage(*node);
const float dimensions = storage.dimensions;
const float kernel = storage.kernel;
const float anisotropic = storage.anisotropic;
const float periodic = storage.periodic;
const float use_normalize = storage.normalize;
return GPU_stack_link(mat,
node,
"node_tex_gabor",
in,
out,
GPU_uniform(&kernel),
GPU_uniform(&anisotropic),
GPU_uniform(&periodic));
GPU_constant(&dimensions),
GPU_constant(&kernel),
GPU_constant(&anisotropic),
GPU_constant(&use_normalize),
GPU_constant(&periodic));
}
static void node_update(bNodeTree *ntree, bNode *node)
@ -133,6 +166,7 @@ static void node_update(bNodeTree *ntree, bNode *node)
bNodeSocket *inDirectionSock = nodeFindSocket(node, SOCK_IN, "Direction");
bNodeSocket *inRotationSock = nodeFindSocket(node, SOCK_IN, "Rotation");
bNodeSocket *inVarianceSock = nodeFindSocket(node, SOCK_IN, "Rotation Variance");
bNodeSocket *inRotationLacunaritySock = nodeFindSocket(node, SOCK_IN, "Rotation Lacunarity");
const NodeTexGabor &storage = node_storage(*node);
@ -142,69 +176,45 @@ static void node_update(bNodeTree *ntree, bNode *node)
ntree, inRotationSock, (storage.anisotropic != SHD_GABOR_MODE_ANISOTROPIC));
bke::nodeSetSocketAvailability(
ntree, inVarianceSock, (storage.anisotropic != SHD_GABOR_MODE_ANISOTROPIC));
if (storage.anisotropic == SHD_GABOR_MODE_HYBRID) {
inRotationSock->typeinfo->subtype = PROP_FLOAT;
}
else if (storage.anisotropic == SHD_GABOR_MODE_ISOTROPIC) {
inRotationSock->typeinfo->subtype = PROP_ANGLE;
}
bke::nodeSetSocketAvailability(
ntree, inRotationLacunaritySock, (storage.anisotropic != SHD_GABOR_MODE_ANISOTROPIC));
}
#ifndef M_EPI_F
# define M_EPI_F 0.0432139182637722f
#endif
static int gabor_rng_seed(float3 cell, int seed)
{
float3 fcell = math::floor(cell);
int chash = int(noise::hash_float(float4(fcell[0], fcell[1], fcell[2], float(seed))));
if (chash == 0)
chash = 1;
return chash * 1519588931;
}
static float gabor_rng_uniform(int &rng)
{
const float int_max_inv = 1.0f / 2147483647.0f;
float res = float(rng) * int_max_inv * 0.5f + 0.5f;
rng *= 1519588931;
return res;
}
static int gabor_rng_poisson(int &rng, float mean)
{
if (mean >= 1.0f) {
return int(mean);
}
float g = math::exp(-mean);
int em = 0;
float t = gabor_rng_uniform(rng);
while (t > g) {
++em;
t *= gabor_rng_uniform(rng);
}
return em;
}
typedef struct GaborParams {
float detail;
float variance;
float weight;
float randomness;
float impulses;
float frequency;
float lacunarity;
float phase;
float rotation;
float radius;
float base_frequency;
float octaves;
float roughness;
float scl_lacunarity;
float fre_lacunarity;
float rot_lacunarity;
float bandwidth;
int periodic;
int kernel;
float radius;
float impulses;
float phase;
float phase_variance;
float cell_randomness;
float rotation;
float rot_variance;
int kernel_shape;
int anisotropic;
float3 direction;
} GaborParams;
int impulses_per_cell(int seed, float3 cell, float impulses)
{
int n = int(impulses);
float rmd = impulses - math::floor(impulses);
if (rmd > 0.0f) {
float t = noise::hash_float_to_float(float4(cell, float(seed - 1259)));
return (t <= rmd) ? n + 1 : n;
}
return n;
}
static float3 gabor_kernel(const GaborParams gp,
const float freq,
const float3 omega,
@ -214,20 +224,22 @@ static float3 gabor_kernel(const GaborParams gp,
{
float h;
if (gp.kernel == SHD_GABOR_KERNEL_PHASOR) {
if (gp.kernel_shape == SHD_GABOR_KERNEL_PHASOR) {
const float phase = freq * math::dot(omega, position) + phi;
return float3(math::cos(phase), math::sin(phase), 0.0f) * g;
}
else if (gp.kernel == SHD_GABOR_KERNEL_SQUARE) {
else if (gp.kernel_shape == SHD_GABOR_KERNEL_SQUARE) {
float3 positionyx = float3(position.y, position.x, position.z);
h = math::cos(freq * math::dot(omega, position) + phi) +
math::cos(freq * math::dot(omega, positionyx) + phi);
h = (math::cos(freq * math::dot(omega, position) + phi) +
math::cos(freq * math::dot(omega, positionyx) + phi)) *
0.5f;
}
else if (gp.kernel == SHD_GABOR_KERNEL_CROSS) {
else if (gp.kernel_shape == SHD_GABOR_KERNEL_CROSS) {
h = math::cos(freq * math::length(position * omega) + phi);
}
else if (gp.kernel == SHD_GABOR_KERNEL_RING) {
h = math::cos(freq * math::dot(position, omega) + phi) - math::cos(freq * math::length(position) + phi);
else if (gp.kernel_shape == SHD_GABOR_KERNEL_RING) {
h = math::cos(freq * math::dot(position, omega) + phi) -
math::cos(freq * math::length(position) + phi);
}
else { // SHD_GABOR_KERNEL_GABOR
h = math::cos(freq * math::dot(omega, position) + phi);
@ -243,21 +255,15 @@ static void gabor_sample(const GaborParams gp,
float3 &omega,
float &phi)
{
const float pvar = math::interpolate(0.0f, prand * 2.0f - 1.0f, gp.weight);
const float pvar = math::interpolate(0.0f, prand * 2.0f - 1.0f, gp.phase_variance);
phi = 2.0f * float(M_PI) * pvar + gp.phase;
const float ovar = float(M_PI) * (orand * 2.0f - 1.0f);
const float omega_t = ovar * gp.variance - gp.rotation;
if (gp.anisotropic == SHD_GABOR_MODE_ANISOTROPIC) { /* ANISO */
omega = math::normalize(gp.direction);
}
else if (gp.anisotropic == SHD_GABOR_MODE_HYBRID) { /* HYBRID */
const float sin_omega_t = math::sin(omega_t);
const float cos_omega_t = math::cos(omega_t);
omega = math::length(gp.direction) * float3(cos_omega_t, sin_omega_t, 0.0f);
omega = gp.direction;
}
else { /* ISO */
const float ovar = float(M_PI) * (orand * 2.0f - 1.0f);
const float omega_t = ovar * gp.rot_variance - gp.rotation;
const float cos_omega_p = 1.0f - 2.0f * orand2;
const float sin_omega_p = math::sqrt(1.0f - cos_omega_p * cos_omega_p);
const float sin_omega_t = math::sin(omega_t);
@ -267,77 +273,79 @@ static void gabor_sample(const GaborParams gp,
}
}
static float3 gabor_fractal(GaborParams gp,
const float dv,
const float3 omega,
const float phi,
const float3 kernel_position)
/* Generate noise based on the cell position. */
static float3 gabor_cell_3d(GaborParams &gp, float3 cell, float3 cell_position, int seed)
{
float freq = gp.frequency;
float3 sum = float3(0.0f);
const float g = (1.0f + M_EPI_F) *
(math::exp(-float(M_PI) * gp.bandwidth * gp.bandwidth * dv) - M_EPI_F);
const float amp = 1.0f;
float maxamp = 0.0f;
const float octaves = math::clamp(gp.detail, 0.0f, 15.0f);
const int n = int(octaves);
for (int i = 0; i <= n; i++) {
float3 t = gabor_kernel(gp, freq, omega, phi, kernel_position, g);
sum += t * amp;
maxamp += amp;
freq *= gp.lacunarity;
}
const float rmd = octaves - math::floor(octaves);
if (rmd > 0.0f) {
const float3 t = gabor_kernel(gp, freq, omega, phi, kernel_position, g);
float3 sum2 = sum + t * amp;
sum /= maxamp;
sum2 /= maxamp + amp;
return (1.0f - rmd) * sum + rmd * sum2;
}
else {
return sum / maxamp;
}
}
static float3 gabor_cell_3d(GaborParams gp, float3 cell, float3 cell_position)
{
int rng = gabor_rng_seed(cell, 0);
const int num_impulses = gabor_rng_poisson(rng, gp.impulses);
int num_impulses = impulses_per_cell(seed, cell, gp.impulses);
float3 sum = float3(0.0f);
for (int i = 0; i < num_impulses; ++i) {
float xrand = gabor_rng_uniform(rng);
float yrand = gabor_rng_uniform(rng);
float zrand = gabor_rng_uniform(rng);
xrand = math::interpolate(0.0f, xrand, gp.randomness);
yrand = math::interpolate(0.0f, yrand, gp.randomness);
zrand = math::interpolate(0.0f, zrand, gp.randomness);
float3 rand_position = float3(0.0f);
if (gp.cell_randomness > 0.0f) {
rand_position = math::interpolate(
rand_position,
noise::hash_float_to_float3(float4(cell, float(seed + i * 1259))),
gp.cell_randomness);
}
const float3 kernel_position = (cell_position - float3(xrand, yrand, zrand)) * gp.radius;
const float3 kernel_position = (cell_position - rand_position);
const float dv = math::dot(kernel_position, kernel_position);
const float orand = gabor_rng_uniform(rng);
const float prand = gabor_rng_uniform(rng);
const float orand2 = gabor_rng_uniform(rng);
const float dv = math::dot(kernel_position, kernel_position) / gp.radius;
if (dv < gp.radius * gp.radius) {
if (dv <= 1.0f) {
float3 omega;
float phi;
const float3 rand_values = noise::hash_float_to_float3(
float4(cell, float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, orand, orand2, prand, omega, phi);
sum += gabor_fractal(gp, dv, omega, phi, kernel_position);
gabor_sample(gp, rand_values.x, rand_values.y, rand_values.z, omega, phi);
const float g = (1.0f + M_EPI_F) * (math::exp(-float(M_PI) * dv) - M_EPI_F);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g) * gp.bandwidth;
}
}
return sum;
}
static float3 gabor_cell_2d(GaborParams &gp, float3 cell, float3 cell_position, int seed)
{
int num_impulses = impulses_per_cell(seed, cell, gp.impulses);
float3 sum = float3(0.0f);
for (int i = 0; i < num_impulses; ++i) {
float3 rand_position = float3(0.0f);
if (gp.cell_randomness > 0.0f) {
rand_position = math::interpolate(
rand_position,
noise::hash_float_to_float3(float4(cell, float(seed + i * 1259))),
gp.cell_randomness);
rand_position.z = 0.0f;
}
const float3 kernel_position = (cell_position - rand_position);
const float dv = math::dot(kernel_position, kernel_position) / gp.radius;
if (dv <= 1.0f) {
float3 omega;
float phi;
const float3 rand_values = noise::hash_float_to_float3(
float4(cell, float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, rand_values.x, rand_values.y, rand_values.z, omega, phi);
const float g = (1.0f + M_EPI_F) * (math::exp(-float(M_PI) * dv) - M_EPI_F);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g) * gp.bandwidth;
}
}
return sum;
}
/* Wrap value. */
static float gabor_wrap(const float a, const float b)
{
return (b != 0.0f) ? a - b * math::floor(a / b) : 0.0f;
}
static float gabor_grid_3d(const GaborParams gp, const float3 p, const float scale)
/* 3*3 cell grid */
static float gabor_grid_3d(
GaborParams &gp, const float3 p, const float scale, int seed, int periodic)
{
const float3 coords = p * scale;
const float3 position = math::floor(coords);
@ -348,105 +356,204 @@ static float gabor_grid_3d(const GaborParams gp, const float3 p, const float sca
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.0) {
continue;
}
float3 cell = position + cell_offset;
const float3 cell_position = local_position - cell_offset;
if (gp.periodic) {
if (periodic) {
cell[0] = gabor_wrap(cell[0], scale);
cell[1] = gabor_wrap(cell[1], scale);
cell[2] = gabor_wrap(cell[2], scale);
}
sum += gabor_cell_3d(gp, cell, cell_position);
sum += gabor_cell_3d(gp, cell, cell_position, seed);
}
}
}
if (gp.kernel == SHD_GABOR_KERNEL_PHASOR) {
const float pn = atan2f(sum.y, sum.x);
return pn * gp.radius;
if (gp.kernel_shape == SHD_GABOR_KERNEL_PHASOR) {
const float pn = math::atan2(sum.y, sum.x) / float(M_PI);
return pn;
}
else {
return sum.x * gp.radius;
return sum.x;
}
}
/* 2*2 cell grid */
static float gabor_grid_2d(
GaborParams &gp, const float3 p, const float scale, int seed, int periodic)
{
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, 1.0f) - local_position) * cell_offset;
if (math::dot(Pr, Pr) >= 1.0) {
continue;
}
float3 cell = position + cell_offset;
const float3 cell_position = local_position - cell_offset;
if (periodic) {
cell[0] = gabor_wrap(cell[0], scale);
cell[1] = gabor_wrap(cell[1], scale);
}
sum += gabor_cell_2d(gp, cell, cell_position, seed);
}
}
if (gp.kernel_shape == SHD_GABOR_KERNEL_PHASOR) {
const float pn = math::atan2(sum.y, sum.x) / float(M_PI);
return pn;
}
else {
return sum.x;
}
}
/* Fractal gabor noise. Layered noise with octaves, lacunarity, frequency and roughness control. */
static float gabor_fractal_noise(
GaborParams &gp, const float3 p, const float scale, const int dimensions, const int periodic)
{
float fscale = 1.0f;
float amp = 1.0f;
float maxamp = 0.0f;
float sum = 0.0f;
float octaves = gp.octaves;
if (gp.roughness == 0.0f || gp.scl_lacunarity == 0.0f) {
octaves = 0.0f;
}
const int n = int(octaves);
for (int i = 0; i <= n; i++) {
const float t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, i, periodic) :
gabor_grid_2d(gp, fscale * p, scale, i, periodic);
gp.base_frequency *= gp.fre_lacunarity;
gp.rotation -= gp.rot_lacunarity;
sum += t * amp;
maxamp += amp;
amp *= gp.roughness;
fscale *= gp.scl_lacunarity;
}
float rmd = octaves - math::floor(octaves);
if (rmd != 0.0f) {
const float t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, n + 1, periodic) :
gabor_grid_2d(gp, fscale * p, scale, n + 1, periodic);
const float sum2 = sum + t * amp;
return ((1.0f - rmd) * sum + rmd * sum2) / maxamp;
}
else {
return sum / maxamp;
}
}
/* Set parameters used by Gabor noise. */
static GaborParams gabor_parameters(float3 direction,
float base_frequency,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float bandwidth,
float radius,
float impulses,
float frequency,
float lacunarity,
float phase,
float weight,
float randomness,
float phase_variance,
float cell_randomness,
float rotation,
float variance,
int kernel,
int anisotropic,
int periodic)
float rot_variance,
int kernel_shape,
int anisotropic)
{
GaborParams gp;
gp.periodic = periodic;
gp.variance = variance;
gp.lacunarity = lacunarity;
gp.roughness = math::clamp(roughness, 0.0f, 1.0f);
gp.octaves = math::clamp(detail, 0.0f, 15.0f);
gp.impulses = math::clamp(impulses, 0.0001f, 32.0f);
gp.rot_variance = rot_variance;
gp.scl_lacunarity = scl_lacunarity;
gp.fre_lacunarity = fre_lacunarity;
gp.rot_lacunarity = rot_lacunarity;
gp.anisotropic = anisotropic;
gp.kernel = kernel;
gp.kernel_shape = kernel_shape;
gp.direction = direction;
gp.phase = phase;
gp.rotation = rotation;
gp.detail = detail;
gp.impulses = math::clamp(impulses, 0.0001f, 32.0f);
gp.weight = weight;
gp.randomness = randomness;
const float sqrt_pi_over_ln2 = math::sqrt(float(M_PI / M_LN2));
const float bandwidth_pow = exp2(bandwidth);
gp.bandwidth = ((bandwidth_pow - 1.0f) / (bandwidth_pow + 1.0f)) * sqrt_pi_over_ln2;
gp.radius = 1.0f / gp.bandwidth;
gp.frequency = frequency * gp.bandwidth;
gp.phase_variance = phase_variance;
gp.cell_randomness = cell_randomness;
gp.bandwidth = bandwidth;
gp.radius = radius;
gp.base_frequency = base_frequency;
return gp;
}
static float gabor_noise(float3 p,
float3 direction,
float scale,
float base_frequency,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float bandwidth,
float radius,
float impulses,
float frequency,
float lacunarity,
float phase,
float weight,
float randomness,
float phase_variance,
float cell_randomness,
float rotation,
float variance,
float rot_variance,
int dimensions,
int kernel,
int anisotropic,
int use_normalize,
int periodic)
{
if (impulses == 0.0f) {
return 0.5f;
if (impulses == 0.0f || bandwidth == 0.0f || radius == 0.0f || scale == 0.0f) {
return (use_normalize) ? 0.5f : 0.0f;
}
GaborParams gp;
gp = gabor_parameters(direction,
base_frequency,
detail,
roughness,
scl_lacunarity,
fre_lacunarity,
rot_lacunarity,
bandwidth,
radius,
impulses,
frequency,
lacunarity,
phase,
weight,
randomness,
phase_variance,
cell_randomness,
rotation,
variance,
rot_variance,
kernel,
anisotropic,
periodic);
anisotropic);
return std::min(std::max(gabor_grid_3d(gp, p, scale) * 0.5f + 0.5f, 0.0f), 1.0f);
float g = gabor_fractal_noise(gp, p, scale, dimensions, periodic);
float impulse_scale = impulses < 1.0f ? (1.2613446229f * math::sqrt(gp.impulses)) :
1.0f; // sqrt(0.75 / pi) = 1.26
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, int anistropic)
@ -455,19 +562,25 @@ static mf::Signature gabor_signature(const char *name, int anistropic)
mf::SignatureBuilder builder{name, signature};
builder.single_input<float3>("Vector");
builder.single_input<float>("Scale");
builder.single_input<float>("Base Frequency");
builder.single_input<float>("Detail");
builder.single_input<float>("Lacunarity");
builder.single_input<float>("Roughness");
builder.single_input<float>("Scale Lacunarity");
builder.single_input<float>("Frequency Lacunarity");
if (anistropic == SHD_GABOR_MODE_ISOTROPIC) {
builder.single_input<float>("Rotation Lacunarity");
}
builder.single_input<float>("Bandwidth");
builder.single_input<float>("Radius");
builder.single_input<float>("Impulses");
builder.single_input<float>("Frequency");
builder.single_input<float>("Phase");
builder.single_input<float>("Phase Randomness");
builder.single_input<float>("Phase Variance");
builder.single_input<float>("Cell Randomness");
if (anistropic != SHD_GABOR_MODE_ANISOTROPIC) {
if (anistropic == SHD_GABOR_MODE_ISOTROPIC) {
builder.single_input<float>("Rotation");
builder.single_input<float>("Rotation Variance");
}
if (anistropic != SHD_GABOR_MODE_ISOTROPIC) {
if (anistropic == SHD_GABOR_MODE_ANISOTROPIC) {
builder.single_input<float3>("Direction");
}
builder.single_output<float>("Value");
@ -476,11 +589,14 @@ static mf::Signature gabor_signature(const char *name, int anistropic)
class GaborNoiseFunction : public mf::MultiFunction {
private:
int kernel_;
int dimensions_;
int kernel_shape_;
int periodic_;
int normalize_;
public:
GaborNoiseFunction(int kernel, int periodic) : kernel_(kernel), periodic_(periodic)
GaborNoiseFunction(int dimensions, int kernel, int periodic, int normalize)
: dimensions_(dimensions), kernel_shape_(kernel), periodic_(periodic), normalize_(normalize)
{
static mf::Signature signature = gabor_signature("GaborNoise", SHD_GABOR_MODE_ISOTROPIC);
this->set_signature(&signature);
@ -491,18 +607,28 @@ class GaborNoiseFunction : public mf::MultiFunction {
int param = 0;
const VArray<float3> &vector = params.readonly_single_input<float3>(param++, "Vector");
const VArray<float> &scale = params.readonly_single_input<float>(param++, "Scale");
const VArray<float> &base_frequency = params.readonly_single_input<float>(param++,
"Base Frequency");
const VArray<float> &detail = params.readonly_single_input<float>(param++, "Detail");
const VArray<float> &lacunarity = params.readonly_single_input<float>(param++, "Lacunarity");
const VArray<float> &roughness = params.readonly_single_input<float>(param++, "Roughness");
const VArray<float> &scale_lacunarity = params.readonly_single_input<float>(
param++, "Scale Lacunarity");
const VArray<float> &freq_lacunarity = params.readonly_single_input<float>(
param++, "Frequency Lacunarity");
const VArray<float> &rot_lacunarity = params.readonly_single_input<float>(
param++, "Rotation Lacunarity");
const VArray<float> &bandwidth = params.readonly_single_input<float>(param++, "Bandwidth");
const VArray<float> &radius = params.readonly_single_input<float>(param++, "Radius");
const VArray<float> &impulses = params.readonly_single_input<float>(param++, "Impulses");
const VArray<float> &frequency = params.readonly_single_input<float>(param++, "Frequency");
const VArray<float> &phase = params.readonly_single_input<float>(param++, "Phase");
const VArray<float> &weight = params.readonly_single_input<float>(param++, "Phase Randomness");
const VArray<float> &randomness = params.readonly_single_input<float>(param++,
"Cell Randomness");
const VArray<float> &phase_variance = params.readonly_single_input<float>(param++,
"Phase Variance");
const VArray<float> &cell_randomness = params.readonly_single_input<float>(param++,
"Cell Randomness");
const VArray<float> &rotation = params.readonly_single_input<float>(param++, "Rotation");
const VArray<float> &variance = params.readonly_single_input<float>(param++,
"Rotation Variance");
const VArray<float> &rot_variance = params.readonly_single_input<float>(param++,
"Rotation Variance");
MutableSpan<float> r_value = params.uninitialized_single_output_if_required<float>(param++,
"Value");
@ -510,18 +636,24 @@ class GaborNoiseFunction : public mf::MultiFunction {
r_value[i] = gabor_noise(vector[i],
float3(0.0f),
scale[i],
base_frequency[i],
detail[i],
roughness[i],
scale_lacunarity[i],
freq_lacunarity[i],
rot_lacunarity[i],
bandwidth[i],
radius[i],
impulses[i],
frequency[i],
lacunarity[i],
phase[i],
weight[i],
randomness[i],
phase_variance[i],
cell_randomness[i],
rotation[i],
variance[i],
kernel_,
rot_variance[i],
dimensions_,
kernel_shape_,
SHD_GABOR_MODE_ISOTROPIC,
normalize_,
periodic_);
});
}
@ -537,11 +669,17 @@ class GaborNoiseFunction : public mf::MultiFunction {
class AnisotropicGaborNoiseFunction : public mf::MultiFunction {
private:
int kernel_;
int dimensions_;
int kernel_shape_;
int periodic_;
int normalize_;
public:
AnisotropicGaborNoiseFunction(int kernel, int periodic) : kernel_(kernel), periodic_(periodic)
AnisotropicGaborNoiseFunction(int dimensions, int kernel_shape, int periodic, int normalize)
: dimensions_(dimensions),
kernel_shape_(kernel_shape),
periodic_(periodic),
normalize_(normalize)
{
static mf::Signature signature = gabor_signature("AnistropicGaborNoise",
SHD_GABOR_MODE_ANISOTROPIC);
@ -553,15 +691,22 @@ class AnisotropicGaborNoiseFunction : public mf::MultiFunction {
int param = 0;
const VArray<float3> &vector = params.readonly_single_input<float3>(param++, "Vector");
const VArray<float> &scale = params.readonly_single_input<float>(param++, "Scale");
const VArray<float> &base_frequency = params.readonly_single_input<float>(param++,
"Base Frequency");
const VArray<float> &detail = params.readonly_single_input<float>(param++, "Detail");
const VArray<float> &lacunarity = params.readonly_single_input<float>(param++, "Lacunarity");
const VArray<float> &roughness = params.readonly_single_input<float>(param++, "Roughness");
const VArray<float> &scale_lacunarity = params.readonly_single_input<float>(
param++, "Scale Lacunarity");
const VArray<float> &freq_lacunarity = params.readonly_single_input<float>(
param++, "Frequency Lacunarity");
const VArray<float> &bandwidth = params.readonly_single_input<float>(param++, "Bandwidth");
const VArray<float> &radius = params.readonly_single_input<float>(param++, "Radius");
const VArray<float> &impulses = params.readonly_single_input<float>(param++, "Impulses");
const VArray<float> &frequency = params.readonly_single_input<float>(param++, "Frequency");
const VArray<float> &phase = params.readonly_single_input<float>(param++, "Phase");
const VArray<float> &weight = params.readonly_single_input<float>(param++, "Phase Randomness");
const VArray<float> &randomness = params.readonly_single_input<float>(param++,
"Cell Randomness");
const VArray<float> &phase_variance = params.readonly_single_input<float>(param++,
"Phase Variance");
const VArray<float> &cell_randomness = params.readonly_single_input<float>(param++,
"Cell Randomness");
const VArray<float3> &direction = params.readonly_single_input<float3>(param++, "Direction");
MutableSpan<float> r_value = params.uninitialized_single_output_if_required<float>(param++,
"Value");
@ -570,18 +715,24 @@ class AnisotropicGaborNoiseFunction : public mf::MultiFunction {
r_value[i] = gabor_noise(vector[i],
direction[i],
scale[i],
base_frequency[i],
detail[i],
roughness[i],
scale_lacunarity[i],
freq_lacunarity[i],
0.0f,
bandwidth[i],
radius[i],
impulses[i],
frequency[i],
lacunarity[i],
phase[i],
weight[i],
randomness[i],
phase_variance[i],
cell_randomness[i],
0.0f,
0.0f,
kernel_,
dimensions_,
kernel_shape_,
SHD_GABOR_MODE_ANISOTROPIC,
normalize_,
periodic_);
});
}
@ -595,81 +746,17 @@ class AnisotropicGaborNoiseFunction : public mf::MultiFunction {
}
};
class HybridGaborNoiseFunction : public mf::MultiFunction {
private:
int kernel_;
int periodic_;
public:
HybridGaborNoiseFunction(int kernel, int periodic) : kernel_(kernel), periodic_(periodic)
{
static mf::Signature signature = gabor_signature("HybridGaborNoise", SHD_GABOR_MODE_HYBRID);
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
int param = 0;
const VArray<float3> &vector = params.readonly_single_input<float3>(param++, "Vector");
const VArray<float> &scale = params.readonly_single_input<float>(param++, "Scale");
const VArray<float> &detail = params.readonly_single_input<float>(param++, "Detail");
const VArray<float> &lacunarity = params.readonly_single_input<float>(param++, "Lacunarity");
const VArray<float> &bandwidth = params.readonly_single_input<float>(param++, "Bandwidth");
const VArray<float> &impulses = params.readonly_single_input<float>(param++, "Impulses");
const VArray<float> &frequency = params.readonly_single_input<float>(param++, "Frequency");
const VArray<float> &phase = params.readonly_single_input<float>(param++, "Phase");
const VArray<float> &weight = params.readonly_single_input<float>(param++, "Phase Randomness");
const VArray<float> &randomness = params.readonly_single_input<float>(param++,
"Cell Randomness");
const VArray<float> &rotation = params.readonly_single_input<float>(param++, "Rotation");
const VArray<float> &variance = params.readonly_single_input<float>(param++,
"Rotation Variance");
const VArray<float3> &direction = params.readonly_single_input<float3>(param++, "Direction");
MutableSpan<float> r_value = params.uninitialized_single_output_if_required<float>(param++,
"Value");
mask.foreach_index([&](const int64_t i) {
r_value[i] = gabor_noise(vector[i],
direction[i],
scale[i],
detail[i],
bandwidth[i],
impulses[i],
frequency[i],
lacunarity[i],
phase[i],
weight[i],
randomness[i],
rotation[i],
variance[i],
kernel_,
SHD_GABOR_MODE_HYBRID,
periodic_);
});
}
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());
switch (storage.anisotropic) {
case SHD_GABOR_MODE_ISOTROPIC:
builder.construct_and_set_matching_fn<GaborNoiseFunction>(storage.kernel, storage.periodic);
builder.construct_and_set_matching_fn<GaborNoiseFunction>(
storage.dimensions, storage.kernel, storage.periodic, storage.normalize);
break;
case SHD_GABOR_MODE_ANISOTROPIC:
builder.construct_and_set_matching_fn<AnisotropicGaborNoiseFunction>(storage.kernel,
storage.periodic);
break;
case SHD_GABOR_MODE_HYBRID:
builder.construct_and_set_matching_fn<HybridGaborNoiseFunction>(storage.kernel,
storage.periodic);
builder.construct_and_set_matching_fn<AnisotropicGaborNoiseFunction>(
storage.dimensions, storage.kernel, storage.periodic, storage.normalize);
break;
}
}