WebGL是否可以在片段着色器中加载动态缓冲区?

时间:2020-06-09 20:38:16

标签: webgl

我有一个片段着色器,可以基于一组参数绘制圆弧。想法是使着色器分辨率独立,因此我将弧的中心和边界半径作为屏幕上的像素值传递。然后,您可以通过将顶点位置设置为正方形来渲染着色器。这是着色器:

precision mediump float;

#define PI 3.14159265359
#define _2_PI 6.28318530718
#define PI_2 1.57079632679

// inputs
vec2 center = u_resolution / 2.;
vec2 R = vec2( 100., 80. );
float ang1 = 1.0 * PI;
float ang2 = 0.8 * PI;
vec3 color = vec3( 0., 1.0, 0. );

// prog vars
uniform vec2 u_resolution;
float smOOth = 1.3;
vec3 bkgd = vec3( 0.0 );    // will be a sampler

void main () {
    // get the dist from the current pixel to the coord.
    float r = distance( gl_FragCoord.xy, center );
    if ( r < R.x && r > R.y ) {

        // If we are in the radius, do some trig to find the angle and normalize
        // to 
        float theta = -( atan( gl_FragCoord.y - center.y, 
                    center.x - gl_FragCoord.x ) ) + PI;

        // This is to make sure the angles are clipped at 2 pi, but if you pass
        // the values already clipped, then you can safely delete this and make
        // the code more efficinent.
        ang1 = mod( ang1, _2_PI );
        ang2 = mod( ang2, _2_PI );

        float angSum = ang1 + ang2;
        bool thetaCond;
        vec2 thBound;   // short for theta bounds: used to calculate smoothing
                        // at the edges of the circle.

        if ( angSum > _2_PI ) {
            thBound = vec2( ang2, angSum - _2_PI );
            thetaCond = ( theta > ang2 && theta < _2_PI ) || 
                        ( theta < thetaBounds.y );
        } else {
            thBound = vec2( ang2, angSum );
            thetaCond = theta > ang2 && theta < angSum;
        }

        if ( thetaCond ) {
            float angOpMult = 10000. / ( R.x - R.y ) / smOOth;
            float opacity = smoothstep( 0.0, 1.0, ( R.x - r ) / smOOth ) - 
                            smoothstep( 1.0, 0.0, ( r - R.y ) / smOOth ) - 
                            smoothstep( 1.0, 0.0, ( theta - thBound.x ) 
                                                    * angOpMult ) - 
                            smoothstep( 1.0, 0.0, ( thBound.y - theta )
                                                    * angOpMult );
            gl_FragColor = vec4( mix( bkgd, color, opacity ), 1.0 );
        } else
            discard;
    } else
        discard;
}

我认为,这种画圆的方法比加载一堆顶点和画三角形扇形的方法能产生更好的画质,并且麻烦的少,即使它可能效率不高。这很好用,但我不只是想画一个固定的圆。我想在屏幕上画出任何想要的圆圈。因此,我想到了将“输入”设置为变化量,并将带有参数的缓冲区传递给给定边界正方形的每个顶点的方法。所以我的顶点着色器看起来像这样:

attribute vec2 a_square;
attribute vec2 a_center;
attribute vec2 a_R;
attribute float a_ang1;
attribute float a_ang2;
attribute vec3 a_color;

varying vec2 center;
varying vec2 R;
varying float ang1;
varying float ang2;
varying vec3 color;

void main () {
    gl_Position = vec4( a_square, 0.0, 1.0 );

    center = a_center;
    R = a_R;
    ang1 = a_ang1;
    ang2 = a_ang2;
    color = a_color;
}

'a_square'只是圆将位于其中的边界正方形的顶点。

接下来,我为一个测试圈(在JS中)的输入定义一个缓冲区。这样做的问题之一是必须为每个顶点重复圆参数,对于一个盒子,这意味着四次。 “ pw”和“ ph”分别是画布的宽度和高度。

var circleData = new Float32Array( [
    pw / 2, ph / 2,
    440, 280,
    Math.PI * 1.2, Math.PI * 0.2,
    1000, 0, 0,

    pw/2,ph/2,440,280,Math.PI*1.2,Math.PI*0.2,1000,0,0,
    pw/2,ph/2,440,280,Math.PI*1.2,Math.PI*0.2,1000,0,0,
    pw/2,ph/2,440,280,Math.PI*1.2,Math.PI*0.2,1000,0,0,
] );

然后,我只需将数据加载到gl缓冲区(circleBuffer)中,并将适当的属性绑定到该缓冲区。

gl.bindBuffer( gl.ARRAY_BUFFER, bkgd.circleBuffer );
gl.vertexAttribPointer( bkgd.aCenter, 2, gl.FLOAT, false, 0 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aCenter );
gl.vertexAttribPointer( bkgd.aR, 2, gl.FLOAT, false, 2 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aR );
gl.vertexAttribPointer( bkgd.aAng1, 1, gl.FLOAT, false, 4 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aAng1 );
gl.vertexAttribPointer( bkgd.aAng2, 1, gl.FLOAT, false, 5 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aAng2 );
gl.vertexAttribPointer( bkgd.aColor, 3, gl.FLOAT, false, 6 * floatSiz, 9 * floatSiz );
gl.enableVertexAttribArray( bkgd.aColor );

当我加载页面时,确实看到了一个圆圈,但是在我看来,半径是实际上反映任何类型响应性的唯一属性。角度,中心和颜色没有反映它们应具有的值,我绝对不知道为什么半径是唯一实际起作用的东西。

尽管如此,这似乎是一种将参数加载到片段着色器以绘制圆的效率不高的方法,因为我必须重新加载框的每个顶点的值,然后GPU无缘无故地插值这些值。是否有更好的方法将诸如属性缓冲区之类的内容传递给片段着色器,或者通常以这种方式使用片段着色器?还是应该只使用顶点绘制圆?

1 个答案:

答案 0 :(得分:0)

如果您仅绘制圆,则可以使用实例化绘制不重复信息。

请参阅以下问答:what does instancing do in webgl

this article

实例化使您可以按实例在每个实例中使用某些数据

您还可以使用纹理存储每个圆的数据或所有数据。请参阅以下问答:How to do batching without UBOs?

效率的高低取决于GPU /驱动程序/ OS /浏览器。如果您需要绘制1000个圆圈,则可能会比较有效。大多数应用程序会绘制各种各样的东西,因此除非有特殊需要绘制数千个圆圈,否则会选择更通用的解决方案。 同样,它可能并不高效,因为您仍在为正方形而非圆形中的每个像素调用片段着色器。与使用三角形相比,对片段着色器的调用要多30%,并且假定您的代码正在绘制适合圆的四边形。乍一看,您的实际代码正在绘制完整的画布四边形,这是非常低效的。

相关问题