AcademySoftwareFoundation / MaterialX

MaterialX is an open standard for the exchange of rich material and look-development content across applications and renderers.
http://www.materialx.org/
Apache License 2.0
1.82k stars 332 forks source link

Bump node not working with genglsl #1818

Open frohnej-adsk opened 3 months ago

frohnej-adsk commented 3 months ago

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) ```XML ```

bump

Implementation of ND_bump_vector3

ND_bump_vector3 is implemented using a functional node graph consisting of a heighttonormal and a normalmap node:

NG_bump_vector3 (click to expand) ```XML ```

Using heighttonormal directly

Using heighttonormal and normalmap directly produces the expected results:

heighttonormal_example.mtlx (click to expand) ```XML ```

heighttonormal

So, even though the individual nodes that make up NG_bump_vector3 work correctly, the bump 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 the bump 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 function NG_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) returns false 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.

zicher3d commented 1 month 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