gluSphere()
を使用せずにOpenGLで球体を描画する方法を説明するチュートリアルはありますか?
OpenGLの3Dチュートリアルの多くは、キューブに関するものです。私は検索しましたが、球体を描くためのほとんどの解決策はgluSphere()
を使用することです。 このサイト に球を描くためのコードがあるサイトもありますが、球を描く背後にある数学を説明していません。また、リンク内の四角形ではなく多角形で球を描画する方法の他のバージョンもあります。しかし、繰り返しますが、球体がコードでどのように描画されるか理解できません。必要に応じて球体を修正できるように視覚化できるようにしたいと思います。
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コンポーネントをnormalize関数に追加するだけです。
Epcot の球体を見ると、このテクニックが動作しているのを見ることができます。丸みを帯びた外観にするために、顔が膨らんだ12面体です。
緯度と経度を使用して球を生成する一般的な方法について説明します(別の方法、icospheresは、この執筆の時間。)
球体は、次のパラメトリック方程式で表現できます。
[〜#〜] f [〜#〜](u、v)= [cos(u)* sin(v)* r、cos(v)* r、sin(u)* sin(v)* r]
どこ:
球体を生成するには、固定間隔でパラメトリック関数を評価する必要があります。
たとえば、16の経度線を生成するには、π= 8(2π/ 16)のステップでu軸に沿って17のグリッド線があります。 )(17行目は折り返します)。
次の擬似コードは、定期的な間隔でパラメトリック関数を評価することにより、三角形メッシュを生成します(これは、球だけでなく、anyパラメトリック表面関数に対しても機能します)。
以下の擬似コードでは、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)
}
}
サンプルのコードはすぐに説明されます。関数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
は、球体に必要な水平線の数とlon
垂直線の数を定義します。 r
は球体の半径です。
lat
/lon
の二重反復があり、単純な三角法を使用して頂点座標が計算されます。
計算された頂点は、glVertex...()
を_GL_QUAD_STRIP
_として使用してGPUに送信されます。これは、前に送信した2つの頂点とクワッドを形成する各2つの頂点を送信することを意味します。
ここで理解しなければならないのは、三角関数がどのように機能するかだけですが、簡単に理解できると思います。
キツネのようにになりたければ、GLUのコードを0.5インチにすることができます。 MesaGLソースコード(http://cgit.freedesktop.org/mesa/mesa/)をご覧ください。
受け入れられた答えは問題を解決しますが、最後には少し誤解があります。 12面体は、すべての面が同じ面積をもつ正多面体です(または可能性があります)。これは、エプコットの場合のようです(ちなみに、これは十二面体ではありません)。 @Kevinによって提案された解決策はこの特性を提供していないので、そうするアプローチを追加できると思いました。
すべての頂点が同じ球体に置かれ、すべての面が同様の面積/表面を持つN面の多面体を生成するための良い方法は、20面体と反復的に分割することから始まります三角形の面を正規化する(受け入れられた回答で提案されているように)。たとえば、12面体は実際には truncatedicosahedrons です。
正二十面体 20面(12頂点)を持ち、3つの黄金の長方形から簡単に構築できます。これは、八面体の代わりにこれを開始点として持つだけの問題です。例を見つけることができます here 。
私はこれが少し話題から外れていることを知っていますが、誰かがこの特定のケースを探してここに来れば助けになると思います。
OpenGLのレッドブックを参照してください: http://www.glprogramming.com/red/chapter02.html#name8 ポリゴンの細分化によって問題を解決します。
「三角形のストリップ」を使用して「極」球体を描画する方法の私の例では、ペアで点を描画します。
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つの方法は、カメラに面するクワッドを作成し、球体のように見えるものをレンダリングする頂点シェーダーとフラグメントシェーダーを作成することです。インターネットで見つけることができる円/球の方程式を使用できます。
良いことの1つは、球体のシルエットがどの角度から見ても同じように見えることです。ただし、球体がパースペクティブビューの中心にない場合は、おそらく楕円のように見えます。この方程式を計算して、フラグメントシェーディングに入れることができます。球体の周りの3D空間で実際にプレーヤーが移動している場合、プレーヤーの移動に合わせてライトシェーディングを変更する必要があります。
彼らがこれを試みた場合、または実用的であるには高すぎるかどうかについて、誰でもコメントできますか?
struct v3
{
double x,y,z;
v3(double _x=0, double _y=0, double _z=0){x=_x;y=_y;z=_z; }
v3 operator + ( v3 v) {return { x+v.x, y+v.y, z+v.z };}
v3 operator * ( double k) {return { x*k, y*k, z*k };}
v3 operator / ( double k) {return { x/k, y/k, z/k };}
v3 normalize(){
double L=sqrt( x*x+y*y+z*z);
return { x/L , y/L , z/L };}
};
void draw_spheree(double r,int adim)
{
// z
// |
// __
// /|
// |
// |
// | * \
// | _ _| _ _ _ | _y
// / \c |n / a4 --- a3
// / \o |i | |
// / \s|s z=sin(v) a1 --- a2
// |/__ y=cos(v) *sin(u)
// x=cos(v) *cos(u)
// /
// x
//
//glEnable(GL_LIGHTING);
//glEnable(GL_LIGHT0);
//glEnable(GL_TEXTURE_2D);
double pi=3.141592;
double d=pi/adim;
for(double u=-pi ; u<pi ; u+=d) //horizonal xy düzlemi Longitude -180 -180
for(double v=-pi/2; v<pi/2; v+=d) //vertical z aks Latitude -90 90
{
v3 a1 = { cos(v)*cos(u) ,cos(v)*sin(u) ,sin(v) },
a2 = { cos(v)*cos(u+d) ,cos(v)*sin(u+d) ,sin(v) },
a3 = { cos(v+d)*cos(u+d) ,cos(v+d)*sin(u+d) ,sin(v+d) },
a4 = { cos(v+d)*cos(u) ,cos(v+d)*sin(u) ,sin(v+d) };
v3 normal=(a1+a2+a3+a4)/4.0; //normal vector
a1=a1*r;
a2=a2*r;
a3=a3*r;
a4=a4*r;
double tu=(u+pi) / (2*pi); //0 to 1 horizonal
double tv=(v+pi/2)/ pi; //0 to 1 vertical
double td=1.0/2./adim;
glNormal3dv((double *)&normal);
glBegin(GL_POLYGON);
glTexCoord2d(tu ,tv ); glVertex3dv((double *) &a1);
glTexCoord2d(tu+td ,tv ); glVertex3dv((double *) &a2);
glTexCoord2d(tu+td ,tv+2*td ); glVertex3dv((double *) &a3);
glTexCoord2d(tu ,tv+2*td ); glVertex3dv((double *) &a4);
glEnd();
}
}