gluSphere()を使用せずにOpenGLで球を描画しますか?


81

使用せずにOpenGLで球を描画する方法を説明するチュートリアルはありますgluSphere()か?

OpenGLの3Dチュートリアルの多くは、キューブ上にあります。私は検索しましたが、球を描くための解決策のほとんどはを使用することgluSphere()です。このサイトには球を描くためのコードがあるサイトもありますが、球を描く背後にある数学については説明していません。そのリンクの四角形ではなく、ポリゴンで球を描画する方法の他のバージョンもあります。しかし、繰り返しになりますが、球がコードでどのように描画されるのかわかりません。必要に応じて球を変更できるように、視覚化できるようにしたいと思います。


3
数学の説明(具体的には、球座標からデカルト座標への変換)について球座標を検索します。
ネッドビンガム

回答:


270

それを行う1つの方法は、三角形の辺を持つ正多面体(たとえば、八面体)から始めることです。次に、各三角形を取得し、次のように再帰的に小さな三角形に分割します。

再帰的に描かれた三角形

十分な数のポイントができたら、それらのベクトルを正規化して、それらがすべてソリッドの中心から一定の距離になるようにします。これにより、側面が球に似た形状に膨らみ、ポイントの数を増やすと滑らかさが増します。

ここでの正規化とは、別のポイントに対する角度が同じになるようにポイントを移動することを意味しますが、それらの間の距離は異なります。これが2次元の例です。

ここに画像の説明を入力してください

AとBは6ユニット離れています。しかし、Aから12単位離れた線AB上の点を見つけたいとします。

ここに画像の説明を入力してください

Cは、距離12のAに関してBの正規化された形式であると言えます。次のようなコードでCを取得できます。

#returns a point collinear to A and B, a given distance away from A. 
function normalize(a, b, length):
    #get the distance between a and b along the x and y axes
    dx = b.x - a.x
    dy = b.y - a.y
    #right now, sqrt(dx^2 + dy^2) = distance(a,b).
    #we want to modify them so that sqrt(dx^2 + dy^2) = the given length.
    dx = dx * length / distance(a,b)
    dy = dy * length / distance(a,b)
    point c =  new point
    c.x = a.x + dx
    c.y = a.y + dy
    return c

この正規化プロセスを多くの点で実行すると、すべて同じ点Aに関して、同じ距離Rで、正規化された点はすべて、中心Aと半径Rの円の円弧上にあります。

膨らんだ線分

ここで、黒い点は線から始まり、弧に「膨らみ」ます。

このプロセスは3次元に拡張できます。その場合、円ではなく球を取得します。正規化関数にdzコンポーネントを追加するだけです。

正規化されたポリゴン

レベル1の膨らんだ八面体 level 3 bulging octahedron

エプコットの球体を見ると、このテクニックが機能していることがわかります。丸みを帯びたように顔が膨らんだ十二面体です。


1
エプコット球へのリンクを削除したいです。すべての三角形が再び3つの二等辺三角形に細分されるため(sqrt(3)-細分割の最初の部分と同様)、初心者を混乱させる可能性があります。もっと良い例が見つかると思います。
クリスチャンラウ2011年

私はこれを自宅のマシンにうまく実装しています。仕事が終わったら、いくつかのスクリーンショットを編集させていただきます。
ケビン

アイデアをありがとう。しかし、ベクトルを正規化することで、球に似た形に側面を膨らませることができるという部分がわかりません。どうすれば側面を膨らませることができますか?
Carven 2011年

1
@xEnOn、正規化についてもう少し説明するために回答を編集しました。問題は、正規化が私が説明しようとしていたプロセスの実際の専門用語ではないことだと思います。そのため、他の場所でそれに関する詳細情報を見つけるのは難しいでしょう。申し訳ありません。
ケビン

1
ここでの「正規化」プロセスを説明するためのより良い方法は、ポイントが球に投影されていることです。また、結果は、正規化/射影が最後に1回だけ適用されるか(すべての細分割の後、ここで提案されているようです)、(再帰的な)細分割ステップでインターリーブされるかによって異なることに注意してください。最後に1回だけ投影すると、最初の八面体の頂点の近くにクラスター化された頂点が生成されますが、インターリーブされた細分割と投影では、頂点間の距離が均一になります。
タイラーストリーター2014

26

緯度と経度を使用して球を生成する一般的な方法についてさらに説明します(別の方法であるicospheresは、この記事の執筆時点で最も一般的な回答ですでに説明されています)。

球は、次のパラメトリック方程式で表すことができます。

Fuv)= [cos(u)* sin(v)* r、cos(v)* r、sin(u)* sin(v)* r]

どこ:

  • rは半径です。
  • uは経度で、0から2πの範囲です。そして
  • vは緯度で、0からπの範囲です。

次に、球を生成するには、一定の間隔でパラメトリック関数を評価する必要があります。

たとえば、経度の16の線を生成するには、u軸に沿って17のグリッド線があり、ステップはπ/ 8(2π/ 16)です(17番目の線は折り返されます)。

