0

I am trying to implement a weight-based height blending shader. First, I try to find the 4 layers with the highest weight (layer alpha). Then mix from these 4 layers.

Below is my simplified code:

// find the 4 layers with the largest weight.
void find_max_layers(float2 texCoord, out int4 max_indics)
{
    max_indics = int4(-1, -1, -1, -1);
    max_weights = float4(.0001, .0001, .0001, .0001);
    [unroll]
    for (int Layer = 0; Layer < 2; ++Layer) // 2 is for test... 
    {
        const int surfaceIndex = (int)cSurfaceIndex[Layer];
        const float alpha = tLayerWeight.Sample(sLayerWeight, float3(texCoord, Layer)).r;
        if (alpha > max_weights.x) {
            max_indics = int4(surfaceIndex, max_indics.xyz);
            max_weights = float4(alpha, max_weights.xyz);
        }
        else if (alpha > max_weights.y) {
            max_indics.yzw = int3(surfaceIndex, max_indics.yz);
            max_weights.yzw = float3(alpha, max_weights.yz);
        }
        else if (alpha > max_weights.z) {
            max_indics.zw = int2(surfaceIndex, max_indics.z);
            max_weights.zw = float2(alpha, max_weights.z);
        }
        else if (alpha > max_weights.w) {
            max_indics.w = surfaceIndex;
            max_weights.w = alpha;
        }
    }
}

// get surface albedo, normal, height.
half get_surface_color_normal_height(int surface, float3 texCoord, out half3 color)
{
    const float surface_tiling[2] = { 64.0f,32.0f }; // hard coded for show my bugs.
    const float tiling = surface_tiling[surface];
    half4 color_h = tAlbedoMap.Sample(sLinearWrap, float3(texCoord.xy * tiling, surface));
    color = color_h.rgb;
    return color_h.w;
}

void blend_layers(float3 texCoord, out half3 albedo)
{
    int4 surfaceIndics; // [0,1,-1,-1] or [1,0,-1,-1]
    find_max_layers(texCoord.xy, surfaceIndics);
    half3 colors[4] = { (half3)0 , (half3)0 , (half3)0 , (half3)0 };

    /*A: works
     if(surfaceIndics.x == 0)
      {
        get_surface_color_normal_height ( 0 , texCoord , colors [ 0 ]  );
        get_surface_color_normal_height ( 1 , texCoord , colors [ 1 ]  );
    }else
    {
      get_surface_color_normal_height (1 , texCoord , colors [ 0 ]  );
      get_surface_color_normal_height ( 0 , texCoord , colors [ 1 ]  );
    }*/
    /*B:works
   if(surfaceIndics.x == 0)
      {
        get_surface_color_normal_height ( 0 , texCoord , colors [ 0 ]  );
        get_surface_color_normal_height ( 1 , texCoord , colors [ 1 ]  );
    }else
    {
      get_surface_color_normal_height (0 , texCoord , colors [ 0 ]  );
      get_surface_color_normal_height ( 1 , texCoord , colors [ 1 ]  );
    }
    */

    //C: error!!!
    if (surfaceIndics.x != -1)
    {
        get_surface_color_normal_height(surfaceIndics.x, texCoord, colors[0]);
    }
    if (surfaceIndics.y != -1)
    {
        get_surface_color_normal_height(surfaceIndics.y, texCoord, colors[1]);
    }
    // output
    albedo = (colors[0] + colors[1]) * 0.5;
}

Let me simplify the problem first, I have 2 textures 0,1 that need to be blended. function find_max_layers has only two return values:[0,1,-1,-1] or [1,0,-1,-1].

the code section A and B in function blend_layers works fine.

the output image is : A,B the right result

but the code section C in function blend_layers output unexpected results: C,the error result

  • I found that SampleGrad works fine, with ddx,ddy set to zero. – star dust.r Mar 17 '21 at 08:11
  • What Shader Profile are you using? – Chuck Walbourn Mar 17 '21 at 23:20
  • @ChuckWalbourn SM 5. Thank you for your reply. I have solved the problem. The abnormal pixels are caused by the inconsistent mipmap when sampling adjacent pixels. According to http://www.aclockworkberry.com/shader-derivative-functions/ , I have Use **sampleGrad** instead of **sample**. And use dd*(world_pos) to solve the problem. – star dust.r Mar 18 '21 at 02:29
  • You should post the solution as an answer to your own question... – Chuck Walbourn Mar 18 '21 at 04:40

2 Answers2

1

This is an old question but maybe someone finds this and google and is wondering about an explanation:

Sampling with automatic mip selection (e.g. Sample functions without the Level suffix in HLSL, or texture...() functions without the grad suffix in GLSL) inside dynamic branch is illegal. Direct texel fetching functions are unaffected by this (Load in HLSL / texelFetch in GLSL).

The underlying reason is that mip mapping needs derivatives of texture coordinates (which are used to compute the level of mip map to sample from), and derivatives (ddx/y in HLSL, dFdx/y in GLSL) are undefined in divergent control flow, meaning control flow where one set of threads/pixels that are processed together on the hardware can go one side of the branch, and another set of threads/pixels can go into the other.

The reason derivatives are undefined in those circumstances is because of how derivatives are computed: Numerically derivatives are based on some kind of differencing scheme of the values that you want a derivative of between neighbouring pixels (see for example here for more details Difference between dFdxFine and dFdxCoarse).

But since neighbouring pixels might not even be in the 'same line of code' because they went to the other side of the branch, there are no values available for those pixels to do any differencing with.

There are small exceptions to this, but in practice I don't even want to mention them because they are only going to get you into trouble with different implementations based on hardware vendors. The jist of it is that you should just avoid using derivatives or sampling functions that need derivatives in divergent control flow.

TravisG
  • 2,373
  • 2
  • 30
  • 47
0

After some attempts, I found that using SampleLevel instead of Sample, and the specified textures are sampled at the same mipmap level, the problem will disappear.
So I judged that the reason was the inconsistency of mipmap sampling of neighboring pixels.
then i manually constructed mipmaps of two textures in different colors. It was further verified.
According to the guidance of this article (http://www.aclockworkberry.com/shader-derivative-functions/), finally using SampleGrad instead of Sample to fix the problem.