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.
23 changed files with 2821 additions and 5 deletions

View File

@ -941,6 +941,18 @@ static ShaderNode *add_node(Scene *scene,
get_tex_mapping(noise, b_texture_mapping);
node = noise;
}
else if (b_node.is_a(&RNA_ShaderNodeTexGabor)) {
BL::ShaderNodeTexGabor b_gabor_node(b_node);
GaborTextureNode *gabor = graph->create_node<GaborTextureNode>();
gabor->set_mode((NodeGaborMode)b_gabor_node.mode());
gabor->set_dimensions(b_gabor_node.gabor_dimensions());
gabor->set_periodic(b_gabor_node.periodic());
gabor->set_use_normalize(b_gabor_node.normalize());
gabor->set_use_origin_offset(b_gabor_node.use_origin_offset());
BL::TexMapping b_texture_mapping(b_gabor_node.texture_mapping());
get_tex_mapping(gabor, b_texture_mapping);
node = gabor;
}
else if (b_node.is_a(&RNA_ShaderNodeTexMusgrave)) {
BL::ShaderNodeTexMusgrave b_musgrave_node(b_node);
MusgraveTextureNode *musgrave_node = graph->create_node<MusgraveTextureNode>();

View File

@ -162,6 +162,7 @@ set(SRC_KERNEL_SVM_HEADERS
svm/fresnel.h
svm/wireframe.h
svm/wavelength.h
svm/gabor.h
svm/gamma.h
svm/brightness.h
svm/geometry.h

View File

@ -33,6 +33,7 @@ set(SRC_OSL
node_environment_texture.osl
node_float_curve.osl
node_fresnel.osl
node_gabor_texture.osl
node_gamma.osl
node_geometry.osl
node_glass_bsdf.osl

View File

@ -0,0 +1,463 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
/* Gabor Noise
*
* Based on: Blender patch D287
*
* Adapted from Open Shading Language
* Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al.
* All Rights Reserved.
*
* Gabor noise originally based on:
* Lagae, A. and Drettakis, G. 2011. Filtering Solid Gabor Noise.
*/
/* See GLSL implementation for code comments. */
#include "node_hash.h"
#include "node_math.h"
#include "node_noise.h"
#include "stdcycles.h"
#include "vector2.h"
#include "vector4.h"
#define vector3 point
#define GABOR_SEED 1259
struct GaborParams {
float frequency;
float radius;
float impulses;
float phase;
float phase_variance;
float rotation;
float init_rotation;
float rot_variance;
float tilt_randomness;
float cell_randomness;
float anisotropy;
string mode;
vector direction;
};
struct FractalParams {
float octaves;
float roughness;
float scl_lacunarity;
float fre_lacunarity;
float rot_lacunarity;
};
int impulses_per_cell(vector3 cell, float impulses, int seed)
{
int n = int(impulses);
float rmd = impulses - floor(impulses);
if (rmd > 0.0) {
float t = hash_vector4_to_float(vector4(cell[0], cell[1], cell[2], float(seed - GABOR_SEED)));
if (t <= rmd) {
return n + 1;
}
return n;
}
return n;
}
vector3 gabor_kernel(GaborParams gp, point omega, float phi, point position, float dv)
{
float g = cos(M_PI * sqrt(dv)) * 0.5 + 0.5;
vector3 r = vector3(0.0);
float h;
if (gp.mode == "gabor") { /* SHD_GABOR_MODE_GABOR */
h = gp.frequency * dot(omega, position) + phi;
r = vector3(cos(h), 0.0, 0.0);
}
else if (gp.mode == "phasor") { /* SHD_GABOR_MODE_PHASOR */
h = gp.frequency * dot(omega, position) + phi;
r = vector3(cos(h), sin(h), 0.0);
}
else if (gp.mode == "gabor_cross") { /* SHD_GABOR_MODE_CROSS */
h = gp.frequency * length(omega * position) + phi;
r = vector3(cos(h), 0.0, 0.0);
}
else if (gp.mode == "phasor_cross") { /* SHD_GABOR_MODE_PHASOR_CROSS */
h = gp.frequency * length(omega * position) + phi;
r = vector3(cos(h), sin(h), 0.0);
}
else if (gp.mode == "gabor_ring") { /* SHD_GABOR_MODE_RING */
h = cos(gp.frequency * dot(omega, position) + phi) +
cos(gp.frequency * length(position) + phi);
r = vector3(h, 0.0, 0.0) * 0.5;
;
}
else if (gp.mode == "phasor_ring") { /* SHD_GABOR_MODE_PHASOR_RING */
h = cos(gp.frequency * dot(omega, position) + phi) +
cos(gp.frequency * length(position) + phi);
float h2 = sin(gp.frequency * dot(omega, position) + phi) +
sin(gp.frequency * length(position) + phi);
r = vector3(h, h2, 0.0) * 0.5;
}
else if (gp.mode == "gabor_square") { /* SHD_GABOR_MODE_SQUARE */
vector3 positionyxz = vector3(position.y, position.x, position.z);
h = cos(gp.frequency * dot(omega, position) + phi) +
cos(gp.frequency * dot(omega, positionyxz) + phi);
r = vector3(h, 0.0, 0.0) * 0.5;
}
else if (gp.mode == "phasor_square") {
vector3 positionyxz = vector3(position.y, position.x, position.z);
h = cos(gp.frequency * dot(omega, position) + phi) +
cos(gp.frequency * dot(omega, positionyxz) + phi);
float h2 = sin(gp.frequency * dot(omega, position) + phi) +
sin(gp.frequency * dot(omega, positionyxz) + phi);
r = vector3(h, h2, 0.0) * 0.5;
}
return r * g;
}
vector gabor_sample(GaborParams gp, vector3 cell, int seed, output float phi)
{
vector3 rand_values = hash_vector4_to_color(vector4(cell.x, cell.y, cell.z, float(seed))) * 2.0 -
1.0;
float pvar = mix(0.0, rand_values.z, gp.phase_variance);
phi = M_2PI * pvar + gp.phase;
float omega_t = M_PI * (rand_values.x) * gp.rot_variance - gp.rotation - gp.init_rotation;
float cos_omega_p = clamp(rand_values.y * gp.tilt_randomness, -1.0, 1.0);
float sin_omega_p = sqrt(1.0 - cos_omega_p * cos_omega_p);
float sin_omega_t = sin(omega_t);
float cos_omega_t = cos(omega_t);
return mix(normalize(vector(cos_omega_t * sin_omega_p, sin_omega_t * sin_omega_p, cos_omega_p)),
normalize(transform(euler_to_mat(vector3(0.0, 0.0, -gp.rotation)), gp.direction)),
gp.anisotropy);
}
vector3 gabor_cell_3d(output GaborParams gp, point cell, point cell_position, int seed)
{
int num_impulses = impulses_per_cell(cell, gp.impulses, seed);
vector3 sum = vector3(0.0);
for (int i = 0; i < num_impulses; ++i) {
vector3 rand_position = mix(
vector3(0.0),
hash_vector4_to_color(vector4(cell[0], cell[1], cell[2], float(seed + i * GABOR_SEED))),
gp.cell_randomness);
point kernel_position = (cell_position - rand_position);
float dv = dot(kernel_position, kernel_position) / gp.radius;
if (dv <= 1.0) {
float phi;
vector3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
}
vector3 gabor_cell_2d(output GaborParams gp, point cell, point cell_position, int seed)
{
int num_impulses = impulses_per_cell(cell, gp.impulses, seed);
vector3 sum = vector3(0.0);
for (int i = 0; i < num_impulses; ++i) {
vector3 rand_position = mix(
vector3(0.0),
hash_vector4_to_color(vector4(cell[0], cell[1], cell[2], float(seed + i * GABOR_SEED))),
gp.cell_randomness);
rand_position.z = 0.0;
point kernel_position = (cell_position - rand_position);
float dv = dot(kernel_position, kernel_position) / gp.radius;
if (dv <= 1.0) {
float phi;
vector3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
}
float gabor_coord_wrap(float a, float b)
{
return (b != 0.0) ? a - b * floor(a / b) : 0.0;
}
vector3 gabor_grid_3d(output GaborParams gp, point p, float scale, int periodic, int seed)
{
point coords = p * scale;
point position = floor(coords);
point local_position = coords - position;
vector3 sum = 0.0;
for (int k = -1; k <= 1; k++) {
for (int j = -1; j <= 1; j++) {
for (int i = -1; i <= 1; i++) {
point cell_offset = point(i, j, k);
point cell = position + cell_offset;
point cell_position = local_position - cell_offset;
/* Skip this cell if it's too far away to contribute - Bruemmer.osl */
point Pr = (point(i > 0, j > 0, k > 0) - local_position) * cell_offset;
if (dot(Pr, Pr) >= 1.0) {
continue;
}
if (periodic == 1) {
cell[0] = gabor_coord_wrap(cell[0], scale);
cell[1] = gabor_coord_wrap(cell[1], scale);
cell[2] = gabor_coord_wrap(cell[2], scale);
}
sum += gabor_cell_3d(gp, cell, cell_position, seed);
}
}
}
return sum;
}
vector3 gabor_grid_2d(output GaborParams gp, point p, float scale, int periodic, int seed)
{
point coords = point(p.x, p.y, 0.0) * scale;
point position = floor(coords);
point local_position = coords - position;
vector3 sum = 0.0;
for (int j = -1; j <= 1; j++) {
for (int i = -1; i <= 1; i++) {
point cell_offset = point(i, j, 0.0);
point cell = position + cell_offset;
point cell_position = local_position - cell_offset;
/* Skip this cell if it's too far away to contribute - Bruemmer.osl */
point Pr = (point(i > 0, j > 0, 0) - local_position) * cell_offset;
if (dot(Pr, Pr) >= 1.0) {
continue;
}
if (periodic == 1) {
cell[0] = gabor_coord_wrap(cell[0], scale);
cell[1] = gabor_coord_wrap(cell[1], scale);
}
sum += gabor_cell_2d(gp, cell, cell_position, seed);
}
}
return sum;
}
float gabor_fractal_noise(FractalParams fp,
output GaborParams gp,
vector3 p,
float scale,
string dimensions,
int periodic,
int use_origin_offset)
{
float fscale = 1.0;
float amp = 1.0;
float maxamp = 0.0;
vector3 sum = 0.0;
float octaves = clamp(fp.octaves, 0.0, 15.0);
if (fp.roughness == 0.0) {
octaves = 0.0;
}
int n = int(octaves);
for (int i = 0; i <= n; i++) {
int seed = use_origin_offset * i * GABOR_SEED;
vector3 t = (dimensions == "3D") ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) :
gabor_grid_2d(gp, fscale * p, scale, periodic, seed);
gp.frequency *= fp.fre_lacunarity;
gp.rotation -= fp.rot_lacunarity;
sum += t * amp;
maxamp += amp;
amp *= fp.roughness;
fscale *= fp.scl_lacunarity;
}
float rmd = octaves - floor(octaves);
if (rmd != 0.0) {
int seed = use_origin_offset * (n + 1) * GABOR_SEED;
vector3 t = (dimensions == "3D") ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) :
gabor_grid_2d(gp, fscale * p, scale, periodic, seed);
vector3 sum2 = sum + t * amp;
sum = mix(sum, sum2, rmd);
}
sum /= maxamp;
if (gp.mode == "phasor" || gp.mode == "phasor_ring" || gp.mode == "phasor_cross" ||
gp.mode == "phasor_square")
{
float pn = atan2(sum.y, sum.x) / M_PI;
return pn;
}
else {
return sum.x;
}
}
GaborParams gabor_parameters(vector direction,
float frequency,
float radius,
float impulses,
float phase,
float phase_variance,
float rotation,
float rot_variance,
float tilt_randomness,
float cell_randomness,
float anisotropy,
string mode)
{
GaborParams gp;
gp.impulses = clamp(impulses, 0.0001, 32.0);
gp.rot_variance = rot_variance;
gp.anisotropy = anisotropy;
gp.mode = mode;
gp.direction = direction;
gp.phase = phase;
gp.rotation = 0.0;
gp.init_rotation = rotation;
gp.phase_variance = phase_variance;
gp.tilt_randomness = tilt_randomness;
gp.cell_randomness = cell_randomness;
gp.radius = radius;
gp.frequency = frequency * M_PI;
return gp;
}
/* Shader */
float gabor_noise(point p,
vector direction,
float scale,
float frequency,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float gain,
float radius,
float impulses,
float phase,
float phase_variance,
float rotation,
float rot_variance,
float tilt_randomness,
float cell_randomness,
float anisotropy,
string dimensions,
string mode,
int use_normalize,
int periodic,
int use_origin_offset)
{
if (impulses == 0.0 || gain == 0.0 || radius <= 0.0 || scale == 0.0) {
if (use_normalize == 1) {
return 0.5;
}
return 0.0;
}
FractalParams fp;
fp.roughness = roughness;
fp.octaves = detail;
fp.scl_lacunarity = scl_lacunarity;
fp.fre_lacunarity = fre_lacunarity;
fp.rot_lacunarity = rot_lacunarity;
GaborParams gp = gabor_parameters(direction,
frequency,
radius,
impulses,
phase,
phase_variance,
rotation,
rot_variance,
tilt_randomness,
cell_randomness,
anisotropy,
mode);
float g = gabor_fractal_noise(fp, gp, p, scale, dimensions, periodic, use_origin_offset) * gain;
if (gp.mode == "gabor" || gp.mode == "gabor_ring" || gp.mode == "gabor_cross" ||
gp.mode == "gabor_square")
{
float impulse_scale = 1.2613446229;
if (impulses > 1.0) {
impulse_scale = 1.2613446229 * sqrt(gp.impulses);
}
g = g / impulse_scale;
}
if (use_normalize == 1) {
return clamp(0.5 * g + 0.5, 0.0, 1.0);
}
return g;
}
/* Gabor */
shader node_gabor_texture(int use_mapping = 0,
matrix mapping = matrix(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
string dimensions = "2D",
string mode = "gabor",
int use_normalize = 1,
int periodic = 0,
int use_origin_offset = 1,
vector3 Vector = vector(0, 0, 0),
float Scale = 5.0,
float Detail = 0.0,
float Roughness = 0.5,
float ScaleLacunarity = 2.0,
float FrequencyLacunarity = 2.0,
float RotationLacunarity = 0.0,
float Gain = 1.0,
float Frequency = 4.0,
float Radius = 1.0,
float Impulses = 2.0,
float PhaseOffset = 0.0,
float PhaseVariance = 1.0,
float CellRandomness = 1.0,
float Rotation = 0.0,
float RotationVariance = 0.0,
float TiltRandomness = 1.0,
vector3 Direction = vector(0, 0, 1),
float AnisotropicFactor = 0.0,
output float Value = 0.0)
{
vector3 p = Vector;
if (use_mapping)
p = transform(mapping, p);
Value = gabor_noise(p,
Direction,
Scale,
Frequency,
Detail,
Roughness,
ScaleLacunarity,
FrequencyLacunarity,
RotationLacunarity,
Gain,
Radius,
Impulses,
PhaseOffset,
PhaseVariance,
Rotation,
RotationVariance,
TiltRandomness,
CellRandomness,
AnisotropicFactor,
dimensions,
mode,
use_normalize,
periodic,
use_origin_offset);
}

View File

@ -0,0 +1,484 @@
/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
CCL_NAMESPACE_BEGIN
/* Gabor Noise
*
* Based on: Blender patch D287
*
* Adapted from Open Shading Language
* Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al.
* All Rights Reserved.
*
* Gabor noise originally based on:
* Lagae, A. and Drettakis, G. 2011. Filtering Solid Gabor Noise.
*/
/* See GLSL implementation for code comments. */
#define GABOR_SEED 1259
typedef struct GaborParams {
float frequency;
float radius;
float impulses;
float phase;
float phase_variance;
float rotation;
float init_rotation;
float rot_variance;
float tilt_randomness;
float cell_randomness;
float anisotropy;
int mode;
float3 direction;
} GaborParams;
typedef struct FractalParams {
float octaves;
float roughness;
float scl_lacunarity;
float fre_lacunarity;
float rot_lacunarity;
} FractalParams;
ccl_device int impulses_per_cell(float3 cell, float impulses, int seed)
{
int n = int(impulses);
float rmd = impulses - floorf(impulses);
if (rmd > 0.0f) {
float t = hash_float4_to_float(make_float4(cell.x, cell.y, cell.z, float(seed - GABOR_SEED)));
return (t <= rmd) ? n + 1 : n;
}
return n;
}
ccl_device float3 gabor_kernel(GaborParams gp, float3 omega, float phi, float3 position, float dv)
{
float g = cos(M_PI_F * sqrtf(dv)) * 0.5f + 0.5f;
float3 r = zero_float3();
float h;
if (gp.mode == SHD_GABOR_MODE_GABOR) {
h = gp.frequency * dot(omega, position) + phi;
r = make_float3(cos(h), 0.0f, 0.0f);
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR) {
h = gp.frequency * dot(omega, position) + phi;
r = make_float3(cos(h), sin(h), 0.0f);
}
else if (gp.mode == SHD_GABOR_MODE_CROSS) {
h = gp.frequency * len(omega * position) + phi;
r = make_float3(cos(h), 0.0f, 0.0f);
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) {
h = gp.frequency * len(omega * position) + phi;
r = make_float3(cos(h), sin(h), 0.0f);
}
else if (gp.mode == SHD_GABOR_MODE_RING) {
h = cos(gp.frequency * dot(omega, position) + phi) + cos(gp.frequency * len(position) + phi);
r = make_float3(h, 0.0f, 0.0f) * 0.5f;
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR_RING) {
h = cos(gp.frequency * dot(omega, position) + phi) + cos(gp.frequency * len(position) + phi);
const float h2 = sin(gp.frequency * dot(omega, position) + phi) +
sin(gp.frequency * len(position) + phi);
r = make_float3(h, h2, 0.0f) * 0.5f;
}
else if (gp.mode == SHD_GABOR_MODE_SQUARE) {
const float3 positionyxz = make_float3(position.y, position.x, position.z);
h = cos(gp.frequency * dot(omega, position) + phi) +
cos(gp.frequency * dot(omega, positionyxz) + phi);
r = make_float3(h, 0.0f, 0.0f) * 0.5f;
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR_SQUARE) {
const float3 positionyxz = make_float3(position.y, position.x, position.z);
h = cos(gp.frequency * dot(omega, position) + phi) +
cos(gp.frequency * dot(omega, positionyxz) + phi);
const float h2 = sin(gp.frequency * dot(omega, position) + phi) +
sin(gp.frequency * dot(omega, positionyxz) + phi);
r = make_float3(h, h2, 0.0f) * 0.5f;
}
return r * g;
}
ccl_device float3 gabor_sample(GaborParams gp, float3 cell, int seed, ccl_private float *phi)
{
float3 rand_values = hash_float4_to_float3(make_float4(cell.x, cell.y, cell.z, float(seed))) *
2.0f -
1.0f;
float pvar = mix(0.0f, rand_values.z, gp.phase_variance);
*phi = M_2PI_F * pvar + gp.phase;
float omega_t = M_PI_F * (rand_values.x) * gp.rot_variance - gp.rotation - gp.init_rotation;
float cos_omega_p = clamp(rand_values.y * gp.tilt_randomness, -1.0f, 1.0f);
float sin_omega_p = sqrtf(1.0f - cos_omega_p * cos_omega_p);
float sin_omega_t = sin(omega_t);
float cos_omega_t = cos(omega_t);
Transform rotationTransform = euler_to_transform(make_float3(0.0f, 0.0f, -gp.rotation));
return mix(
normalize(make_float3(cos_omega_t * sin_omega_p, sin_omega_t * sin_omega_p, cos_omega_p)),
normalize(transform_direction(&rotationTransform, gp.direction)),
gp.anisotropy);
}
ccl_device float3 gabor_cell_3d(GaborParams gp, float3 cell, float3 cell_position, int seed)
{
int num_impulses = impulses_per_cell(cell, gp.impulses, seed);
float3 sum = zero_float3();
for (int i = 0; i < num_impulses; ++i) {
float3 rand_position = mix(
zero_float3(),
hash_float4_to_float3(make_float4(cell.x, cell.y, cell.z, float(seed + i * GABOR_SEED))),
gp.cell_randomness);
float3 kernel_position = (cell_position - rand_position);
float dv = dot(kernel_position, kernel_position) / gp.radius;
if (dv <= 1.0f) {
float phi;
float3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, &phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
}
ccl_device float3 gabor_cell_2d(GaborParams gp, float3 cell, float3 cell_position, int seed)
{
int num_impulses = impulses_per_cell(cell, gp.impulses, seed);
float3 sum = zero_float3();
for (int i = 0; i < num_impulses; ++i) {
float3 rand_position = mix(
zero_float3(),
hash_float4_to_float3(make_float4(cell.x, cell.y, cell.z, float(seed + i * GABOR_SEED))),
gp.cell_randomness);
rand_position.z = 0.0f;
float3 kernel_position = (cell_position - rand_position);
float dv = dot(kernel_position, kernel_position) / gp.radius;
if (dv <= 1.0f) {
float phi;
float3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, &phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
}
ccl_device float gabor_coord_wrap(float a, float b)
{
return (b != 0.0f) ? a - b * floorf(a / b) : 0.0f;
}
ccl_device float3 gabor_grid_3d(GaborParams gp, float3 p, float scale, int periodic, int seed)
{
float3 coords = p * scale;
float3 position = floor(coords);
float3 local_position = coords - position;
float3 sum = zero_float3();
for (int k = -1; k <= 1; k++) {
for (int j = -1; j <= 1; j++) {
for (int i = -1; i <= 1; i++) {
float3 cell_offset = make_float3(i, j, k);
float3 cell = position + cell_offset;
float3 cell_position = local_position - cell_offset;
/* Skip this cell if it's too far away to contribute - Bruemmer.osl */
float3 Pr = (make_float3(i > 0, j > 0, k > 0) - local_position) * cell_offset;
if (dot(Pr, Pr) >= 1.0f) {
continue;
}
if (periodic) {
cell.x = gabor_coord_wrap(cell.x, scale);
cell.y = gabor_coord_wrap(cell.y, scale);
cell.z = gabor_coord_wrap(cell.z, scale);
}
sum += gabor_cell_3d(gp, cell, cell_position, seed);
}
}
}
return sum;
}
ccl_device float3 gabor_grid_2d(GaborParams gp, float3 p, float scale, int periodic, int seed)
{
float3 coords = make_float3(p.x, p.y, 0.0f) * scale;
float3 position = floor(coords);
float3 local_position = coords - position;
float3 sum = zero_float3();
for (int j = -1; j <= 1; j++) {
for (int i = -1; i <= 1; i++) {
float3 cell_offset = make_float3(i, j, 0.0f);
float3 cell = position + cell_offset;
float3 cell_position = local_position - cell_offset;
/* Skip this cell if it's too far away to contribute - Bruemmer.osl */
float3 Pr = (make_float3(i > 0, j > 0, 0) - local_position) * cell_offset;
if (dot(Pr, Pr) >= 1.0f) {
continue;
}
if (periodic) {
cell.x = gabor_coord_wrap(cell.x, scale);
cell.y = gabor_coord_wrap(cell.y, scale);
}
sum += gabor_cell_2d(gp, cell, cell_position, seed);
}
}
return sum;
}
ccl_device float gabor_fractal_noise(FractalParams fp,
GaborParams gp,
float3 p,
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.
float scale,
int dimensions,
int periodic,
int use_origin_offset)
{
float fscale = 1.0f;
float amp = 1.0f;
float maxamp = 0.0f;
float3 sum = zero_float3();
float octaves = clamp(fp.octaves, 0.0f, 15.0f);
if (fp.roughness == 0.0f) {
octaves = 0.0f;
}
int n = int(octaves);
for (int i = 0; i <= n; i++) {
int seed = use_origin_offset * i * GABOR_SEED;
float3 t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) :
gabor_grid_2d(gp, fscale * p, scale, periodic, seed);
gp.frequency *= fp.fre_lacunarity;
gp.rotation -= fp.rot_lacunarity;
sum += t * amp;
maxamp += amp;
amp *= fp.roughness;
fscale *= fp.scl_lacunarity;
}
float rmd = octaves - floorf(octaves);
if (rmd != 0.0f) {
int seed = use_origin_offset * (n + 1) * GABOR_SEED;
float3 t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) :
gabor_grid_2d(gp, fscale * p, scale, periodic, seed);
float3 sum2 = sum + t * amp;
sum = mix(sum, sum2, rmd);
}
sum /= maxamp;
if (gp.mode == SHD_GABOR_MODE_PHASOR || gp.mode == SHD_GABOR_MODE_PHASOR_RING ||
gp.mode == SHD_GABOR_MODE_PHASOR_CROSS || gp.mode == SHD_GABOR_MODE_PHASOR_SQUARE)
{
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 pn = atan2f(sum.y, sum.x) / M_PI_F;
return pn;
}
else {
return sum.x;
}
}
ccl_device GaborParams gabor_parameters(float3 direction,
float frequency,
float radius,
float impulses,
float phase,
float phase_variance,
float rotation,
float rot_variance,
float tilt_randomness,
float cell_randomness,
float anisotropy,
int mode)
{
GaborParams gp;
gp.impulses = clamp(impulses, 0.0001f, 32.0f);
gp.rot_variance = rot_variance;
gp.anisotropy = anisotropy;
gp.mode = mode;
gp.direction = direction;
gp.phase = phase;
gp.rotation = 0.0f;
gp.init_rotation = rotation;
gp.phase_variance = phase_variance;
gp.tilt_randomness = tilt_randomness;
gp.cell_randomness = cell_randomness;
gp.radius = radius;
gp.frequency = frequency * M_PI_F;
return gp;
}
/* Gabor shader */
ccl_device float gabor_noise(float3 p,
float3 direction,
float scale,
float frequency,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float gain,
float radius,
float impulses,
float phase,
float phase_variance,
float rotation,
float rot_variance,
float tilt_randomness,
float cell_randomness,
float anisotropy,
int dimensions,
int mode,
int normalize,
int periodic,
int use_origin_offset)
{
if (impulses == 0.0f || gain == 0.0f || radius <= 0.0f || scale == 0.0f) {
return (normalize == 1) ? 0.5f : 0.0f;
}
FractalParams fp;
fp.roughness = roughness;
fp.octaves = detail;
fp.scl_lacunarity = scl_lacunarity;
fp.fre_lacunarity = fre_lacunarity;
fp.rot_lacunarity = rot_lacunarity;
GaborParams gp = gabor_parameters(direction,
frequency,
radius,
impulses,
phase,
phase_variance,
rotation,
rot_variance,
tilt_randomness,
cell_randomness,
anisotropy,
mode);
float g = gabor_fractal_noise(fp, gp, p, scale, dimensions, periodic, use_origin_offset) * gain;
if (gp.mode == SHD_GABOR_MODE_GABOR || gp.mode == SHD_GABOR_MODE_RING ||
gp.mode == SHD_GABOR_MODE_CROSS || gp.mode == SHD_GABOR_MODE_SQUARE)
{
float impulse_scale = impulses > 1.0f ? 1.2613446229f * sqrt(gp.impulses) : 1.2613446229f;
g = g / impulse_scale;
}
if (normalize == 1) {
return clamp(0.5f * g + 0.5f, 0.0f, 1.0f);
}
return g;
}
ccl_device_noinline int svm_node_tex_gabor(
KernelGlobals kg, ccl_private ShaderData *sd, ccl_private float *stack, uint4 node, int offset)
{
uint4 node2 = read_node(kg, &offset);
uint4 defaults_node3 = read_node(kg, &offset);
uint4 defaults_node4 = read_node(kg, &offset);
uint4 defaults_node5 = read_node(kg, &offset);
uint4 defaults_node6 = read_node(kg, &offset);
uint4 defaults_node7 = read_node(kg, &offset);
/* Input and Output Sockets */
uint vector_in_offset, scale_offset, detail_offset, phase_offset, impulse_offset;
uint direction_offset, value_out_offset, fre_lacunarity_offset;
uint mode_offset, aniso_offset, periodic_offset, roughness_offset;
uint rot_variance_offset, phase_variance_offset, rotation_offset, gain_offset,
tilt_randomness_offset, cell_randomness_offset;
uint scl_lacunarity_offset, use_normalize_offset, dimension_offset, rot_lacunarity_offset;
uint frequency_offset, radius_offset;
svm_unpack_node_uchar4(
node.y, &vector_in_offset, &scale_offset, &frequency_offset, &detail_offset);
svm_unpack_node_uchar4(node.z,
&roughness_offset,
&scl_lacunarity_offset,
&fre_lacunarity_offset,
&rot_lacunarity_offset);
svm_unpack_node_uchar4(node.w, &gain_offset, &radius_offset, &impulse_offset, &phase_offset);
svm_unpack_node_uchar4(node2.x,
&phase_variance_offset,
&cell_randomness_offset,
&rotation_offset,
&tilt_randomness_offset);
svm_unpack_node_uchar4(
node2.y, &rot_variance_offset, &direction_offset, &value_out_offset, &dimension_offset);
svm_unpack_node_uchar4(
node2.z, &mode_offset, &aniso_offset, &periodic_offset, &use_normalize_offset);
float3 vector_in = stack_load_float3(stack, vector_in_offset);
float scale = stack_load_float_default(stack, scale_offset, defaults_node3.x);
float frequency = stack_load_float_default(stack, frequency_offset, defaults_node3.y);
float detail = stack_load_float_default(stack, detail_offset, defaults_node3.z);
float roughness = stack_load_float_default(stack, roughness_offset, defaults_node3.w);
float scl_lacunarity = stack_load_float_default(stack, scl_lacunarity_offset, defaults_node4.x);
float fre_lacunarity = stack_load_float_default(stack, fre_lacunarity_offset, defaults_node4.y);
float rot_lacunarity = stack_load_float_default(stack, rot_lacunarity_offset, defaults_node4.z);
float gain = stack_load_float_default(stack, gain_offset, defaults_node4.w);
float radius = stack_load_float_default(stack, radius_offset, defaults_node5.x);
float impulses = stack_load_float_default(stack, impulse_offset, defaults_node5.y);
float phase = stack_load_float_default(stack, phase_offset, defaults_node5.z);
float phase_variance = stack_load_float_default(stack, phase_variance_offset, defaults_node5.w);
float cell_randomness = stack_load_float_default(
stack, cell_randomness_offset, defaults_node6.x);
float rotation = stack_load_float_default(stack, rotation_offset, defaults_node6.y);
float rot_variance = stack_load_float_default(stack, rot_variance_offset, defaults_node6.z);
float tilt_randomness = stack_load_float_default(
stack, tilt_randomness_offset, defaults_node6.w);
float anisotropy = stack_load_float_default(stack, aniso_offset, defaults_node7.x);
float3 direction = stack_load_float3(stack, direction_offset);
if (stack_valid(value_out_offset)) {
float value = gabor_noise(vector_in,
direction,
scale,
frequency,
detail,
roughness,
scl_lacunarity,
fre_lacunarity,
rot_lacunarity,
gain,
radius,
impulses,
phase,
phase_variance,
rotation,
rot_variance,
tilt_randomness,
cell_randomness,
anisotropy,
dimension_offset,
mode_offset,
use_normalize_offset,
periodic_offset,
int(node2.w));
stack_store_float(stack, value_out_offset, value);
}
return offset;
}
CCL_NAMESPACE_END

View File

@ -108,8 +108,9 @@ SHADER_NODE_TYPE(NODE_MIX_COLOR)
SHADER_NODE_TYPE(NODE_MIX_FLOAT)
SHADER_NODE_TYPE(NODE_MIX_VECTOR)
SHADER_NODE_TYPE(NODE_MIX_VECTOR_NON_UNIFORM)
SHADER_NODE_TYPE(NODE_TEX_GABOR)
/* Padding for struct alignment. */
SHADER_NODE_TYPE(NODE_PAD1)
// SHADER_NODE_TYPE(NODE_PAD1)
#undef SHADER_NODE_TYPE

View File

@ -165,6 +165,7 @@ CCL_NAMESPACE_END
#include "kernel/svm/convert.h"
#include "kernel/svm/displace.h"
#include "kernel/svm/fresnel.h"
#include "kernel/svm/gabor.h"
#include "kernel/svm/gamma.h"
#include "kernel/svm/geometry.h"
#include "kernel/svm/gradient.h"
@ -603,6 +604,9 @@ ccl_device void svm_eval_nodes(KernelGlobals kg,
SVM_CASE(NODE_MIX_VECTOR_NON_UNIFORM)
svm_node_mix_vector_non_uniform(sd, stack, node.y, node.z);
break;
SVM_CASE(NODE_TEX_GABOR)
offset = svm_node_tex_gabor(kg, sd, stack, node, offset);
break;
default:
kernel_assert(!"Unknown node type was passed to the SVM machine");
return;

View File

@ -409,6 +409,17 @@ typedef enum NodeCombSepColorType {
NODE_COMBSEP_COLOR_HSL,
} NodeCombSepColorType;
typedef enum NodeGaborMode {
SHD_GABOR_MODE_GABOR,
SHD_GABOR_MODE_RING,
SHD_GABOR_MODE_CROSS,
SHD_GABOR_MODE_SQUARE,
SHD_GABOR_MODE_PHASOR,
SHD_GABOR_MODE_PHASOR_RING,
SHD_GABOR_MODE_PHASOR_CROSS,
SHD_GABOR_MODE_PHASOR_SQUARE,
} NodeGaborMode;
/* Closure */
typedef enum ClosureType {

View File

@ -1206,6 +1206,179 @@ void NoiseTextureNode::compile(OSLCompiler &compiler)
compiler.add(this, "node_noise_texture");
}
/* Gabor Texture */
NODE_DEFINE(GaborTextureNode)
{
NodeType *type = NodeType::add("gabor_texture", create, NodeType::SHADER);
TEXTURE_MAPPING_DEFINE(GaborTextureNode);
static NodeEnum dimensions_enum;
dimensions_enum.insert("2D", 2);
dimensions_enum.insert("3D", 3);
SOCKET_ENUM(dimensions, "Dimensions", dimensions_enum, 2);
static NodeEnum mode_enum;
mode_enum.insert("gabor", SHD_GABOR_MODE_GABOR);
mode_enum.insert("gabor_ring", SHD_GABOR_MODE_RING);
mode_enum.insert("gabor_cross", SHD_GABOR_MODE_CROSS);
mode_enum.insert("gabor_square", SHD_GABOR_MODE_SQUARE);
mode_enum.insert("phasor", SHD_GABOR_MODE_PHASOR);
mode_enum.insert("phasor_ring", SHD_GABOR_MODE_PHASOR_RING);
mode_enum.insert("phasor_cross", SHD_GABOR_MODE_PHASOR_CROSS);
mode_enum.insert("phasor_square", SHD_GABOR_MODE_PHASOR_SQUARE);
SOCKET_ENUM(mode, "Mode", mode_enum, SHD_GABOR_MODE_GABOR);
SOCKET_BOOLEAN(periodic, "Periodic", false);
SOCKET_BOOLEAN(use_normalize, "Normalize", true);
SOCKET_BOOLEAN(use_origin_offset, "Origin Offset", true);
SOCKET_IN_POINT(vector, "Vector", zero_float3(), SocketType::LINK_TEXTURE_GENERATED);
SOCKET_IN_FLOAT(scale, "Scale", 5.0f);
SOCKET_IN_FLOAT(frequency, "Frequency", 4.0f);
SOCKET_IN_FLOAT(gain, "Gain", 1.0f);
SOCKET_IN_FLOAT(detail, "Detail", 0.0f);
SOCKET_IN_FLOAT(roughness, "Roughness", 0.5f);
SOCKET_IN_FLOAT(scl_lacunarity, "Scale Lacunarity", 2.0f);
SOCKET_IN_FLOAT(fre_lacunarity, "Frequency Lacunarity", 2.0f);
SOCKET_IN_FLOAT(rot_lacunarity, "Rotation Lacunarity", 0.0f);
SOCKET_IN_FLOAT(radius, "Radius", 1.0f);
SOCKET_IN_FLOAT(impulses, "Impulses", 2.0f);
SOCKET_IN_FLOAT(phase, "Phase Offset", 0.0f);
SOCKET_IN_FLOAT(phase_variance, "Phase Variance", 1.0f);
SOCKET_IN_FLOAT(rotation, "Rotation", 0.0f);
SOCKET_IN_FLOAT(rot_variance, "Rotation Variance", 0.0f);
SOCKET_IN_FLOAT(tilt_randomness, "Tilt Randomness", 1.0f);
SOCKET_IN_POINT(direction, "Direction", make_float3(0.0f, 0.0f, 1.0f));
SOCKET_IN_FLOAT(cell_randomness, "Cell Randomness", 1.0f);
SOCKET_IN_FLOAT(anisotropy, "Anisotropic Factor", 1.0f);
SOCKET_OUT_FLOAT(value, "Value");
return type;
}
GaborTextureNode::GaborTextureNode() : TextureNode(node_type) {}
void GaborTextureNode::constant_fold(const ConstantFolder &folder)
{
ShaderInput *scale_in = input("Scale");
ShaderInput *gain_in = input("Gain");
ShaderInput *radius_in = input("Radius");
ShaderInput *impulses_in = input("Impulses");
if ((!scale_in->link && scale == 0.0f) || (!gain_in->link && gain == 0.0f) ||
(!radius_in->link && radius == 0.0f) || (!impulses_in->link && impulses == 0.0f))
{
folder.make_constant(use_normalize ? 0.5f : 0.0f);
}
}
void GaborTextureNode::compile(SVMCompiler &compiler)
{
ShaderInput *vector_in = input("Vector");
ShaderInput *scale_in = input("Scale");
ShaderInput *frequency_in = input("Frequency");
ShaderInput *detail_in = input("Detail");
ShaderInput *roughness_in = input("Roughness");
ShaderInput *scl_lacunarity_in = input("Scale Lacunarity");
ShaderInput *fre_lacunarity_in = input("Frequency Lacunarity");
ShaderInput *rot_lacunarity_in = input("Rotation Lacunarity");
ShaderInput *gain_in = input("Gain");
ShaderInput *radius_in = input("Radius");
ShaderInput *impulses_in = input("Impulses");
ShaderInput *phase_in = input("Phase Offset");
ShaderInput *phase_variance_in = input("Phase Variance");
ShaderInput *rotation_in = input("Rotation");
ShaderInput *rot_variance_in = input("Rotation Variance");
ShaderInput *tilt_randomness_in = input("Tilt Randomness");
ShaderInput *direction_in = input("Direction");
ShaderInput *cell_randomness_in = input("Cell Randomness");
ShaderInput *anisotropy_in = input("Anisotropic Factor");
ShaderOutput *value_out = output("Value");
int vector_stack_offset = tex_mapping.compile_begin(compiler, vector_in);
int scale_in_stack_offset = compiler.stack_assign(scale_in);
int frequency_in_stack_offset = compiler.stack_assign(frequency_in);
int detail_in_stack_offset = compiler.stack_assign(detail_in);
int roughness_in_stack_offset = compiler.stack_assign(roughness_in);
int scl_lacunarity_in_stack_offset = compiler.stack_assign(scl_lacunarity_in);
int fre_lacunarity_in_stack_offset = compiler.stack_assign(fre_lacunarity_in);
int rot_lacunarity_in_stack_offset = compiler.stack_assign(rot_lacunarity_in);
int gain_in_stack_offset = compiler.stack_assign(gain_in);
int radius_in_stack_offset = compiler.stack_assign(radius_in);
int impulses_in_stack_offset = compiler.stack_assign(impulses_in);
int phase_in_stack_offset = compiler.stack_assign(phase_in);
int phase_variance_in_stack_offset = compiler.stack_assign(phase_variance_in);
int rotation_in_stack_offset = compiler.stack_assign(rotation_in);
int rot_variance_in_stack_offset = compiler.stack_assign(rot_variance_in);
int tilt_randomness_in_stack_offset = compiler.stack_assign(tilt_randomness_in);
int cell_randomness_in_stack_offset = compiler.stack_assign(cell_randomness_in);
int anisotropy_in_stack_offset = compiler.stack_assign(anisotropy_in);
int direction_in_stack_offset = compiler.stack_assign(direction_in);
int value_out_stack_offset = compiler.stack_assign_if_linked(value_out);
compiler.add_node(NODE_TEX_GABOR,
compiler.encode_uchar4(vector_stack_offset,
scale_in_stack_offset,
frequency_in_stack_offset,
detail_in_stack_offset),
compiler.encode_uchar4(roughness_in_stack_offset,
scl_lacunarity_in_stack_offset,
fre_lacunarity_in_stack_offset,
rot_lacunarity_in_stack_offset),
compiler.encode_uchar4(gain_in_stack_offset,
radius_in_stack_offset,
impulses_in_stack_offset,
phase_in_stack_offset));
compiler.add_node(
compiler.encode_uchar4(phase_variance_in_stack_offset,
cell_randomness_in_stack_offset,
rotation_in_stack_offset,
tilt_randomness_in_stack_offset),
compiler.encode_uchar4(rot_variance_in_stack_offset,
direction_in_stack_offset,
value_out_stack_offset,
dimensions),
compiler.encode_uchar4(mode, anisotropy_in_stack_offset, periodic, use_normalize),
use_origin_offset);
compiler.add_node(__float_as_int(scale),
__float_as_int(frequency),
__float_as_int(detail),
__float_as_int(roughness));
compiler.add_node(__float_as_int(scl_lacunarity),
__float_as_int(fre_lacunarity),
__float_as_int(rot_lacunarity),
__float_as_int(gain));
compiler.add_node(__float_as_int(radius),
__float_as_int(impulses),
__float_as_int(phase),
__float_as_int(phase_variance));
compiler.add_node(__float_as_int(cell_randomness),
__float_as_int(rotation),
__float_as_int(rot_variance),
__float_as_int(tilt_randomness));
compiler.add_node(__float_as_int(anisotropy));
tex_mapping.compile_end(compiler, vector_in, vector_stack_offset);
}
void GaborTextureNode::compile(OSLCompiler &compiler)
{
tex_mapping.compile(compiler);
compiler.parameter(this, "dimensions");
compiler.parameter(this, "mode");
compiler.parameter(this, "periodic");
compiler.parameter(this, "use_normalize");
compiler.parameter(this, "use_origin_offset");
compiler.add(this, "node_gabor_texture");
}
/* Voronoi Texture */
NODE_DEFINE(VoronoiTextureNode)

View File

@ -238,6 +238,38 @@ class NoiseTextureNode : public TextureNode {
NODE_SOCKET_API(float3, vector)
};
class GaborTextureNode : public TextureNode {
public:
SHADER_NODE_CLASS(GaborTextureNode)
void constant_fold(const ConstantFolder &folder);
NODE_SOCKET_API(int, dimensions)
NODE_SOCKET_API(NodeGaborMode, mode);
NODE_SOCKET_API(bool, periodic)
NODE_SOCKET_API(bool, use_normalize)
NODE_SOCKET_API(bool, use_origin_offset)
NODE_SOCKET_API(float3, vector)
NODE_SOCKET_API(float, scale)
NODE_SOCKET_API(float, frequency)
NODE_SOCKET_API(float, detail)
NODE_SOCKET_API(float, roughness)
NODE_SOCKET_API(float, scl_lacunarity)
NODE_SOCKET_API(float, fre_lacunarity)
NODE_SOCKET_API(float, rot_lacunarity)
NODE_SOCKET_API(float, gain)
NODE_SOCKET_API(float, radius)
NODE_SOCKET_API(float, impulses)
NODE_SOCKET_API(float, phase)
NODE_SOCKET_API(float, phase_variance)
NODE_SOCKET_API(float, rotation)
NODE_SOCKET_API(float, rot_variance)
NODE_SOCKET_API(float, tilt_randomness)
NODE_SOCKET_API(float, cell_randomness)
NODE_SOCKET_API(float, anisotropy)
NODE_SOCKET_API(float3, direction)
};
class VoronoiTextureNode : public TextureNode {
public:
SHADER_NODE_CLASS(VoronoiTextureNode)

View File

@ -519,6 +519,7 @@ class NODE_MT_category_GEO_TEXTURE(Menu):
layout = self.layout
node_add_menu.add_node_type(layout, "ShaderNodeTexBrick")
node_add_menu.add_node_type(layout, "ShaderNodeTexChecker")
node_add_menu.add_node_type(layout, "ShaderNodeTexGabor")
node_add_menu.add_node_type(layout, "ShaderNodeTexGradient")
node_add_menu.add_node_type(layout, "GeometryNodeImageTexture")
node_add_menu.add_node_type(layout, "ShaderNodeTexMagic")

View File

@ -288,6 +288,7 @@ class NODE_MT_category_shader_texture(Menu):
node_add_menu.add_node_type(layout, "ShaderNodeTexBrick")
node_add_menu.add_node_type(layout, "ShaderNodeTexChecker")
node_add_menu.add_node_type(layout, "ShaderNodeTexEnvironment")
node_add_menu.add_node_type(layout, "ShaderNodeTexGabor")
node_add_menu.add_node_type(layout, "ShaderNodeTexGradient")
node_add_menu.add_node_type(layout, "ShaderNodeTexIES")
node_add_menu.add_node_type(layout, "ShaderNodeTexImage")

View File

@ -36,6 +36,13 @@ class ShaderNodeCategory(SortedNodeCategory):
context.space_data.tree_type == 'ShaderNodeTree')
class TextureNodeCategory(SortedNodeCategory):
@classmethod
def poll(cls, context):
return (context.space_data.type == 'NODE_EDITOR' and
context.space_data.tree_type == 'TextureNodeTree')
# Menu entry for node group tools.
def group_tools_draw(_self, layout, _context):
layout.operator("node.group_make")
@ -75,10 +82,10 @@ def node_group_items(context):
for group in context.blend_data.node_groups:
if group.bl_idname != ntree.bl_idname:
continue
# Filter out recursive groups.
# filter out recursive groups
if group.contains_tree(ntree):
continue
# Filter out hidden node-trees.
# filter out hidden nodetrees
if group.name.startswith('.'):
continue
yield NodeItem(node_tree_group_type[group.bl_idname],
@ -94,12 +101,359 @@ def group_input_output_item_poll(context):
return False
# only show input/output nodes when editing line style node trees
def line_style_shader_nodes_poll(context):
snode = context.space_data
return (snode.tree_type == 'ShaderNodeTree' and
snode.shader_type == 'LINESTYLE')
# only show nodes working in world node trees
def world_shader_nodes_poll(context):
snode = context.space_data
return (snode.tree_type == 'ShaderNodeTree' and
snode.shader_type == 'WORLD')
# only show nodes working in object node trees
def object_shader_nodes_poll(context):
snode = context.space_data
return (snode.tree_type == 'ShaderNodeTree' and
snode.shader_type == 'OBJECT')
def cycles_shader_nodes_poll(context):
return context.engine == 'CYCLES'
def eevee_shader_nodes_poll(context):
return context.engine == 'BLENDER_EEVEE'
def eevee_cycles_shader_nodes_poll(context):
return (cycles_shader_nodes_poll(context) or
eevee_shader_nodes_poll(context))
def object_cycles_shader_nodes_poll(context):
return (object_shader_nodes_poll(context) and
cycles_shader_nodes_poll(context))
def object_eevee_shader_nodes_poll(context):
return (object_shader_nodes_poll(context) and
eevee_shader_nodes_poll(context))
def object_eevee_cycles_shader_nodes_poll(context):
return (object_shader_nodes_poll(context) and
eevee_cycles_shader_nodes_poll(context))
# All standard node categories currently used in nodes.
shader_node_categories = [
# Shader Nodes (Cycles and Eevee)
ShaderNodeCategory("SH_NEW_INPUT", "Input", items=[
NodeItem("ShaderNodeTexCoord"),
NodeItem("ShaderNodeAttribute"),
NodeItem("ShaderNodeLightPath"),
NodeItem("ShaderNodeFresnel"),
NodeItem("ShaderNodeLayerWeight"),
NodeItem("ShaderNodeRGB"),
NodeItem("ShaderNodeValue"),
NodeItem("ShaderNodeTangent"),
NodeItem("ShaderNodeNewGeometry"),
NodeItem("ShaderNodeWireframe"),
NodeItem("ShaderNodeBevel"),
NodeItem("ShaderNodeAmbientOcclusion"),
NodeItem("ShaderNodeObjectInfo"),
NodeItem("ShaderNodeHairInfo"),
NodeItem("ShaderNodePointInfo"),
NodeItem("ShaderNodeVolumeInfo"),
NodeItem("ShaderNodeParticleInfo"),
NodeItem("ShaderNodeCameraData"),
NodeItem("ShaderNodeUVMap"),
NodeItem("ShaderNodeVertexColor"),
NodeItem("ShaderNodeUVAlongStroke", poll=line_style_shader_nodes_poll),
]),
ShaderNodeCategory("SH_NEW_OUTPUT", "Output", items=[
NodeItem("ShaderNodeOutputMaterial", poll=object_eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeOutputLight", poll=object_cycles_shader_nodes_poll),
NodeItem("ShaderNodeOutputAOV"),
NodeItem("ShaderNodeOutputWorld", poll=world_shader_nodes_poll),
NodeItem("ShaderNodeOutputLineStyle", poll=line_style_shader_nodes_poll),
]),
ShaderNodeCategory("SH_NEW_SHADER", "Shader", items=[
NodeItem("ShaderNodeMixShader", poll=eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeAddShader", poll=eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBsdfDiffuse", poll=object_eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBsdfPrincipled", poll=object_eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBsdfGlossy", poll=object_eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBsdfTransparent", poll=object_eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBsdfRefraction", poll=object_eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBsdfGlass", poll=object_eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBsdfTranslucent", poll=object_eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBsdfSheen", poll=object_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBsdfToon", poll=object_cycles_shader_nodes_poll),
NodeItem("ShaderNodeSubsurfaceScattering", poll=object_eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeEmission", poll=eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBsdfHair", poll=object_cycles_shader_nodes_poll),
NodeItem("ShaderNodeBackground", poll=world_shader_nodes_poll),
NodeItem("ShaderNodeHoldout", poll=object_eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeVolumeAbsorption", poll=eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeVolumeScatter", poll=eevee_cycles_shader_nodes_poll),
NodeItem("ShaderNodeVolumePrincipled"),
NodeItem("ShaderNodeEeveeSpecular", poll=object_eevee_shader_nodes_poll),
NodeItem("ShaderNodeBsdfHairPrincipled", poll=object_cycles_shader_nodes_poll),
]),
ShaderNodeCategory("SH_NEW_TEXTURE", "Texture", items=[
NodeItem("ShaderNodeTexImage"),
NodeItem("ShaderNodeTexEnvironment"),
NodeItem("ShaderNodeTexSky"),
NodeItem("ShaderNodeTexNoise"),
NodeItem("ShaderNodeTexWave"),
NodeItem("ShaderNodeTexVoronoi"),
NodeItem("ShaderNodeTexGabor"),
NodeItem("ShaderNodeTexMusgrave"),
NodeItem("ShaderNodeTexGradient"),
NodeItem("ShaderNodeTexMagic"),
NodeItem("ShaderNodeTexChecker"),
NodeItem("ShaderNodeTexBrick"),
NodeItem("ShaderNodeTexPointDensity"),
NodeItem("ShaderNodeTexIES"),
NodeItem("ShaderNodeTexWhiteNoise"),
]),
ShaderNodeCategory("SH_NEW_OP_COLOR", "Color", items=[
NodeItem("ShaderNodeMix", label="Mix Color", settings={"data_type": "'RGBA'"}),
NodeItem("ShaderNodeRGBCurve"),
NodeItem("ShaderNodeInvert"),
NodeItem("ShaderNodeLightFalloff"),
NodeItem("ShaderNodeHueSaturation"),
NodeItem("ShaderNodeGamma"),
NodeItem("ShaderNodeBrightContrast"),
]),
ShaderNodeCategory("SH_NEW_OP_VECTOR", "Vector", items=[
NodeItem("ShaderNodeMapping"),
NodeItem("ShaderNodeBump"),
NodeItem("ShaderNodeDisplacement"),
NodeItem("ShaderNodeVectorDisplacement"),
NodeItem("ShaderNodeNormalMap"),
NodeItem("ShaderNodeNormal"),
NodeItem("ShaderNodeVectorCurve"),
NodeItem("ShaderNodeVectorRotate"),
NodeItem("ShaderNodeVectorTransform"),
]),
ShaderNodeCategory("SH_NEW_CONVERTOR", "Converter", items=[
NodeItem("ShaderNodeMapRange"),
NodeItem("ShaderNodeFloatCurve"),
NodeItem("ShaderNodeClamp"),
NodeItem("ShaderNodeMath"),
NodeItem("ShaderNodeMix"),
NodeItem("ShaderNodeValToRGB"),
NodeItem("ShaderNodeRGBToBW"),
NodeItem("ShaderNodeShaderToRGB", poll=object_eevee_shader_nodes_poll),
NodeItem("ShaderNodeVectorMath"),
NodeItem("ShaderNodeSeparateColor"),
NodeItem("ShaderNodeCombineColor"),
NodeItem("ShaderNodeSeparateXYZ"),
NodeItem("ShaderNodeCombineXYZ"),
NodeItem("ShaderNodeWavelength"),
NodeItem("ShaderNodeBlackbody"),
]),
ShaderNodeCategory("SH_NEW_SCRIPT", "Script", items=[
NodeItem("ShaderNodeScript"),
]),
ShaderNodeCategory("SH_NEW_GROUP", "Group", items=node_group_items),
ShaderNodeCategory("SH_NEW_LAYOUT", "Layout", items=[
NodeItem("NodeFrame"),
NodeItem("NodeReroute"),
]),
]
compositor_node_categories = [
# Compositor Nodes
CompositorNodeCategory("CMP_INPUT", "Input", items=[
NodeItem("CompositorNodeRLayers"),
NodeItem("CompositorNodeImage"),
NodeItem("CompositorNodeMovieClip"),
NodeItem("CompositorNodeMask"),
NodeItem("CompositorNodeRGB"),
NodeItem("CompositorNodeValue"),
NodeItem("CompositorNodeTexture"),
NodeItem("CompositorNodeBokehImage"),
NodeItem("CompositorNodeTime"),
NodeItem("CompositorNodeSceneTime"),
NodeItem("CompositorNodeTrackPos"),
]),
CompositorNodeCategory("CMP_OUTPUT", "Output", items=[
NodeItem("CompositorNodeComposite"),
NodeItem("CompositorNodeViewer"),
NodeItem("CompositorNodeSplitViewer"),
NodeItem("CompositorNodeOutputFile"),
NodeItem("CompositorNodeLevels"),
]),
CompositorNodeCategory("CMP_OP_COLOR", "Color", items=[
NodeItem("CompositorNodeMixRGB"),
NodeItem("CompositorNodeAlphaOver"),
NodeItem("CompositorNodeInvert"),
NodeItem("CompositorNodeCurveRGB"),
NodeItem("CompositorNodeHueSat"),
NodeItem("CompositorNodeColorBalance"),
NodeItem("CompositorNodeHueCorrect"),
NodeItem("CompositorNodeBrightContrast"),
NodeItem("CompositorNodeGamma"),
NodeItem("CompositorNodeExposure"),
NodeItem("CompositorNodeColorCorrection"),
NodeItem("CompositorNodePosterize"),
NodeItem("CompositorNodeTonemap"),
NodeItem("CompositorNodeZcombine"),
]),
CompositorNodeCategory("CMP_CONVERTOR", "Converter", items=[
NodeItem("CompositorNodeMath"),
NodeItem("CompositorNodeValToRGB"),
NodeItem("CompositorNodeSetAlpha"),
NodeItem("CompositorNodePremulKey"),
NodeItem("CompositorNodeIDMask"),
NodeItem("CompositorNodeRGBToBW"),
NodeItem("CompositorNodeSeparateColor"),
NodeItem("CompositorNodeCombineColor"),
NodeItem("CompositorNodeSeparateXYZ"),
NodeItem("CompositorNodeCombineXYZ"),
NodeItem("CompositorNodeSwitchView"),
NodeItem("CompositorNodeConvertColorSpace"),
]),
CompositorNodeCategory("CMP_OP_FILTER", "Filter", items=[
NodeItem("CompositorNodeBlur"),
NodeItem("CompositorNodeBilateralblur"),
NodeItem("CompositorNodeDilateErode"),
NodeItem("CompositorNodeDespeckle"),
NodeItem("CompositorNodeFilter"),
NodeItem("CompositorNodeBokehBlur"),
NodeItem("CompositorNodeVecBlur"),
NodeItem("CompositorNodeDefocus"),
NodeItem("CompositorNodeGlare"),
NodeItem("CompositorNodeInpaint"),
NodeItem("CompositorNodeDBlur"),
NodeItem("CompositorNodePixelate"),
NodeItem("CompositorNodeSunBeams"),
NodeItem("CompositorNodeDenoise"),
NodeItem("CompositorNodeAntiAliasing"),
NodeItem("CompositorNodeKuwahara"),
]),
CompositorNodeCategory("CMP_OP_VECTOR", "Vector", items=[
NodeItem("CompositorNodeNormal"),
NodeItem("CompositorNodeMapValue"),
NodeItem("CompositorNodeMapRange"),
NodeItem("CompositorNodeNormalize"),
NodeItem("CompositorNodeCurveVec"),
]),
CompositorNodeCategory("CMP_MATTE", "Matte", items=[
NodeItem("CompositorNodeKeying"),
NodeItem("CompositorNodeKeyingScreen"),
NodeItem("CompositorNodeChannelMatte"),
NodeItem("CompositorNodeColorSpill"),
NodeItem("CompositorNodeBoxMask"),
NodeItem("CompositorNodeEllipseMask"),
NodeItem("CompositorNodeLumaMatte"),
NodeItem("CompositorNodeDiffMatte"),
NodeItem("CompositorNodeDistanceMatte"),
NodeItem("CompositorNodeChromaMatte"),
NodeItem("CompositorNodeColorMatte"),
NodeItem("CompositorNodeDoubleEdgeMask"),
NodeItem("CompositorNodeCryptomatte"),
NodeItem("CompositorNodeCryptomatteV2"),
]),
CompositorNodeCategory("CMP_DISTORT", "Distort", items=[
NodeItem("CompositorNodeScale"),
NodeItem("CompositorNodeLensdist"),
NodeItem("CompositorNodeMovieDistortion"),
NodeItem("CompositorNodeTranslate"),
NodeItem("CompositorNodeRotate"),
NodeItem("CompositorNodeFlip"),
NodeItem("CompositorNodeCrop"),
NodeItem("CompositorNodeDisplace"),
NodeItem("CompositorNodeMapUV"),
NodeItem("CompositorNodeTransform"),
NodeItem("CompositorNodeStabilize"),
NodeItem("CompositorNodePlaneTrackDeform"),
NodeItem("CompositorNodeCornerPin"),
]),
CompositorNodeCategory("CMP_GROUP", "Group", items=node_group_items),
CompositorNodeCategory("CMP_LAYOUT", "Layout", items=[
NodeItem("NodeFrame"),
NodeItem("NodeReroute"),
NodeItem("CompositorNodeSwitch"),
]),
]
texture_node_categories = [
# Texture Nodes
TextureNodeCategory("TEX_INPUT", "Input", items=[
NodeItem("TextureNodeCurveTime"),
NodeItem("TextureNodeCoordinates"),
NodeItem("TextureNodeTexture"),
NodeItem("TextureNodeImage"),
]),
TextureNodeCategory("TEX_OUTPUT", "Output", items=[
NodeItem("TextureNodeOutput"),
NodeItem("TextureNodeViewer"),
]),
TextureNodeCategory("TEX_OP_COLOR", "Color", items=[
NodeItem("TextureNodeMixRGB"),
NodeItem("TextureNodeCurveRGB"),
NodeItem("TextureNodeInvert"),
NodeItem("TextureNodeHueSaturation"),
NodeItem("TextureNodeCombineColor"),
NodeItem("TextureNodeSeparateColor"),
]),
TextureNodeCategory("TEX_PATTERN", "Pattern", items=[
NodeItem("TextureNodeChecker"),
NodeItem("TextureNodeBricks"),
]),
TextureNodeCategory("TEX_TEXTURE", "Textures", items=[
NodeItem("TextureNodeTexNoise"),
NodeItem("TextureNodeTexDistNoise"),
NodeItem("TextureNodeTexClouds"),
NodeItem("TextureNodeTexBlend"),
NodeItem("TextureNodeTexVoronoi"),
NodeItem("TextureNodeTexMagic"),
NodeItem("TextureNodeTexMarble"),
NodeItem("TextureNodeTexWood"),
NodeItem("TextureNodeTexMusgrave"),
NodeItem("TextureNodeTexStucci"),
]),
TextureNodeCategory("TEX_CONVERTOR", "Converter", items=[
NodeItem("TextureNodeMath"),
NodeItem("TextureNodeValToRGB"),
NodeItem("TextureNodeRGBToBW"),
NodeItem("TextureNodeValToNor"),
NodeItem("TextureNodeDistance"),
]),
TextureNodeCategory("TEX_DISTORT", "Distort", items=[
NodeItem("TextureNodeScale"),
NodeItem("TextureNodeTranslate"),
NodeItem("TextureNodeRotate"),
NodeItem("TextureNodeAt"),
]),
TextureNodeCategory("TEX_GROUP", "Group", items=node_group_items),
TextureNodeCategory("TEX_LAYOUT", "Layout", items=[
NodeItem("NodeFrame"),
NodeItem("NodeReroute"),
]),
]
def register():
pass
nodeitems_utils.register_node_categories('SHADER', shader_node_categories)
nodeitems_utils.register_node_categories('COMPOSITING', compositor_node_categories)
nodeitems_utils.register_node_categories('TEXTURE', texture_node_categories)
def unregister():
pass
nodeitems_utils.unregister_node_categories('SHADER')
nodeitems_utils.unregister_node_categories('COMPOSITING')
nodeitems_utils.unregister_node_categories('TEXTURE')
if __name__ == "__main__":

View File

@ -966,6 +966,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define SH_NODE_COMBINE_COLOR 711
#define SH_NODE_SEPARATE_COLOR 712
#define SH_NODE_MIX 713
#define SH_NODE_TEX_GABOR 714
/** \} */

View File

@ -546,6 +546,7 @@ set(GLSL_SRC
shaders/material/gpu_shader_material_tex_brick.glsl
shaders/material/gpu_shader_material_tex_checker.glsl
shaders/material/gpu_shader_material_tex_environment.glsl
shaders/material/gpu_shader_material_tex_gabor.glsl
shaders/material/gpu_shader_material_tex_gradient.glsl
shaders/material/gpu_shader_material_tex_image.glsl
shaders/material/gpu_shader_material_tex_magic.glsl

View File

@ -0,0 +1,467 @@
#pragma BLENDER_REQUIRE(gpu_shader_common_hash.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_common_math_utils.glsl)
/* Gabor Noise
*
* Based on: Blender patch D287 & D3495
* With additional controls for kernel variance.
*
* This implementation is adapted from the original built-in OSL implementation based on the 2009
* paper "Procedural noise using sparse Gabor convolution". Some parts are also adapted from the
* 2011 paper "Filtering Solid Gabor Noise" but this does not include the filtering and slicing.
* References to the papers are for copyright and reference.
*
* Notes and changes from the original OSL implementation and reference papers:
* - For 2D noise, as with Voronoi the calculation uses a 3x3x1 grid rather than slicing 3D noise.
* This is more performant when only 2D texture is required.
CharlieJolly marked this conversation as resolved Outdated

Brackets to follow Blender's coding style.

Brackets to follow Blender's coding style.
* - For artistic control, calculations for Bandwidth have been simplified and replaced with
* separate controls for Frequency, Gain and Radius. This provides finer control where
* before frequency and bandwidth were bound to the same parameter. Radius values over 1 may result
* in artefacts and discontinuities. This is not clamped as pushing the radius can create some
* potentially useful noise.
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
* - Phasor noise has been added. Since this is based on Gabor and sums the changes in phase it is
* trivial to add.
* - Additional sincos based kernels have been added which provide different texture control in a
* similar way to distance metrics in Voronoi.
* - Added Roughness, Scale Lacunarity, Frequency Lacunarity and Rotation Lacunarity to control
* additive fractal noise.
* - Uses built-in Blender hashes instead of adding a separate gabor rng.
*
* Adapted from Open Shading Language implementation.
* Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al.
* All Rights Reserved.
*
* OSL Gabor noise references:
* Lagae, Lefebvre, Drettakis and Dutré, 2009. Procedural noise using sparse Gabor convolution
* Lagae, A. and Drettakis, G. 2011. Filtering Solid Gabor Noise
* Tavernier et al. 2018, Gabor Noise Revisited
*
* Phasor noise reference:
* Tricard et al. 2019. Procedural Phasor Noise
*/
#define SHD_GABOR_MODE_GABOR 0
#define SHD_GABOR_MODE_RING 1
#define SHD_GABOR_MODE_CROSS 2
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)
#define SHD_GABOR_MODE_SQUARE 3
#define SHD_GABOR_MODE_PHASOR 4
#define SHD_GABOR_MODE_PHASOR_RING 5
#define SHD_GABOR_MODE_PHASOR_CROSS 6
#define SHD_GABOR_MODE_PHASOR_SQUARE 7
/* Large prime number for grid offset and impulse seed. */
#define GABOR_SEED 1259
/* Struct to hold Gabor parameters. */
struct GaborParams {
float frequency;
float radius;
float impulses;
float phase;
float phase_variance;
float rotation;
float init_rotation;
float rot_variance;
float tilt_randomness;
float cell_randomness;
float anisotropy;
int mode;
vec3 direction;
};
/* Struct to hold Fractal parameters. */
struct FractalParams {
float octaves;
float roughness;
float scl_lacunarity;
float fre_lacunarity;
float rot_lacunarity;
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.
};
/* Calculate impulses per cell. Performance is optimised when impulses are set to whole numbers.
* It's useful to allow impulses less than 1 to create spotted textures which is why int(impulses)
* is not directly used. Above 1 this provides a linear increase in impulses as the input value
* increases. */
int impulses_per_cell(vec3 cell, float impulses, int seed)
{
int n = int(impulses);
float rmd = impulses - floor(impulses);
if (rmd > 0.0) {
float t = hash_vec4_to_float(vec4(cell, float(seed - GABOR_SEED)));
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.
return (t <= rmd) ? n + 1 : n;
}
return n;
}
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.
/* Calculates the kernel shape that is multiplied by the gaussian envelope. For Phasor a sum of two
* values is required. For Gabor a sum of one value is required. These are passed as a vector in
* all cases. */
vec3 gabor_kernel(GaborParams gp, vec3 omega, float phi, vec3 position, float dv)
{
float g = cos(M_PI * sqrt(dv)) * 0.5 + 0.5;
vec3 r = vec3(0.0);
float h;
if (gp.mode == SHD_GABOR_MODE_GABOR) {
h = gp.frequency * dot(omega, position) + phi;
r = vec3(cos(h), 0.0, 0.0);
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR) {
h = gp.frequency * dot(omega, position) + phi;
r = vec3(cos(h), sin(h), 0.0);
}
else if (gp.mode == SHD_GABOR_MODE_CROSS) {
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.
h = gp.frequency * length(omega * position) + phi;
r = vec3(cos(h), 0.0, 0.0);
}
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.
else if (gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) {
h = gp.frequency * length(omega * position) + phi;
r = vec3(cos(h), sin(h), 0.0);
}
else if (gp.mode == SHD_GABOR_MODE_RING) {
h = cos(gp.frequency * dot(omega, position) + phi) +
cos(gp.frequency * length(position) + phi);
r = vec3(h, 0.0, 0.0) * 0.5;
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR_RING) {
h = cos(gp.frequency * dot(omega, position) + phi) +
cos(gp.frequency * length(position) + phi);
float h2 = sin(gp.frequency * dot(omega, position) + phi) +
sin(gp.frequency * length(position) + phi);
r = vec3(h, h2, 0.0) * 0.5;
}
else if (gp.mode == SHD_GABOR_MODE_SQUARE) {
h = cos(gp.frequency * dot(omega, position) + phi) +
cos(gp.frequency * dot(omega, position.yxz) + phi);
r = vec3(h, 0.0, 0.0) * 0.5;
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR_SQUARE) {
h = cos(gp.frequency * dot(omega, position) + phi) +
cos(gp.frequency * dot(omega, position.yxz) + phi);
float h2 = sin(gp.frequency * dot(omega, position) + phi) +
sin(gp.frequency * dot(omega, position.yxz) + phi);
r = vec3(h, h2, 0.0) * 0.5;
}
return r * g;
}
/* Set omega (angular frequency/direction) and phi (phase) for the impulse based on the anisotropy
* parameters. Isotropic and Anisotropic modes have been combined using a factor to mix between
* these modes. */
vec3 gabor_sample(GaborParams gp, vec3 cell, int seed, out float phi)
{
vec3 rand_values = hash_vec4_to_vec3(vec4(cell, float(seed))) * 2.0 - 1.0;
/* Phase. */
float pvar = mix(0.0, rand_values.z, gp.phase_variance);
phi = M_2PI * pvar + gp.phase;
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.
/* Isotropic direction. */
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.
float omega_t = M_PI * (rand_values.x) * gp.rot_variance - gp.rotation - gp.init_rotation;
float cos_omega_p = clamp(rand_values.y * gp.tilt_randomness, -1.0, 1.0);
float sin_omega_p = sqrt(1.0 - cos_omega_p * cos_omega_p);
float sin_omega_t = sin(omega_t);
float cos_omega_t = cos(omega_t);
/* Mix between Isotropic and Anisotropic direction. */
return mix(normalize(vec3(cos_omega_t * sin_omega_p, sin_omega_t * sin_omega_p, cos_omega_p)),
normalize(euler_to_mat3(vec3(0.0, 0.0, -gp.rotation)) * gp.direction),
gp.anisotropy);
}
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?
/* Generate noise based on the cell position and number of impulses. */
vec3 gabor_cell_3d(GaborParams gp, vec3 cell, vec3 cell_position, int seed)
{
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
int num_impulses = impulses_per_cell(cell, gp.impulses, seed);
vec3 sum = vec3(0.0);
for (int i = 0; i < num_impulses; ++i) {
vec3 rand_position = mix(vec3(0.0),
hash_vec4_to_vec3(vec4(cell, float(seed + i * GABOR_SEED))),
gp.cell_randomness);
vec3 kernel_position = (cell_position - rand_position);
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(...)`.
float dv = dot(kernel_position, kernel_position) / gp.radius;
if (dv <= 1.0) {
float phi;
vec3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
}
/* Generate noise based on the cell position and number of impulses. Z position is zeroed for 2D
* cell as the kernel is still processed in 3D. */
vec3 gabor_cell_2d(GaborParams gp, vec3 cell, vec3 cell_position, int seed)
{
int num_impulses = impulses_per_cell(cell, gp.impulses, seed);
vec3 sum = vec3(0.0);
for (int i = 0; i < num_impulses; ++i) {
vec3 rand_position = mix(vec3(0.0),
hash_vec4_to_vec3(vec4(cell, float(seed + i * GABOR_SEED))),
gp.cell_randomness);
rand_position.z = 0.0;
vec3 kernel_position = (cell_position - rand_position);
float dv = dot(kernel_position, kernel_position) / gp.radius;
if (dv <= 1.0) {
float phi;
vec3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
}
/* Utility function to wrap coords for periodic mode. */
float gabor_coord_wrap(float s, float p)
{
return (p != 0.0) ? s - p * floor(s / p) : 0.0;
}
/* Calculate 3D noise using 3x3x3 cell grid. */
vec3 gabor_grid_3d(GaborParams gp, vec3 p, float scale, int periodic, int seed)
{
vec3 coords = p * scale;
vec3 position = floor(coords);
vec3 local_position = coords - position;
vec3 sum = vec3(0.0);
for (int k = -1; k <= 1; k++) {
for (int j = -1; j <= 1; j++) {
for (int i = -1; i <= 1; i++) {
vec3 cell_offset = vec3(i, j, k);
vec3 cell = position + cell_offset;
vec3 cell_position = local_position - cell_offset;
/* Skip this cell if it's too far away to contribute - Lee Bruemmer.osl */
vec3 Pr = (vec3(i > 0, j > 0, k > 0) - local_position) * cell_offset;
if (dot(Pr, Pr) >= 1.0) {
continue;
}
/* Wrap cells for periodic noise. */
if (periodic == 1) {
cell.x = gabor_coord_wrap(cell.x, scale);
cell.y = gabor_coord_wrap(cell.y, scale);
cell.z = gabor_coord_wrap(cell.z, scale);
}
sum += gabor_cell_3d(gp, cell, cell_position, seed);
}
}
}
return sum;
}
/* Calculate 2D noise using 3x3x1 cell grid. Less computational than 3x3x3 grid. */
vec3 gabor_grid_2d(GaborParams gp, vec3 p, float scale, int periodic, int seed)
{
vec3 coords = vec3(p.xy, 0.0) * scale;
vec3 position = floor(coords);
vec3 local_position = coords - position;
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.
vec3 sum = vec3(0.0);
for (int j = -1; j <= 1; j++) {
for (int i = -1; i <= 1; i++) {
vec3 cell_offset = vec3(i, j, 0.0);
vec3 cell = position + cell_offset;
vec3 cell_position = local_position - cell_offset;
/* Skip this cell if it's too far away to contribute - Lee Bruemmer.osl */
vec3 Pr = (vec3(i > 0, j > 0, 0) - local_position) * cell_offset;
if (dot(Pr, Pr) >= 1.0) {
continue;
}
/* Wrap cells for periodic noise. */
if (periodic == 1) {
cell.x = gabor_coord_wrap(cell.x, scale);
cell.y = gabor_coord_wrap(cell.y, scale);
}
sum += gabor_cell_2d(gp, cell, cell_position, seed);
}
}
return sum;
}
/* Layered fractal noise. Optionally, for each layer, the offset is changed. This ensures that
* the impulses occur in different places if scale lacunarity is set to 1. Gabor has more variables
* to consider when layering the noise compared to Perlin noise. Kernel frequency and rotation have
* lacunarity parameters in addition to scale lacunarity and roughness that is found in Noise and
* Voronoi textures. */
float gabor_fractal_noise(FractalParams fp,
GaborParams gp,
vec3 p,
float scale,
int dimensions,
int periodic,
int use_origin_offset)
{
float fscale = 1.0;
float amp = 1.0;
float maxamp = 0.0;
float3 sum = vec3(0.0);
float octaves = clamp(fp.octaves, 0.0, 15.0);
if (fp.roughness == 0.0) {
octaves = 0.0;
}
int n = int(octaves);
for (int i = 0; i <= n; i++) {
int seed = use_origin_offset * i * GABOR_SEED;
float3 t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) :
gabor_grid_2d(gp, fscale * p, scale, periodic, seed);
gp.frequency *= fp.fre_lacunarity;
gp.rotation -= fp.rot_lacunarity;
sum += t * amp;
maxamp += amp;
amp *= fp.roughness;
fscale *= fp.scl_lacunarity;
}
float rmd = octaves - floor(octaves);
if (rmd != 0.0) {
int seed = use_origin_offset * (n + 1) * GABOR_SEED;
float3 t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) :
gabor_grid_2d(gp, fscale * p, scale, periodic, seed);
float3 sum2 = sum + t * amp;
sum = mix(sum, sum2, rmd);
}
sum /= maxamp;
/* Extract summed values for Phasor mode. */
if (gp.mode == SHD_GABOR_MODE_PHASOR || gp.mode == SHD_GABOR_MODE_PHASOR_RING ||
gp.mode == SHD_GABOR_MODE_PHASOR_CROSS || gp.mode == SHD_GABOR_MODE_PHASOR_SQUARE)
{
float pn = atan(sum.y, sum.x) / M_PI;
return pn;
}
else {
return sum.x;
}
}
/* Gabor parameters. Impulses are clamped as these directly impact performance. */
GaborParams gabor_parameters(vec3 direction,
float frequency,
float radius,
float impulses,
float phase,
float phase_variance,
float rotation,
float rot_variance,
float tilt_randomness,
float cell_randomness,
float anisotropy,
int mode)
{
GaborParams gp;
gp.impulses = clamp(impulses, 0.0001, 32.0);
gp.rot_variance = rot_variance;
gp.anisotropy = anisotropy;
gp.mode = mode;
gp.direction = direction;
gp.phase = phase;
gp.rotation = 0.0;
gp.init_rotation = rotation;
gp.phase_variance = phase_variance;
gp.tilt_randomness = tilt_randomness;
gp.cell_randomness = cell_randomness;
gp.radius = radius;
gp.frequency = frequency * M_PI;
return gp;
}
/* Gabor texture node. */
void node_tex_gabor(vec3 co,
float scale,
float gain,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float frequency,
float radius,
float impulses,
float phase,
float phase_variance,
float cell_randomness,
float rotation,
float rot_variance,
float tilt_randomness,
float anisotropy,
vec3 direction,
float dimensions,
float mode,
float use_normalize,
float periodic,
float use_origin_offset,
out float value)
{
/* Return early with mid level value. */
if (impulses == 0.0 || gain == 0.0 || radius <= 0.0 || scale == 0.0) {
value = (int(use_normalize) == 1) ? 0.5 : 0.0;
return;
}
/* Set Fractal params. */
FractalParams fp;
fp.roughness = roughness;
fp.octaves = detail;
fp.scl_lacunarity = scl_lacunarity;
fp.fre_lacunarity = fre_lacunarity;
fp.rot_lacunarity = rot_lacunarity;
/* Set Gabor params. */
GaborParams gp = gabor_parameters(direction,
frequency,
radius,
impulses,
phase,
phase_variance,
rotation,
rot_variance,
tilt_randomness,
cell_randomness,
anisotropy,
CharlieJolly marked this conversation as resolved Outdated

What does sqrt(0.75 / pi) correspond to here?

What does `sqrt(0.75 / pi)` correspond to here?

Locally I have updated the comment.

  /* Scale height of noise by the number of impulses. This is empircal as there is no easy way
   * to analytically determine the scaling factor for the sum of impulses.
   * Phasor does not require this because it is always returns [-1,1].
   * Following calculation from Lee Bruemmer OSL script implementation
   * Scale the noise so the range [-1,1] covers 6 standard deviations, or 3*sqrt(variance)
   * Since the radius is set to 1/a the scale simplifies to 3/4*sqrt(3*n/(pi*sqrt(2)))
   * float scale = 0.6162961511 * sqrt( Impulses ); but with a fixed number of impulses also divide
   * by sqrt(0.75/pi) (0.4886025119) which give 1.2613446229 * sqrt(gp.impulses).
   * In tests I've found that sqrt(2) 1.41 clips less when clamped.
   */
Locally I have updated the comment. ``` /* Scale height of noise by the number of impulses. This is empircal as there is no easy way * to analytically determine the scaling factor for the sum of impulses. * Phasor does not require this because it is always returns [-1,1]. * Following calculation from Lee Bruemmer OSL script implementation * Scale the noise so the range [-1,1] covers 6 standard deviations, or 3*sqrt(variance) * Since the radius is set to 1/a the scale simplifies to 3/4*sqrt(3*n/(pi*sqrt(2))) * float scale = 0.6162961511 * sqrt( Impulses ); but with a fixed number of impulses also divide * by sqrt(0.75/pi) (0.4886025119) which give 1.2613446229 * sqrt(gp.impulses). * In tests I've found that sqrt(2) 1.41 clips less when clamped. */ ```
int(mode));
float g = gabor_fractal_noise(
fp, gp, co, scale, int(dimensions), int(periodic), int(use_origin_offset)) *
gain;
CharlieJolly marked this conversation as resolved Outdated

Does clamping here mean we will get flat areas? Can't we adjust the scale factor to ensure a correct range instead?

Does clamping here mean we will get flat areas? Can't we adjust the scale factor to ensure a correct range instead?

See previous comment. Due to the way the impulse sum, it is a compromise between a scale factor that is too low and clips and is too high and creates a mid level grey 0.5 texture when scale factor is too high.

See previous comment. Due to the way the impulse sum, it is a compromise between a scale factor that is too low and clips and is too high and creates a mid level grey 0.5 texture when scale factor is too high.

Honestly, I'm also not entirely convinced by this solution mainly because it loses all the detail in the highs and lows.
I thought of a solution which involves using a piecewise function which would need to converge towards 0.0 when x -> -infinity and 1.0 when x -> +infinity. The function should simply be linear in the middle part and be continuously differentiable everywhere.
That way we could both remap it into a [0.0, 1.0] range and preserve all details, however I'd first need to think of a fitting function, so if necessary I'd do it in a separate PR after this one has been merged.

But ideally @CharlieJolly can find a better way.

Honestly, I'm also not entirely convinced by this solution mainly because it loses all the detail in the highs and lows. I thought of a solution which involves using a piecewise function which would need to converge towards 0.0 when x -> -infinity and 1.0 when x -> +infinity. The function should simply be linear in the middle part and be continuously differentiable everywhere. That way we could both remap it into a [0.0, 1.0] range **and** preserve all details, however I'd first need to think of a fitting function, so if necessary I'd do it in a separate PR after this one has been merged. But ideally @CharlieJolly can find a better way.

Some kind of gain and bias function would be useful to do this I think. Users can run this through a float curve but I'm not sure there is a perfect solution here.

Some kind of gain and bias function would be useful to do this I think. Users can run this through a float curve but I'm not sure there is a perfect solution here.

If you can't think of anything immediately I think it's also fine to leave it for now.
I'll think about an appropriate function in the meantime.

If you can't think of anything immediately I think it's also fine to leave it for now. I'll think about an appropriate function in the meantime.
/* For Gabor modes, scale height of noise by the number of impulses. This is empircal as there is
* no easy way to analytically determine the scaling factor for the sum of impulses. The
* following calculation is taken from Lee Bruemmer's OSL script implementation.
*
* Scale the noise so the range [-1,1] covers 6 standard deviations, or 3*sqrt(variance) Since
* the radius is set to 1/a the scale simplifies to 3/4*sqrt(3*n/(pi*sqrt(2))) float scale =
* 0.6162961511 * sqrt( Impulses ); but with a fixed number of impulses also divide by
* sqrt(0.75/pi) (0.4886025119) which give 1.2613446229 * sqrt(gp.impulses).
*
* In tests I've found that sqrt(2) 1.41 clips less but the previous value is better overall.
* Artists can overcome this limitation by using the gain control to tweak the texture as
* required.
*
* Phasor does not require this because it always returns [-1,1].
*/
if (gp.mode == SHD_GABOR_MODE_GABOR || gp.mode == SHD_GABOR_MODE_RING ||
gp.mode == SHD_GABOR_MODE_CROSS || gp.mode == SHD_GABOR_MODE_SQUARE)
{
float impulse_scale = impulses > 1.0 ? 1.2613446229 * sqrt(gp.impulses) : 1.2613446229;
g = g / impulse_scale;
}
/* Normalise and clamp noise to [0.1] range. */
if (int(use_normalize) == 1) {
g = clamp(0.5 * g + 0.5, 0.0, 1.0);
}
value = g;
}

View File

@ -1275,6 +1275,16 @@ typedef struct NodeTexNoise {
char _pad[3];
} NodeTexNoise;
typedef struct NodeTexGabor {
NodeTexBase base;
char mode;
char periodic;
char normalize;
char dimensions;
char use_origin_offset;
char _pad[3];
} NodeTexGabor;
typedef struct NodeTexVoronoi {
NodeTexBase base;
int dimensions;
@ -2028,6 +2038,17 @@ enum {
SHD_VORONOI_N_SPHERE_RADIUS = 4,
};
typedef enum NodeGaborMode {
SHD_GABOR_MODE_GABOR,
SHD_GABOR_MODE_RING,
SHD_GABOR_MODE_CROSS,
SHD_GABOR_MODE_SQUARE,
SHD_GABOR_MODE_PHASOR,
SHD_GABOR_MODE_PHASOR_RING,
SHD_GABOR_MODE_PHASOR_CROSS,
SHD_GABOR_MODE_PHASOR_SQUARE,
} NodeGaborMode;
/* musgrave texture */
enum {
SHD_MUSGRAVE_MULTIFRACTAL = 0,

View File

@ -4964,6 +4964,58 @@ static void def_sh_tex_musgrave(StructRNA *srna)
RNA_def_property_update(prop, 0, "rna_ShaderNode_socket_update");
}
static void def_sh_tex_gabor(StructRNA *srna)
{
static const EnumPropertyItem prop_gabor_mode[] = {
{SHD_GABOR_MODE_GABOR, "GABOR", 0, "Gabor", "Gabor default kernel"},
{SHD_GABOR_MODE_RING, "GABOR_RING", 0, "Gabor Ring", "Gabor ring kernel"},
{SHD_GABOR_MODE_CROSS, "GABOR_CROSS", 0, "Gabor Cross", "Gabor cross kernel"},
{SHD_GABOR_MODE_SQUARE, "GABOR_SQUARE", 0, "Gabor Square", "Gabor square kernel"},
{SHD_GABOR_MODE_PHASOR, "PHASOR", 0, "Phasor", "Phasor default kernel"},
{SHD_GABOR_MODE_PHASOR_RING, "PHASOR_RING", 0, "Phasor Ring", "Phasor ring kernel"},
{SHD_GABOR_MODE_PHASOR_CROSS, "PHASOR_CROSS", 0, "Phasor Cross", "Phasor cross kernel"},
{SHD_GABOR_MODE_PHASOR_SQUARE, "PHASOR_SQUARE", 0, "Phasor Square", "Phasor square kernel"},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem prop_gabor_dimensions[] = {
{2, "2D", 0, "2D", "Use the 2D vector (X, Y) as input. The Z component is ignored"},
{3, "3D", 0, "3D", "Use the 3D vector (X, Y, Z) as input"},
{0, nullptr, 0, nullptr, nullptr},
};
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeTexGabor", "storage");
def_sh_tex(srna);
prop = RNA_def_property(srna, "gabor_dimensions", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "dimensions");
RNA_def_property_enum_items(prop, prop_gabor_dimensions);
RNA_def_property_ui_text(prop, "Dimensions", "Number of dimensions to output noise for");
RNA_def_property_update(prop, 0, "rna_ShaderNode_socket_update");
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "mode");
RNA_def_property_enum_items(prop, prop_gabor_mode);
RNA_def_property_ui_text(prop, "Mode", "Mode");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_ShaderNode_socket_update");
prop = RNA_def_property(srna, "periodic", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "periodic", 0);
RNA_def_property_ui_text(prop, "Periodic", "Periodic noise");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "normalize", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "normalize", 0);
RNA_def_property_ui_text(prop, "Normalize", "Normalize output to 0.0 to 1.0 range");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
prop = RNA_def_property(srna, "use_origin_offset", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "use_origin_offset", 0);
RNA_def_property_ui_text(prop, "Offset Origin", "Offset origin for each octave");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_ShaderNode_socket_update");
}
static void def_sh_tex_voronoi(StructRNA *srna)
{
static EnumPropertyItem prop_distance_items[] = {

View File

@ -124,6 +124,7 @@ DefNode(ShaderNode, SH_NODE_CURVE_FLOAT, def_float_curve, "CUR
DefNode(ShaderNode, SH_NODE_COMBINE_COLOR, def_sh_combsep_color, "COMBINE_COLOR", CombineColor, "Combine Color", "Create a color from individual components using multiple models")
DefNode(ShaderNode, SH_NODE_SEPARATE_COLOR, def_sh_combsep_color, "SEPARATE_COLOR", SeparateColor, "Separate Color", "Split a color into its individual components using multiple models")
DefNode(ShaderNode, SH_NODE_MIX, def_sh_mix, "MIX", Mix, "Mix", "Mix values by a factor")
DefNode(ShaderNode, SH_NODE_TEX_GABOR, def_sh_tex_gabor, "TEX_GABOR", TexGabor, "Gabor Texture", "Generate Gabor wavelet noise" )
DefNode(CompositorNode, CMP_NODE_VIEWER, def_cmp_viewer, "VIEWER", Viewer, "Viewer", "" )
DefNode(CompositorNode, CMP_NODE_RGB, 0, "RGB", RGB, "RGB", "" )

View File

@ -95,6 +95,7 @@ set(SRC
nodes/node_shader_tex_checker.cc
nodes/node_shader_tex_coord.cc
nodes/node_shader_tex_environment.cc
nodes/node_shader_tex_gabor.cc
nodes/node_shader_tex_gradient.cc
nodes/node_shader_tex_image.cc
nodes/node_shader_tex_magic.cc

View File

@ -84,6 +84,7 @@ void register_shader_nodes()
register_node_type_sh_tex_checker();
register_node_type_sh_tex_coord();
register_node_type_sh_tex_environment();
register_node_type_sh_tex_gabor();
register_node_type_sh_tex_gradient();
register_node_type_sh_tex_ies();
register_node_type_sh_tex_image();

View File

@ -83,6 +83,7 @@ void register_node_type_sh_tex_brick();
void register_node_type_sh_tex_checker();
void register_node_type_sh_tex_coord();
void register_node_type_sh_tex_environment();
void register_node_type_sh_tex_gabor();
void register_node_type_sh_tex_gradient();
void register_node_type_sh_tex_ies();
void register_node_type_sh_tex_image();

View File

@ -0,0 +1,732 @@
/* SPDX-FileCopyrightText: 2005 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/* Gabor Noise
*
* Based on: Blender patch D287 & D3495
* With additional controls for kernel variance.
*
* Adapted from Open Shading Language implementation.
* Copyright (c) 2009-2010 Sony Pictures Imageworks Inc., et al.
* All Rights Reserved.
*
* OSL Gabor noise originally based on:
* Lagae, Lefebvre, Drettakis and Dutré, 2009. Procedural noise using sparse Gabor convolution
* Lagae, A. and Drettakis, G. 2011. Filtering Solid Gabor Noise
*
* Phasor noise option is based on:
* Tricard et al. 2019. Procedural Phasor Noise
*/
/* See GLSL implementation for code comments. */
#include "node_shader_util.hh"
#include "node_util.hh"
#include "BKE_texture.h"
#include "BLI_hash.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.hh"
#include "BLI_noise.hh"
#include "NOD_multi_function.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_shader_tex_gabor_cc {
NODE_STORAGE_FUNCS(NodeTexGabor)
static void node_declare(NodeDeclarationBuilder &b)
{
b.is_function_node();
b.use_custom_socket_order();
b.add_output<decl::Float>("Value").no_muted_links();
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>("Gain").min(-10.0f).max(10.0f).default_value(1.0f);
PanelDeclarationBuilder &fractal = b.add_panel("Fractal").default_closed(true).draw_buttons(
[](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) {
uiItemR(layout, ptr, "use_origin_offset", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
});
fractal.add_input<decl::Float>("Detail").min(0.0f).max(15.0f).default_value(0.0f).description(
"Number of noise octaves, high values are slower to compute");
fractal.add_input<decl::Float>("Roughness")
.min(0.0f)
.max(1.0f)
.default_value(0.5f)
.subtype(PROP_FACTOR);
fractal.add_input<decl::Float>("Scale Lacunarity")
.min(-10.0f)
.max(10.0f)
.default_value(2.0f)
.description("The difference between the scale of each consecutive octave");
fractal.add_input<decl::Float>("Frequency Lacunarity")
.min(-10.0f)
.max(10.0f)
.default_value(2.0f)
.description("The difference between the kernel frequency of each consecutive octave");
fractal.add_input<decl::Float>("Rotation Lacunarity")
.subtype(PROP_ANGLE)
.description(
"The difference between the kernel rotation of each consecutive octave, does not work "
"when Anisotropic is set to 1");
PanelDeclarationBuilder &kernel = b.add_panel("Kernel").default_closed(true);
kernel.add_input<decl::Float>("Frequency")
.min(-100.0f)
.max(100.0f)
.default_value(4.0f)
.description("Frequency for the kernel shape, higher values provides more detail");
kernel.add_input<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 may produce artefacts");
kernel.add_input<decl::Float>("Impulses")
.min(0.0f)
.max(16.0f)
.default_value(2.0f)
.description("Controls the amount of kernel impulses, high values are slower to compute");
kernel.add_input<decl::Float>("Phase Offset")
.subtype(PROP_ANGLE)
.description("Kernel shape phase offset");
kernel.add_input<decl::Float>("Phase Variance")
.min(0.0f)
.max(1.0f)
.default_value(1.0f)
.subtype(PROP_FACTOR);
kernel.add_input<decl::Float>("Cell Randomness")
.min(0.0f)
.max(1.0f)
.default_value(1.0f)
.subtype(PROP_FACTOR);
PanelDeclarationBuilder &aniso = b.add_panel("Anisotropy").default_closed(false);
aniso.add_input<decl::Float>("Rotation")
.subtype(PROP_ANGLE)
.description("Kernel shape rotation");
aniso.add_input<decl::Float>("Rotation Variance")
.subtype(PROP_ANGLE)
.description("Rotation randomness");
aniso.add_input<decl::Float>("Tilt Randomness")
.min(0.0f)
.max(1.0f)
.default_value(1.0f)
.subtype(PROP_FACTOR);
aniso.add_input<decl::Float>("Anisotropic Factor")
.min(0.0f)
.max(1.0f)
.default_value(0.0f)
.subtype(PROP_FACTOR)
.description("Mix between Isotropic and fixed Anisotropic control");
aniso.add_input<decl::Vector>("Direction")
.description("Direction and magnitude of the anisotropy")
.default_value({0.0f, 0.0f, 1.0f})
.min(-1.0f)
.max(1.0f)
.subtype(PROP_DIRECTION);
}
static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
{
uiLayout *col;
uiLayout *split;
col = uiLayoutColumn(layout, true);
split = uiLayoutSplit(col, 0.33f, true);
uiItemR(split, ptr, "gabor_dimensions", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
uiItemR(split, ptr, "mode", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
col = uiLayoutColumn(layout, true);
uiItemR(col, ptr, "periodic", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
uiItemR(col, ptr, "normalize", UI_ITEM_R_SPLIT_EMPTY_NAME, nullptr, ICON_NONE);
}
static void node_init(bNodeTree * /*ntree*/, bNode *node)
{
NodeTexGabor *tex = MEM_cnew<NodeTexGabor>(__func__);
BKE_texture_mapping_default(&tex->base.tex_mapping, TEXMAP_TYPE_POINT);
BKE_texture_colormapping_default(&tex->base.color_mapping);
tex->mode = SHD_GABOR_MODE_GABOR;
tex->periodic = 0;
tex->normalize = 1;
tex->dimensions = 2;
tex->use_origin_offset = 1;
node->storage = tex;
}
static int node_shader_gpu_tex_gabor(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
node_shader_gpu_default_tex_coord(mat, node, &in[0].link);
node_shader_gpu_tex_mapping(mat, node, in, out);
const NodeTexGabor &storage = node_storage(*node);
const float dimensions = storage.dimensions;
const float mode = storage.mode;
const float periodic = storage.periodic;
const float use_normalize = storage.normalize;
const float use_origin_offset = storage.use_origin_offset;
return GPU_stack_link(mat,
node,
"node_tex_gabor",
in,
out,
GPU_constant(&dimensions),
GPU_constant(&mode),
GPU_constant(&use_normalize),
GPU_constant(&periodic),
GPU_constant(&use_origin_offset));
}
#define GABOR_SEED 1259
typedef struct GaborParams {
float frequency;
float radius;
float impulses;
float phase;
float phase_variance;
float rotation;
float init_rotation;
float rot_variance;
float tilt_randomness;
float cell_randomness;
float anisotropy;
int mode;
float3 direction;
} GaborParams;
typedef struct FractalParams {
float octaves;
float roughness;
float scl_lacunarity;
float fre_lacunarity;
float rot_lacunarity;
} FractalParams;
int impulses_per_cell(const float3 cell, const float impulses, const int seed)
{
const int n = int(impulses);
const float rmd = impulses - math::floor(impulses);
if (rmd > 0.0f) {
const float t = noise::hash_float_to_float(float4(cell, float(seed - GABOR_SEED)));
return (t <= rmd) ? n + 1 : n;
}
return n;
}
static float3 gabor_kernel(const GaborParams gp,
const float3 omega,
const float phi,
const float3 position,
const float dv)
{
const float g = math::cos(float(M_PI) * math::sqrt(dv)) * 0.5f + 0.5f;
float3 r = float3(0.0f);
float h;
if (gp.mode == SHD_GABOR_MODE_GABOR) {
h = gp.frequency * math::dot(omega, position) + phi;
r = float3(math::cos(h), 0.0f, 0.0f);
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR) {
h = gp.frequency * math::dot(omega, position) + phi;
r = float3(math::cos(h), math::sin(h), 0.0f);
}
else if (gp.mode == SHD_GABOR_MODE_CROSS) {
h = gp.frequency * math::length(omega * position) + phi;
r = float3(math::cos(h), 0.0f, 0.0f);
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) {
h = gp.frequency * math::length(omega * position) + phi;
r = float3(math::cos(h), math::sin(h), 0.0f);
}
else if (gp.mode == SHD_GABOR_MODE_RING) {
h = math::cos(gp.frequency * math::dot(omega, position) + phi) +
math::cos(gp.frequency * math::length(position) + phi);
r = float3(h, 0.0f, 0.0f) * 0.5f;
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR_RING) {
h = math::cos(gp.frequency * math::dot(omega, position) + phi) +
math::cos(gp.frequency * math::length(position) + phi);
const float h2 = math::sin(gp.frequency * math::dot(omega, position) + phi) +
math::sin(gp.frequency * math::length(position) + phi);
r = float3(h, h2, 0.0f) * 0.5f;
}
else if (gp.mode == SHD_GABOR_MODE_SQUARE) {
const float3 positionyxz = float3(position.y, position.x, position.z);
h = math::cos(gp.frequency * math::dot(omega, position) + phi) +
math::cos(gp.frequency * math::dot(omega, positionyxz) + phi);
r = float3(h, 0.0f, 0.0f) * 0.5f;
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR_SQUARE) {
const float3 positionyxz = float3(position.y, position.x, position.z);
h = math::cos(gp.frequency * math::dot(omega, position) + phi) +
math::cos(gp.frequency * math::dot(omega, positionyxz) + phi);
const float h2 = math::sin(gp.frequency * math::dot(omega, position) + phi) +
math::sin(gp.frequency * math::dot(omega, positionyxz) + phi);
r = float3(h, h2, 0.0f) * 0.5f;
}
return r * g;
}
static float3 rotate_z(const float3 &vector, float angle)
{
float mat[3][3];
float3 result = vector;
eul_to_mat3(mat, float3(0.0f, 0.0f, angle));
mul_m3_v3(mat, result);
return result;
}
static float3 gabor_sample(const GaborParams gp, const float3 cell, const int seed, float &phi)
{
const float3 rand_values = noise::hash_float_to_float3(float4(cell, float(seed))) * 2.0f - 1.0f;
const float pvar = math::interpolate(0.0f, rand_values.z, gp.phase_variance);
phi = 2.0f * float(M_PI) * pvar + gp.phase;
const float omega_t = float(M_PI) * (rand_values.x) * gp.rot_variance - gp.rotation -
gp.init_rotation;
const float cos_omega_p = math::clamp(rand_values.y * gp.tilt_randomness, -1.0f, 1.0f);
const float sin_omega_p = math::sqrt(1.0f - cos_omega_p * cos_omega_p);
const float sin_omega_t = math::sin(omega_t);
const float cos_omega_t = math::cos(omega_t);
return math::interpolate(
math::normalize(float3(cos_omega_t * sin_omega_p, sin_omega_t * sin_omega_p, cos_omega_p)),
math::normalize(rotate_z(gp.direction, -gp.rotation)),
gp.anisotropy);
}
/* Generate noise based on the cell position. */
static float3 gabor_cell_3d(GaborParams &gp, float3 cell, float3 cell_position, int seed)
{
int num_impulses = impulses_per_cell(cell, gp.impulses, seed);
float3 sum = float3(0.0f);
for (int i = 0; i < num_impulses; ++i) {
float3 rand_position = math::interpolate(
float3(0.0f),
noise::hash_float_to_float3(float4(cell, float(seed + i * GABOR_SEED))),
gp.cell_randomness);
const float3 kernel_position = (cell_position - rand_position);
const float dv = math::dot(kernel_position, kernel_position) / gp.radius;
if (dv <= 1.0f) {
float phi;
float3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
}
static float3 gabor_cell_2d(GaborParams &gp, float3 cell, float3 cell_position, int seed)
{
int num_impulses = impulses_per_cell(cell, gp.impulses, seed);
float3 sum = float3(0.0f);
for (int i = 0; i < num_impulses; ++i) {
float3 rand_position = math::interpolate(
float3(0.0f),
noise::hash_float_to_float3(float4(cell, float(seed + i * GABOR_SEED))),
gp.cell_randomness);
rand_position.z = 0.0f;
float3 kernel_position = (cell_position - rand_position);
const float dv = math::dot(kernel_position, kernel_position) / gp.radius;
if (dv <= 1.0f) {
float phi;
float3 omega = gabor_sample(gp, cell, seed + (num_impulses + i) * GABOR_SEED, phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
}
/* Wrap value. */
static float gabor_coord_wrap(const float a, const float b)
{
return (b != 0.0f) ? a - b * math::floor(a / b) : 0.0f;
}
/* 3*3 cell grid */
static float3 gabor_grid_3d(
GaborParams &gp, const float3 p, const float scale, const int periodic, const int seed)
{
const float3 coords = p * scale;
const float3 position = math::floor(coords);
const float3 local_position = coords - position;
float3 sum = float3(0.0f);
for (int k = -1; k <= 1; k++) {
for (int j = -1; j <= 1; j++) {
for (int i = -1; i <= 1; i++) {
const float3 cell_offset = float3(i, j, k);
/* Skip this cell if it's too far away to contribute - Bruemmer.osl */
const float3 Pr = (float3(i > 0, j > 0, k > 0) - local_position) * cell_offset;
if (math::dot(Pr, Pr) >= 1.0f) {
continue;
}
float3 cell = position + cell_offset;
const float3 cell_position = local_position - cell_offset;
if (periodic) {
cell[0] = gabor_coord_wrap(cell[0], scale);
cell[1] = gabor_coord_wrap(cell[1], scale);
cell[2] = gabor_coord_wrap(cell[2], scale);
}
sum += gabor_cell_3d(gp, cell, cell_position, seed);
}
}
}
return sum;
}
/* 2*2 cell grid */
static float3 gabor_grid_2d(
GaborParams &gp, const float3 p, const float scale, const int periodic, const int seed)
{
const float3 coords = float3(p.x, p.y, 0.0f) * scale;
const float3 position = math::floor(coords);
const float3 local_position = coords - position;
float3 sum = float3(0.0f);
for (int j = -1; j <= 1; j++) {
for (int i = -1; i <= 1; i++) {
const float3 cell_offset = float3(i, j, 0.0f);
/* Skip this cell if it's too far away to contribute - Bruemmer.osl */
const float3 Pr = (float3(i > 0, j > 0, 0) - local_position) * cell_offset;
if (math::dot(Pr, Pr) >= 1.0f) {
continue;
}
float3 cell = position + cell_offset;
const float3 cell_position = local_position - cell_offset;
if (periodic) {
cell[0] = gabor_coord_wrap(cell[0], scale);
cell[1] = gabor_coord_wrap(cell[1], scale);
}
sum += gabor_cell_2d(gp, cell, cell_position, seed);
}
}
return sum;
}
/* Fractal gabor noise. Layered noise with octaves, lacunarity, frequency and roughness control. */
static float gabor_fractal_noise(FractalParams fp,
GaborParams &gp,
const float3 p,
const float scale,
const int dimensions,
const int periodic,
const int use_origin_offset)
{
float fscale = 1.0f;
float amp = 1.0f;
float maxamp = 0.0f;
float3 sum = float3(0.0f);
float octaves = math::clamp(fp.octaves, 0.0f, 15.0f);
if (fp.roughness == 0.0f) {
octaves = 0.0f;
}
const int n = int(octaves);
for (int i = 0; i <= n; i++) {
const int seed = use_origin_offset * i * GABOR_SEED;
const float3 t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) :
gabor_grid_2d(gp, fscale * p, scale, periodic, seed);
gp.frequency *= fp.fre_lacunarity;
gp.rotation -= fp.rot_lacunarity;
sum += t * amp;
maxamp += amp;
amp *= fp.roughness;
fscale *= fp.scl_lacunarity;
}
float rmd = octaves - math::floor(octaves);
if (rmd != 0.0f) {
const int seed = use_origin_offset * (n + 1) * GABOR_SEED;
const float3 t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) :
gabor_grid_2d(gp, fscale * p, scale, periodic, seed);
const float3 sum2 = sum + t * amp;
sum = ((1.0f - rmd) * sum + rmd * sum2);
}
sum /= maxamp;
if (gp.mode == SHD_GABOR_MODE_PHASOR || gp.mode == SHD_GABOR_MODE_PHASOR_RING ||
gp.mode == SHD_GABOR_MODE_PHASOR_CROSS || gp.mode == SHD_GABOR_MODE_PHASOR_SQUARE)
{
const float pn = math::atan2(sum.y, sum.x) / float(M_PI);
return pn;
}
else {
return sum.x;
}
}
/* Set parameters used by Gabor noise. */
static GaborParams gabor_parameters(float3 direction,
float frequency,
float radius,
float impulses,
float phase,
float phase_variance,
float rotation,
float rot_variance,
float tilt_randomness,
float cell_randomness,
float anisotropy,
int mode)
{
GaborParams gp;
gp.impulses = math::clamp(impulses, 0.0001f, 32.0f);
gp.rot_variance = rot_variance;
gp.anisotropy = anisotropy;
gp.mode = mode;
gp.direction = direction;
gp.phase = phase;
gp.rotation = 0.0f;
gp.init_rotation = rotation;
gp.phase_variance = phase_variance;
gp.tilt_randomness = tilt_randomness;
gp.cell_randomness = cell_randomness;
gp.radius = radius;
gp.frequency = frequency * float(M_PI);
return gp;
}
static float gabor_noise(const float3 p,
const float3 direction,
const float scale,
const float frequency,
const float detail,
const float roughness,
const float scl_lacunarity,
const float fre_lacunarity,
const float rot_lacunarity,
const float gain,
const float radius,
const float impulses,
const float phase,
const float phase_variance,
const float rotation,
const float rot_variance,
const float tilt_randomness,
const float cell_randomness,
const float anisotropy,
const int dimensions,
const int mode,
const int use_normalize,
const int periodic,
const int use_origin_offset)
{
if (impulses == 0.0f || gain == 0.0f || radius <= 0.0f || scale == 0.0f) {
return (use_normalize) ? 0.5f : 0.0f;
}
FractalParams fp;
fp.roughness = roughness;
fp.octaves = detail;
fp.scl_lacunarity = scl_lacunarity;
fp.fre_lacunarity = fre_lacunarity;
fp.rot_lacunarity = rot_lacunarity;
GaborParams gp = gabor_parameters(direction,
frequency,
radius,
impulses,
phase,
phase_variance,
rotation,
rot_variance,
tilt_randomness,
cell_randomness,
anisotropy,
mode);
float g = gabor_fractal_noise(fp, gp, p, scale, dimensions, periodic, use_origin_offset) * gain;
if (gp.mode == SHD_GABOR_MODE_GABOR || gp.mode == SHD_GABOR_MODE_RING ||
gp.mode == SHD_GABOR_MODE_CROSS || gp.mode == SHD_GABOR_MODE_SQUARE)
{
const float impulse_scale = impulses > 1.0 ? 1.2613446229f * math::sqrt(gp.impulses) :
1.2613446229f;
g = g / impulse_scale;
}
if (use_normalize) {
return math::clamp(0.5f * g + 0.5f, 0.0f, 1.0f);
}
return g;
}
static mf::Signature gabor_signature(const char *name)
{
mf::Signature signature;
mf::SignatureBuilder builder{name, signature};
builder.single_input<float3>("Vector");
builder.single_input<float>("Scale");
builder.single_input<float>("Gain");
builder.single_input<float>("Detail");
builder.single_input<float>("Roughness");
builder.single_input<float>("Scale Lacunarity");
builder.single_input<float>("Frequency Lacunarity");
builder.single_input<float>("Rotation Lacunarity");
builder.single_input<float>("Frequency");
builder.single_input<float>("Radius");
builder.single_input<float>("Impulses");
builder.single_input<float>("Phase Offset");
builder.single_input<float>("Phase Variance");
builder.single_input<float>("Cell Randomness");
builder.single_input<float>("Rotation");
builder.single_input<float>("Rotation Variance");
builder.single_input<float3>("Direction");
builder.single_input<float>("Tilt Randomness");
builder.single_input<float>("Anisotropic Factor");
builder.single_output<float>("Value");
return signature;
}
class GaborNoiseFunction : public mf::MultiFunction {
private:
int dimensions_;
int mode_;
int periodic_;
int normalize_;
int use_origin_offset_;
public:
GaborNoiseFunction(
int dimensions, int kernel, int periodic, int normalize, int use_origin_offset)
: dimensions_(dimensions),
mode_(kernel),
periodic_(periodic),
normalize_(normalize),
use_origin_offset_(use_origin_offset)
{
static mf::Signature signature = gabor_signature("GaborNoise");
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
int param = 0;
const VArray<float3> &vector = params.readonly_single_input<float3>(param++, "Vector");
const VArray<float> &scale = params.readonly_single_input<float>(param++, "Scale");
const VArray<float> &gain = params.readonly_single_input<float>(param++, "Gain");
const VArray<float> &detail = params.readonly_single_input<float>(param++, "Detail");
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> &frequency = params.readonly_single_input<float>(param++, "Frequency");
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> &phase = params.readonly_single_input<float>(param++, "Phase Offset");
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> &rot_variance = params.readonly_single_input<float>(param++,
"Rotation Variance");
const VArray<float> &tilt_randomness = params.readonly_single_input<float>(param++,
"Tilt Randomness");
const VArray<float> &anisotropy = params.readonly_single_input<float>(param++,
"Anisotropic Factor");
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],
frequency[i],
detail[i],
roughness[i],
scale_lacunarity[i],
freq_lacunarity[i],
rot_lacunarity[i],
gain[i],
radius[i],
impulses[i],
phase[i],
phase_variance[i],
rotation[i],
rot_variance[i],
tilt_randomness[i],
cell_randomness[i],
anisotropy[i],
dimensions_,
mode_,
normalize_,
periodic_,
use_origin_offset_);
});
}
ExecutionHints get_execution_hints() const override
{
ExecutionHints hints;
hints.allocates_array = false;
hints.min_grain_size = 100;
return hints;
}
};
static void build_multi_function(NodeMultiFunctionBuilder &builder)
{
const NodeTexGabor &storage = node_storage(builder.node());
builder.construct_and_set_matching_fn<GaborNoiseFunction>(storage.dimensions,
storage.mode,
storage.periodic,
storage.normalize,
storage.use_origin_offset);
}
} // namespace blender::nodes::node_shader_tex_gabor_cc
void register_node_type_sh_tex_gabor()
{
namespace file_ns = blender::nodes::node_shader_tex_gabor_cc;
static bNodeType ntype;
sh_fn_node_type_base(&ntype, SH_NODE_TEX_GABOR, "Gabor Texture", NODE_CLASS_TEXTURE);
ntype.declare = file_ns::node_declare;
ntype.draw_buttons = file_ns::node_layout;
ntype.initfunc = file_ns::node_init;
ntype.gpu_fn = file_ns::node_shader_gpu_tex_gabor;
node_type_storage(
&ntype, "NodeTexGabor", node_free_standard_storage, node_copy_standard_storage);
blender::bke::node_type_size_preset(&ntype, blender::bke::eNodeSizePreset::MIDDLE);
ntype.build_multi_function = file_ns::build_multi_function;
nodeRegisterType(&ntype);
}