次の擬似コードは、一定の間隔でパラメトリック関数を評価することによって三角形メッシュを生成します(これはすべての球だけでなく、パラメトリック曲面関数に対して機能します)。

以下の擬似コードでは、UResolutionはU軸に沿ったグリッドポイントの数(ここでは経度の線)であり、VResolutionはV軸に沿ったグリッドポイントの数(ここでは緯度の線)です。

var startU=0
var startV=0
var endU=PI*2
var endV=PI
var stepU=(endU-startU)/UResolution // step size between U-points on the grid
var stepV=(endV-startV)/VResolution // step size between V-points on the grid
for(var i=0;i<UResolution;i++){ // U-points
 for(var j=0;j<VResolution;j++){ // V-points
 var u=i*stepU+startU
 var v=j*stepV+startV
 var un=(i+1==UResolution) ? EndU : (i+1)*stepU+startU
 var vn=(j+1==VResolution) ? EndV : (j+1)*stepV+startV
 // Find the four points of the grid
 // square by evaluating the parametric
 // surface function
 var p0=F(u, v)
 var p1=F(u, vn)
 var p2=F(un, v)
 var p3=F(un, vn)
 // NOTE: For spheres, the normal is just the normalized
 // version of each vertex point; this generally won't be the case for
 // other parametric surfaces.
 // Output the first triangle of this grid square
 triangle(p0, p2, p1)
 // Output the other triangle of this grid square
 triangle(p3, p1, p2)
 }
}

反対票は少し厳しいようです。これは、球のパラメトリック方程式による離散構築に言及している唯一の答えの1つです。また、球は、極に近づくにつれて縮小する円のスタックと見なすことができるため、理解しやすい場合があります。
Spacen Jasset 2015

2
こんにちは。p0、p1、p2、p3の各値の2番目は、uまたはunではなく、vまたはvnである必要があることを指摘したいと思います。
ニコール2016

9

サンプルのコードは簡単に説明されています。関数を調べる必要がありますvoid drawSphere(double r, int lats, int longs)

void drawSphere(double r, int lats, int longs) {
    int i, j;
    for(i = 0; i <= lats; i++) {
        double lat0 = M_PI * (-0.5 + (double) (i - 1) / lats);
        double z0  = sin(lat0);
        double zr0 =  cos(lat0);

        double lat1 = M_PI * (-0.5 + (double) i / lats);
        double z1 = sin(lat1);
        double zr1 = cos(lat1);

        glBegin(GL_QUAD_STRIP);
        for(j = 0; j <= longs; j++) {
            double lng = 2 * M_PI * (double) (j - 1) / longs;
            double x = cos(lng);
            double y = sin(lng);

            glNormal3f(x * zr0, y * zr0, z0);
            glVertex3f(r * x * zr0, r * y * zr0, r * z0);
            glNormal3f(x * zr1, y * zr1, z1);
            glVertex3f(r * x * zr1, r * y * zr1, r * z1);
        }
        glEnd();
    }
}

パラメータlatは、球に含める水平線の数と垂直線の数を定義しますlonr球の半径です。

これで、lat/に対して2回の反復lonが行われ、単純な三角法を使用して頂点座標が計算されます。

計算された頂点は、今使ってGPUに送信されているglVertex...()としてGL_QUAD_STRIP、あなたが以前に2て送信してクワッドを形成し、それぞれ2つの頂点を送信していることを意味します。

今理解しなければならないのは、三角関数がどのように機能するかだけですが、簡単に理解できると思います。


@PintoDoido:ある時点で死んだOPの元のリンクからのものでした。わかりやすくするために、Archive.orgがリンクを作成し、関数をこの回答に編集しました。
genpfault

2
半径がありません。
tomasantunes

1
最初のパラメータ「doubler」は使用されません。
ollydbg 2319

1
それは正しいです。コードサンプルは私の元の答えの一部ではありません。@genpfault:編集でコードサンプルを追加しました。例を修正していただけますか?
コンスタンチ

1
たくさんありがとう:)
コンスタンチ


1

