7

私はopenglで最も効率的に動くオブジェクト(一般的に)とラインストリップ(特に)のこつをつかもうとしているので、複数の線分が右から左に一定の速度で移動するアプリケーションを書いています。すべての時点で、最も左のポイントが削除され、ライン全体が左にシフトされ、新しいポイントがラインの右端に追加されます (この新しいデータ ポイントは、オンザフライでストリーミング/受信/計算されます)。 、約 10ms ごと)。私が何を意味するかを説明するには、次の画像を参照してください。

ラインストリップの例

多くのオブジェクトを操作したいので、gl*呼び出しの量を最小限に抑えるために、頂点バッファー オブジェクトを使用することにしました。私の現在のコードは次のようになります。

A) 初期頂点の設定:

# calculate my_func(x) in range [0, n]
# (could also be random data)
data = my_func(0, n)

# create & bind buffer
vbo_id = GLuint()
glGenBuffers(1, vbo_id);
glBindBuffer(GL_ARRAY_BUFFER, vbo_id)

# allocate memory & transfer data to GPU
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_DYNAMIC_DRAW)

B) 頂点を更新する:

draw():

  # get new data and update offset
  data = my_func(n+dx, n+2*dx)

  # update offset 'n' which is the current absolute value of x.
  n = n + 2*dx

  # upload data 
  glBindBuffer(GL_ARRAY_BUFFER, vbo_id)
  glBufferSubData(GL_ARRAY_BUFFER, n, sizeof(data), data)

  # translate scene so it looks like line strip has moved to the left.
  glTranslatef(-local_shift, 0.0, 0.0)

  # draw all points from offset
  glVertexPointer(2, GL_FLOAT, 0, n)
  glDrawArrays(GL_LINE_STRIP, 0, points_per_vbo)

どこでmy_func次のようにしますか:

my_func(start_x, end_x):

  # generate the correct x locations.
  x_values = range(start_x, end_x, STEP_SIZE)

  # generate the y values. We could be getting these values from a sensor.
  y_values = []
  for j in x_values:
      y_values.append(random())

  data = []
  for i, j in zip(x_values, y_values):
     data.extend([i, j])

  return data

これは問題なく動作しますが、たとえば 20 本のライン ストリップが画面全体に広がると、処理速度が大幅に低下します。 したがって、私の質問:

1) glMapBuffer を使用して GPU 上のバッファをバインドし、(glBufferSubData を使用する代わりに) データを直接埋める必要がありますか? それとも、これはパフォーマンスに関して何の違いもありませんか?

2) glTranslatef を呼び出す代わりに、オブジェクト (ここではライン ストリップ) を移動するためにシェーダーを使用する必要がありますか? もしそうなら、そのようなシェーダーはどのように見えますか? (私のライン ストリップは周期関数ではなく、ランダム データが含まれているため、シェーダーは間違った方法であると思われます)。

3) ウィンドウのサイズが変更されるとどうなりますか? 縦横比を維持し、それに応じて頂点をスケーリングするにはどうすればよいですか? glViewport() は、x 方向ではなく、y 方向のスケーリングのみに役立ちます。ウィンドウが x 方向に再スケーリングされた場合、現在の実装では、ライン ストリップ全体の位置を再計算し (my_func新しい x 座標を取得するために呼び出します)、それを GPU にアップロードする必要があります。これはもっとエレガントにできると思いますか?どうすればいいですか?

glTranslatef4)非整数値で使用すると、ライン ストリップが数千のポイントで構成されている場合、画面がちらつき始めることに気付きました。これはおそらく、ライン ストリップを計算するために使用する細かい解像度が画面のピクセル解像度と一致しないためです。そのため、一部のポイントが前に表示されたり、他のポイントの後ろに表示されることがあります (これは、レンダリングを行わない場合に特に厄介です)。正弦波ですが、いくつかの「ランダム」データ)。これが起こらないようにするにはどうすればよいですか (1 ピクセルの整数倍で変換するという明白な解決策以外に)? ウィンドウのサイズが元の 800x800 ピクセルから 100x100 ピクセルに変更された場合でも、20 秒のライン ストリップを視覚化したい場合、x 方向にシフトすると、ちらつきがなくサブピクセル精度で何らかの方法で動作する必要があります。

5) ご覧のとおり、私は常に電話をかけますglTranslatef(-local_shift, 0.0, 0.0)。反対のことは決してしません。したがって、ビュー全体を右にシフトし続けます。そのため、絶対 x 位置を追跡する必要があります (新しいデータを正しい位置に配置するため)。この問題は最終的に、ラインがウィンドウの端と重なるアーティファクトにつながります。これを行うためのより良い方法があるに違いないと思いますよね?x 値を固定したまま、y 値を移動して更新するだけですか?

