球を作成するためのアルゴリズム?


27

誰もが、la緯度線、lo経度線、および半径の量で球を手続き的に作成するアルゴリズムを持っていますrか?Unityと連携するために必要なので、頂点の位置を定義してから、インデックスを使用して三角形を定義する必要があります(詳細)。


編集

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

私はコードを統一して機能させることができました。しかし、私は何か間違ったことをしたかもしれないと思います。を上げるとdetailLevel、頂点とポリゴンを移動せずに追加するだけです。何か忘れましたか?


編集2

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

法線に沿ってメッシュをスケーリングしてみました。これは私が得たものです。私は何かが欠けていると思います。特定の法線のみをスケーリングすることになっていますか?


1
既存のオープンソース実装がどのようにそれを行うのかを見てみませんか?たとえば、Three.jsがメッシュを使用してそれをどのように行うかを見てください。
ブライス

3
小さな注意点としては:あなたは緯度/経度しなければならない場合を除き、あなたはほぼ確実にしていないしたいあなたが得る三角形は、あなたが他の方法で得たものより均一から更に多くなるために。(北極近くの三角形と赤道近くの三角形を比較します:どちらの場合も同じ数の三角形を使用して緯度の1行を回避していますが、極の近くでは緯度の円周が非常に小さいのに対して赤道ではそれはあなたの地球の全周です。)David Livelyの答えのようなテクニックは、一般的にはるかに優れています。
スティーブンスタドニッキー

1
分割後に頂点位置を正規化するわけではありません。例にはその部分を含めませんでした。正規化すると、それらはすべて中心から等距離になり、目的の曲線近似が作成されます。
3Dave

二十面体の中心で風船を膨らませると考えてください。バルーンがメッシュを押すと、バルーン(球)の形状に一致します。
3Dave

4
「正規化」とは、ベクトルの長さを1に設定することを意味しますvertices[i] = normalize(vertices[i])。ちなみに、これにより、新しい正しい法線も得られるため、normals[i] = vertices[i]後で行う必要があります。
サムホセバル

回答:


31

このようなものを取得するには:

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

二十面体(20面の通常のソリッド)を作成し、面を細分割して球体を取得します(以下のコードを参照)。

基本的には次のとおりです。

  • 通常のn面体(すべての面が同じサイズのソリッド)を作成します。二十面体を使用するのは、すべての面が同じサイズで、面の数が最も多いソリッドだからです。(そのための証拠がどこかにあります。本当に興味があれば、Googleにご連絡ください。)これにより、ほぼすべての顔が同じサイズの球体が得られ、テクスチャリングが少し簡単になります。

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

  • 各面を同じサイズの4つの面に分割します。これを行うたびに、モデル内の面の数が4倍になります。

    ///      i0
    ///     /  \
    ///    m02-m01
    ///   /  \ /  \
    /// i2---m12---i1

i0i1、およびi2元の三角形の頂点です。(実際には、頂点バッファーへのインデックスがありますが、それは別のトピックです)。m01エッジの中点は(i0,i1)、M12は、エッジの中点であり(i1,12)、そしてm02、明らかに、エッジの中点です(i0,i2)

面を細分割するときは常に、重複した頂点を作成しないようにしてください。各エッジは他の1つのソースフェースと共有されます(エッジはフェース間で共有されるため)。以下のコードは、作成された名前付きミッドポイントのディクショナリを維持し、新しいミッドポイントを作成するのではなく、使用可能なときに以前に作成されたミッドポイントのインデックスを返すことにより、それを説明します。

  • キューブに必要な面の数に達するまで繰り返します。

  • 完了したら、すべての頂点を正規化して、サーフェスを滑らかにします。これを行わないと、球ではなく高解像度の20面体が得られます。

  • 出来上がり!できました。得られたベクターとインデックスバッファを変換するVertexBufferIndexBuffer、として描きますDevice.DrawIndexedPrimitives()