キツネのようにずる賢くなりたい場合は、GLUのコードを0.5インチにすることができます。MesaGLソースコード(http://cgit.freedesktop.org/mesa/mesa/)を確認してください。


4
この文脈での「0.5インチ」の意味は理解しましたが、コックニーの押韻俗語に堪能でない他の95%の読者のために編集したいと思うかもしれません。
フレキソ

1

「三角ストリップ」を使用して「極」球を描画する方法の私の例では、ペアで点を描画することで構成されています。

const float PI = 3.141592f;
GLfloat x, y, z, alpha, beta; // Storage for coordinates and angles        
GLfloat radius = 60.0f;
int gradation = 20;

for (alpha = 0.0; alpha < GL_PI; alpha += PI/gradation)
{        
    glBegin(GL_TRIANGLE_STRIP);
    for (beta = 0.0; beta < 2.01*GL_PI; beta += PI/gradation)            
    {            
        x = radius*cos(beta)*sin(alpha);
        y = radius*sin(beta)*sin(alpha);
        z = radius*cos(alpha);
        glVertex3f(x, y, z);
        x = radius*cos(beta)*sin(alpha + PI/gradation);
        y = radius*sin(beta)*sin(alpha + PI/gradation);
        z = radius*cos(alpha + PI/gradation);            
        glVertex3f(x, y, z);            
    }        
    glEnd();
}

入力された最初のポイント(glVertex3f)は次のパラメトリック方程式であり、2番目のポイントは(次の平行から)アルファ角度の1ステップだけシフトされます。


1

受け入れられた答えは質問を解決しますが、最後に少し誤解があります。十二面体は、すべての面が同じ面積を持つ正多面体です(またはそうなる可能性があります)。それはエプコットの場合のようです(ちなみに、これは十二面体ではありませんで)。@Kevinによって提案されたソリューションはこの特性を提供しないので、私は提供するアプローチを追加できると思いました。

すべての頂点が同じ球にあり、すべての面が同様の面積/表面積を持つN面多面体を生成する良い方法は、二十面体から始めて、三角形の面を繰り返し細分割して正規化することです(受け入れられた回答で提案されているように) )。たとえば、十二面体は実際には切頂二十面体です。

正二十面体には20の面(12の頂点)があり、3つの黄金長方形から簡単に作成できます。八面体ではなく、これを開始点として使用するだけです。ここに例があります

これは少し話題から外れていることは知っていますが、誰かがこの特定のケースを探してここに来ると役立つかもしれないと思います。


0

1つの方法は、カメラに面するクワッドを作成し、球のように見えるものをレンダリングする頂点とフラグメントシェーダーを作成することです。インターネットで見つけることができる円/球の方程式を使用できます。

良い点の1つは、球のシルエットがどの角度から見ても同じに見えることです。ただし、球がパースビューの中心にない場合は、おそらく楕円のように見えます。このための方程式を解き、それらをフラグメントシェーディングに入れることができます。次に、球の周りの3D空間で実際にプレーヤーが移動している場合は、プレーヤーが移動するときにライトシェーディングを変更する必要があります。

誰かがこれを試したのか、それとも費用がかかりすぎて実用的でないのかについてコメントできますか?


これは、平行投影の下でのみ当てはまります。透視投影を使用する場合、レンダリング出力の球のシルエットは通常、円ではありません
Reto Koradi 2016

0

@Constantiniusの回答のPython適応:

lats = 10
longs = 10
r = 10

for i in range(lats):
    lat0 = pi * (-0.5 + i / lats)
    z0 = sin(lat0)
    zr0 = cos(lat0)

    lat1 = pi * (-0.5 + (i+1) / lats)
    z1 = sin(lat1)
    zr1 = cos(lat1)

    glBegin(GL_QUAD_STRIP)
    for j in range(longs+1):
        lng = 2 * pi * (j+1) / longs
        x = cos(lng)
        y = sin(lng)

        glNormal(x * zr0, y * zr0, z0)
        glVertex(r * x * zr0, r * y * zr0, r * z0)
        glNormal(x * zr1, y * zr1, z1)
        glVertex(r * x * zr1, r * y * zr1, r * z1)

    glEnd()

0
void draw_sphere()
{

    //              z
    //              |
    //               __
    //             /|          
    //              |           
    //              |           
    //              |    *      \
    //              | _ _| _ _ _ |    _y
    //             / \c  |n     /                    p3 --- p2
    //            /   \o |i                           |     |
    //           /     \s|s      z=sin(v)            p0 --- p1
    //         |/__              y=cos(v) *sin(u)
    //                           x=cos(v) *cos(u) 
    //       /
    //      x
    //


    double pi = 3.141592;
    double di =0.02;
    double dj =0.04;
    double du =di*2*pi;
    double dv =dj*pi;


    for (double i = 0; i < 1.0; i+=di)  //horizonal
    for (double j = 0; j < 1.0; j+=dj)  //vertical
    {       
        double u = i*2*pi;      //0     to  2pi
        double v = (j-0.5)*pi;  //-pi/2 to pi/2

        double  p[][3] = { 
            cos(v)     * cos(u)      ,cos(v)     * sin(u)       ,sin(v),
            cos(v)     * cos(u + du) ,cos(v)     * sin(u + du)  ,sin(v),
            cos(v + dv)* cos(u + du) ,cos(v + dv)* sin(u + du)  ,sin(v + dv),
            cos(v + dv)* cos(u)      ,cos(v + dv)* sin(u)       ,sin(v + dv)};

        //normal
        glNormal3d(cos(v+dv/2)*cos(u+du/2),cos(v+dv/2)*sin(u+du/2),sin(v+dv/2));

        glBegin(GL_POLYGON);
            glTexCoord2d(i,   j);    glVertex3dv(p[0]);
            glTexCoord2d(i+di,j);    glVertex3dv(p[1]);
            glTexCoord2d(i+di,j+dj); glVertex3dv(p[2]);
            glTexCoord2d(i,   j+dj); glVertex3dv(p[3]);
        glEnd();
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.