WIP: Node: Gabor Noise Texture #110802

Draft
Charlie Jolly wants to merge 2 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.
22 changed files with 2471 additions and 0 deletions

View File

@ -953,6 +953,18 @@ static ShaderNode *add_node(Scene *scene,
get_tex_mapping(gabor, b_texture_mapping);
node = gabor;
}
else if (b_node.is_a(&RNA_ShaderNodeTexGaborF)) {
BL::ShaderNodeTexGaborF b_gaborf_node(b_node);
GaborFTextureNode *gaborf = graph->create_node<GaborFTextureNode>();
gaborf->set_mode((NodeGaborFMode)b_gaborf_node.mode());
gaborf->set_dimensions(b_gaborf_node.gabor_dimensions());
gaborf->set_periodic(b_gaborf_node.periodic());
gaborf->set_use_normalize(b_gaborf_node.normalize());
gaborf->set_use_origin_offset(b_gaborf_node.use_origin_offset());
BL::TexMapping b_texture_mapping(b_gaborf_node.texture_mapping());
get_tex_mapping(gaborf, b_texture_mapping);
node = gaborf;
}
else if (b_node.is_a(&RNA_ShaderNodeTexCoord)) {
BL::ShaderNodeTexCoord b_tex_coord_node(b_node);
TextureCoordinateNode *tex_coord = graph->create_node<TextureCoordinateNode>();

View File

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

View File

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

View File

@ -0,0 +1,466 @@
/* SPDX-FileCopyrightText: 2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* 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);
}
#undef vector3

View File

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

View File

@ -70,6 +70,7 @@ SHADER_NODE_TYPE(NODE_TEX_SKY)
SHADER_NODE_TYPE(NODE_TEX_GRADIENT)
SHADER_NODE_TYPE(NODE_TEX_VORONOI)
SHADER_NODE_TYPE(NODE_TEX_GABOR)
SHADER_NODE_TYPE(NODE_TEX_GABORF)
SHADER_NODE_TYPE(NODE_TEX_WAVE)
SHADER_NODE_TYPE(NODE_TEX_MAGIC)
SHADER_NODE_TYPE(NODE_TEX_CHECKER)

View File

@ -166,6 +166,7 @@ CCL_NAMESPACE_END
#include "kernel/svm/displace.h"
#include "kernel/svm/fresnel.h"
#include "kernel/svm/gabor.h"
#include "kernel/svm/gaborf.h"
#include "kernel/svm/gamma.h"
#include "kernel/svm/geometry.h"
#include "kernel/svm/gradient.h"
@ -484,6 +485,9 @@ ccl_device void svm_eval_nodes(KernelGlobals kg,
SVM_CASE(NODE_TEX_GABOR)
offset = svm_node_tex_gabor(kg, sd, stack, node.y, node.z, node.w, offset);
break;
SVM_CASE(NODE_TEX_GABORF)
offset = svm_node_tex_gabor(kg, sd, stack, node, offset);
break;
SVM_CASE(NODE_TEX_WAVE)
offset = svm_node_tex_wave(kg, sd, stack, node, offset);
break;

View File

@ -281,6 +281,17 @@ typedef enum NodeGaborType {
NODE_GABOR_TYPE_3D,
} NodeGaborType;
typedef enum NodeGaborFMode {
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,
} NodeGaborFMode;
typedef enum NodeWaveType { NODE_WAVE_BANDS, NODE_WAVE_RINGS } NodeWaveType;
typedef enum NodeWaveBandsDirection {

View File

@ -1299,6 +1299,179 @@ void GaborTextureNode::compile(OSLCompiler &compiler)
compiler.add(this, "node_gabor_texture");
}
/* GaborF Texture */
NODE_DEFINE(GaborFTextureNode)
{
NodeType *type = NodeType::add("gaborf_texture", create, NodeType::SHADER);
TEXTURE_MAPPING_DEFINE(GaborFTextureNode);
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;
}
GaborFTextureNode::GaborFTextureNode() : TextureNode(node_type) {}
void GaborFTextureNode::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 GaborFTextureNode::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_GABORF,
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 GaborFTextureNode::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_gaborf_texture");
}
/* Voronoi Texture */
NODE_DEFINE(VoronoiTextureNode)

View File

@ -254,6 +254,38 @@ class GaborTextureNode : public TextureNode {
NODE_SOCKET_API(float3, orientation_3d)
};
class GaborFTextureNode : public TextureNode {
public:
SHADER_NODE_CLASS(GaborFTextureNode)
void constant_fold(const ConstantFolder &folder);
NODE_SOCKET_API(int, dimensions)
NODE_SOCKET_API(NodeGaborFMode, 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

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

@ -295,6 +295,7 @@ class NODE_MT_category_shader_texture(Menu):
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, "ShaderNodeTexGaborF")
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

@ -997,6 +997,7 @@ void BKE_nodetree_remove_layer_n(bNodeTree *ntree, Scene *scene, int layer_index
#define SH_NODE_MIX 713
#define SH_NODE_BSDF_RAY_PORTAL 714
#define SH_NODE_TEX_GABOR 715
#define SH_NODE_TEX_GABORF 716
/** \} */

View File

@ -596,6 +596,7 @@ set(GLSL_SRC
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_gaborf.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,472 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma BLENDER_REQUIRE(gpu_shader_common_hash.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_base_lib.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_math_vector_lib.glsl)
/* Gabor Noise
*
* Based on: Blender patch D287 & D3495
* With additional controls for kernel variance.
*
* This implementation is adapted from the original built-in OSL implementation based on the 2009
* paper "Procedural noise using sparse Gabor convolution". Some parts are also adapted from the
* 2011 paper "Filtering Solid Gabor Noise" but this does not include the filtering and slicing.
* References to the papers are for copyright and reference.
*
* Notes and changes from the original OSL implementation and reference papers:
* - For 2D noise, as with Voronoi the calculation uses a 3x3x1 grid rather than slicing 3D noise.
* This is more performant when only 2D texture is required.
* - For artistic control, calculations for Bandwidth have been simplified and replaced with
* separate controls for Frequency, Gain and Radius. This provides finer control where
*