モデルを作成するために「Sphere」クラスで使用するものは次のとおりです(XNAデータ型とC#ですが、明確なはずです)。

        var vectors = new List<Vector3>();
        var indices = new List<int>();

        GeometryProvider.Icosahedron(vectors, indices);

        for (var i = 0; i < _detailLevel; i++)
            GeometryProvider.Subdivide(vectors, indices, true);

        /// normalize vectors to "inflate" the icosahedron into a sphere.
        for (var i = 0; i < vectors.Count; i++)
            vectors[i]=Vector3.Normalize(vectors[i]);

そしてGeometryProviderクラス

public static class GeometryProvider
{

    private static int GetMidpointIndex(Dictionary<string, int> midpointIndices, List<Vector3> vertices, int i0, int i1)
    {

        var edgeKey = string.Format("{0}_{1}", Math.Min(i0, i1), Math.Max(i0, i1));

        var midpointIndex = -1;

        if (!midpointIndices.TryGetValue(edgeKey, out midpointIndex))
        {
            var v0 = vertices[i0];
            var v1 = vertices[i1];

            var midpoint = (v0 + v1) / 2f;

            if (vertices.Contains(midpoint))
                midpointIndex = vertices.IndexOf(midpoint);
            else
            {
                midpointIndex = vertices.Count;
                vertices.Add(midpoint);
                midpointIndices.Add(edgeKey, midpointIndex);
            }
        }


        return midpointIndex;

    }

    /// <remarks>
    ///      i0
    ///     /  \
    ///    m02-m01
    ///   /  \ /  \
    /// i2---m12---i1
    /// </remarks>
    /// <param name="vectors"></param>
    /// <param name="indices"></param>
    public static void Subdivide(List<Vector3> vectors, List<int> indices, bool removeSourceTriangles)
    {
        var midpointIndices = new Dictionary<string, int>();

        var newIndices = new List<int>(indices.Count * 4);

        if (!removeSourceTriangles)
            newIndices.AddRange(indices);

        for (var i = 0; i < indices.Count - 2; i += 3)
        {
            var i0 = indices[i];
            var i1 = indices[i + 1];
            var i2 = indices[i + 2];

            var m01 = GetMidpointIndex(midpointIndices, vectors, i0, i1);
            var m12 = GetMidpointIndex(midpointIndices, vectors, i1, i2);
            var m02 = GetMidpointIndex(midpointIndices, vectors, i2, i0);

            newIndices.AddRange(
                new[] {
                    i0,m01,m02
                    ,
                    i1,m12,m01
                    ,
                    i2,m02,m12
                    ,
                    m02,m01,m12
                }
                );

        }

        indices.Clear();
        indices.AddRange(newIndices);
    }

    /// <summary>
    /// create a regular icosahedron (20-sided polyhedron)
    /// </summary>
    /// <param name="primitiveType"></param>
    /// <param name="size"></param>
    /// <param name="vertices"></param>
    /// <param name="indices"></param>
    /// <remarks>
    /// You can create this programmatically instead of using the given vertex 
    /// and index list, but it's kind of a pain and rather pointless beyond a 
    /// learning exercise.
    /// </remarks>

    /// note: icosahedron definition may have come from the OpenGL red book. I don't recall where I found it. 
    public static void Icosahedron(List<Vector3> vertices, List<int> indices)
    {

        indices.AddRange(
            new int[]
            {
                0,4,1,
                0,9,4,
                9,5,4,
                4,5,8,
                4,8,1,
                8,10,1,
                8,3,10,
                5,3,8,
                5,2,3,
                2,7,3,
                7,10,3,
                7,6,10,
                7,11,6,
                11,0,6,
                0,1,6,
                6,1,10,
                9,0,11,
                9,11,2,
                9,2,5,
                7,2,11 
            }
            .Select(i => i + vertices.Count)
        );

        var X = 0.525731112119133606f;
        var Z = 0.850650808352039932f;

        vertices.AddRange(
            new[] 
            {
                new Vector3(-X, 0f, Z),
                new Vector3(X, 0f, Z),
                new Vector3(-X, 0f, -Z),
                new Vector3(X, 0f, -Z),
                new Vector3(0f, Z, X),
                new Vector3(0f, Z, -X),
                new Vector3(0f, -Z, X),
                new Vector3(0f, -Z, -X),
                new Vector3(Z, X, 0f),
                new Vector3(-Z, X, 0f),
                new Vector3(Z, -X, 0f),
                new Vector3(-Z, -X, 0f) 
            }
        );


    }



}

素晴らしい答え。ありがとう。わかりませんが、この統一コードですか?解像度を設定できる限り、緯度と経度は関係ありません。
ダニエルペンダーガスト

Unity(XNA)ではありませんが、頂点座標とインデックスリストを提供します。Vector3をUnityに相当するものに置き換えます。Subdivideの反復回数を調整して、解像度を設定します。各ループは、面の数に4を掛けます。2回または3回の繰り返しで、素敵な球体が得られます。
-3Dave

ああ、なるほど。Unity C#とほとんど同じです。いくつかの質問...インデックスを定義するときに、int配列内に配置するのはなぜですか?そして、何をし.Select(i => i + vertices.Count)ますか?
ダニエルペンダーガスト

.Select(i => i + vertices.Count)すべての私のために動作しません。XNAのみの機能ですか?
ダニエルペンダーガスト

1
定義に「System.Linqの使用」が含まれていることを確認してください。選択など
3Dave

5

球のパラメトリック定義を考えてみましょう:

球のパラメトリック定義

ここでthetaとphiは2つの増分角度であり、var tand var uおよびRx、Ry、Rzは3つのデカルト方向すべての独立した半径(半径)です。半径var rad

...シンボルがループの使用を示唆する反復を示しているという事実を考えてみましょう。との概念はstacksrows「何回繰り返すか」です。各反復でtまたはuの値が加算されるため、反復が多いほど値は小さくなり、したがって球体の曲率はより正確になります。

「球体描画」機能の前提条件は、次のパラメーターを指定することですint latitudes, int longitudes, float radius。事後条件(出力)は、計算された頂点を返すか適用することです。これをどのように使用するかに応じて、関数はvector3(3次元ベクトル)の配列を返すか、バージョン2.0より前の何らかの単純なOpenGLを使用している場合は、頂点をコンテキストに直接適用することができます。

注意:openGLで頂点を適用すると、次の関数が呼び出されますglVertex3f(x, y, z)。頂点を保存する場合、vector3(x, y, z)簡単に保存できるように新しいものを追加します。

また、緯度と経度のシステムを機能させるように要求するには、球体の定義を調整する必要がありました(基本的にzとyを切り替えます)が、これは定義が非常に順応性があり、自由に切り替えられることを示していますx、y、zパラメーターは、球体が描画される方向(緯度と経度の位置)を変更します。

次に、緯度と経度をどのように処理するかを見てみましょう。緯度は変数uで表され、0〜2πラジアン(360度)で繰り返します。したがって、その反復を次のようにコーディングできます。

float latitude_increment = 360.0f / latitudes;

for (float u = 0; u < 360.0f; u += latitude_increment) {
    // further code ...
}

これで、経度は変数で表され、t0〜π(180度)で繰り返されます。したがって、次のコードは前のコードに似ています。

float latitude_increment = 360.0f / latitudes;
float longitude_increment = 180.0f / longitudes;

for (float u = 0; u <= 360.0f; u += latitude_increment) {
    for (float t = 0; t <= 180.0f; t += longitude_increment) {
        // further code ...
    }
}

(ループがあることに注意してくださいインクルーシブパラメトリックな統合のための間隔を0から2πまであるので、そこの端末状態のインクルーシブを。あなたの条件は非包括している場合は、部分的な球体を取得します。)

さて、球体の簡単な定義に従って、変数定義を次のように導き出すことができます(仮定float rad = radius;):

float x = (float) (rad * Math.sin(Math.toRadians(t)) * Math.sin(Math.toRadians(u)));
float y = (float) (rad * Math.cos(Math.toRadians(t)));
float z = (float) (rad * Math.sin(Math.toRadians(t)) * Math.cos(Math.toRadians(u)));

もう1つの重要な警告!ほとんどの場合、何らかの形式のOpenGLを使用しますが、そうでない場合でも、これを行う必要があります。3次元のオブジェクトを定義するには、いくつかの頂点が必要です。これは一般に、計算可能な次の頂点を提供することで実現されます。

複数の頂点を使用して(プリミティブ)形状を定義する方法

上の図で、さまざまな座標がx+∂y+∂である方法だけで、目的の用途に合わせて他の3つの頂点を簡単に生成できます。他の頂点は次のとおりです(仮定float rad = radius;):

float x = (float) (rad * Math.sin(Math.toRadians(t + longitude_increment)) * Math.sin(Math.toRadians(u)));
float y = (float) (rad * Math.cos(Math.toRadians(t + longitude_increment)));
float z = (float) (rad * Math.sin(Math.toRadians(t + longitude_increment)) * Math.cos(Math.toRadians(u)));

float x = (float) (rad * Math.sin(Math.toRadians(t)) * Math.sin(Math.toRadians(u + latitude_increment)));
float y = (float) (rad * Math.cos(Math.toRadians(t)));
float z = (float) (rad * Math.sin(Math.toRadians(t)) * Math.cos(Math.toRadians(u + latitude_increment)));

float x = (float) (rad * Math.sin(Math.toRadians(t + longitude_increment)) * Math.sin(Math.toRadians(u + latitude_increment)));
float y = (float) (rad * Math.cos(Math.toRadians(t + longitude_increment)));
float z = (float) (rad * Math.sin(Math.toRadians(t + longitude_increment)) * Math.cos(Math.toRadians(u + latitude_increment)));

最後に、球体のすべての頂点を返す完全な機能を次に示します。2番目の機能は、OpenGLで機能するコードの実装を示しています(これはJavaScriptではなくCスタイルの構文です。これはすべてのCスタイル言語で動作します。 Unity使用時のC#を含む)。

static Vector3[] generateSphere(float radius, int latitudes, int longitudes) {

    float latitude_increment = 360.0f / latitudes;
    float longitude_increment = 180.0f / longitudes;

    // if this causes an error, consider changing the size to [(latitude + 1)*(longitudes + 1)], but this should work.
    Vector3[] vertices = new Vector3[latitude*longitudes];

    int counter = 0;

    for (float u = 0; u < 360.0f; u += latitude_increment) {
        for (float t = 0; t < 180.0f; t += longitude_increment) {

            float rad = radius;

            float x = (float) (rad * Math.sin(Math.toRadians(t)) * Math.sin(Math.toRadians(u)));
            float y = (float) (rad * Math.cos(Math.toRadians(t)));
            float z = (float) (rad * Math.sin(Math.toRadians(t)) * Math.cos(Math.toRadians(u)));

            vertices[counter++] = new Vector3(x, y, z);

        }
    }

    return vertices;

}

OpenGLコード:

static int createSphereBuffer(float radius, int latitudes, int longitudes) {

    int lst;

    lst = glGenLists(1);

    glNewList(lst, GL_COMPILE);
    {

        float latitude_increment = 360.0f / latitudes;
        float longitude_increment = 180.0f / longitudes;

        for (float u = 0; u < 360.0f; u += latitude_increment) {

            glBegin(GL_TRIANGLE_STRIP);

            for (float t = 0; t < 180.0f; t += longitude_increment) {

                float rad = radius;

                float x = (float) (rad * Math.sin(Math.toRadians(t)) * Math.sin(Math.toRadians(u)));
                float y = (float) (rad * Math.cos(Math.toRadians(t)));
                float z = (float) (rad * Math.sin(Math.toRadians(t)) * Math.cos(Math.toRadians(u)));

                vertex3f(x, y, z);

                float x1 = (float) (rad * Math.sin(Math.toRadians(t + longitude_increment)) * Math.sin(Math.toRadians(u + latitude_increment)));
                float y1 = (float) (rad * Math.cos(Math.toRadians(t + longitude_increment)));
                float z1 = (float) (rad * Math.sin(Math.toRadians(t + longitude_increment)) * Math.cos(Math.toRadians(u + latitude_increment)));

                vertex3f(x1, y1, z1);

            }

            glEnd();

        }

    }
    glEndList()

    return lst;

}

// to render VVVVVVVVV

// external variable in main file
static int sphereList = createSphereBuffer(desired parameters)

// called by the main program
void render() {

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glCallList(sphereList);

    // any additional rendering and buffer swapping if not handled already.

}

PSあなたはこの声明に気づいたかもしれませんrad = radius;。これにより、位置または角度に基づいて、ループ内で半径を変更できます。これは、望みの効果が惑星のようなものであれば、球体にノイズを適用して粗くすることができることを意味します。例えばfloat rad = radius * noise[x][y][z];

クロード・ヘンリー。


行 `float z =(float)(rad * Math.sin(Math.toRadians(t))* Math.cos(Math.toRadians(u)));`は間違っています。あなたはすでに斜辺でX、Yを計算しましたrad。今、あなたは三角形の片方の足を作り、その三角形の斜辺もであることを暗示していradます。これにより、半径がになりrad * sqrt(2)ます。
3Dave

@DavidLivelyそれを指摘してくれてありがとう、私はこれをしばらく前に書いたので、その悪いまたはまったく間違っていても驚かない。
claudehenry

何年も前の私の投稿の1つで間違いを見つけたとき、それはいつも楽しいです。それは起こります。:)
3Dave

4

楽しさと科学のために、私はしばらく前にこのようなものを作成して立方体を作りました。難しくありません。基本的に、頂点の円を作成する関数を使用し、球体を作成するために必要な半径で各高さに円を作成する高さの増分をステップスルーします。ここでは、キューブ用ではないようにコードを変更しました。

public static void makeSphere(float sphereRadius, Vector3f center, float heightStep, float degreeStep) {
    for (float y = center.y - sphereRadius; y <= center.y + sphereRadius; y+=heightStep) {
        double radius = SphereRadiusAtHeight(sphereRadius, y - center.y); //get the radius of the sphere at this height
        if (radius == 0) {//for the top and bottom points of the sphere add a single point
            addNewPoint((Math.sin(0) * radius) + center.x, y, (Math.cos(0) * radius) + center.z));
        } else { //otherwise step around the circle and add points at the specified degrees
            for (float d = 0; d <= 360; d += degreeStep) {
                addNewPoint((Math.sin(d) * radius) + center.x, y, (Math.cos(d) * radius) + center.z));
            }
        }
    }
}