編集正弦波の例を削除し、より良い例に置き換えました。私の質問は、一般的に、スペース内でライン ストリップを最も効率的に移動する方法 (新しい値を追加しながら) に関するものです。したがって、「t -> 無限大の値を事前計算する」などの提案はここでは役に立ちません (家の前で測定された現在の温度を描画することもできます)。

EDIT2 各時間ステップの後、最初のポイントが削除され、新しいポイントが最後に追加されるこのおもちゃの例を検討してください。

t = 0

   * 
  * *    *
 *   **** *

 1234567890

t = 1

  * 
 * *    * *
    **** *

 2345678901

t = 2

 *        * 
  *    * *
   **** *

 3456789012

ここではシェーダーを使用できないと思いますよね?

編集 3: 2 つのライン ストリップの例。 2 つのライン ストリップを示す例

編集 4:ティムの回答に基づいて、次のコードを使用しています。これはうまく機能しますが、行が 2 つに分かれています ( の呼び出しが 2 つあるためglDrawArrays)。次の 2 つのスクリーンショットも参照してください。

完全なライン 不完全な行

# calculate the difference 
diff_first = x[1] - x[0]


''' first part of the line '''

# push the matrix
glPushMatrix()

move_to = -(diff_first * c)
print 'going to %d ' % (move_to)
glTranslatef(move_to, 0, 0)

# format of glVertexPointer: nbr points per vertex, data type, stride, byte offset
# calculate the offset into the Vertex
offset_bytes = c * BYTES_PER_POINT
stride = 0
glVertexPointer(2, GL_FLOAT, stride, offset_bytes)  

# format of glDrawArrays:  mode, Specifies the starting index in the enabled arrays, nbr of points
nbr_points_to_render = (nbr_points - c)
starting_point_in_above_selected_Vertex = 0
glDrawArrays(GL_POINTS, starting_point_in_above_selected_Vertex, nbr_points_to_render)  

# pop the matrix
glPopMatrix()


''' second part of the line '''

# push the matrix
glPushMatrix()

move_to = (nbr_points - c) * diff_first
print 'moving to %d ' %(move_to)
glTranslatef(move_to, 0, 0)


# select the vertex
offset_bytes = 0
stride = 0
glVertexPointer(2, GL_FLOAT, stride, offset_bytes)

# draw the line
nbr_points_to_render = c
starting_point_in_above_selected_Vertex = 0
glDrawArrays(GL_POINTS, starting_point_in_above_selected_Vertex, nbr_points_to_render)  


# pop the matrix
glPopMatrix()

# update counter
c += 1
if c == nbr_points:
    c = 0

EDIT5結果のソリューションは、明らかに画面全体に1行をレンダリングする必要があり、接続が失われている2行はありません。Tim による循環バッファー ソリューションは、プロットを移動する方法に関するソリューションを提供しますが、1 行ではなく 2 行になってしまいます。

4

3 に答える 3

8

改訂された質問に対する私の考えは次のとおりです。

1) glMapBuffer を使用して GPU 上のバッファをバインドし、(glBufferSubData を使用する代わりに) データを直接入力する必要がありますか? それとも、これはパフォーマンスに関して何の違いもありませんか?

おそらくglBufferSubDataを好むでしょうが、2つの間に重要なパフォーマンスがあることを認識していません。

あなたのケースで私が提案するのは、N フロートで VBO を作成し、それを循環バッファと同様に使用することです。バッファの「終わり」がどこにあるのかローカルにインデックスを保持し、更新ごとに「終わり」の下の値を新しい値に置き換え、ポインタをインクリメントします。このようにして、サイクルごとに 1 つのフロートを更新するだけで済みます。

それが完了したら、2x の変換と 2x の glDrawArrays/Elements を使用してこのバッファを描画できます。

10 個の要素の配列があり、バッファーの終了ポインターが要素 4 にあるとします。配列には次の 10 個の値が含まれます。x は定数値で、f(n -d ) はランダム サンプルです。dサイクル前:

0: (0, f(n-4) )
1: (1, f(n-3) )
2: (2, f(n-2) )
3: (3, f(n-1) )  
4: (4, f(n)   )  <-- end of buffer 
5: (5, f(n-9) )  <-- start of buffer
6: (6, f(n-8) )
7: (7, f(n-7) )
8: (8, f(n-6) )
9: (9, f(n-5) )

これを描画するには (疑似推測コードであり、正確には正しくない可能性があります):

glTranslatef( -end, 0, 0);
glDrawArrays( LINE_STRIP, end+1, (10-end)); //draw elems 5-9 shifted left by 4
glPopMatrix();
glTranslatef( end+1, 0, 0);
glDrawArrays(LINE_STRIP, 0, end); // draw elems 0-4 shifted right by 5 

