【问题标题】:WebGL: Loop index cannot be compared with non-constant expressionWebGL:循环索引无法与非常量表达式进行比较
【发布时间】:2016-12-23 11:54:29
【问题描述】:

我有一个 webgl 模糊着色器:

precision mediump float;
precision mediump int;

uniform sampler2D u_image;
uniform float blur;       
uniform int u_horizontalpass; // 0 or 1 to indicate vertical or horizontal pass
uniform float sigma;        // The sigma value for the gaussian function: higher value means more blur
                            // A good value for 9x9 is around 3 to 5
                            // A good value for 7x7 is around 2.5 to 4
                            // A good value for 5x5 is around 2 to 3.5
                            // ... play around with this based on what you need :)

varying vec4 v_texCoord;

const vec2 texOffset = vec2(1.0, 1.0);
// uniform vec2 texOffset;
const float PI = 3.14159265;

void main() {  
  vec2 p = v_texCoord.st;
  float numBlurPixelsPerSide = blur / 2.0; 

  // Incremental Gaussian Coefficent Calculation (See GPU Gems 3 pp. 877 - 889)
  vec3 incrementalGaussian;
  incrementalGaussian.x = 1.0 / (sqrt(2.0 * PI) * sigma);
  incrementalGaussian.y = exp(-0.5 / (sigma * sigma));
  incrementalGaussian.z = incrementalGaussian.y * incrementalGaussian.y;

  vec4 avgValue = vec4(0.0, 0.0, 0.0, 0.0);
  float coefficientSum = 0.0;

  // Take the central sample first...
  avgValue += texture2D(u_image, p) * incrementalGaussian.x;
  coefficientSum += incrementalGaussian.x;
  incrementalGaussian.xy *= incrementalGaussian.yz;

  // Go through the remaining 8 vertical samples (4 on each side of the center)
  for (float i = 1.0; i <= numBlurPixelsPerSide; i += 1.0) { 
    avgValue += texture2D(u_image, p - i * texOffset) * incrementalGaussian.x;         
    avgValue += texture2D(u_image, p + i * texOffset) * incrementalGaussian.x;         
    coefficientSum += 2.0 * incrementalGaussian.x;
    incrementalGaussian.xy *= incrementalGaussian.yz;
  }

  gl_FragColor = avgValue / coefficientSum;
}

当我构建时,我收到以下错误消息:

webgl-renderer.js?2eb3:137 Uncaught 无法编译着色器:错误: 0:38: 'i' : 循环索引无法与非常量表达式进行比较

我也尝试使用 just 统一浮动模糊来比较 i 。有没有什么办法解决这一问题?

问题在这里进一步详细说明:https://www.khronos.org/webgl/public-mailing-list/archives/1012/msg00063.php

我发现环顾四周的解决方案是在比较循环变量时仅使用常量表达式。这不符合我需要做的事情,即根据模糊半径改变循环的次数。

对此有什么想法吗?