public static double SphereRadiusAtHeight(double SphereRadius, double Height) {
    return Math.sqrt((SphereRadius * SphereRadius) - (Height * Height));
}

これで、このコードは緯度のポイントを作成するだけです。ただし、ほぼ同じコードを使用して経度線を作成できます。ただし、各反復間で回転し、それぞれで完全な円を描く必要がありますdegreeStep

申し訳ありませんが、これは完全な回答またはUnity固有のものではありませんが、うまくいけば開始できます。


緯度/経度の球体が必要な場合、これはかなり良いですが、最後のステップまで球体座標で作業することでそれを少し単純化できます。
-3Dave

1
@Davidに感謝します。球座標を使用したバージョンの作成に取り掛かる場合は、ここに投稿します。
マイケルハウス

3

単純な形状から始めるだけでなく、中心から隅までの距離がrのボックスにすることもできます。より詳細な球体を作成するには、すべてのポリゴンを再分割してから、中心からrの距離まで頂点を移動し、ベクトルが現在の位置を通過するようにします。

あなたの好みに十分な球形になるまで繰り返してください。


これは基本的に二十面体アプローチと同じですが、開始形状が異なります。私が言及していないと思うキューブから始めることの利点の1つは、キューブマップを使用して、テクスチャシームが球体メッシュのエッジと完全に整列することを知ることができるため、まともなUVマップを構築することは実質的に簡単です。
スティーブンスタドニッキー

