5

我正在尝试找到一个解决方案,它允许我围绕 z 轴旋转具有不同属性的点精灵(即统一不会这样做)。

在我的应用程序中,我每帧绘制了成百上千个点精灵,然后将它们存储在 VBO 中(很可能最终会超过 1,000,000)。因此,我正在寻找内存使用和性能之间的最佳折衷方案。

当前的顶点和片段着色器如下所示:

// VERTEX SHADER
attribute vec4 a_position;
attribute vec4 a_color;
attribute float a_size;
uniform mat4 u_mvpMatrix;
varying vec4 v_color;

void main()
{
    v_color = a_color;
    gl_Position = u_mvpMatrix * a_position;
    gl_PointSize = a_size;
}


// FRAGMENT SHADER
precision mediump float;
uniform sampler2D s_texture;
varying vec4 v_color;

void main()
{
    vec4 textureColor = texture2D(s_texture, gl_PointCoord);
    gl_FragColor = v_color * textureColor;
}

我目前可以想象以下可能性:

  • mat4 rotMatrix为我的点精灵数据添加一个属性。将此传递给片段着色器并旋转每个片段:

    vec2 texCoord = (rotMatrix * vec4(gl_PointCoord, 0, 1)).xy
    gl_FragColor = v_color * texture2D(s_texture, texCoord);
    
    • 优点:
      • 保持着色器简单。
      • 计算着色器之外的矩阵的简单代码(GLKit例如使用)。
    • 缺点:
      • 大大增加了我的点精灵数据的大小(对于 4x4 矩阵,从 16 字节/点增加到 80 字节/点;对于 3x3 矩阵,增加到 52 字节/点……我相信可以使用 3x3 旋转矩阵?)。这可能会导致我的应用程序提前 3-5 次崩溃!
      • 将更多的计算推到 CPU 上(每帧数十/千次矩阵计算)。


  • 为我的点精灵数据添加一个float angle属性,然后在顶点着色器中计算旋转矩阵。如上所述将旋转矩阵传递给片段着色器。

    • 优点:
      • 使点精灵数据大小保持较小(从 16 到 20 字节/点)。
      • 将繁重的矩阵数学推送到 GPU。
    • 缺点:
      • 需要编写自定义 GLSL 函数来创建旋转矩阵。不是一个大问题,但我的矩阵数学生锈了,所以这可能容易出错,特别是如果我试图找出 3x3 矩阵解决方案......
      • 鉴于这必须发生在成百上千个顶点上,这是否会严重拖累性能(尽管由 GPU 处理)?


  • 我可以实际处理角度属性的 1 个字节(255 个不同的角度就足够了)。有什么方法可以使用某种查找,这样我就不需要不必要地重新计算相同的旋转矩阵?在顶点着色器中存储常量是我的第一个想法,但我不想开始在我的着色器中放置分支语句。

关于好方法的任何想法?

4

4 回答 4

2

我最终采用的解决方案是问题的第二个:计算顶点着色器中的旋转矩阵。这具有以下优点:

  • 使点精灵数据大小保持较小。
  • 旋转计算由 GPU 执行。

我猜测的缺点似乎并不适用。即使在第一代 iPad 上运行,我也没有注意到性能下降。GLSL 中的矩阵计算有些麻烦,但效果很好。为了其他尝试做同样事情的人的利益,这里是顶点着色器的相关部分:

//...
attribute float a_angle;
varying mat4 v_rotationMatrix;

void main()
{
    //...

    float cos = cos(a_angle);
    float sin = sin(a_angle);
    mat4 transInMat = mat4(1.0, 0.0, 0.0, 0.0,
                           0.0, 1.0, 0.0, 0.0,
                           0.0, 0.0, 1.0, 0.0,
                           0.5, 0.5, 0.0, 1.0);
    mat4 rotMat = mat4(cos, -sin, 0.0, 0.0,
                       sin, cos, 0.0, 0.0,
                       0.0, 0.0, 1.0, 0.0,
                       0.0, 0.0, 0.0, 1.0);
    mat4 resultMat = transInMat * rotMat;
    resultMat[3][0] = resultMat[3][0] + resultMat[0][0] * -0.5 + resultMat[1][0] * -0.5;
    resultMat[3][1] = resultMat[3][1] + resultMat[0][1] * -0.5 + resultMat[1][1] * -0.5;
    resultMat[3][2] = resultMat[3][2] + resultMat[0][2] * -0.5 + resultMat[1][2] * -0.5;
    v_rotationMatrix = resultMat;

    //...
}

鉴于没有明显的性能影响,这个解决方案是理想的,因为不需要创建纹理贴图/查找并消耗额外的内存,并且它保持其余代码的简洁和简单。

我不能说为每个顶点计算矩阵没有缺点(例如减少电池寿命),并且在不同的情况下性能可能是一个问题,但它对我的需要有好处。

于 2013-01-11T13:57:59.640 回答
1

Here is your pre-multiplied rotation matrix:

v_rotationMatrix = mat3(cos, sin, 0.0,
                        -sin, cos, 0.0,
                        (sin-cos+1.0)*0.5, (-sin-cos+1.0)*0.5, 1.0);
于 2013-06-14T05:00:57.323 回答
1

您是否考虑过使用不同的预先计算和旋转的纹理(纹理图集)?如果只有几个角度就足以达到您想要达到的效果,这将是一个非常快速的解决方案。

另一方面,在片段着色器中计算纹理坐标(间接纹理查找)会降低性能。这对您的情况可能并不重要,但值得牢记。

于 2013-01-11T02:59:59.230 回答
1

FWIW,这是我得到的与 Stuart 的代码匹配的 3x3 预计算矩阵:

v_rotationMatrix = mat3(cos, -sin, 0.0, sin, cos, 0.0, (1.0-cos-sin)*0.5, (1.0+sin-cos)*0.5, 1.0);

请注意, glsl 矩阵采用列优先格式。

于 2013-06-29T21:15:56.733 回答