【问题讨论】:

    标签: glsl webgl


    【解决方案1】:

    发生这种情况是因为在某些硬件上,GLSL 循环被展开为原生 GPU 指令。这意味着通过for 循环的次数需要有一个硬性上限,它控制将生成多少个循环内部代码的副本。如果将numBlurPixelsPerSide 替换为const float 甚至#define 指令,那么着色器编译器可以在编译时确定传递次数,并相应地生成代码。但是那里有统一的,上限在编译时是未知的。

    这条规则有一个有趣的问题:你可以break 或在for 循环中调用早期的return,即使在编译时必须可以识别最大迭代次数。例如,考虑this tiny Mandelbrot shader。这几乎不是 GLSL Sandbox 上最漂亮的分形,但我选择它是因为它的体积小:

    precision mediump float;
    uniform float time;
    uniform vec2 mouse;
    uniform vec2 resolution;
    varying vec2 surfacePosition;
    
    const float max_its = 100.;
    
    float mandelbrot(vec2 z){
        vec2 c = z;
        for(float i=0.;i<max_its;i++){     // for loop is here.
            if(dot(z,z)>4.) return i;      // conditional early return here.
            z = vec2(z.x*z.x-z.y*z.y,2.*z.x*z.y)+c;
        }
        return max_its;
    }
    
    
    void main( void ) {
        vec2 p = surfacePosition;
        gl_FragColor = vec4(mandelbrot(p)/max_its);
    }
    

    在此示例中,max_itsconst,因此编译器知道上限,并且可以在需要时展开此循环。在循环内部,return 语句为 Mandelbrot 集之外的像素提供了一种提前离开循环的方法。

    您仍然不想将最大迭代次数设置得太高,因为这会产生大量 GPU 指令并可能会损害性能。

    【讨论】:

      【解决方案2】:

      试试这样的:

      const float MAX_ITERATIONS = 100.0;
      
      // Go through the remaining 8 vertical samples (4 on each side of the center)
      for (float i = 1.0; i <= MAX_ITERATIONS; i += 1.0) { 
          if (i >= numBlurPixelsPerSide){break;}
          avgValue += texture2D(u_image, p - i * texOffset) * incrementalGaussian.x;         
          avgValue += texture2D(u_image, p + i * texOffset) * incrementalGaussian.x;         
          coefficientSum += 2.0 * incrementalGaussian.x;
          incrementalGaussian.xy *= incrementalGaussian.yz;
      }
      

      【讨论】:

      • 简单而精彩。
      【解决方案3】:

      有时您可以使用我非常简单的问题解决方法。

      我的shader源码片段:

      const int cloudPointsWidth = %s;
      for ( int i = 0; i < cloudPointsWidth; i++ ) {
         //TO DO something
      }
      

      你可以看到 '%' : 上面的语法错误。但是在使用我的着色器之前,我将 %s 替换为我的 javascript 代码中的一个数字。例如:

      vertexCode = vertexCode.replace( '%s', 10 );
      

      vertexCode 是我的着色器源代码。

      每次如果我想更改cloudPointsWidth,我都会销毁我的旧着色器并使用新的cloudPointsWidth 创建新的着色器。

      希望有时我的解决方案能对你有所帮助。

      【讨论】:

      • 这真的很聪明,而且是一种非常简单的处理方法。
      【解决方案4】:

      你可以只做一个大常数的for循环并使用一个break。

      for(int i = 0; i < 1000000; ++i) 
      { 
          // your code here
          if(i >= n){
              break;
          }
      }
      

      【讨论】:

        【解决方案5】:

        我在使用图像下采样着色器时遇到了类似的问题。代码基本一致:

        for (int dx = -2 * SCALE_FACTOR; dx < 2 * SCALE_FACTOR; dx += 2) {
            for (int dy = -2 * SCALE_FACTOR; dy < 2 * SCALE_FACTOR; dy += 2) {
                /* accumulate fragment's color */
            }
        }
        

        我最终做的是使用预处理器并为每个使用的SCALE_FACTOR 创建单独的着色器程序(幸运的是,只需要 4 个)。为了实现这一点,我们实现了一个小辅助函数来将#define ... 语句添加到着色器代码中:

        function insertDefines (shaderCode, defines) {
            var defineString = '';
        
            for (var define in defines) {
                if (defines.hasOwnProperty(define)) {
                    defineString +=
                        '#define ' + define + ' ' + defines[define] + '\n';
                }
            }
        
            var versionIdx = shaderCode.indexOf('#version');
        
            if (versionIdx == -1) {
                return defineString + shaderCode;
            }
        
            var nextLineIdx = shaderCode.indexOf('\n', versionIdx) + 1;
        
            return shaderCode.slice(0, nextLineIdx) +
                defineString +
                shaderCode.slice(nextLineIdx);
        }
        

        实现有点棘手,因为如果代码中已经有 #version 预处理器语句,则所有其他语句都必须遵循它。

        然后我添加了一个检查 SCALE_FACROR 被定义:

        #ifndef SCALE_FACTOR
        #   error SCALE_FACTOR is undefined
        #endif
        

        在我的 javascript 代码中,我做了这样的事情:

        var SCALE_FACTORS = [4, 8, 16, 32],
            shaderCode, // the code of my shader
            shaderPrograms = SCALE_FACTORS.map(function (factor) {
                var codeWithDefines = insertDefines(shaderCode, { SCALE_FACTOR: factor });
                /* compile shaders, link program, return */
            });
        

        【讨论】:

        • 聪明,但不如公认的解决方案优雅
        【解决方案6】:

        我在android上使用opengl es3并通过在程序开头使用扩展来解决这个问题:

        #extension GL_EXT_gpu_shader5 : require
        

        我不知道它是否适用于 webGL,但你可以尝试一下。 希望对您有所帮助。

        【讨论】:

        • 必须类似于 jshint ignore:start
        【解决方案7】:

        您也可以使用模板文字来设置循环的长度

        onBeforeCompile(shader) {
          const array = [1,2,3,4,5];
          shader.uniforms.myArray = { value: array };
        
          let token = "#include <begin_vertex>";
          const insert = `
            uniform float myArray[${array.length}];
            for ( int i = 0; i < ${array.length}; i++ ) {
               float test = myArray[ i ];
            }
          `;
          shader.vertexShader = shader.vertexShader.replace(token, token + insert);
        }
        

        【讨论】:

          猜你喜欢
          • 2020-07-29
          • 1970-01-01
          • 2011-04-27
          • 2013-12-16
          • 1970-01-01
          • 2016-01-10
          • 1970-01-01
          • 1970-01-01
          • 2019-10-02
          相关资源
          最近更新 更多