@StevenStadnickiキューブに関して私が抱えている唯一の問題は、いくつかの細分化の後、顔のサイズが大きく異なる傾向があることです。
-3Dave

@DavidLivelyそれは細分化方法に大きく依存します-立方体の正方形の面を均等なグリッドに切り刻んで外側に投影/正規化すると、それは本当ですが、顔を不均一にグリッド化すると、実際に投影は、エッジの円弧に沿って等間隔に配置されます。それはかなりうまくいくことがわかりました。
スティーブンスタドニッキー

おはようございます!
3Dave

@EricJohansson btw、先生として、これは明らかにこれまで細分化方法を見たことがない人にとって非常に重要な洞察であることに言及することを強いられたと感じます。あなたは次の12時間、人類に対する私の信仰を新たにしました。
3Dave

2

実際に必要なのは3Dジオメトリですか、それとも形状だけですか?

単一のクワッドを使用して「フェイク」球を作成できます。それに円を付けて、正しくシェーディングしてください。これには、カメラまでの距離や解像度に関係なく、必要な解像度が正確に得られるという利点があります。

こちらにチュートリアルがあります


1
素敵なハックですが、テクスチャする必要がある場合は失敗します。
-3Dave

@DavidLivelyポリゴンを個別にテクスチャリングする必要がない限り、その回転に基づいてピクセルごとのテクスチャ座標を計算できるはずです。
デビッドC.ビショップ

@DavidCBishop表面の「レンズ」を考慮する必要があります-テクセル座標は遠近法により円の境界の近くに絞られます-その時点で回転を偽造しています。また、これには、頂点シェーダーで実行できるピクセルシェーダーにさらに多くの作業を移動する必要があります(そして、VSの方がはるかに安価であることがわかっています!)。
3Dave

0

これは、球体の等間隔の任意の数の頂点のコードです。オレンジの皮のように、球体の周りに点線をらせん状に巻いています。その後、頂点に参加する方法はあなた次第です。ループ内の隣のドットを各三角形の2つとして使用し、3つ目は球の周りを上下に比例する1つのねじれであることがわかります。より良い方法を知っていますか?

var spherevertices = vector3 generic list...

public var numvertices= 1234;
var size = .03;  

function sphere ( N:float){//<--- N is the number of vertices i.e 123

var inc =  Mathf.PI  * (3 - Mathf.Sqrt(5));
var off = 2 / N;
for (var k = 0; k < (N); k++)
{
    var y = k * off - 1 + (off / 2);
    var r = Mathf.Sqrt(1 - y*y);
    var phi = k * inc;
    var pos = Vector3((Mathf.Cos(phi)*r*size), y*size, Mathf.Sin(phi)*r*size); 

    spherevertices   add pos...

}

};


-1

デビッドの答えは絶対に正しいですが、別の視点を提供したいと思います。

手続き型コンテンツ生成の割り当てでは、(特に)二十面体と従来の細分化された球体を比較しました。これらの手続き的に生成された球体を見てください:

素晴らしい球

どちらも完全に有効な球体のように見えますよね?それでは、ワイヤーフレームを見てみましょう。

すごいね

うわー、そこで何が起こったの?2番目の球体のワイヤフレームバージョンは非常に密度が高いため、テクスチャがあります。秘密をお伝えします。2番目のバージョンは20面体です。ほぼ完璧な球体ですが、高価です。

球1は、x軸に31個のサブディビジョン、z軸に31個のサブディビジョンを使用して、合計3,844個の面を作成します。

Sphere 2は5つの再帰的なサブディビジョンを使用し、合計109,220の面になります。

しかし、大丈夫、それは本当に公平ではありません。品質を大幅に縮小しましょう。

ゴツゴツ

球1は、x軸に5つのサブディビジョン、z軸に5つのサブディビジョンを使用して、合計100の面を作成します。

Sphere 2は、合計100の面に対して、0の再帰的なサブディビジョンを使用します。

それらは同じ量の顔を使用しますが、私の意見では、左側の球体はより良く見えます。ゴツゴツした感じが少なく、丸みが増しています。両方の方法で生成する面の数を見てみましょう。

