Алгоритм создания сфер?


27

У кого-нибудь есть алгоритм для создания сферы процедурно с laколичеством линий широты, loколичеством линий долготы и радиусом r? Мне нужно, чтобы он работал с Unity, поэтому необходимо определить позиции вершин, а затем треугольники, определенные с помощью индексов ( подробнее ).


РЕДАКТИРОВАТЬ

введите описание изображения здесь

Мне удалось заставить код работать в единстве. Но я думаю, что мог сделать что-то не так. Когда я поднимаю detailLevel, Все, что он делает, это добавляет больше вершин и многоугольников, не перемещая их. Я что-то забыл?


РЕДАКТИРОВАТЬ 2

введите описание изображения здесь

Я попытался масштабировать сетку вдоль ее нормалей. Это то, что я получил. Я думаю, что что-то упустил. Я должен масштабировать только определенные нормы?


1
Почему бы вам не посмотреть, как это делают существующие реализации с открытым исходным кодом? Посмотрите, как Three.js делает это, используя, например, сетки.
Брайс

3
В качестве небольшого примечания: если вы не должны использовать широту / долготу, вы почти наверняка не захотите , потому что треугольники, которые вы получите, будут намного дальше от униформы, чем те, которые вы получаете другими методами. (Сравните треугольники около северного полюса с треугольниками около экватора: вы используете одинаковое количество треугольников для обхода одной линии широты в любом случае, но около полюса эта линия широты имеет очень маленькую окружность, тогда как на экваторе это полная окружность вашего земного шара.) Такие методы, как в ответе Дэвида Лайвли, как правило, намного лучше.
Стивен Стадницкий,

1
Вы не нормализуете положение вершин после разделения. Я не включил эту часть в мой пример. Нормализация делает их все равноудаленными от центра, что создает искомое приближение кривой.
3Dave

Подумайте надуть воздушный шар в центре икосаэдра. По мере того, как воздушный шар толкает нашу сетку, он соответствует форме воздушного шара (сферы).
3Dave

4
«Нормализация» означает установку длины вектора равной 1. Вам нужно сделать что-то вроде vertices[i] = normalize(vertices[i]). Кстати, это также дает вам ваши новые, правильные нормы, так что вы должны сделать это normals[i] = vertices[i]потом.
Сам Хочевар

Ответы:


31

Чтобы получить что-то вроде этого:

введите описание изображения здесь

Создайте икосаэдр (20-стороннее правильное тело) и разделите грани, чтобы получить сферу (см. Код ниже).

Идея в основном:

  • Создайте обычный n-адрон (твердое тело, в котором все лица одного размера). Я использую икосаэдр, потому что это твердое тело с наибольшим числом граней, где каждое лицо имеет одинаковый размер. (Существует доказательство тому, что где-то там. Не стесняйтесь в Google, если вам действительно любопытно.) Это даст вам сферу, где почти все лица имеют одинаковый размер, делая текстурирование немного легче.

введите описание изображения здесь

  • Разделите каждое лицо на четыре лица одинакового размера. Каждый раз, когда вы делаете это, он в четыре раза увеличивает количество граней в модели.

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

i0, i1и i2являются вершинами исходного треугольника. (На самом деле, индексы в буфер вершин, но это уже другая тема). m01середина ребра (i0,i1), m12 - середина ребра (i1,12)и m02, очевидно, середина ребра (i0,i2).

Всякий раз, когда вы разделяете лицо, убедитесь, что вы не создаете дублирующиеся вершины. Каждая средняя точка будет совместно использоваться одной другой исходной гранью (поскольку ребра совместно используются гранями). Приведенный ниже код объясняет это, поддерживая созданный словарь именованных средних точек и возвращая индекс ранее созданной средней точки, когда она доступна, вместо создания новой.

  • Повторяйте, пока не достигнете нужного количества граней для вашего куба.

  • Когда вы закончите, нормализуйте все вершины, чтобы сгладить поверхность. Если вы этого не сделаете, вы просто получите икосаэдр с высоким разрешением вместо сферы.

  • Вуаля! Вы сделали Преобразуйте результирующие векторные и индексные буферы в а VertexBufferи IndexBufferи рисуйте с помощью Device.DrawIndexedPrimitives().

Вот что вы использовали бы в своем классе "Сфера" для создания модели (типы данных 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
Убедитесь, что вы
включили

5

Рассмотрим параметрическое определение сферы:

параметрическое определение сферы

где theta и phi - два увеличивающихся угла, которые мы будем называть var tи, var uа Rx, Ry и Rz - независимые радиусы (радиусы) во всех трех декартовых направлениях, которые в случае сферы будут определены как один радиус var rad.

Давайте теперь рассмотрим тот факт, что ...символ указывает на итерацию, которая намекает на использование цикла. Концепция stacksи rows«сколько раз вы будете повторять». Поскольку каждая итерация добавляет значение t или u, чем больше итераций, тем меньше значение, следовательно, тем точнее кривизна сферы.

«Сферы рисование» предусловия функции является иметь следующие данные параметров: int latitudes, int longitudes, float radius. Условия отправки (выходные данные) должны возвращать или применять вычисленные вершины. В зависимости от того, как вы намереваетесь использовать это, функция может возвращать массив vector3(трехмерных векторов) или, если вы используете какой-то простой OpenGL, до версии 2.0, вы можете напрямую применить вершины к контексту.

NB Применение вершины в 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 ...
}

