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.
4 changed files with 40 additions and 18 deletions
Showing only changes of commit 436b7fd2ae - Show all commits

View File

@ -16,6 +16,7 @@
/* See GLSL implementation for code comments. */
#include "node_hash.h"
#include "node_math.h"
#include "node_noise.h"
#include "stdcycles.h"
#include "vector2.h"
@ -33,6 +34,7 @@ struct GaborParams {
float phase;
float phase_variance;
float rotation;
float init_rotation;
float rot_variance;
float tilt_randomness;
float cell_randomness;
@ -123,14 +125,14 @@ vector gabor_sample(GaborParams gp, vector3 cell, int seed, output float phi)
float pvar = mix(0.0, rand_values.z, gp.phase_variance);
phi = M_2PI * pvar + gp.phase;
float ovar = M_PI * (rand_values.x) * gp.rot_variance;
float omega_t = ovar - gp.rotation;
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(gp.direction),
normalize(transform(euler_to_mat(vector3(0.0, 0.0, -gp.rotation)), gp.direction)),
gp.anisotropy);
}
@ -288,7 +290,8 @@ GaborParams gabor_parameters(vector direction,
gp.mode = mode;
gp.direction = direction;
gp.phase = phase;
gp.rotation = rotation;
gp.rotation = 0.0;
gp.init_rotation = rotation;
gp.phase_variance = phase_variance;
gp.tilt_randomness = tilt_randomness;
gp.cell_randomness = cell_randomness;

View File

@ -27,6 +27,7 @@ typedef struct GaborParams {
float phase;
float phase_variance;
float rotation;
float init_rotation;
float rot_variance;
float tilt_randomness;
float cell_randomness;
@ -112,15 +113,16 @@ ccl_device float3 gabor_sample(GaborParams gp, float3 cell, int seed, ccl_privat
float pvar = mix(0.0f, rand_values.z, gp.phase_variance);
*phi = M_2PI_F * pvar + gp.phase;
float ovar = M_PI_F * (rand_values.x);
float omega_t = ovar * gp.rot_variance - gp.rotation;
float omega_t = M_PI_F * (rand_values.x) * gp.rot_variance - gp.rotation - gp.init_rotation;
float cos_omega_p = clamp(rand_values.y * gp.tilt_randomness, -1.0f, 1.0f);
float sin_omega_p = sqrtf(1.0f - cos_omega_p * cos_omega_p);
float sin_omega_t = sin(omega_t);
float cos_omega_t = cos(omega_t);
Transform rotationTransform = euler_to_transform(make_float3(0.0f, 0.0f, -gp.rotation));
return mix(
normalize(make_float3(cos_omega_t * sin_omega_p, sin_omega_t * sin_omega_p, cos_omega_p)),
normalize(gp.direction),
normalize(transform_direction(&rotationTransform, gp.direction)),
gp.anisotropy);
}
@ -319,7 +321,8 @@ ccl_device GaborParams gabor_parameters(float3 direction,
gp.mode = mode;
gp.direction = direction;
gp.phase = phase;
gp.rotation = rotation;
gp.rotation = 0.0f;
gp.init_rotation = rotation;
gp.phase_variance = phase_variance;
gp.tilt_randomness = tilt_randomness;
gp.cell_randomness = cell_randomness;

View File

@ -1,4 +1,5 @@
#pragma BLENDER_REQUIRE(gpu_shader_common_hash.glsl)
#pragma BLENDER_REQUIRE(gpu_shader_common_math_utils.glsl)
/* Gabor Noise
*
@ -60,6 +61,7 @@ struct GaborParams {
float phase;
float phase_variance;
float rotation;
float init_rotation;
float rot_variance;
float tilt_randomness;
float cell_randomness;
@ -157,8 +159,7 @@ vec3 gabor_sample(GaborParams gp, vec3 cell, int seed, out float phi)
phi = M_2PI * pvar + gp.phase;
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.
/* Isotropic direction. */
float ovar = M_PI * (rand_values.x);
float omega_t = ovar * gp.rot_variance - gp.rotation;
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);
@ -166,7 +167,7 @@ vec3 gabor_sample(GaborParams gp, vec3 cell, int seed, out float phi)
/* Mix between Isotropic and Anisotropic direction. */
return mix(normalize(vec3(cos_omega_t * sin_omega_p, sin_omega_t * sin_omega_p, cos_omega_p)),
normalize(gp.direction),
normalize(euler_to_mat3(vec3(0.0, 0.0, -gp.rotation)) * gp.direction),
gp.anisotropy);
CharlieJolly marked this conversation as resolved Outdated

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

If this is simply only passed to `gabor_sample`, why not call it in `gabor_sample` directly?
}
@ -333,7 +334,8 @@ GaborParams gabor_parameters(vec3 direction,
gp.mode = mode;
gp.direction = direction;
gp.phase = phase;
gp.rotation = rotation;
gp.rotation = 0.0;
gp.init_rotation = rotation;
gp.phase_variance = phase_variance;
gp.tilt_randomness = tilt_randomness;
gp.cell_randomness = cell_randomness;

View File

@ -27,6 +27,8 @@
#include "BKE_texture.h"
#include "BLI_hash.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.hh"
#include "BLI_noise.hh"
@ -197,6 +199,7 @@ typedef struct GaborParams {
float phase;
float phase_variance;
float rotation;
float init_rotation;
float rot_variance;
float tilt_randomness;
float cell_randomness;
@ -280,21 +283,31 @@ static float3 gabor_kernel(const GaborParams gp,
return r * g;
}
static float3 rotate_z(const float3 &vector, float angle)
{
float mat[3][3];
float3 result = vector;
eul_to_mat3(mat, float3(0.0f, 0.0f, angle));
mul_m3_v3(mat, result);
return result;
}
static float3 gabor_sample(const GaborParams gp, const float3 cell, const int seed, float &phi)
{
const float3 rand_values = noise::hash_float_to_float3(float4(cell, float(seed))) * 2.0f - 1.0f;
const float pvar = math::interpolate(0.0f, rand_values.z, gp.phase_variance);
phi = 2.0f * float(M_PI) * pvar + gp.phase;
const float ovar = float(M_PI) * (rand_values.x);
const float omega_t = ovar * gp.rot_variance - gp.rotation;
const float omega_t = float(M_PI) * (rand_values.x) * gp.rot_variance - gp.rotation -
gp.init_rotation;
const float cos_omega_p = math::clamp(rand_values.y * gp.tilt_randomness, -1.0f, 1.0f);
const float sin_omega_p = math::sqrt(1.0f - cos_omega_p * cos_omega_p);
const float sin_omega_t = math::sin(omega_t);
const float cos_omega_t = math::cos(omega_t);
return math::interpolate(
math::normalize(float3(cos_omega_t * sin_omega_p, sin_omega_t * sin_omega_p, cos_omega_p)),
math::normalize(gp.direction),
math::normalize(rotate_z(gp.direction, -gp.rotation)),
gp.anisotropy);
}
@ -458,7 +471,7 @@ static float gabor_fractal_noise(FractalParams fp,
}
const int n = int(octaves);
for (int i = 0; i <= n; i++) {
int seed = use_origin_offset * i * GABOR_SEED;
const int seed = use_origin_offset * i * GABOR_SEED;
const float 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;
@ -470,7 +483,7 @@ static float gabor_fractal_noise(FractalParams fp,
}
float rmd = octaves - math::floor(octaves);
if (rmd != 0.0f) {
int seed = use_origin_offset * (n + 1) * GABOR_SEED;
const int seed = use_origin_offset * (n + 1) * GABOR_SEED;
const float t = (dimensions == 3) ? gabor_grid_3d(gp, fscale * p, scale, periodic, seed) :
gabor_grid_2d(gp, fscale * p, scale, periodic, seed);
const float sum2 = sum + t * amp;
@ -503,7 +516,8 @@ static GaborParams gabor_parameters(float3 direction,
gp.mode = mode;
gp.direction = direction;
gp.phase = phase;
gp.rotation = rotation;
gp.rotation = 0.0f;
gp.init_rotation = rotation;
gp.phase_variance = phase_variance;
gp.tilt_randomness = tilt_randomness;
gp.cell_randomness = cell_randomness;