正二十面体:

  • レベル0-100面
  • レベル1-420面
  • レベル2-1,700面
  • レベル3-6,820面
  • レベル4-27,300面
  • レベル5-109,220面

細分化された球:

  • YZ:5〜100面
  • YZ:10〜400面
  • YZ:15〜900面
  • YZ:20-1,600面
  • YZ:25-2,500面
  • YZ:30-3,600面

ご覧のとおり、正二十面体は指数関数的に3乗に増加します!これは、すべての三角形について、3つの新しい三角形に分割する必要があるためです。

真実は次のとおりです。二十面体が与える精度は必要ありません。どちらもはるかに難しい問題を隠しているためです:3D球上の2D平面のテクスチャリング。トップは次のようになります。

トップ吸う

左上に、使用されているテクスチャが表示されます。偶然にも、手続き的に生成されています。(ねえ、それは手続き型生成のコースでしたよね?)

ひどいですね。まあ、これは得ようとしているのと同じくらい良いです。ほとんどの人がこれを正しく理解さえしていないため、テクスチャマッピングで最高の評価を得ました。

したがって、球を生成するためにコサインとサインの使用を検討してください。同じ量のディテールに対して、生成される顔がはるかに少なくなります。


6
私はこれしか支持できないと思う。icosphereは指数関数的にスケーリングしますか?指数関数的にスケーリングする必要があると判断したからです。UV球は、同じ量のディテールに対して、イコ球よりも少ない面を生成しますか?それは間違っている、絶対に間違っている、全く逆です。
サムホセバル

4
サブディビジョンは再帰的である必要はありません。三角形のエッジを必要な数の等しい部分に分割できます。Nパーツを使用すると、N*N新しい三角形が作成されます。これは、UV球を使用した場合とまったく同じように、2次曲線です。
サムホセバル

6
また、あなたが言う球体は「ゴツゴツでなく、丸い」ように見えることを最高の角度から見ていることを付け加えなければならず、その主張も不誠実です。上から見た球体で同じスクリーンショットを実行するだけで、意味がわかります。
サムホセバル

4
また、二十面体の数字が正しく表示されません。レベル0は(定義により)20面、次に80、320、1280などです。任意の数とパターンに分割できます。モデルの滑らかさは最終的に、最終結果の面の数と分布によって決まります(それらを生成するために使用される方法に関係なく)。各面のサイズを可能な限り均一に保ちます(極はありません)スクイージング)ビュー角度に関係なく一貫したプロファイルを維持します。それに加えて、細分化コードがはるかに単純であるという事実(imho)
...-3Dave

2
この答えにはいくつかの作業が組み込まれているため、それを下げることについて少し気分が悪くなります。しかし、それは完全にまったく間違っているので、そうしなければなりません。FullHDで画面全体を完全に円形に見せるIcosphereには、5つのサブディビジョンが必要で、基本的な20面体にはサブディビジョンがありません。サブディビジョンのない20面体には、100の面はなく、20があります。Icosa=20。これが名前です!各サブディビジョンは、顔の数に4を掛けます。したがって、1-> 80、2-> 320、3-> 1280、4-> 5120、5-> 20,480になります。地球圏では、等しく丸い球体を得るために少なくとも40'000の面が必要です。
ピーター

-1

以下のスクリプトは、n個の多角形を含む正二十面体を作成します... base12。また、多角形を個別のメッシュに分割し、verts-duplicatesとpolygonsの合計を計算します。

似たようなものが見つからなかったので、これを作成しました。スクリプトをGameObjectに添付し、エディターでサブディビジョンを設定するだけです。次にノイズ修正に取り組んでいます。


/* Creates an initial Icosahedron with 12 vertices...
 * ...Adapted from https://medium.com/@peter_winslow/creating-procedural-icosahedrons-in-unity-part-1-df83ecb12e91
 * ...And a couple other Icosahedron C# for Unity scripts
 * 
 * Allows an Icosahedron to be created with multiple separate polygon meshes
 * I used a dictionary of Dictionary<int, List<Vector3>> to represent the 
 * Polygon index and the vertice index
 * polygon[0] corresponds to vertice[0]
 * so that all vertices in dictionary vertice[0] will correspond to the polygons in polygon[0]
 * 
 * If you need help understanding Dictionaries
 * https://msdn.microsoft.com/en-us/library/xfhwa508(v=vs.110).aspx
 * 
 * --I used dictionaries because I didn't know what programming instrument to use, so there may be more
 * elegant or efficient ways to go about this.
 * 
 * Essentially int represents the index, and 
 * List<Vector3> represents the actual Vector3 Transforms of the triangle
 * OR List<Vector3> in the polygon dictionary will act as a reference to the indice/index number of the vertices
 * 
 * For example the polygon dictionary at key[0] will contain a list of Vector3's representing polygons
 * ... Vector3.x , Vector3.y, Vector3.z in the polygon list would represent the 3 indexes of the vertice[0] list
 * AKA the three Vector3 transforms that make up the triangle
 *    .
 *  ./_\.
 * 
 * Create a new GameObject and attach this script
 *  -The folders for the material and saving of the mesh data will be created automatically 
 *    -Line 374/448
 * 
 * numOfMainTriangles will represent the individual meshes created
 * numOfSubdivisionsWithinEachTriangle represents the number of subdivisions within each mesh
 * 
 * Before running with Save Icosahedron checked be aware that it can take several minutes to 
 *   generate and save all the meshes depending on the level of divisions
 * 
 * There may be a faster way to save assets - Line 430 - AssetDatabase.CreateAsset(asset,path);
 * */

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class UnityIcosahedronGenerator : MonoBehaviour {
    IcosahedronGenerator icosahedron;
    public const int possibleSubDivisions = 7;
    public static readonly int[] supportedChunkSizes = { 20, 80, 320, 1280, 5120, 20480, 81920};

    [Range(0, possibleSubDivisions - 1)]
    public int numOfMainTriangles = 0;
    [Range(0,possibleSubDivisions - 1)]
    public int numOfSubdivisionsWithinEachTriangle = 0;
    public bool saveIcosahedron = false;

    // Use this for initialization
    void Start() {
        icosahedron = ScriptableObject.CreateInstance<IcosahedronGenerator>();

        // 0 = 12 verts, 20 tris
        icosahedron.GenBaseIcosahedron();
        icosahedron.SeparateAllPolygons();

        // 0 = 12 verts, 20 tris - Already Generated with GenBaseIcosahedron()
        // 1 = 42 verts, 80 tris
        // 2 = 162 verts, 320 tris
        // 3 = 642 verts, 1280 tris
        // 5 = 2562 verts, 5120 tris
        // 5 = 10242 verts, 20480 tris
        // 6 = 40962verts, 81920 tris
        if (numOfMainTriangles > 0) {
            icosahedron.Subdivide(numOfMainTriangles);
        }
        icosahedron.SeparateAllPolygons();

        if (numOfSubdivisionsWithinEachTriangle > 0) {
            icosahedron.Subdivide(numOfSubdivisionsWithinEachTriangle);
        }

        icosahedron.CalculateMesh(this.gameObject, numOfMainTriangles,numOfSubdivisionsWithinEachTriangle, saveIcosahedron);
        icosahedron.DisplayVertAndPolygonCount();
    }
}