Теперь долготы представлены переменной tи повторяются от 0 до π (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 ...
    }
}

(Обратите внимание , что петли Inclusive из там терминального состояния, потому что интервал для параметрического интегрирования от 0 до 2л Inclusive . Вы получите частичную сферу , если ваши условия не включены.)

Теперь, следуя простому определению сферы, мы можем получить определение переменной следующим образом (предположим 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)));

Еще одно важное предупреждение! В большинстве случаев вы будете использовать какую-то форму OpenGL, и даже если это не так, вам все равно может понадобиться это сделать. Для трехмерного объекта необходимо определить несколько вершин. Обычно это достигается путем предоставления следующей вершины, которая вычислима.

как несколько вершин используются для определения (примитивной) формы

То, как на рисунке выше разные координаты, x+∂и y+∂мы можем легко сгенерировать три другие вершины для любого желаемого использования. Другие вершины (предположим 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)));

Наконец, вот рабочая полноценная функция, которая будет возвращать все вершины сферы, а вторая показывает работающую реализацию кода OpenGL (это синтаксис в стиле C, а не JavaScript, это должно работать со всеми языками в стиле C, в том числе C # при использовании Unity).

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

это всегда весело, когда я нахожу ошибку в одном из моих постов лет назад. Бывает. :)
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
Спасибо, Дэвид. Я согласен, если мне удастся написать версию с использованием сферических координат, я опубликую ее здесь.
MichaelHouse

3

Не могли бы вы просто начать с простой формы, может быть поле с г расстояние от центра до угла. Чтобы сделать более детальную сферу, разделите все многоугольники и затем переместите вершины на расстояние r от центра, чтобы вектор прошел их текущее положение.

Продолжайте повторять, пока не станет достаточно сферическим для ваших вкусов.


По сути, это то же самое, что икосаэдрический подход, только с другой начальной формой. Было упомянуто одно преимущество, начинающееся с куба, о котором я не думаю: существенно проще создавать приличные карты UV, потому что вы можете использовать кубическую карту и знать, что ваши текстурные швы будут идеально совмещаться с краями в вашей сфере.
Стивен Стадницки

@StevenStadnicki Единственная проблема, с которой я сталкиваюсь с кубами, заключается в том, что после нескольких делений лица имеют тенденцию к очень разным размерам.
3Dave

@DavidLively Это во многом зависит от того, как вы подразделяете - если вы нарезаете квадратные грани вашего куба на четную сетку, а затем проецируете наружу / нормализуете, то это правда, но если вы выстроите свои грани неравномерно, то вы можете сделать проекция должна быть равномерно распределена по дугам ребер; это работает довольно хорошо.
Стивен Стадницки

@StevenStadnicki отличный!
3Dave

@EricJohansson, кстати, как учитель, я чувствую себя обязанным упомянуть, что это довольно значимое понимание для кого-то, кто, очевидно, раньше не видел метод подразделения. Вы обновили мою веру в человечество на следующие 12 часов.
3Dave

2

Вам действительно нужна трехмерная геометрия или просто форма?

Вы можете создать «поддельную» сферу, используя один квад. Просто нарисуйте круг и закрасьте его правильно. Это имеет то преимущество, что у него будет именно то разрешение, которое требуется независимо от расстояния до камеры или разрешения.

Здесь есть учебник .


1
Хороший взлом, но не получается, если вам нужно его текстурировать.
3Dave

@DavidLively Должна быть возможность рассчитывать координаты текстуры для каждого пикселя на основе его поворота, если вам не нужно текстурировать полигоны по отдельности.
Дэвид С. Бишоп

@DavidCBishop Вы должны были бы учитывать «линзирование» поверхности - тексельные координаты сжимаются близко к границе круга из-за перспективы - в какой момент вы имитируете вращение. Кроме того, для этого требуется перенести гораздо больше работы в пиксельный шейдер, что можно выполнить в вершинном шейдере (и мы все знаем, что VS намного дешевле!).
3Dave

0

Вот некоторый код для любого числа равноотстоящих вершин сферы, он, как апельсиновая корка, обматывает линию точек вокруг сферы по спирали. потом, как вы присоединитесь к вершинам, зависит от вас. вы можете использовать соседние точки в цикле как 2 каждого треугольника, а затем найти, что третья будет пропорциональной крутизной вокруг сферы выше или ниже ... вы также можете делать треугольники петлей и ближайшим соседом на ней, кто-то делает знаете лучший способ?

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

Хотя Дэвид абсолютно прав в своем ответе, я хочу предложить другую точку зрения.

Для моего задания по генерации процедурного контента я посмотрел (помимо прочего) на икосаэдр по сравнению с более традиционными подразделенными сферами. Посмотрите на эти процедурно сгенерированные сферы:

Потрясающие сферы

