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.
8 changed files with 503 additions and 419 deletions
Showing only changes of commit fec2f071d3 - Show all commits

View File

@ -27,11 +27,6 @@
struct GaborParams {
float base_frequency;
float octaves;
float roughness;
float scl_lacunarity;
float fre_lacunarity;
float rot_lacunarity;
float bandwidth;
float radius;
float impulses;
@ -45,6 +40,14 @@ struct GaborParams {
vector direction;
};
struct FractalParams {
float octaves;
float roughness;
float scl_lacunarity;
float fre_lacunarity;
float rot_lacunarity;
};
int impulses_per_cell(int seed, vector3 cell, float impulses)
{
int n = int(impulses);
@ -59,51 +62,72 @@ int impulses_per_cell(int seed, vector3 cell, float impulses)
return n;
}
vector3 gabor_kernel(GaborParams gp, float freq, point omega, float phi, point position, float g)
vector3 gabor_kernel(GaborParams gp, point omega, float phi, point position, float dv)
{
float g = (1.0 + M_EPI) * (exp(-M_PI * dv) - M_EPI);
vector3 r = vector3(0.0);
float h;
if (gp.mode == "phasor") { /* SHD_GABOR_MODE_PHASOR */
float phase = freq * dot(omega, position) + phi;
return vector3(cos(phase), sin(phase), 0.0) * g;
if (gp.mode == "gabor") { /* SHD_GABOR_MODE_GABOR */
h = gp.base_frequency * dot(omega, position) + phi;
r = vector3(cos(h), 0.0, 0.0);
}
else if (gp.mode == "phasor") { /* SHD_GABOR_MODE_PHASOR */
h = gp.base_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.base_frequency * length(omega * position) + phi;
r = vector3(cos(h), 0.0, 0.0);
}
else if (gp.mode == "phasor_cross") { /* SHD_GABOR_MODE_PHASOR_CROSS */
float phase = freq * length(position * omega) + phi;
return g * vector3(cos(phase), sin(phase), 0.0);
h = gp.base_frequency * length(omega * position) + phi;
r = vector3(cos(h), sin(h), 0.0);
}
else if (gp.mode == "square") { /* SHD_GABOR_MODE_SQUARE */
vector3 positionyx = vector3(position.y, position.x, position.z);
h = (cos(freq * dot(omega, position) + phi) + cos(freq * dot(omega, positionyx) + phi)) * 0.5;
else if (gp.mode == "gabor_ring") { /* SHD_GABOR_MODE_RING */
h = cos(gp.base_frequency * dot(omega, position) + phi) +
cos(gp.base_frequency * length(position) + phi);
r = vector3(h, 0.0, 0.0) * 0.5;
;
}
else if (gp.mode == "cross") { /* SHD_GABOR_MODE_CROSS */
h = cos(freq * length(position * omega) + phi);
else if (gp.mode == "phasor_ring") { /* SHD_GABOR_MODE_PHASOR_RING */
h = cos(gp.base_frequency * dot(omega, position) + phi) +
cos(gp.base_frequency * length(position) + phi);
float h2 = sin(gp.base_frequency * dot(omega, position) + phi) +
sin(gp.base_frequency * length(position) + phi);
r = vector3(h, h2, 0.0) * 0.5;
}
else if (gp.mode == "ring" /* SHD_GABOR_MODE_RING */) {
h = cos(freq * dot(position, omega) + phi) - cos(freq * length(position) + phi);
else if (gp.mode == "gabor_square") { /* SHD_GABOR_MODE_SQUARE */
vector3 positionyxz = vector3(position.y, position.x, position.z);
h = cos(gp.base_frequency * dot(omega, position) + phi) +
cos(gp.base_frequency * dot(omega, positionyxz) + phi);
r = vector3(h, 0.0, 0.0) * 0.5;
}
else {
h = cos(freq * dot(omega, position) + phi);
else if (gp.mode == "phasor_square") {
vector3 positionyxz = vector3(position.y, position.x, position.z);
h = cos(gp.base_frequency * dot(omega, position) + phi) +
cos(gp.base_frequency * dot(omega, positionyxz) + phi);
float h2 = sin(gp.base_frequency * dot(omega, position) + phi) +
sin(gp.base_frequency * dot(omega, positionyxz) + phi);
r = vector3(h, h2, 0.0) * 0.5;
}
return vector3(h) * g;
return r * g;
}
void gabor_sample(GaborParams gp,
float orand,
float orand2,
output float prand,
output vector omega,
output float phi)
void gabor_sample(GaborParams gp, vector3 cell, int seed, output vector omega, output float phi)
{
float pvar = mix(0.0, prand * 2.0 - 1.0, gp.phase_variance);
vector3 rand_values = hash_vector4_to_color(vector4(cell[0], cell[1], cell[2], float(seed)));
float pvar = mix(0.0, rand_values.z * 2.0 - 1.0, gp.phase_variance);
phi = M_2PI * pvar + gp.phase;
if (gp.anisotropic == "aniso") { /* ANISO */
omega = gp.direction;
}
else { /* ISO */
float ovar = M_PI * (orand * 2.0 - 1.0) * gp.rot_variance;
float ovar = M_PI * (rand_values.x * 2.0 - 1.0) * gp.rot_variance;
float omega_t = ovar - gp.rotation;
float cos_omega_p = 1.0 - 2.0 * orand2;
float cos_omega_p = 1.0 - 2.0 * rand_values.y;
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);
@ -117,12 +141,10 @@ vector3 gabor_cell_3d(output GaborParams gp, point cell, point cell_position, in
vector3 sum = vector3(0.0);
for (int i = 0; i < num_impulses; ++i) {
vector3 rand_position = vector3(0.0);
if (gp.cell_randomness > 0.0) {
rand_position = mix(
rand_position,
hash_vector4_to_color(vector4(cell[0], cell[1], cell[2], float(seed + i * 1259))),
gp.cell_randomness);
}
rand_position = mix(
rand_position,
hash_vector4_to_color(vector4(cell[0], cell[1], cell[2], float(seed + i * 1259))),
gp.cell_randomness);
point kernel_position = (cell_position - rand_position);
@ -131,12 +153,9 @@ vector3 gabor_cell_3d(output GaborParams gp, point cell, point cell_position, in
if (dv <= 1.0) {
vector3 omega;
float phi;
vector3 rand_values = hash_vector4_to_color(
vector4(cell[0], cell[1], cell[2], float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, rand_values[0], rand_values[1], rand_values[2], omega, phi);
float g = (1.0 + M_EPI) * (exp(-M_PI * dv) - M_EPI);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g);
gabor_sample(gp, cell, seed + (num_impulses + i) * 1259, omega, phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
@ -148,13 +167,11 @@ vector3 gabor_cell_2d(output GaborParams gp, point cell, point cell_position, in
vector3 sum = vector3(0.0);
for (int i = 0; i < num_impulses; ++i) {
vector3 rand_position = vector3(0.0);
if (gp.cell_randomness > 0.0) {
rand_position = mix(
rand_position,
hash_vector4_to_color(vector4(cell[0], cell[1], cell[2], float(seed + i * 1259))),
gp.cell_randomness);
rand_position.z = 0.0;
}
rand_position = mix(
rand_position,
hash_vector4_to_color(vector4(cell[0], cell[1], cell[2], float(seed + i * 1259))),
gp.cell_randomness);
rand_position.z = 0.0;
point kernel_position = (cell_position - rand_position);
@ -163,12 +180,9 @@ vector3 gabor_cell_2d(output GaborParams gp, point cell, point cell_position, in
if (dv <= 1.0) {
vector3 omega;
float phi;
vector3 rand_values = hash_vector4_to_color(
vector4(cell[0], cell[1], cell[2], float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, rand_values[0], rand_values[1], rand_values[2], omega, phi);
float g = (1.0 + M_EPI) * (exp(-M_PI * dv) - M_EPI);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g);
gabor_sample(gp, cell, seed + (num_impulses + i) * 1259, omega, phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
@ -210,7 +224,9 @@ float gabor_grid_3d(output GaborParams gp, point p, float scale, int seed, int p
}
}
if (gp.mode == "phasor" || gp.mode == "phasor_cross") {
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;
}
@ -247,7 +263,9 @@ float gabor_grid_2d(output GaborParams gp, point p, float scale, int seed, int p
}
}
if (gp.mode == "phasor" || gp.mode == "phasor_cross") {
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;
}
@ -258,11 +276,6 @@ float gabor_grid_2d(output GaborParams gp, point p, float scale, int seed, int p
GaborParams gabor_parameters(vector direction,
float base_frequency,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float bandwidth,
float radius,
float impulses,
@ -276,13 +289,8 @@ GaborParams gabor_parameters(vector direction,
int periodic)
{
GaborParams gp;
gp.roughness = clamp(roughness, 0.0, 1.0);
gp.octaves = clamp(detail, 0.0, 15.0);
gp.impulses = clamp(impulses, 0.0001, 32.0);
gp.rot_variance = rot_variance;
gp.scl_lacunarity = scl_lacunarity;
gp.fre_lacunarity = fre_lacunarity;
gp.rot_lacunarity = rot_lacunarity;
gp.anisotropic = anisotropic;
gp.mode = mode;
gp.direction = direction;
@ -296,37 +304,41 @@ GaborParams gabor_parameters(vector direction,
return gp;
}
float gabor_fractal_noise(
output GaborParams gp, vector3 p, float scale, string dimensions, int periodic)
float gabor_fractal_noise(FractalParams fp,
output GaborParams gp,
vector3 p,
float scale,
string dimensions,
int periodic)
{
float fscale = 1.0;
float amp = 1.0;
float maxamp = 0.0;
float sum = 0.0;
float octaves = gp.octaves;
if (gp.roughness == 0.0) {
float octaves = fp.octaves;
if (fp.roughness == 0.0) {
octaves = 0.0;
}
int n = int(octaves);
for (int i = 0; i <= n; i++) {
float t = (dimensions == "3D") ? gabor_grid_3d(gp, fscale * p, scale, i, periodic) :
gabor_grid_2d(gp, fscale * p, scale, i, periodic);
gp.base_frequency *= gp.fre_lacunarity;
gp.rotation -= gp.rot_lacunarity;
gp.base_frequency *= fp.fre_lacunarity;
gp.rotation -= fp.rot_lacunarity;
sum += t * amp;
maxamp += amp;
amp *= gp.roughness;
fscale *= gp.scl_lacunarity;
amp *= fp.roughness;
fscale *= fp.scl_lacunarity;
}
float rmd = octaves - floor(octaves);
if (rmd != 0.0) {
float t = (dimensions == "3D") ? gabor_grid_3d(gp, fscale * p, scale, n + 1, periodic) :
gabor_grid_2d(gp, fscale * p, scale, n + 1, periodic);
float sum2 = sum + t * amp;
return mix(sum, sum2, rmd) / maxamp * gp.bandwidth;
return mix(sum, sum2, rmd) * gp.bandwidth / maxamp;
}
else {
return sum / maxamp * gp.bandwidth;
return sum * gp.bandwidth / maxamp;
}
}
@ -361,32 +373,40 @@ float gabor_noise(point p,
}
return 0.0;
}
GaborParams gp;
gp = gabor_parameters(direction,
base_frequency,
detail,
roughness,
scl_lacunarity,
fre_lacunarity,
rot_lacunarity,
bandwidth,
radius,
impulses,
phase,
phase_variance,
cell_randomness,
rotation,
rot_variance,
mode,
anisotropic,
use_normalize);
float g = gabor_fractal_noise(gp, p, scale, dimensions, periodic);
float impulse_scale = 1.0; // sqrt(0.75 / pi) = 1.26
if (impulses < 1.0) {
impulse_scale = 1.2613446229 * sqrt(gp.impulses);
FractalParams fp;
fp.roughness = clamp(roughness, 0.0, 1.0);
fp.octaves = clamp(detail, 0.0, 15.0);
fp.scl_lacunarity = scl_lacunarity;
fp.fre_lacunarity = fre_lacunarity;
fp.rot_lacunarity = rot_lacunarity;
GaborParams gp = gabor_parameters(direction,
base_frequency,
bandwidth,
radius,
impulses,
phase,
phase_variance,
cell_randomness,
rotation,
rot_variance,
mode,
anisotropic,
use_normalize);
float g = gabor_fractal_noise(fp, gp, p, scale, dimensions, periodic);
if (gp.mode == "gabor" || gp.mode == "gabor_ring" || gp.mode == "gabor_cross" ||
gp.mode == "gabor_square")
{
float impulse_scale = M_SQRT2;
if (impulses > 1.0) {
impulse_scale = M_SQRT2 * sqrt(gp.impulses);
}
g = g / impulse_scale;
}
g = g / impulse_scale;
if (use_normalize == 1) {
return clamp(0.5 * g + 0.5, 0.0, 1.0);
}
@ -404,13 +424,13 @@ shader node_gabor_texture(int use_mapping = 0,
int periodic = 0,
vector3 Vector = vector(0, 0, 0),
float Scale = 5.0,
float BaseFrequency = 2.0,
float BaseFrequency = 16.0,
float Detail = 0.0,
float Roughness = 0.5,
float ScaleLacunarity = 2.0,
float FrequencyLacunarity = 2.0,
float RotationLacunarity = 0.0,
float Bandwidth = 2.0,
float Bandwidth = 1.0,
float Radius = 1.0,
float Impulses = 2.0,
float Phase = 0.0,

View File

@ -21,11 +21,6 @@ CCL_NAMESPACE_BEGIN
typedef struct GaborParams {
float base_frequency;
float octaves;
float roughness;
float scl_lacunarity;
float fre_lacunarity;
float rot_lacunarity;
float bandwidth;
float radius;
float impulses;
@ -39,6 +34,14 @@ typedef struct GaborParams {
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(int seed, float3 cell, float impulses)
{
int n = int(impulses);
@ -50,53 +53,72 @@ ccl_device int impulses_per_cell(int seed, float3 cell, float impulses)
return n;
}
ccl_device float3
gabor_kernel(GaborParams gp, float freq, float3 omega, float phi, float3 position, float g)
ccl_device float3 gabor_kernel(GaborParams gp, float3 omega, float phi, float3 position, float dv)
{
float g = (1.0f + M_EPI_F) * (expf(-M_PI_F * dv) - M_EPI_F);
float3 r = zero_float3();
float h;
if (gp.mode == SHD_GABOR_MODE_PHASOR) {
float phase = freq * dot(omega, position) + phi;
return make_float3(cos(phase), sin(phase), 0.0f) * g;
if (gp.mode == SHD_GABOR_MODE_GABOR) {
h = gp.base_frequency * dot(omega, position) + phi;
r = make_float3(cos(h), 0.0f, 0.0f);
}
if (gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) {
float phase = freq * len(position * omega) + phi;
return make_float3(cos(phase), sin(phase), 0.0f) * g;
}
else if (gp.mode == SHD_GABOR_MODE_SQUARE) {
float3 positionyx = make_float3(position.y, position.x, position.z);
h = (cos(freq * dot(omega, position) + phi) + cos(freq * dot(omega, positionyx) + phi)) * 0.5f;
else if (gp.mode == SHD_GABOR_MODE_PHASOR) {
h = gp.base_frequency * dot(omega, position) + phi;
r = make_float3(cos(h), sin(h), 0.0f);
}
else if (gp.mode == SHD_GABOR_MODE_CROSS) {
h = cos(freq * len(position * omega) + phi);
h = gp.base_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.base_frequency * len(omega * position) + phi;
r = make_float3(cos(h), sin(h), 0.0f);
}
else if (gp.mode == SHD_GABOR_MODE_RING) {
h = cos(freq * dot(position, omega) + phi) - cos(freq * len(position) + phi);
h = cos(gp.base_frequency * dot(omega, position) + phi) +
cos(gp.base_frequency * len(position) + phi);
r = make_float3(h, 0.0f, 0.0f) * 0.5f;
}
else { // SHD_GABOR_MODE_GABOR
h = cos(freq * dot(omega, position) + phi);
else if (gp.mode == SHD_GABOR_MODE_PHASOR_RING) {
h = cos(gp.base_frequency * dot(omega, position) + phi) +
cos(gp.base_frequency * len(position) + phi);
const float h2 = sin(gp.base_frequency * dot(omega, position) + phi) +
sin(gp.base_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.base_frequency * dot(omega, position) + phi) +
cos(gp.base_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.base_frequency * dot(omega, position) + phi) +
cos(gp.base_frequency * dot(omega, positionyxz) + phi);
const float h2 = sin(gp.base_frequency * dot(omega, position) + phi) +
sin(gp.base_frequency * dot(omega, positionyxz) + phi);
r = make_float3(h, h2, 0.0f) * 0.5f;
}
return make_float3(h) * g;
return r * g;
}
ccl_device void gabor_sample(GaborParams gp,
float orand,
float orand2,
float prand,
ccl_private float3 *omega,
ccl_private float *phi)
ccl_device void gabor_sample(
GaborParams gp, float3 cell, int seed, ccl_private float3 *omega, ccl_private float *phi)
{
float pvar = mix(0.0f, prand * 2.0f - 1.0f, gp.phase_variance);
float3 rand_values = hash_float4_to_float3(make_float4(cell.x, cell.y, cell.z, float(seed)));
float pvar = mix(0.0f, rand_values.z * 2.0f - 1.0f, gp.phase_variance);
*phi = M_2PI_F * pvar + gp.phase;
if (gp.anisotropic == 1) { /* ANISO */
*omega = gp.direction;
}
else { /* ISO */
float ovar = M_PI_F * (orand * 2.0f - 1.0f);
float ovar = M_PI_F * (rand_values.x * 2.0f - 1.0f);
float omega_t = ovar * gp.rot_variance - gp.rotation;
float cos_omega_p = 1.0f - 2.0f * orand2;
float cos_omega_p = 1.0f - 2.0f * rand_values.y;
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);
@ -111,12 +133,10 @@ ccl_device float3 gabor_cell_3d(GaborParams gp, float3 cell, float3 cell_positio
float3 sum = zero_float3();
for (int i = 0; i < num_impulses; ++i) {
float3 rand_position = zero_float3();
if (gp.cell_randomness > 0.0f) {
rand_position = mix(
rand_position,
hash_float4_to_float3(make_float4(cell.x, cell.y, cell.z, float(seed + i * 1259))),
gp.cell_randomness);
}
rand_position = mix(
rand_position,
hash_float4_to_float3(make_float4(cell.x, cell.y, cell.z, float(seed + i * 1259))),
gp.cell_randomness);
float3 kernel_position = (cell_position - rand_position);
@ -125,12 +145,9 @@ ccl_device float3 gabor_cell_3d(GaborParams gp, float3 cell, float3 cell_positio
if (dv <= 1.0f) {
float3 omega;
float phi;
float3 rand_values = hash_float4_to_float3(
make_float4(cell.x, cell.y, cell.z, float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, rand_values.x, rand_values.y, rand_values.z, &omega, &phi);
float g = (1.0f + M_EPI_F) * (expf(-M_PI_F * dv) - M_EPI_F);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g);
gabor_sample(gp, cell, seed + (num_impulses + i) * 1259, &omega, &phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
@ -142,13 +159,11 @@ ccl_device float3 gabor_cell_2d(GaborParams gp, float3 cell, float3 cell_positio
float3 sum = zero_float3();
for (int i = 0; i < num_impulses; ++i) {
float3 rand_position = zero_float3();
if (gp.cell_randomness > 0.0f) {
rand_position = mix(
rand_position,
hash_float4_to_float3(make_float4(cell.x, cell.y, cell.z, float(seed + i * 1259))),
gp.cell_randomness);
rand_position.z = 0.0f;
}
rand_position = mix(
rand_position,
hash_float4_to_float3(make_float4(cell.x, cell.y, cell.z, float(seed + i * 1259))),
gp.cell_randomness);
rand_position.z = 0.0f;
float3 kernel_position = (cell_position - rand_position);
@ -157,12 +172,9 @@ ccl_device float3 gabor_cell_2d(GaborParams gp, float3 cell, float3 cell_positio
if (dv <= 1.0f) {
float3 omega;
float phi;
float3 rand_values = hash_float4_to_float3(
make_float4(cell.x, cell.y, cell.z, float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, rand_values.x, rand_values.y, rand_values.z, &omega, &phi);
float g = (1.0f + M_EPI_F) * (expf(-M_PI_F * dv) - M_EPI_F);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g);
gabor_sample(gp, cell, seed + (num_impulses + i) * 1259, &omega, &phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
@ -204,7 +216,9 @@ ccl_device float gabor_grid_3d(GaborParams gp, float3 p, float scale, int seed,
}
}
if (gp.mode == SHD_GABOR_MODE_PHASOR || gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) {
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;
}
@ -241,7 +255,9 @@ ccl_device float gabor_grid_2d(GaborParams gp, float3 p, float scale, int seed,
}
}
if (gp.mode == SHD_GABOR_MODE_PHASOR || gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) {
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;
}
@ -251,46 +267,41 @@ ccl_device float gabor_grid_2d(GaborParams gp, float3 p, float scale, int seed,
}
ccl_device float gabor_fractal_noise(
GaborParams gp, float3 p, float scale, int dimensions, int periodic)
FractalParams fp, GaborParams gp, float3 p, float scale, int dimensions, int periodic)
{
float fscale = 1.0f;
float amp = 1.0f;
float maxamp = 0.0f;
float sum = 0.0f;
float octaves = gp.octaves;
if (gp.roughness == 0.0f) {
float octaves = fp.octaves;
if (fp.roughness == 0.0f) {
octaves = 0.0f;
}
int n = int(octaves);
for (int i = 0; i <= n; i++) {
float t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, i, periodic) :
gabor_grid_2d(gp, fscale * p, scale, i, periodic);
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.
gp.base_frequency *= gp.fre_lacunarity;
gp.rotation -= gp.rot_lacunarity;
gp.base_frequency *= fp.fre_lacunarity;
gp.rotation -= fp.rot_lacunarity;
sum += t * amp;
maxamp += amp;
amp *= gp.roughness;
fscale *= gp.scl_lacunarity;
amp *= fp.roughness;
fscale *= fp.scl_lacunarity;
}
float rmd = octaves - floorf(octaves);
if (rmd != 0.0f) {
float t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, n + 1, periodic) :
gabor_grid_2d(gp, fscale * p, scale, n + 1, periodic);
float sum2 = sum + t * amp;
return mix(sum, sum2, rmd) / maxamp * gp.bandwidth;
return mix(sum, sum2, rmd) * gp.bandwidth / maxamp;
}
else {
return sum / maxamp * gp.bandwidth;
return sum * gp.bandwidth / maxamp;
}
}
ccl_device GaborParams gabor_parameters(float3 direction,
float base_frequency,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float bandwidth,
float radius,
float impulses,
@ -303,13 +314,8 @@ ccl_device GaborParams gabor_parameters(float3 direction,
int anisotropic)
{
GaborParams gp;
gp.roughness = clamp(roughness, 0.0f, 1.0f);
gp.octaves = clamp(detail, 0.0f, 15.0f);
gp.impulses = clamp(impulses, 0.0001f, 32.0f);
gp.rot_variance = rot_variance;
gp.scl_lacunarity = scl_lacunarity;
gp.fre_lacunarity = fre_lacunarity;
gp.rot_lacunarity = rot_lacunarity;
gp.anisotropic = anisotropic;
gp.mode = mode;
gp.direction = direction;
@ -351,29 +357,36 @@ ccl_device float gabor_noise(float3 p,
if (impulses == 0.0f || bandwidth == 0.0f || radius == 0.0f || scale == 0.0f) {
return (normalize == 1) ? 0.5f : 0.0f;
}
GaborParams gp;
gp = gabor_parameters(direction,
base_frequency,
detail,
roughness,
scl_lacunarity,
fre_lacunarity,
rot_lacunarity,
bandwidth,
radius,
impulses,
phase,
phase_variance,
cell_randomness,
rotation,
rot_variance,
mode,
anisotropic);
float g = gabor_fractal_noise(gp, p, scale, dimensions, periodic);
float impulse_scale = impulses < 1.0f ? (1.2613446229f * sqrt(gp.impulses)) :
1.0f; // sqrt(0.75 / pi) = 1.26
g = g / impulse_scale;
FractalParams fp;
fp.roughness = clamp(roughness, 0.0f, 1.0f);
fp.octaves = clamp(detail, 0.0f, 15.0f);
fp.scl_lacunarity = scl_lacunarity;
fp.fre_lacunarity = fre_lacunarity;
fp.rot_lacunarity = rot_lacunarity;
GaborParams gp = gabor_parameters(direction,
base_frequency,
bandwidth,
radius,
impulses,
phase,
phase_variance,
cell_randomness,
rotation,
rot_variance,
mode,
anisotropic);
float g = gabor_fractal_noise(fp, gp, p, scale, dimensions, periodic);
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 ? M_SQRT2_F * sqrt(gp.impulses) : M_SQRT2_F;
g = g / impulse_scale;
}
if (normalize == 1) {
return clamp(0.5f * g + 0.5f, 0.0f, 1.0f);
}

View File

@ -415,7 +415,9 @@ typedef enum NodeGaborMode {
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;
typedef enum NodeGaborAnisotropic {

View File

@ -1209,11 +1209,13 @@ NODE_DEFINE(GaborTextureNode)
static NodeEnum mode_enum;
mode_enum.insert("gabor", SHD_GABOR_MODE_GABOR);
mode_enum.insert("ring", SHD_GABOR_MODE_RING);
mode_enum.insert("cross", SHD_GABOR_MODE_CROSS);
mode_enum.insert("square", SHD_GABOR_MODE_SQUARE);
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);
@ -1221,13 +1223,13 @@ NODE_DEFINE(GaborTextureNode)
SOCKET_IN_POINT(vector, "Vector", zero_float3(), SocketType::LINK_TEXTURE_GENERATED);
SOCKET_IN_FLOAT(scale, "Scale", 5.0f);
SOCKET_IN_FLOAT(base_frequency, "Base Frequency", 4.0f);
SOCKET_IN_FLOAT(base_frequency, "Base Frequency", 16.0f);
SOCKET_IN_FLOAT(detail, "Detail", 0.0f);
SOCKET_IN_FLOAT(roughness, "Roughness", 0.5f);
SOCKET_IN_FLOAT(scl_lacunarity, "Scale Lacunarity", 2.0f);
SOCKET_IN_FLOAT(fre_lacunarity, "Frequency Lacunarity", 2.0f);
SOCKET_IN_FLOAT(rot_lacunarity, "Rotation Lacunarity", 0.0f);
SOCKET_IN_FLOAT(bandwidth, "Bandwidth", 2.0f);
SOCKET_IN_FLOAT(bandwidth, "Bandwidth", 1.0f);
SOCKET_IN_FLOAT(radius, "Radius", 1.0f);
SOCKET_IN_FLOAT(impulses, "Impulses", 2.0f);
SOCKET_IN_FLOAT(phase, "Phase", 0.0f);

View File

@ -50,15 +50,12 @@
#define SHD_GABOR_MODE_CROSS 2
#define SHD_GABOR_MODE_SQUARE 3
#define SHD_GABOR_MODE_PHASOR 4
#define SHD_GABOR_MODE_PHASOR_CROSS 5
#define SHD_GABOR_MODE_PHASOR_RING 5
#define SHD_GABOR_MODE_PHASOR_CROSS 6
#define SHD_GABOR_MODE_PHASOR_SQUARE 7
struct GaborParams {
float base_frequency;
float octaves;
float roughness;
float scl_lacunarity;
float fre_lacunarity;
float rot_lacunarity;
float bandwidth;
float radius;
float impulses;
@ -72,6 +69,14 @@ struct GaborParams {
vec3 direction;
};
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.
* Uniform distribution is faster than running a poisson for calculating impulses for remaining
* fractional part. */
@ -86,42 +91,65 @@ int impulses_per_cell(int seed, vec3 cell, float impulses)
return n;
}
/* Calculates the kernel shape that is multiplied by the gaussian envelope. */
vec3 gabor_kernel(GaborParams gp, float freq, vec3 omega, float phi, vec3 position, float g)
/* 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
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.
* all cases. */
vec3 gabor_kernel(GaborParams gp, vec3 omega, float phi, vec3 position, float dv)
{
float g = (1.0 + M_EPI) * (exp(-M_PI * dv) - M_EPI);
vec3 r = vec3(0.0);
float h;
if (gp.mode == SHD_GABOR_MODE_PHASOR) {
float phase = freq * dot(omega, position) + phi;
return g * vec3(cos(phase), sin(phase), 0.0);
if (gp.mode == SHD_GABOR_MODE_GABOR) {
h = gp.base_frequency * dot(omega, position) + phi;
r = vec3(cos(h), 0.0, 0.0);
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) {
float phase = freq * length(position * omega) + phi;
return g * vec3(cos(phase), sin(phase), 0.0);
}
else if (gp.mode == SHD_GABOR_MODE_SQUARE) {
h = (cos(freq * dot(omega, position) + phi) + cos(freq * dot(omega, position.yxz) + phi)) *
0.5;
else if (gp.mode == SHD_GABOR_MODE_PHASOR) {
h = gp.base_frequency * dot(omega, position) + phi;
r = vec3(cos(h), sin(h), 0.0);
}
else if (gp.mode == SHD_GABOR_MODE_CROSS) {
h = cos(freq * length(position * omega) + phi);
h = gp.base_frequency * length(omega * position) + phi;
r = vec3(cos(h), 0.0, 0.0);
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.
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) {
h = gp.base_frequency * length(omega * position) + phi;
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.
r = vec3(cos(h), sin(h), 0.0);
}
else if (gp.mode == SHD_GABOR_MODE_RING) {
h = cos(freq * dot(position, omega) + phi) - cos(freq * length(position) + phi);
h = cos(gp.base_frequency * dot(omega, position) + phi) +
cos(gp.base_frequency * length(position) + phi);
r = vec3(h, 0.0, 0.0) * 0.5;
}
else { // SHD_GABOR_MODE_GABOR
h = cos(freq * dot(omega, position) + phi);
else if (gp.mode == SHD_GABOR_MODE_PHASOR_RING) {
h = cos(gp.base_frequency * dot(omega, position) + phi) +
cos(gp.base_frequency * length(position) + phi);
float h2 = sin(gp.base_frequency * dot(omega, position) + phi) +
sin(gp.base_frequency * length(position) + phi);
r = vec3(h, h2, 0.0) * 0.5;
}
else if (gp.mode == SHD_GABOR_MODE_SQUARE) {
h = cos(gp.base_frequency * dot(omega, position) + phi) +
cos(gp.base_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.base_frequency * dot(omega, position) + phi) +
cos(gp.base_frequency * dot(omega, position.yxz) + phi);
float h2 = sin(gp.base_frequency * dot(omega, position) + phi) +
sin(gp.base_frequency * dot(omega, position.yxz) + phi);
r = vec3(h, h2, 0.0) * 0.5;
}
return vec3(g * h);
return r * g;
}
/* Set omega (angular frequency) and phi (phase) for the impulse. Unlike reference papers,
* Anisotropic is not normalised to provide additional artistic control. */
GaborParams gabor_sample(
GaborParams gp, float orand, float orand2, float prand, out vec3 omega, out float phi)
GaborParams gabor_sample(GaborParams gp, vec3 cell, int seed, out vec3 omega, out float phi)
{
float pvar = mix(0.0, prand * 2.0 - 1.0, gp.phase_variance);
vec3 rand_values = hash_vec4_to_vec3(vec4(cell, float(seed)));
float pvar = mix(0.0, rand_values.z * 2.0 - 1.0, gp.phase_variance);
phi = M_2PI * pvar + gp.phase;
/* Anisotropic direction. */
@ -129,16 +157,16 @@ GaborParams gabor_sample(
omega = gp.direction;
}
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.
else { /* Isotropic. */
float ovar = M_PI * (orand * 2.0 - 1.0);
float ovar = M_PI * (rand_values.x * 2.0 - 1.0);
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 = ovar * gp.rot_variance - gp.rotation;
float cos_omega_p = 1.0 - 2.0 * orand2;
float cos_omega_p = 1.0 - 2.0 * rand_values.y;
float sin_omega_p = sqrt(1.0 - cos_omega_p * cos_omega_p);
float sin_omega_t = sin(omega_t);
float cos_omega_t = cos(omega_t);
omega = normalize(vec3(cos_omega_t * sin_omega_p, sin_omega_t * sin_omega_p, cos_omega_p));
}
/* Return GaborParams to workaround gpu_codegen parser warning when return type is set to void :
* Unknown parameter type "GaborParams". */
* Unknown parameter type "GaborParams". Known issue: see 111353 */
return gp;
}
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?
@ -149,11 +177,8 @@ vec3 gabor_cell_3d(GaborParams gp, vec3 cell, vec3 cell_position, int seed)
vec3 sum = vec3(0.0);
for (int i = 0; i < num_impulses; ++i) {
vec3 rand_position = vec3(0.0);
if (gp.cell_randomness > 0.0) {
rand_position = mix(rand_position,
hash_vec4_to_vec3(vec4(cell, float(seed + i * 1259))),
gp.cell_randomness);
}
rand_position = mix(
rand_position, hash_vec4_to_vec3(vec4(cell, float(seed + i * 1259))), 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(...)`.
@ -162,11 +187,9 @@ vec3 gabor_cell_3d(GaborParams gp, vec3 cell, vec3 cell_position, int seed)
if (dv <= 1.0) {
vec3 omega;
float phi;
vec3 rand_values = hash_vec4_to_vec3(vec4(cell, float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, rand_values.x, rand_values.y, rand_values.z, omega, phi);
const float g = (1.0 + M_EPI) * (exp(-M_PI * dv) - M_EPI);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g);
gabor_sample(gp, cell, seed + (num_impulses + i) * 1259, omega, phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
@ -180,12 +203,9 @@ vec3 gabor_cell_2d(GaborParams gp, vec3 cell, vec3 cell_position, int seed)
vec3 sum = vec3(0.0);
for (int i = 0; i < num_impulses; ++i) {
vec3 rand_position = vec3(0.0);
if (gp.cell_randomness > 0.0) {
rand_position = mix(rand_position,
hash_vec4_to_vec3(vec4(cell, float(seed + i * 1259))),
gp.cell_randomness);
rand_position.z = 0.0;
}
rand_position = mix(
rand_position, hash_vec4_to_vec3(vec4(cell, float(seed + i * 1259))), gp.cell_randomness);
rand_position.z = 0.0;
vec3 kernel_position = (cell_position - rand_position);
@ -194,11 +214,9 @@ vec3 gabor_cell_2d(GaborParams gp, vec3 cell, vec3 cell_position, int seed)
if (dv <= 1.0) {
vec3 omega;
float phi;
vec3 rand_values = hash_vec4_to_vec3(vec4(cell, float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, rand_values.x, rand_values.y, rand_values.z, omega, phi);
const float g = (1.0 + M_EPI) * (exp(-M_PI * dv) - M_EPI);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g);
gabor_sample(gp, cell, seed + (num_impulses + i) * 1259, omega, phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
@ -224,7 +242,7 @@ float gabor_grid_3d(GaborParams gp, vec3 p, float scale, int seed, int periodic)
vec3 cell = position + cell_offset;
vec3 cell_position = local_position - cell_offset;
/* Skip this cell if it's too far away to contribute - Bruemmer.osl */
/* 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;
@ -242,7 +260,9 @@ float gabor_grid_3d(GaborParams gp, vec3 p, float scale, int seed, int periodic)
}
}
if (gp.mode == SHD_GABOR_MODE_PHASOR || gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) {
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;
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.
}
@ -265,7 +285,7 @@ float gabor_grid_2d(GaborParams gp, vec3 p, float scale, int seed, int periodic)
vec3 cell = position + cell_offset;
vec3 cell_position = local_position - cell_offset;
/* Skip this cell if it's too far away to contribute - Bruemmer.osl */
/* 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;
@ -281,7 +301,9 @@ float gabor_grid_2d(GaborParams gp, vec3 p, float scale, int seed, int periodic)
}
}
if (gp.mode == SHD_GABOR_MODE_PHASOR || gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) {
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;
}
@ -293,11 +315,6 @@ float gabor_grid_2d(GaborParams gp, vec3 p, float scale, int seed, int periodic)
/* Gabor parameters. Octaves and impulses are clamped as these directly impact performance. */
GaborParams gabor_parameters(vec3 direction,
float base_frequency,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float bandwidth,
float radius,
float impulses,
@ -310,13 +327,8 @@ GaborParams gabor_parameters(vec3 direction,
int anisotropic)
{
GaborParams gp;
gp.roughness = clamp(roughness, 0.0, 1.0);
gp.octaves = clamp(detail, 0.0, 15.0);
gp.impulses = clamp(impulses, 0.0001, 32.0);
gp.rot_variance = rot_variance;
gp.scl_lacunarity = scl_lacunarity;
gp.fre_lacunarity = fre_lacunarity;
gp.rot_lacunarity = rot_lacunarity;
gp.anisotropic = anisotropic;
gp.mode = mode;
gp.direction = direction;
@ -330,44 +342,45 @@ GaborParams gabor_parameters(vec3 direction,
return gp;
}
/* Layered fractal noise. For each layer, the seed is changed. This ensures that the impulses are
* occur in different places if lacunarity is set to 1. Kernel frequency and rotation have
* lacunarity parameters. Unlike Perlin noise, Gabor has more variables to consider when layering
* the noise. */
float gabor_fractal_noise(GaborParams gp, vec3 p, float scale, int dimensions, int periodic)
/* Layered fractal noise. For each layer, the seed is changed. This ensures that the impulses
* occur in different places if scale lacunarity is set to 1. Kernel frequency and rotation have
* lacunarity parameters in addition to scale lacunarity and roughness. Unlike Perlin noise, Gabor
* has more variables to consider when layering the noise. */
float gabor_fractal_noise(
FractalParams fp, GaborParams gp, vec3 p, float scale, int dimensions, int periodic)
{
float fscale = 1.0;
float amp = 1.0;
float maxamp = 0.0;
float sum = 0.0;
float octaves = gp.octaves;
if (gp.roughness == 0.0) {
float octaves = fp.octaves;
if (fp.roughness == 0.0) {
octaves = 0.0;
}
int n = int(octaves);
for (int i = 0; i <= n; i++) {
float t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, i, periodic) :
gabor_grid_2d(gp, fscale * p, scale, i, periodic);
gp.base_frequency *= gp.fre_lacunarity;
gp.rotation -= gp.rot_lacunarity;
gp.base_frequency *= fp.fre_lacunarity;
gp.rotation -= fp.rot_lacunarity;
sum += t * amp;
maxamp += amp;
amp *= gp.roughness;
fscale *= gp.scl_lacunarity;
amp *= fp.roughness;
fscale *= fp.scl_lacunarity;
}
float rmd = octaves - floor(octaves);
if (rmd != 0.0) {
float t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, n + 1, periodic) :
gabor_grid_2d(gp, fscale * p, scale, n + 1, periodic);
float sum2 = sum + t * amp;
return mix(sum, sum2, rmd) / maxamp * gp.bandwidth;
return mix(sum, sum2, rmd) * gp.bandwidth / maxamp;
}
else {
return sum / maxamp * gp.bandwidth;
return sum * gp.bandwidth / maxamp;
}
}
/* Gabor node. */
/* Gabor texture node. */
void node_tex_gabor(vec3 co,
float scale,
@ -399,32 +412,48 @@ void node_tex_gabor(vec3 co,
return;
}
/* Set Gabor params. */
GaborParams gp;
gp = gabor_parameters(direction,
base_frequency,
detail,
roughness,
scl_lacunarity,
fre_lacunarity,
rot_lacunarity,
bandwidth,
radius,
impulses,
phase,
phase_variance,
cell_randomness,
rotation,
rot_variance,
int(mode),
int(anisotropic));
/* Set Fractal params. */
FractalParams fp;
fp.roughness = clamp(roughness, 0.0, 1.0);
fp.octaves = clamp(detail, 0.0, 15.0);
fp.scl_lacunarity = scl_lacunarity;
fp.fre_lacunarity = fre_lacunarity;
fp.rot_lacunarity = rot_lacunarity;
/* Normalise height of noise by the number of impulses. This is empircal as there is no easy way
* to analytically determine the scaling factor for the sum of impulses. */
float g = gabor_fractal_noise(gp, co, scale, int(dimensions), int(periodic));
float impulse_scale = impulses < 1.0 ? (1.2613446229 * sqrt(gp.impulses)) :
1.0; // sqrt(0.75 / pi) = 1.26
g = g / impulse_scale;
/* Set Gabor params. */
GaborParams gp = gabor_parameters(direction,
base_frequency,
bandwidth,
radius,
impulses,
phase,
phase_variance,
cell_randomness,
rotation,
rot_variance,
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),
int(anisotropic));
float g = gabor_fractal_noise(fp, gp, co, scale, int(dimensions), int(periodic));
/* Scale height of noise by the number of impulses. This is empircal as there is no easy way
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.
* 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 but the previous value is better overall.
* Artists can overcome this limitation by using the bandwidth control to tweak the texture as
* required.
*/
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) {

View File

@ -1997,7 +1997,9 @@ typedef enum NodeGaborMode {
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;
typedef enum NodeGaborAnisotropic {

View File

@ -5285,11 +5285,13 @@ 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, "RING", 0, "Ring", "Gabor ring kernel"},
{SHD_GABOR_MODE_CROSS, "CROSS", 0, "Cross", "Gabor cross kernel"},
{SHD_GABOR_MODE_SQUARE, "SQUARE", 0, "Square", "Gabor square 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},
};

View File

@ -45,9 +45,9 @@ static void node_declare(NodeDeclarationBuilder &b)
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>("Base Frequency")
.min(-32.0f)
.max(32.0f)
.default_value(4.0f)
.min(-100.0f)
.max(100.0f)
.default_value(16.0f)
.description("Frequency for the kernel shape, higher values provides more detail");
b.add_input<decl::Float>("Detail").min(0.0f).max(15.0f).default_value(0.0f).description(
"Number of noise octaves, high values are slower to compute");
@ -57,13 +57,13 @@ static void node_declare(NodeDeclarationBuilder &b)
.default_value(0.5f)
.subtype(PROP_FACTOR);
b.add_input<decl::Float>("Scale Lacunarity")
.min(-100.0f)
.max(100.0f)
.min(-10.0f)
.max(10.0f)
.default_value(2.0f)
.description("The difference between the scale of each consecutive octave");
b.add_input<decl::Float>("Frequency Lacunarity")
.min(-16.0f)
.max(16.0f)
.min(-10.0f)
.max(10.0f)
.default_value(2.0f)
.description("The difference between the kernel frequency of each consecutive octave");
b.add_input<decl::Float>("Rotation Lacunarity")
@ -72,7 +72,7 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_input<decl::Float>("Bandwidth")
.min(0.0f)
.max(32.0f)
.default_value(2.0f)
.default_value(1.0f)
.description("Controls the power of the kernel");
b.add_input<decl::Float>("Radius")
.min(0.0f)
@ -192,11 +192,6 @@ static void node_update(bNodeTree *ntree, bNode *node)
typedef struct GaborParams {
float base_frequency;
float octaves;
float roughness;
float scl_lacunarity;
float fre_lacunarity;
float rot_lacunarity;
float bandwidth;
float radius;
float impulses;
@ -210,6 +205,14 @@ typedef struct GaborParams {
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 int seed, const float3 cell, const float impulses)
{
const int n = int(impulses);
@ -222,59 +225,76 @@ int impulses_per_cell(const int seed, const float3 cell, const float impulses)
}
static float3 gabor_kernel(const GaborParams gp,
const float freq,
const float3 omega,
const float phi,
const float3 position,
const float g)
const float dv)
{
const float g = (1.0f + M_EPI_F) * (math::exp(-float(M_PI) * dv) - M_EPI_F);
float3 r = float3(0.0f);
float h;
if (gp.mode == SHD_GABOR_MODE_PHASOR) {
const float phase = freq * math::dot(omega, position) + phi;
return float3(math::cos(phase), math::sin(phase), 0.0f) * g;
if (gp.mode == SHD_GABOR_MODE_GABOR) {
h = gp.base_frequency * math::dot(omega, position) + phi;
r = float3(math::cos(h), 0.0f, 0.0f);
}
else if (gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) {
const float phase = freq * math::length(position * omega) + phi;
return float3(math::cos(phase), math::sin(phase), 0.0f) * g;
}
else if (gp.mode == SHD_GABOR_MODE_SQUARE) {
const float3 positionyx = float3(position.y, position.x, position.z);
h = (math::cos(freq * math::dot(omega, position) + phi) +
math::cos(freq * math::dot(omega, positionyx) + phi)) *
0.5f;
else if (gp.mode == SHD_GABOR_MODE_PHASOR) {
h = gp.base_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 = math::cos(freq * math::length(position * omega) + phi);
h = gp.base_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.base_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(freq * math::dot(position, omega) + phi) -
math::cos(freq * math::length(position) + phi);
h = math::cos(gp.base_frequency * math::dot(omega, position) + phi) +
math::cos(gp.base_frequency * math::length(position) + phi);
r = float3(h, 0.0f, 0.0f) * 0.5f;
}
else { // SHD_GABOR_MODE_GABOR
h = math::cos(freq * math::dot(omega, position) + phi);
else if (gp.mode == SHD_GABOR_MODE_PHASOR_RING) {
h = math::cos(gp.base_frequency * math::dot(omega, position) + phi) +
math::cos(gp.base_frequency * math::length(position) + phi);
const float h2 = math::sin(gp.base_frequency * math::dot(omega, position) + phi) +
math::sin(gp.base_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.base_frequency * math::dot(omega, position) + phi) +
math::cos(gp.base_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.base_frequency * math::dot(omega, position) + phi) +
math::cos(gp.base_frequency * math::dot(omega, positionyxz) + phi);
const float h2 = math::sin(gp.base_frequency * math::dot(omega, position) + phi) +
math::sin(gp.base_frequency * math::dot(omega, positionyxz) + phi);
r = float3(h, h2, 0.0f) * 0.5f;
;
}
return float3(h) * g;
return r * g;
}
static void gabor_sample(const GaborParams gp,
const float orand,
const float orand2,
const float prand,
float3 &omega,
float &phi)
static void gabor_sample(
const GaborParams gp, const float3 cell, const int seed, float3 &omega, float &phi)
{
const float pvar = math::interpolate(0.0f, prand * 2.0f - 1.0f, gp.phase_variance);
const float3 rand_values = noise::hash_float_to_float3(float4(cell, float(seed)));
const float pvar = math::interpolate(0.0f, rand_values.z * 2.0f - 1.0f, gp.phase_variance);
phi = 2.0f * float(M_PI) * pvar + gp.phase;
if (gp.anisotropic == SHD_GABOR_ANISOTROPIC) { /* ANISO */
omega = gp.direction;
}
else { /* ISO */
const float ovar = float(M_PI) * (orand * 2.0f - 1.0f);
const float ovar = float(M_PI) * (rand_values.x * 2.0f - 1.0f);
const float omega_t = ovar * gp.rot_variance - gp.rotation;
const float cos_omega_p = 1.0f - 2.0f * orand2;
const float cos_omega_p = 1.0f - 2.0f * rand_values.y;
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);
@ -290,12 +310,10 @@ static float3 gabor_cell_3d(GaborParams &gp, float3 cell, float3 cell_position,
float3 sum = float3(0.0f);
for (int i = 0; i < num_impulses; ++i) {
float3 rand_position = float3(0.0f);
if (gp.cell_randomness > 0.0f) {
rand_position = math::interpolate(
rand_position,
noise::hash_float_to_float3(float4(cell, float(seed + i * 1259))),
gp.cell_randomness);
}
rand_position = math::interpolate(
rand_position,
noise::hash_float_to_float3(float4(cell, float(seed + i * 1259))),
gp.cell_randomness);
const float3 kernel_position = (cell_position - rand_position);
@ -304,12 +322,8 @@ static float3 gabor_cell_3d(GaborParams &gp, float3 cell, float3 cell_position,
if (dv <= 1.0f) {
float3 omega;
float phi;
const float3 rand_values = noise::hash_float_to_float3(
float4(cell, float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, rand_values.x, rand_values.y, rand_values.z, omega, phi);
const float g = (1.0f + M_EPI_F) * (math::exp(-float(M_PI) * dv) - M_EPI_F);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g);
gabor_sample(gp, cell, seed + (num_impulses + i) * 1259, omega, phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
@ -321,13 +335,11 @@ static float3 gabor_cell_2d(GaborParams &gp, float3 cell, float3 cell_position,
float3 sum = float3(0.0f);
for (int i = 0; i < num_impulses; ++i) {
float3 rand_position = float3(0.0f);
if (gp.cell_randomness > 0.0f) {
rand_position = math::interpolate(
rand_position,
noise::hash_float_to_float3(float4(cell, float(seed + i * 1259))),
gp.cell_randomness);
rand_position.z = 0.0f;
}
rand_position = math::interpolate(
rand_position,
noise::hash_float_to_float3(float4(cell, float(seed + i * 1259))),
gp.cell_randomness);
rand_position.z = 0.0f;
const float3 kernel_position = (cell_position - rand_position);
@ -336,12 +348,8 @@ static float3 gabor_cell_2d(GaborParams &gp, float3 cell, float3 cell_position,
if (dv <= 1.0f) {
float3 omega;
float phi;
const float3 rand_values = noise::hash_float_to_float3(
float4(cell, float(seed + (num_impulses + i) * 1259)));
gabor_sample(gp, rand_values.x, rand_values.y, rand_values.z, omega, phi);
const float g = (1.0f + M_EPI_F) * (math::exp(-float(M_PI) * dv) - M_EPI_F);
sum += gabor_kernel(gp, gp.base_frequency, omega, phi, kernel_position, g);
gabor_sample(gp, cell, seed + (num_impulses + i) * 1259, omega, phi);
sum += gabor_kernel(gp, omega, phi, kernel_position, dv);
}
}
return sum;
@ -387,7 +395,9 @@ static float gabor_grid_3d(
}
}
if (gp.mode == SHD_GABOR_MODE_PHASOR || gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) {
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;
}
@ -427,7 +437,9 @@ static float gabor_grid_2d(
}
}
if (gp.mode == SHD_GABOR_MODE_PHASOR || gp.mode == SHD_GABOR_MODE_PHASOR_CROSS) {
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;
}
@ -437,48 +449,47 @@ static float gabor_grid_2d(
}
/* Fractal gabor noise. Layered noise with octaves, lacunarity, frequency and roughness control. */
static float gabor_fractal_noise(
GaborParams &gp, const float3 p, const float scale, const int dimensions, const int periodic)
static float gabor_fractal_noise(FractalParams fp,
GaborParams &gp,
const float3 p,
const float scale,
const int dimensions,
const int periodic)
{
float fscale = 1.0f;
float amp = 1.0f;
float maxamp = 0.0f;
float sum = 0.0f;
float octaves = gp.octaves;
if (gp.roughness == 0.0f) {
float octaves = fp.octaves;
if (fp.roughness == 0.0f) {
octaves = 0.0f;
}
const int n = int(octaves);
for (int i = 0; i <= n; i++) {
const float t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, i, periodic) :
gabor_grid_2d(gp, fscale * p, scale, i, periodic);
gp.base_frequency *= gp.fre_lacunarity;
gp.rotation -= gp.rot_lacunarity;
gp.base_frequency *= fp.fre_lacunarity;
gp.rotation -= fp.rot_lacunarity;
sum += t * amp;
maxamp += amp;
amp *= gp.roughness;
fscale *= gp.scl_lacunarity;
amp *= fp.roughness;
fscale *= fp.scl_lacunarity;
}
float rmd = octaves - math::floor(octaves);
if (rmd != 0.0f) {
const float t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, n + 1, periodic) :
gabor_grid_2d(gp, fscale * p, scale, n + 1, periodic);
const float sum2 = sum + t * amp;
return ((1.0f - rmd) * sum + rmd * sum2) / maxamp * gp.bandwidth;
return ((1.0f - rmd) * sum + rmd * sum2) * gp.bandwidth / maxamp;
}
else {
return sum / maxamp * gp.bandwidth;
return sum * gp.bandwidth / maxamp;
}
}
/* Set parameters used by Gabor noise. */
static GaborParams gabor_parameters(float3 direction,
float base_frequency,
float detail,
float roughness,
float scl_lacunarity,
float fre_lacunarity,
float rot_lacunarity,
float bandwidth,
float radius,
float impulses,
@ -491,13 +502,8 @@ static GaborParams gabor_parameters(float3 direction,
int anisotropic)
{
GaborParams gp;
gp.roughness = math::clamp(roughness, 0.0f, 1.0f);
gp.octaves = math::clamp(detail, 0.0f, 15.0f);
gp.impulses = math::clamp(impulses, 0.0001f, 32.0f);
gp.rot_variance = rot_variance;
gp.scl_lacunarity = scl_lacunarity;
gp.fre_lacunarity = fre_lacunarity;
gp.rot_lacunarity = rot_lacunarity;
gp.anisotropic = anisotropic;
gp.mode = mode;
gp.direction = direction;
@ -529,7 +535,7 @@ static float gabor_noise(const float3 p,
const float rotation,
const float rot_variance,
const int dimensions,
const int kernel,
const int mode,
const int anisotropic,
const int use_normalize,
const int periodic)
@ -537,29 +543,37 @@ static float gabor_noise(const float3 p,
if (impulses == 0.0f || bandwidth == 0.0f || radius == 0.0f || scale == 0.0f) {
return (use_normalize) ? 0.5f : 0.0f;
}
GaborParams gp;
gp = gabor_parameters(direction,
base_frequency,
detail,
roughness,
scl_lacunarity,
fre_lacunarity,
rot_lacunarity,
bandwidth,
radius,
impulses,
phase,
phase_variance,
cell_randomness,
rotation,
rot_variance,
kernel,
anisotropic);
float g = gabor_fractal_noise(gp, p, scale, dimensions, periodic);
float impulse_scale = impulses < 1.0f ? (1.2613446229f * math::sqrt(gp.impulses)) :
1.0f; // sqrt(0.75 / pi) = 1.26
g = g / impulse_scale;
FractalParams fp;
fp.roughness = math::clamp(roughness, 0.0f, 1.0f);
fp.octaves = math::clamp(detail, 0.0f, 15.0f);
fp.scl_lacunarity = scl_lacunarity;
fp.fre_lacunarity = fre_lacunarity;
fp.rot_lacunarity = rot_lacunarity;
GaborParams gp = gabor_parameters(direction,
base_frequency,
bandwidth,
radius,
impulses,
phase,
phase_variance,
cell_randomness,
rotation,
rot_variance,
mode,
anisotropic);
float g = gabor_fractal_noise(fp, gp, p, scale, dimensions, periodic);
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 ? float(M_SQRT2) * math::sqrt(gp.impulses) :
float(M_SQRT2);
g = g / impulse_scale;
}
if (use_normalize) {
return math::clamp(0.5f * g + 0.5f, 0.0f, 1.0f);
}