public class Vector3Dictionary {
    public List<Vector3> vector3List;
    public Dictionary<int, List<Vector3>> vector3Dictionary;

    public Vector3Dictionary() {
        vector3Dictionary = new Dictionary<int, List<Vector3>>();
        return;
    }

    public void Vector3DictionaryList(int x, int y, int z) {
        vector3List = new List<Vector3>();

        vector3List.Add(new Vector3(x, y, z));
        vector3Dictionary.Add(vector3Dictionary.Count, vector3List);

        return;
    }

    public void Vector3DictionaryList(int index, Vector3 vertice) {
        vector3List = new List<Vector3>();

        if (vector3Dictionary.ContainsKey(index)) {
            vector3List = vector3Dictionary[index];
            vector3List.Add(vertice);
            vector3Dictionary[index] = vector3List;
        } else {
            vector3List.Add(vertice);
            vector3Dictionary.Add(index, vector3List);
        }

        return;
    }

    public void Vector3DictionaryList(int index, List<Vector3> vertice, bool list) {
        vector3List = new List<Vector3>();

        if (vector3Dictionary.ContainsKey(index)) {
            vector3List = vector3Dictionary[index];
            for (int a = 0; a < vertice.Count; a++) {
                vector3List.Add(vertice[a]);
            }
            vector3Dictionary[index] = vector3List;
        } else {
            for (int a = 0; a < vertice.Count; a++) {
                vector3List.Add(vertice[a]);
            }
            vector3Dictionary.Add(index, vector3List);
        }

        return;
    }

    public void Vector3DictionaryList(int index, int x, int y, int z) {
        vector3List = new List<Vector3>();

        if (vector3Dictionary.ContainsKey(index)) {
            vector3List = vector3Dictionary[index];
            vector3List.Add(new Vector3(x, y, z));
            vector3Dictionary[index] = vector3List;
        } else {
            vector3List.Add(new Vector3(x, y, z));
            vector3Dictionary.Add(index, vector3List);
        }

        return;
    }

    public void Vector3DictionaryList(int index, float x, float y, float z, bool replace) {
        if (replace) {
            vector3List = new List<Vector3>();

            vector3List.Add(new Vector3(x, y, z));
            vector3Dictionary[index] = vector3List;
        }

        return;
    }
}

public class IcosahedronGenerator : ScriptableObject {
    public Vector3Dictionary icosahedronPolygonDict;
    public Vector3Dictionary icosahedronVerticeDict;
    public bool firstRun = true;

    public void GenBaseIcosahedron() {
        icosahedronPolygonDict = new Vector3Dictionary();
        icosahedronVerticeDict = new Vector3Dictionary();

        // An icosahedron has 12 vertices, and
        // since it's completely symmetrical the
        // formula for calculating them is kind of
        // symmetrical too:

        float t = (1.0f + Mathf.Sqrt(5.0f)) / 2.0f;

        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(-1, t, 0).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(1, t, 0).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(-1, -t, 0).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(1, -t, 0).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(0, -1, t).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(0, 1, t).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(0, -1, -t).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(0, 1, -t).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(t, 0, -1).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(t, 0, 1).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(-t, 0, -1).normalized);
        icosahedronVerticeDict.Vector3DictionaryList(0, new Vector3(-t, 0, 1).normalized);

        // And here's the formula for the 20 sides,
        // referencing the 12 vertices we just created.
        // Each side will be placed in it's own dictionary key.
        // The first number is the key/index, and the next 3 numbers reference the vertice index
        icosahedronPolygonDict.Vector3DictionaryList(0, 0, 11, 5);
        icosahedronPolygonDict.Vector3DictionaryList(1, 0, 5, 1);
        icosahedronPolygonDict.Vector3DictionaryList(2, 0, 1, 7);
        icosahedronPolygonDict.Vector3DictionaryList(3, 0, 7, 10);
        icosahedronPolygonDict.Vector3DictionaryList(4, 0, 10, 11);
        icosahedronPolygonDict.Vector3DictionaryList(5, 1, 5, 9);
        icosahedronPolygonDict.Vector3DictionaryList(6, 5, 11, 4);
        icosahedronPolygonDict.Vector3DictionaryList(7, 11, 10, 2);
        icosahedronPolygonDict.Vector3DictionaryList(8, 10, 7, 6);
        icosahedronPolygonDict.Vector3DictionaryList(9, 7, 1, 8);
        icosahedronPolygonDict.Vector3DictionaryList(10, 3, 9, 4);
        icosahedronPolygonDict.Vector3DictionaryList(11, 3, 4, 2);
        icosahedronPolygonDict.Vector3DictionaryList(12, 3, 2, 6);
        icosahedronPolygonDict.Vector3DictionaryList(13, 3, 6, 8);
        icosahedronPolygonDict.Vector3DictionaryList(14, 3, 8, 9);
        icosahedronPolygonDict.Vector3DictionaryList(15, 4, 9, 5);
        icosahedronPolygonDict.Vector3DictionaryList(16, 2, 4, 11);
        icosahedronPolygonDict.Vector3DictionaryList(17, 6, 2, 10);
        icosahedronPolygonDict.Vector3DictionaryList(18, 8, 6, 7);
        icosahedronPolygonDict.Vector3DictionaryList(19, 9, 8, 1);