次に、次のサイクルで、最も古い値を新しいランダム値に置き換え、循環バッファー ポインターを前方にシフトします。

2) glTranslatef を呼び出す代わりに、オブジェクト (ここではライン ストリップ) を移動するためにシェーダーを使用する必要がありますか? もしそうなら、そのようなシェーダーはどのように見えますか? (私のラインストリップは周期関数ではなく、ランダムデータを含んでいるので、シェーダーは間違った方法だと思います)。

#1 で説明した方法を使用する場合は、おそらくオプションです。ここで使用することに特に利点はありません。

3) ウィンドウのサイズが変更されるとどうなりますか? 縦横比を維持し、それに応じて頂点をスケーリングするにはどうすればよいですか? glViewport() は、x 方向ではなく、y 方向のスケーリングのみに役立ちます。ウィンドウが x 方向に再スケーリングされた場合、現在の実装では、ライン ストリップ全体の位置を再計算し (my_func を呼び出して新しい x 座標を取得)、それを GPU にアップロードする必要があります。これはもっとエレガントにできると思いますか?どうすればいいですか?

データを再計算する必要はありません。意味のある固定座標系ですべてのデータを定義し、射影行列を使用してこの範囲をウィンドウにマップするだけです。詳細がなければ、答えるのは難しいです。

4) 非整数値で glTranslatef を使用すると、ライン ストリップが数千のポイントで構成されている場合、画面がちらつき始めることに気付きました。これはおそらく、ライン ストリップを計算するために使用する細かい解像度が画面のピクセル解像度と一致しないためです。そのため、一部のポイントが前に表示されたり、他のポイントの後ろに表示されることがあります (これは、レンダリングを行わない場合に特に厄介です)。正弦波ですが、いくつかの「ランダム」データ)。これが起こらないようにするにはどうすればよいですか (1 ピクセルの整数倍で変換するという明白な解決策以外に)? ウィンドウのサイズが元の 800x800 ピクセルから 100x100 ピクセルに変更された場合でも、20 秒のライン ストリップを視覚化したい場合、x 方向にシフトすると、ちらつきがなくサブピクセル精度で何らかの方法で動作する必要があります。

あなたの仮定は正しいようです。ここで行うことは、ある種のアンチエイリアシングを有効にするか (その方法については他の投稿を読むことができます)、線を広くすることだと思います。

于 2012-05-29T20:43:54.783 に答える
4

ここで機能する可能性のあるものがいくつかあります。

  • glBindBuffer は、最も遅い OpenGL 操作の 1 つです (シェーダー、テクスチャなどの同様の呼び出しと同様)。
  • glTranslate は、頂点ユニットがすべてのポイントを乗算するモデルビュー マトリックスを調整します。したがって、乗算する行列を変更するだけです。代わりに頂点シェーダーを使用する場合は、頂点ごとに個別に変換する必要があります。つまり、glTranslate の方が高速です。ただし、実際には、これはあまり重要ではありません。
  • 描画するたびに多くのポイントで正弦関数を再計算すると、パフォーマンスの問題が発生します (特に、ソースを見ると、Python を使用しているように見えるため)。
  • 描画するたびに VBO を更新しているため、頂点配列よりも高速ではありません。頂点配列は中間モード (glVertex など) よりも高速ですが、表示リストや静的 VBO ほど高速ではありません。
  • コーディング エラーや冗長な呼び出しがどこかにある可能性があります。

私の評決:

CPUで正弦波とオフセットを計算しています。オーバーヘッドのほとんどは、描画するたびに異なるデータを計算してアップロードすることから来ていると強く思います。これは、不要な OpenGL 呼び出しと、場合によっては不要なローカル呼び出しと結び付いています。

私の推奨事項:

これは、GPU が輝くチャンスです。並列データでの関数値の計算は、(文字通り) GPU が最も得意とすることです。

関数を表す表示リストを作成することをお勧めしますが、すべての y 座標を 0 に設定します (したがって、y=0 の線に沿った一連の点になります)。次に、描画する正弦波ごとに、まったく同じ表示リストを 1 回描画します。通常、これは平坦なグラフを生成するだけですが、ポイントを垂直に正弦波に変換する頂点シェーダーを記述します。シェーダーは、正弦波のオフセット ("sin(x-offset)") を一様に取り、各頂点の y を変更するだけです。

これにより、コードが少なくとも 10 倍は高速になると思います。さらに、頂点の x 座標はすべて整数点にあるため (シェーダーは「sin(x-offset)」を計算することによって関数の空間で「変換」を行います)、浮動小数点値でオフセットするときにジッターが発生することはありません。

于 2012-04-21T06:04:20.750 に答える