webgl centroid质心插值的一点理解

质心插值说的是什么

2023.10.04再次review这个细节点:
https://www.opengl.org/pipeline/article/vol003_6/
https://github.com/WebGLSamples/WebGL2Samples/blob/master/samples/glsl_centroid.html#L69
基本上把这个问题看明白了;centroid代表质心插值;问题来自于在对普通的varying变量进行插值时,默认都是采用片元中心点来进行计算获取最终插值,但是有些情况中心点并不在图形的覆盖范围内,这时候中心点插值就会超出当初设置的一个值域范围,导致在某些边界存在异常情况;比如上面链接中的代码,在蓝色区域竟然出现了黄色就有问题。

产生这个问题的原因在于,针对离散的片元在进行光栅化差值时候,有些片元虽然只有少部分被几何区域覆盖,但这些片元也参与光栅化并被作为图形的一部分;这就导致使用片元中心点参与插值计算时,得到的计算结果会不在顶点的值域范围内。

而质心插值采用的是,几何边界所覆盖片元部分的一个质心点来参与插值计算。这样就能够保证最终光栅化的每个片元获取的数值都在顶点的值域范围内。

下面这个示例就是在表达这种情况:

左边没有使用质心插值,在下面代码构造的场景中就会出现超出值域范围(0-1)的问题;右边采用了质心插值,则没有出现这个问题。

复制代码
<!-- WebGL 2 shaders -->
    <script id="vs-render" type="x-shader/x-vertex">
        #version 300 es
        #define POSITION_LOCATION 0
        #define ATTRIBUTE_DATA_LOCATION 6
        
        precision highp float;
        precision highp int;

        uniform mat4 MVP;

        layout(location = POSITION_LOCATION) in vec2 position;
        layout(location = ATTRIBUTE_DATA_LOCATION) in float data;
        
        out float v_attribute;

        void main()
        {
            gl_Position = MVP * vec4(position, 0.0, 1.0);
            v_attribute = data;
        }
    </script>

    <script id="fs-render" type="x-shader/x-fragment">
        #version 300 es
        precision highp float;
        precision highp int;

        in float v_attribute;
        out vec4 color;

        void main()
        {
            const vec4 blue   = vec4( 0.0, 0.0, 1.0, 1.0 );
            const vec4 yellow = vec4( 1.0, 1.0, 0.0, 1.0 );
            color = v_attribute >= 0.0 ? mix(blue, yellow, sqrt(v_attribute)) : yellow;
        }
    </script>
    
    <script id="vs-render-centroid" type="x-shader/x-vertex">
        #version 300 es
        #define POSITION_LOCATION 0
        #define ATTRIBUTE_DATA_LOCATION 6
        
        precision highp float;
        precision highp int;

        uniform mat4 MVP;

        layout(location = POSITION_LOCATION) in vec2 position;
        layout(location = ATTRIBUTE_DATA_LOCATION) in float data;
        
        centroid out float v_attribute;

        void main()
        {
            gl_Position = MVP * vec4(position, 0.0, 1.0);
            v_attribute = data;
        }
    </script>
    
    <script id="fs-render-centroid" type="x-shader/x-fragment">
        #version 300 es
        precision highp float;
        precision highp int;

        centroid in float v_attribute;
        out vec4 color;

        void main()
        {
            const vec4 blue   = vec4( 0.0, 0.0, 1.0, 1.0 );
            const vec4 yellow = vec4( 1.0, 1.0, 0.0, 1.0 );
            color = v_attribute >= 0.0 ? mix(blue, yellow, sqrt(v_attribute)) : yellow;
        }
    </script>

那么质心怎么计算?
OpenGL allows implementers to choose the ideal centroid, or any location that is inside the intersection of the pixel square and the primitive, such as a sample point or a pixel center.
意思就是OpenGL规范允许具体实现者自己来决定,可以使用几何覆盖区域的中心点,或者直接使用片元的中心点也可以(当然这时候会有问题)。目前看起来当声明了使用质心插值时,大多数显卡的实现方式是,如果覆盖了片元中心点那么就是用片元中心点就是用中心点计算插值,当没有覆盖中心点就是用几何边界与片元覆盖区域(是一个三角形)计算出三角形质心(有专门的计算公式。

到底该不该用

质心插值也不是什么时候都适用,大部分时候影响不大(如果重要webgl1不会舍弃)。如果你的代码里有一些内置函数依赖插值结果,并且可能出现异常情况,比如对一个插值求开平方,如果插值出来结果是负数,对负数求开平方就有问题。


其次当着色器中代码逻辑,因为这个超出值域范围的数字影响特别大时候需要处理,比如对一个数求高次的幂逻辑。
也有一些情况不适用质心插值。比如代码中使用了一些导数计算,如dfx、dfy,因为他们的步长和方向都已经变了(delta的取值不再是两个片元的中心点差值了);另外如果非质心插值对效果影响不大可以不用管他,因为它的开销还是比较大。