        return;
    }

    public void SeparateAllPolygons(){
        // Separates all polygons and vertex keys/indicies into their own key/index
        // For example if the numOfMainTriangles is set to 2,
        // This function will separate each polygon/triangle into it's own index
        // By looping through all polygons in each dictionary key/index

        List<Vector3> originalPolygons = new List<Vector3>();
        List<Vector3> originalVertices = new List<Vector3>();
        List<Vector3> newVertices = new List<Vector3>();
        Vector3Dictionary tempIcosahedronPolygonDict = new Vector3Dictionary();
        Vector3Dictionary tempIcosahedronVerticeDict = new Vector3Dictionary();

        // Cycles through the polygon list
        for (int i = 0; i < icosahedronPolygonDict.vector3Dictionary.Count; i++) {
            originalPolygons = new List<Vector3>();
            originalVertices = new List<Vector3>();

            // Loads all the polygons in a certain index/key
            originalPolygons = icosahedronPolygonDict.vector3Dictionary[i];

            // Since the original script was set up without a dictionary index
            // It was easier to loop all the original triangle vertices into index 0
            // Thus the first time this function runs, all initial vertices will be 
            // redistributed to the correct indicies/index/key

            if (firstRun) {
                originalVertices = icosahedronVerticeDict.vector3Dictionary[0];
            } else {
                // i - 1 to account for the first iteration of pre-set vertices
                originalVertices = icosahedronVerticeDict.vector3Dictionary[i];
            }

            // Loops through all the polygons in a specific Dictionary key/index
            for (int a = 0; a < originalPolygons.Count; a++){
                newVertices = new List<Vector3>();

                int x = (int)originalPolygons[a].x;
                int y = (int)originalPolygons[a].y;
                int z = (int)originalPolygons[a].z;

                // Adds three vertices/transforms for each polygon in the list
                newVertices.Add(originalVertices[x]);
                newVertices.Add(originalVertices[y]);
                newVertices.Add(originalVertices[z]);

                // Overwrites the Polygon indices from their original locations
                // index (20,11,5) for example would become (0,1,2) to correspond to the
                // three new Vector3's added to the list.
                // In the case of this function there will only be 3 Vector3's associated to each dictionary key
                tempIcosahedronPolygonDict.Vector3DictionaryList(0, 1, 2);

                // sets the index to the size of the temp dictionary list
                int tempIndex = tempIcosahedronPolygonDict.vector3Dictionary.Count;
                // adds the new vertices to the corresponding same key in the vertice index
                // which corresponds to the same key/index as the polygon dictionary
                tempIcosahedronVerticeDict.Vector3DictionaryList(tempIndex - 1, newVertices, true);
            }
        }
        firstRun = !firstRun;

        // Sets the temp dictionarys as the main dictionaries
        icosahedronVerticeDict = tempIcosahedronVerticeDict;
        icosahedronPolygonDict = tempIcosahedronPolygonDict;
    }

    public void Subdivide(int recursions) {
        // Divides each triangle into 4 triangles, and replaces the Dictionary entry

        var midPointCache = new Dictionary<int, int>();
        int polyDictIndex = 0;
        List<Vector3> originalPolygons = new List<Vector3>();
        List<Vector3> newPolygons;

        for (int x = 0; x < recursions; x++) {
            polyDictIndex = icosahedronPolygonDict.vector3Dictionary.Count;
            for (int i = 0; i < polyDictIndex; i++) {
                newPolygons = new List<Vector3>();
                midPointCache = new Dictionary<int, int>();
                originalPolygons = icosahedronPolygonDict.vector3Dictionary[i];

                for (int z = 0; z < originalPolygons.Count; z++) {
                    int a = (int)originalPolygons[z].x;
                    int b = (int)originalPolygons[z].y;
                    int c = (int)originalPolygons[z].z;

                    // Use GetMidPointIndex to either create a
                    // new vertex between two old vertices, or
                    // find the one that was already created.
                    int ab = GetMidPointIndex(i,midPointCache, a, b);
                    int bc = GetMidPointIndex(i,midPointCache, b, c);
                    int ca = GetMidPointIndex(i,midPointCache, c, a);

                    // Create the four new polygons using our original
                    // three vertices, and the three new midpoints.
                    newPolygons.Add(new Vector3(a, ab, ca));
                    newPolygons.Add(new Vector3(b, bc, ab));
                    newPolygons.Add(new Vector3(c, ca, bc));
                    newPolygons.Add(new Vector3(ab, bc, ca));
                }
                // Replace all our old polygons with the new set of
                // subdivided ones.
                icosahedronPolygonDict.vector3Dictionary[i] = newPolygons;
            }
        }
        return;
    }

    int GetMidPointIndex(int polyIndex, Dictionary<int, int> cache, int indexA, int indexB) {
        // We create a key out of the two original indices
        // by storing the smaller index in the upper two bytes
        // of an integer, and the larger index in the lower two
        // bytes. By sorting them according to whichever is smaller
        // we ensure that this function returns the same result
        // whether you call
        // GetMidPointIndex(cache, 5, 9)
        // or...
        // GetMidPointIndex(cache, 9, 5)

        int smallerIndex = Mathf.Min(indexA, indexB);
        int greaterIndex = Mathf.Max(indexA, indexB);
        int key = (smallerIndex << 16) + greaterIndex;

        // If a midpoint is already defined, just return it.
        int ret;
        if (cache.TryGetValue(key, out ret))
            return ret;

        // If we're here, it's because a midpoint for these two
        // vertices hasn't been created yet. Let's do that now!
        List<Vector3> tempVertList = icosahedronVerticeDict.vector3Dictionary[polyIndex];

        Vector3 p1 = tempVertList[indexA];
        Vector3 p2 = tempVertList[indexB];
        Vector3 middle = Vector3.Lerp(p1, p2, 0.5f).normalized;

        ret = tempVertList.Count;
        tempVertList.Add(middle);
        icosahedronVerticeDict.vector3Dictionary[polyIndex] = tempVertList;

        cache.Add(key, ret);
        return ret;
    }

    public void CalculateMesh(GameObject icosahedron, int numOfMainTriangles, int numOfSubdivisionsWithinEachTriangle, bool saveIcosahedron) {
        GameObject meshChunk;
        List<Vector3> meshPolyList;
        List<Vector3> meshVertList;
        List<int> triList;

        CreateFolders(numOfMainTriangles, numOfSubdivisionsWithinEachTriangle);
        CreateMaterial();

        // Loads a material from the Assets/Resources/ folder so that it can be saved with the prefab later
        Material material = Resources.Load("BlankSphere", typeof(Material)) as Material;

        int polyDictIndex = icosahedronPolygonDict.vector3Dictionary.Count;

        // Used to assign the child objects as well as to be saved as the .prefab
        // Sets the name
        icosahedron.gameObject.name = "Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle;

        for (int i = 0; i < polyDictIndex; i++) {
            meshPolyList = new List<Vector3>();
            meshVertList = new List<Vector3>();
            triList = new List<int>();
            // Assigns the polygon and vertex indices
            meshPolyList = icosahedronPolygonDict.vector3Dictionary[i];
            meshVertList = icosahedronVerticeDict.vector3Dictionary[i];

            // Sets the child gameobject parameters
            meshChunk = new GameObject("MeshChunk");
            meshChunk.transform.parent = icosahedron.gameObject.transform;
            meshChunk.transform.localPosition = new Vector3(0, 0, 0);
            meshChunk.AddComponent<MeshFilter>();
            meshChunk.AddComponent<MeshRenderer>();
            meshChunk.GetComponent<MeshRenderer>().material = material;
            meshChunk.AddComponent<MeshCollider>();
            Mesh mesh = meshChunk.GetComponent<MeshFilter>().mesh;

            // Adds the triangles to the list
            for (int z = 0; z < meshPolyList.Count; z++) {
                triList.Add((int)meshPolyList[z].x);
                triList.Add((int)meshPolyList[z].y);
                triList.Add((int)meshPolyList[z].z);
            }

            mesh.vertices = meshVertList.ToArray();
            mesh.triangles = triList.ToArray();
            mesh.uv = new Vector2[meshVertList.Count];

            /*
            //Not Needed because all normals have been calculated already
            Vector3[] _normals = new Vector3[meshVertList.Count];
            for (int d = 0; d < _normals.Length; d++){
                _normals[d] = meshVertList[d].normalized;
            }
            mesh.normals = _normals;
            */

            mesh.normals = meshVertList.ToArray();

            mesh.RecalculateBounds();

            // Saves each chunk mesh to a specified folder
            // The folder must exist
            if (saveIcosahedron) {
                string sphereAssetName = "icosahedronChunk" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle + "_" + i + ".asset";
                AssetDatabase.CreateAsset(mesh, "Assets/Icosahedrons/Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle + "/" + sphereAssetName);
                AssetDatabase.SaveAssets();
            }
        }

        // Removes the script for the prefab save
        // Saves the prefab to a specified folder
        // The folder must exist
        if (saveIcosahedron) {
            DestroyImmediate(icosahedron.GetComponent<UnityIcosahedronGenerator>());
            PrefabUtility.CreatePrefab("Assets/Icosahedrons/Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle + "/Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle + ".prefab", icosahedron);
        }

        return;
    }

    void CreateFolders(int numOfMainTriangles, int numOfSubdivisionsWithinEachTriangle){
        // Creates the folders if they don't exist
        if (!AssetDatabase.IsValidFolder("Assets/Icosahedrons")) {
            AssetDatabase.CreateFolder("Assets", "Icosahedrons");
        }
        if (!AssetDatabase.IsValidFolder("Assets/Icosahedrons/Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle)) {
            AssetDatabase.CreateFolder("Assets/Icosahedrons", "Icosahedron" + numOfMainTriangles + "Recursion" + numOfSubdivisionsWithinEachTriangle);
        }
        if (!AssetDatabase.IsValidFolder("Assets/Resources")) {
            AssetDatabase.CreateFolder("Assets", "Resources");
        }

        return;
    }

    static void CreateMaterial() {
        if (Resources.Load("BlankSphere", typeof(Material)) == null) {
            // Create a simple material asset if one does not exist
            Material material = new Material(Shader.Find("Standard"));
            material.color = Color.blue;
            AssetDatabase.CreateAsset(material, "Assets/Resources/BlankSphere.mat");
        }

        return;
    }

    // Displays the Total Polygon/Triangle and Vertice Count
    public void DisplayVertAndPolygonCount(){
        List<Vector3> tempVertices;
        HashSet<Vector3> verticeHash = new HashSet<Vector3>();

        int polygonCount = 0;
        List<Vector3> tempPolygons;

        // Saves Vertices to a hashset to ensure no duplicate vertices are counted
        for (int a = 0; a < icosahedronVerticeDict.vector3Dictionary.Count; a++) {
            tempVertices = new List<Vector3>();
            tempVertices = icosahedronVerticeDict.vector3Dictionary[a];
            for (int b = 0; b < tempVertices.Count; b++) {
                verticeHash.Add(tempVertices[b]);
            }
        }

        for (int a = 0; a < icosahedronPolygonDict.vector3Dictionary.Count; a++) {
            tempPolygons = new List<Vector3>();
            tempPolygons = icosahedronPolygonDict.vector3Dictionary[a];
            for (int b = 0; b < tempPolygons.Count; b++) {
                polygonCount++;
            }
        }

        Debug.Log("Vertice Count: " + verticeHash.Count);
        Debug.Log("Polygon Count: " + polygonCount);

        return;
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.