Статьи

Как создать 3D-рельеф для iOS

Обзор: как создать 3D-рельеф для iOS. Это предполагает базовые знания программирования на iOS (как создать проект и добавить в него файлы).

Я работал над приложением для iOS, которое использует cocos3d и алгоритм генерации холмов Роберта , которые создают хорошие холмы, которые напоминают мне о ранних уровнях Mario. Например:

Хороший стартовый мир

Мои прекрасные зеленые комочки.

Вы можете скачать библиотеку поколения холмов с Github . Я преобразовал код Роба из C ++ в Objective-C и добавил поддержку прямоугольной местности (вместо просто квадратной местности).

Чтобы создать свой собственный ландшафт, загрузите и установите cocos3d и создайте новый проект cocos3d2 под названием «TerrainGenerator». Это создаст 3D «Привет, мир!» заявление. Нажмите кнопку «Play», чтобы убедиться, что он работает:

Привет мир

Абсолютно великолепна.

Чтобы добавить код генерации холма, загрузите библиотеку с Github и добавьте HillTerrain.m и HillTerrain.h в свой проект.

Замените содержимое TerrainGeneratorScene.m следующим образом :

#import "TerrainGeneratorScene.h"
#import "CC3PODResourceNode.h"
#import "CC3ActionInterval.h"
#import "CC3MeshNode.h"
#import "CC3Camera.h"
#import "CC3Light.h"
#import "CC3ParametricMeshNodes.h"
#import "HillTerrain.h"
 
 
@implementation TerrainGeneratorScene
 
-(void) dealloc {
  [super dealloc];
}
 
-(void) initializeScene {
  // Lights
  CC3Light* lamp = [CC3Light nodeWithName: @"Lamp"];
  lamp.location = cc3v(-2.0, 0.0, 0.0);
  lamp.isDirectionalOnly = NO;
 
  // Camera
  CC3Camera* cam = [CC3Camera nodeWithName: @"Camera"];
  cam.location = cc3v(128.0, 0.0, 300.0);
  [cam rotateByAngle:15 aroundAxis:cc3v(1.0, 0.0, 0.0)];
  [cam addChild: lamp];
  [self addChild: cam];
 
  // Action! (Well, okay, just terrain.)
  [self createTerrain];
 
  // OpenGL fun.
  [self createGLBuffers];
  [self releaseRedundantContent];
  [self selectShaderPrograms];
}
 
-(void) createTerrain {
  HillTerrain *terrain = [[HillTerrain alloc] init];
  [terrain generate];
 
  CC3MeshNode *mesh = [[[CC3MeshNode alloc] init] retain];
  mesh.vertexContentTypes =
    kCC3VertexContentLocation | kCC3VertexContentColor | kCC3VertexContentNormal;
  [mesh populateAsRectangleWithSize:CGSizeMake(terrain.size.width, terrain.size.height)
                  andRelativeOrigin:CGPointZero
                    andTessellation:CC3TessellationMake(terrain.size.width-1,
                                                        terrain.size.height-1)];
 
  int count = 0;
  for (int j = 0; j < terrain.size.height; ++j) {
    for (int i = 0; i < terrain.size.width; ++i) {
      float height = [terrain getCell:cc3p(i,j)];
      [mesh setVertexLocation:CC3VectorMake(i, j, height * 128) at:count];
      [mesh setVertexColor4F:ccc4f(0.0, height, 1.0-height, 1.0) at:count];
      ++count;
    }
  }
 
  [mesh setShouldUseLighting:YES];
  [self addChild:mesh];
  [terrain release];
}
 
@end

Сохраните и запустите, и вы должны увидеть что-то вроде этого:

Местность по умолчанию

Привет, кусковой мир!

В методе createTerrain происходит вся магия, поэтому давайте рассмотрим его по одному разделу за раз:

Генерация местности

Вот часть, которая на самом деле генерирует ландшафт:

  HillTerrain *terrain = [[HillTerrain alloc] init];
  [terrain generate];

Вы можете изменить любые параметры здесь, например, давайте попробуем другое начальное число:

  HillTerrain *terrain = [[HillTerrain alloc] init];
  [terrain setSeed:123]; // Because I'm so creative.
  [terrain generate];

Это создает хороший диапазон гор:

Горный хребет

Величественный Blob Range

Создание чего-то, что OpenGL может использовать

Следующая часть отображает эту местность на массив вершин. Сначала мы создаем сетку , то есть поверхность, которую мы хотим отобразить. Вы можете изобразить это как рыболовную сеть, которую будете драпировать по ландшафту.

  CC3MeshNode *mesh = [[[CC3MeshNode alloc] init] retain];

Указание типов хранилища нам понадобится

Далее, мы должны сообщить Cocos3d, какой тип информации мы хотим сохранить об этом меше. Для нас это включает в себя:

  1. Расположение каждой вершины, потому что в этом смысл всего этого.
  2. Цвет каждой вершины, потому что он будет варьироваться в зависимости от высоты.
  3. Нормали каждой вершины, которая является направление свет будет отражаться от него. Это автоматически заполняется Cocos3d, поэтому вам не нужно беспокоиться об этом сейчас. Тем не менее, интересно попробовать удалить этот и посмотреть, как выглядит местность.
  mesh.vertexContentTypes =
    kCC3VertexContentLocation | kCC3VertexContentColor | kCC3VertexContentNormal;

Выделение сетки

Теперь, когда мы сообщили Cocos3d о типах хранилищ, которые нам нужны, мы сообщим ему форму и размер для выделения: прямоугольную сетку того же размера, что и наш ландшафт.

  [mesh populateAsRectangleWithSize:CGSizeMake(terrain.size.width, terrain.size.height)
                  andRelativeOrigin:CGPointZero
                    andTessellation:CC3TessellationMake(terrain.size.width-1,
                                                        terrain.size.height-1)];

andTessellation указывает, сколько будет квадратов на местности, поэтому вы хотите, чтобы это число было на единицу меньше числа имеющихся у вас вершин. Например, если у вас было тесселяция 1 квадрат на 1 квадрат, вам понадобится 4 вершины (по одной на каждый угол квадрата). Таким образом, -1с.

Отображение местности на сетку

Сначала мы получаем высоту в одной из координат карты. Это будет число от 0 до 1, поэтому мы увеличим его до чего-то разумного, учитывая наш масштаб ( height*128в данном случае).

      float height = [terrain getCell:cc3p(i,j)];
      [mesh setVertexLocation:CC3VectorMake(i, j, height * 128) at:count];

Затем мы устанавливаем цвет, делая самые высокие местоположения самыми зелеными, а самые низкие — голубыми:

      [mesh setVertexColor4F:ccc4f(0.0, height, 1.0-height, 1.0) at:count];

at:countУказывает индекс в мы заходящего вершины. Cocos3d хранит все вершины в большом массиве, и это его индекс.

Включение света

Наконец, мы говорим OpenGL наложить свет на меш. (Попробуйте удалить эту строку и посмотрите, как она выглядит.)

  [mesh setShouldUseLighting:YES];

И мы добавляем меш как ребенка в нашу сцену и освобождаем память ландшафта:

  [self addChild:mesh];
  [terrain release];

Это оно! Если у кого-то есть какие-либо предложения или улучшения, пожалуйста, не стесняйтесь подать запрос на получение . Я еще не слишком доволен поколением островов: я не нашел правильную комбинацию вариантов, поэтому мои острова — это не просто комочки.

Если вам интересно, алгоритм Роба довольно интересный и простой для понимания. Я рекомендую прочитать его описание, если вы планируете использовать его в проекте.