Open frohnej-adsk opened 3 months ago
Adding more context using a different sample. The root cause for these issue is likely to be the same.
Video explanation: https://github.com/user-attachments/assets/98209cbf-e043-4ce7-b464-6fe6bbfcbd23
Sample used in the video: test_bump_procedural.zip
There is a bug in the GLSL shader generation, which causes
ND_bump_vector3
to do nothing.Minimal example
The noise should produce significant bumps, but the normals remain unchanged:
bump_example.mtlx (click to expand)
```XMLImplementation of ND_bump_vector3
ND_bump_vector3
is implemented using a functional node graph consisting of aheighttonormal
and anormalmap
node:NG_bump_vector3 (click to expand)
```XMLUsing heighttonormal directly
Using
heighttonormal
andnormalmap
directly produces the expected results:heighttonormal_example.mtlx (click to expand)
```XMLSo, even though the individual nodes that make up
NG_bump_vector3
work correctly, thebump
node does not. The cause for this becomes visible when looking at the generated GLSL code.Generated GLSL code
To compute a normal from a bump map, the shader calls
mx_normal_from_samples_sobel()
, which requires nine samples of the bump map. However, when using thebump
node, the glsl shader actually samples the bump map only once and passes the same value nine times:GLSL code generated from bump_example.mtlx (click to expand)
```glsl /* ... */ void NG_bump_vector3(float height, float scale, vec3 normal, vec3 tangent, out vec3 out1) { float N_heighttonormal_out_samples[9]; N_heighttonormal_out_samples[0] = height; N_heighttonormal_out_samples[1] = height; N_heighttonormal_out_samples[2] = height; N_heighttonormal_out_samples[3] = height; N_heighttonormal_out_samples[4] = height; N_heighttonormal_out_samples[5] = height; N_heighttonormal_out_samples[6] = height; N_heighttonormal_out_samples[7] = height; N_heighttonormal_out_samples[8] = height; vec3 N_heighttonormal_out = mx_normal_from_samples_sobel(N_heighttonormal_out_samples, 1.000000); vec3 N_normalmap_out = vec3(0.0); mx_normalmap_float(N_heighttonormal_out, 0, scale, normal, tangent, N_normalmap_out); out1 = N_normalmap_out; } /* ... */ void main() { vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); vec2 geomprop_UV0_out1 = vd.texcoord_0.xy; // Sample the bump map once. float unifiednoise2d_float_out = 0.0; NG_unifiednoise2d_float(geomprop_UV0_out1, unifiednoise2d_float_freq, unifiednoise2d_float_offset, unifiednoise2d_float_jitter, unifiednoise2d_float_outmin, unifiednoise2d_float_outmax, unifiednoise2d_float_clampoutput, unifiednoise2d_float_octaves, unifiednoise2d_float_lacunarity, unifiednoise2d_float_diminish, unifiednoise2d_float_type, unifiednoise2d_float_out); // Compute the normal from a single sample. vec3 bump_vector3_out = vec3(0.0); NG_bump_vector3(unifiednoise2d_float_out, bump_vector3_scale, geomprop_Nworld_out1, geomprop_Tworld_out1, bump_vector3_out); surfaceshader convert_vector3_surfaceshader_out = surfaceshader(vec3(0.0),vec3(0.0)); NG_convert_vector3_surfaceshader(bump_vector3_out, convert_vector3_surfaceshader_out); material surfacematerial_out = convert_vector3_surfaceshader_out; out1 = vec4(surfacematerial_out.color, 1.0); } ```When using the
heighttonormal
node directly, the generated shader correctly takes nine separate samples and, thus, works as expected.GLSL code generated from heighttonormal_example.mtlx (click to expand)
```glsl /* ... */ void main() { vec3 geomprop_Nworld_out1 = normalize(vd.normalWorld); vec3 geomprop_Tworld_out1 = normalize(vd.tangentWorld); vec2 geomprop_UV0_out1 = vd.texcoord_0.xy; float unifiednoise2d_float_out = 0.0; NG_unifiednoise2d_float(geomprop_UV0_out1, unifiednoise2d_float_freq, unifiednoise2d_float_offset, unifiednoise2d_float_jitter, unifiednoise2d_float_outmin, unifiednoise2d_float_outmax, unifiednoise2d_float_clampoutput, unifiednoise2d_float_octaves, unifiednoise2d_float_lacunarity, unifiednoise2d_float_diminish, unifiednoise2d_float_type, unifiednoise2d_float_out); vec2 heighttonormal_vector3_out_sample_size = mx_compute_sample_size_uv(geomprop_UV0_out1,1.000000,0.000000); // Sample the bump map nine times. float unifiednoise2d_float_out_heighttonormal_vector3_out0 = 0.0; NG_unifiednoise2d_float(geomprop_UV0_out1 + heighttonormal_vector3_out_sample_size * vec2(-1.000000,-1.000000), unifiednoise2d_float_freq, unifiednoise2d_float_offset, unifiednoise2d_float_jitter, unifiednoise2d_float_outmin, unifiednoise2d_float_outmax, unifiednoise2d_float_clampoutput, unifiednoise2d_float_octaves, unifiednoise2d_float_lacunarity, unifiednoise2d_float_diminish, unifiednoise2d_float_type, unifiednoise2d_float_out_heighttonormal_vector3_out0); float unifiednoise2d_float_out_heighttonormal_vector3_out1 = 0.0; NG_unifiednoise2d_float(geomprop_UV0_out1 + heighttonormal_vector3_out_sample_size * vec2(0.000000,-1.000000), unifiednoise2d_float_freq, unifiednoise2d_float_offset, unifiednoise2d_float_jitter, unifiednoise2d_float_outmin, unifiednoise2d_float_outmax, unifiednoise2d_float_clampoutput, unifiednoise2d_float_octaves, unifiednoise2d_float_lacunarity, unifiednoise2d_float_diminish, unifiednoise2d_float_type, unifiednoise2d_float_out_heighttonormal_vector3_out1); float unifiednoise2d_float_out_heighttonormal_vector3_out2 = 0.0; NG_unifiednoise2d_float(geomprop_UV0_out1 + heighttonormal_vector3_out_sample_size * vec2(1.000000,-1.000000), unifiednoise2d_float_freq, unifiednoise2d_float_offset, unifiednoise2d_float_jitter, unifiednoise2d_float_outmin, unifiednoise2d_float_outmax, unifiednoise2d_float_clampoutput, unifiednoise2d_float_octaves, unifiednoise2d_float_lacunarity, unifiednoise2d_float_diminish, unifiednoise2d_float_type, unifiednoise2d_float_out_heighttonormal_vector3_out2); float unifiednoise2d_float_out_heighttonormal_vector3_out3 = 0.0; NG_unifiednoise2d_float(geomprop_UV0_out1 + heighttonormal_vector3_out_sample_size * vec2(-1.000000,0.000000), unifiednoise2d_float_freq, unifiednoise2d_float_offset, unifiednoise2d_float_jitter, unifiednoise2d_float_outmin, unifiednoise2d_float_outmax, unifiednoise2d_float_clampoutput, unifiednoise2d_float_octaves, unifiednoise2d_float_lacunarity, unifiednoise2d_float_diminish, unifiednoise2d_float_type, unifiednoise2d_float_out_heighttonormal_vector3_out3); float unifiednoise2d_float_out_heighttonormal_vector3_out4 = 0.0; NG_unifiednoise2d_float(geomprop_UV0_out1 + heighttonormal_vector3_out_sample_size * vec2(0.000000,0.000000), unifiednoise2d_float_freq, unifiednoise2d_float_offset, unifiednoise2d_float_jitter, unifiednoise2d_float_outmin, unifiednoise2d_float_outmax, unifiednoise2d_float_clampoutput, unifiednoise2d_float_octaves, unifiednoise2d_float_lacunarity, unifiednoise2d_float_diminish, unifiednoise2d_float_type, unifiednoise2d_float_out_heighttonormal_vector3_out4); float unifiednoise2d_float_out_heighttonormal_vector3_out5 = 0.0; NG_unifiednoise2d_float(geomprop_UV0_out1 + heighttonormal_vector3_out_sample_size * vec2(1.000000,0.000000), unifiednoise2d_float_freq, unifiednoise2d_float_offset, unifiednoise2d_float_jitter, unifiednoise2d_float_outmin, unifiednoise2d_float_outmax, unifiednoise2d_float_clampoutput, unifiednoise2d_float_octaves, unifiednoise2d_float_lacunarity, unifiednoise2d_float_diminish, unifiednoise2d_float_type, unifiednoise2d_float_out_heighttonormal_vector3_out5); float unifiednoise2d_float_out_heighttonormal_vector3_out6 = 0.0; NG_unifiednoise2d_float(geomprop_UV0_out1 + heighttonormal_vector3_out_sample_size * vec2(-1.000000,1.000000), unifiednoise2d_float_freq, unifiednoise2d_float_offset, unifiednoise2d_float_jitter, unifiednoise2d_float_outmin, unifiednoise2d_float_outmax, unifiednoise2d_float_clampoutput, unifiednoise2d_float_octaves, unifiednoise2d_float_lacunarity, unifiednoise2d_float_diminish, unifiednoise2d_float_type, unifiednoise2d_float_out_heighttonormal_vector3_out6); float unifiednoise2d_float_out_heighttonormal_vector3_out7 = 0.0; NG_unifiednoise2d_float(geomprop_UV0_out1 + heighttonormal_vector3_out_sample_size * vec2(0.000000,1.000000), unifiednoise2d_float_freq, unifiednoise2d_float_offset, unifiednoise2d_float_jitter, unifiednoise2d_float_outmin, unifiednoise2d_float_outmax, unifiednoise2d_float_clampoutput, unifiednoise2d_float_octaves, unifiednoise2d_float_lacunarity, unifiednoise2d_float_diminish, unifiednoise2d_float_type, unifiednoise2d_float_out_heighttonormal_vector3_out7); float unifiednoise2d_float_out_heighttonormal_vector3_out8 = 0.0; NG_unifiednoise2d_float(geomprop_UV0_out1 + heighttonormal_vector3_out_sample_size * vec2(1.000000,1.000000), unifiednoise2d_float_freq, unifiednoise2d_float_offset, unifiednoise2d_float_jitter, unifiednoise2d_float_outmin, unifiednoise2d_float_outmax, unifiednoise2d_float_clampoutput, unifiednoise2d_float_octaves, unifiednoise2d_float_lacunarity, unifiednoise2d_float_diminish, unifiednoise2d_float_type, unifiednoise2d_float_out_heighttonormal_vector3_out8); // Compute the normal from the nine samples. float heighttonormal_vector3_out_samples[9]; heighttonormal_vector3_out_samples[0] = unifiednoise2d_float_out_heighttonormal_vector3_out0; heighttonormal_vector3_out_samples[1] = unifiednoise2d_float_out_heighttonormal_vector3_out1; heighttonormal_vector3_out_samples[2] = unifiednoise2d_float_out_heighttonormal_vector3_out2; heighttonormal_vector3_out_samples[3] = unifiednoise2d_float_out_heighttonormal_vector3_out3; heighttonormal_vector3_out_samples[4] = unifiednoise2d_float_out_heighttonormal_vector3_out4; heighttonormal_vector3_out_samples[5] = unifiednoise2d_float_out_heighttonormal_vector3_out5; heighttonormal_vector3_out_samples[6] = unifiednoise2d_float_out_heighttonormal_vector3_out6; heighttonormal_vector3_out_samples[7] = unifiednoise2d_float_out_heighttonormal_vector3_out7; heighttonormal_vector3_out_samples[8] = unifiednoise2d_float_out_heighttonormal_vector3_out8; vec3 heighttonormal_vector3_out = mx_normal_from_samples_sobel(heighttonormal_vector3_out_samples, heighttonormal_vector3_scale); vec3 normalmap_out = vec3(0.0); mx_normalmap_float(heighttonormal_vector3_out, normalmap_space, normalmap_scale, geomprop_Nworld_out1, geomprop_Tworld_out1, normalmap_out); surfaceshader convert_vector3_surfaceshader_out = surfaceshader(vec3(0.0),vec3(0.0)); NG_convert_vector3_surfaceshader(normalmap_out, convert_vector3_surfaceshader_out); material surfacematerial_out = convert_vector3_surfaceshader_out; out1 = vec4(surfacematerial_out.color, 1.0); } ```Root of the problem
When using
heighttonormal
in a functional node graph, the shader generation creates an enclosing functionNG_bump_vector3()
, which doesn't know that it needs nine height samples to work correctly. In this case, ConvolutionNode::emitInputSamplesUV() doesn't find an upstream node that can be sampled.upstreamNode->hasClassification(ShaderNode::Classification::SAMPLE2D)
returnsfalse
and the same input is used for all samples.When using
heighttonormal
directly, the upstream node can be sampled, and everything works as expected.To fix this, the sampling requirement probably needs to be back-propagated further down the connection chain, and across function boundaries, which doesn't sound trivial.