Dynamically modify defines in post-processing

I tried to use the tweak pane in post-processing to dynamically modify the #defines in the shader, but the screen flickered. How can i fix it ?

outline shader

void main() {

  vec4 baseColor = texture2D(uSceneTexture, vUV);

  #ifndef ENABLE

  gl_FragColor = baseColor;

  return; 

  #endif

  vec2 edge = texture2D(textureSampler, vUV).rg;
  vec2 mask = texture2D(uMaskTexture, vUV).rg;

  #ifndef X_RAY

  edge.y = 0.;

  #endif

  edge *= (uEdgeStrength * mask.x);

  vec3 color = edge.x * uLineColor + edge.y * uLineColor;

  float visibilityFactor = 0.0;

  float alpha = max(max(edge.x, edge.y), visibilityFactor);

  vec4 finalColor = vec4(0.0, 0.0, 0.0, 1.0);

  #ifdef ALPHA

  finalColor = vec4(color, alpha);

  #else

  finalColor = vec4(color, max(alpha, baseColor.a));

  #endif

  finalColor = blend(baseColor, finalColor, uOpacity);

  gl_FragColor = finalColor;

}

undate function

 private updateDefines(composer: PostProcess) {
    const defines = [];

    if (this.params.x_ray) {
      defines.push('#define X_RAY');
    }

    if (this.params.mode) {
      defines.push(`#define ${this.params.mode}`);
    }

    if (this.params.enable) {
      defines.push('#define ENABLE');
    }

    composer.updateEffect(defines.join('\n'));
  }

Changing the defines at runtime will probably require a shader compilation, which is not ideal :thinking:

Why not use a uniform like uniform int colorMode; and use its value instead to switch between your colors?

I want to change the blend function by changing defines, but it seems that it will compile in parallel and cause the screen to flash. :joy:

Yes that’s to be expected, that’s why you want to avoid shader recompilation as much as possible. Does using a uniform not work for you? :thinking:

No, I personally think it is not beautiful and intuitive to control functions through uniforms.
just like this

vec4 blend(const in vec4 x, const in vec4 y, const in float opacity) {
  vec4 result;

  if(uMode == 1) {  
        // Alpha blend mode  
    result = mix(x, y, min(y.a, opacity));
  } else if(uMode == 2) {  
        // Screen blend mode  
    result = mix(x, x + y - min(x * y, 1.0), opacity);
  } else {  
        // Normal blend mode (default)  
    result = mix(x, y, opacity);
  }

  return result;
}

That’s very subjective :laughing: I have another solution for you then: pre-compile all possible versions of your shader and then swap them at runtime depending on which one you need.

I am curious though, why do you not find using uniforms for this use case beautiful or intuitive?

Maybe it’s better to use step functions there instead of conditions. In terms of performance.

It may be related to my personal coding habits. When I use threejs to develop, I tend to change defines dynamically. :rofl:

Yes, thanks for your suggestion.

Maybe something like this:

uniform float uModeWeights[3];  // [1,0,0] for mode 0, [0,1,0] for mode 1, etc.

vec4 blend(const in vec4 x, const in vec4 y, const in float opacity) {
    return uModeWeights[0] * mix(x, y, opacity)
         + uModeWeights[1] * mix(x, y, min(y.a, opacity))
         + uModeWeights[2] * mix(x, x + y - min(x * y, 1.0), opacity);
}

Or something like:

vec4 blend(const in vec4 x, const in vec4 y, const in float opacity) {
    // Pre-calculate all possible blend results
    vec4 normalBlend = mix(x, y, opacity);
    vec4 screenBlend = mix(x, x + y - min(x * y, 1.0), opacity);
    vec4 alphaBlend = mix(x, y, min(y.a, opacity));
    
    // Calculate weights for each mode (0 or 1)
    float mode1Weight = abs(sign(float(uMode) - 1.5)) - 0.5;  // ~1.0 when uMode == 1
    float mode2Weight = abs(sign(float(uMode) - 2.5)) - 0.5;  // ~1.0 when uMode == 2
    float defaultWeight = 1.0 - mode1Weight - mode2Weight;    // ~1.0 otherwise
    
    // Combine results using weights
    return mode1Weight * alphaBlend 
         + mode2Weight * screenBlend 
         + defaultWeight * normalBlend;
}