Оба выглядят как совершенно правильные сферы, верно? Что ж, давайте посмотрим на их каркасы:

Вау, это плотно

Вау, что там произошло? Каркасная версия второй сферы настолько плотная , что выглядит текстурированной! Я открою вам секрет: вторая версия - это икосаэдр. Это почти идеальная сфера, но по высокой цене.

Сфера 1 использует 31 подразделение по оси x и 31 подразделение по оси z, в общей сложности 3844 грани.

Сфера 2 использует 5 рекурсивных подразделений, в общей сложности 109 220 лиц.

Но ладно, это не совсем справедливо. Давайте значительно снизим качество:

комковатый

Сфера 1 использует 5 подразделов на оси x и 5 подразделов на оси z, всего 100 граней.

Сфера 2 использует 0 рекурсивных подразделений, в общей сложности 100 граней.

Они используют одинаковое количество лиц, но, на мой взгляд, сфера слева выглядит лучше. Это выглядит менее комковатым и намного более круглым. Давайте посмотрим, сколько лиц мы генерируем с помощью обоих методов.

Икосаэдр:

  • Уровень 0 - 100 лиц
  • Уровень 1 - 420 лиц
  • Уровень 2 - 1700 лиц
  • Уровень 3 - 6820 лиц
  • Уровень 4 - 27 300 лиц
  • Уровень 5 - 109 220 лиц

Подразделенная сфера:

  • YZ: 5 - 100 лиц
  • YZ: 10 - 400 лиц
  • YZ: 15 - 900 лиц
  • YZ: 20 - 1600 лиц
  • YZ: 25 - 2500 лиц
  • YZ: 30 - 3600 лиц

Как вы можете видеть, икосаэдр увеличивается в гранях с экспоненциальной скоростью, до третьей степени! Это потому, что для каждого треугольника мы должны разделить их на три новых треугольника.

Правда в том, что вам не нужна точность, которую вам даст икосаэдр. Потому что они оба скрывают гораздо более сложную проблему: текстурирование 2D-плоскости на 3D-сфере. Вот как выглядит верх:

Топ отстой

В левом верхнем углу вы можете увидеть используемую текстуру. По совпадению, это также генерируется процедурно. (Эй, это был курс по процессуальному поколению, верно?)

Это выглядит ужасно, верно? Ну, это так же хорошо, как и собирается. Я получил высшие оценки за наложение текстур, потому что большинство людей даже не понимают это правильно.

Поэтому, пожалуйста, рассмотрите возможность использования косинуса и синуса для создания сферы. Он генерирует намного меньше граней для того же количества деталей.


6
Боюсь, я могу только понизить это. Икосфера масштабируется экспоненциально? Это только потому, что вы решили, что ваш должен масштабироваться в геометрической прогрессии. УФ сфера генерирует меньше граней, чем икосфера для того же количества деталей? Это неправильно, абсолютно неправильно, полностью задом наперед.
Сэм Хочевар

4
Подразделение не должно быть рекурсивным. Вы можете разделить край треугольника на столько равных частей, сколько пожелаете. Использование Nдеталей даст вам N*Nновые треугольники, которые являются квадратичными, точно так же, как вы делаете с УФ-сферой.
Сэм Хочевар

6
Я также должен добавить, что сфера, которая, как вы говорите, выглядит «менее комковатой и намного более круглой», рассматривается с лучшей стороны, что также делает это утверждение нечестным. Просто сделайте тот же скриншот с сферами, просматриваемыми сверху, чтобы понять, что я имею в виду.
Сэм Хочевар

4
Кроме того, ваши числа икосаэдра не выглядят правильно. Уровень 0 - это 20 лиц (по определению), затем 80, 320, 1280 и т. Д. Вы можете подразделить любое число и любой рисунок, который вы хотите. В конечном итоге гладкость модели будет определяться количеством и распределением граней в конечном результате (независимо от метода, использованного для их создания), и мы хотим, чтобы размер каждой грани был как можно более равномерным (без полярности). сжимая) для поддержания согласованного профиля независимо от угла обзора. Добавьте к этому тот факт, что код подразделения намного проще (imho) ...
3Dave

2
В этот ответ была вложена некоторая работа, которая заставляет меня чувствовать себя немного плохо из-за того, что я отказался от него. Но это совершенно и совершенно неправильно, поэтому я должен. Идеально округлая икосфера, которая заполняет весь экран в FullHD, требует 5 подразделений, при этом базовый икосаэдр не имеет подразделений. Икосаэдр без подразделений не имеет 100 граней, у него 20. Икоса = 20. Это имя! Каждое подразделение умножает количество граней на 4, поэтому 1-> 80, 2-> 320, 3-> 1280, 4-> 5120, 5-> 20,480. В геосфере нам нужно как минимум 40 000 граней, чтобы получить одинаково круглую сферу.
Питер - Унбан Роберт Харви

-1

Приведенный ниже скрипт создаст икосаэдр с n полигонами ... основание 12. Он также разделит полигоны на отдельные сетки и вычислит общее количество вершин-дубликатов и полигонов.

Я не мог найти ничего подобного, поэтому я создал это. Просто прикрепите скрипт к 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;
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.