Эффективный метод рендеринга кубов с разными текстурами на каждой стороне для игры, похожей на Minecraft?

Я пытаюсь решить, какой самый эффективный способ отрисовать кучу кубов с разными текстурами в игре, похожей на Minecraft.

Я обнаружил отрисовку экземпляров. Что я сделал, так это создал единую «модель куба», в которой хранятся все вершины, нормали и координаты текстуры для куба, я создал из этого буфер массива и один раз передал его в графический процессор. Затем я создал массив структур (вектор перевода, индекс текстуры) и использую экземплярный рендеринг, чтобы перерисовывать один и тот же куб снова и снова, каждый раз переводя и с соответствующей текстурой.

(Надеюсь, Нотч не возражает против того, чтобы я использовал его текстуры, пока я не сделаю свою собственную)

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

  1. Используйте разные модели для каждого типа блока. Таким образом, я могу указать разные координаты текстуры для каждой из вершин. Я все еще могу использовать инстансный рендеринг, но мне пришлось бы делать отдельный проход для каждого типа блока.
  2. Передайте 6 "текстурных индексов" (по 1 на каждую грань) вместо 1 на каждый блок.

Второе решение требует передачи гораздо большего количества, возможно, избыточных данных. Я не уверен, насколько велики преимущества инстансного рендеринга... поэтому я не знаю, что лучше сделать, скажем, до 256 "проходов" (по 1 для каждого типа блока) или "один большой pass" со всеми данными и визуализировать каждый блок за один раз.

Или, может быть, есть другой метод, о котором я не знаю?


person mpen    schedule 05.02.2012    source источник


Ответы (4)


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

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

Вы можете сделать что-то на основе разреженных воксельных октодеревьев. Это намного больше работы, но вы сможете эффективно и точно определить, какие части вашего мира видны.

person Piotr Praszmo    schedule 05.02.2012
comment
Хороший вопрос, но здесь я бы дал шанс простому отбрасыванию экземпляров с использованием геометрических шейдеров и преобразованию обратной связи, которые позже можно комбинировать с октодеревьями и разделами. - person Sam; 06.02.2012
comment
Внезапно все стало намного сложнее. Сначала я собирался решить, как сделать текстуры, а затем изучить методы обрезки. Я думал, что в конечном итоге обрежу невидимые кубы, но я думаю, что делать это на основе лиц теперь имеет больше смысла, когда вы упомянули об этом. Однако это поднимает несколько новых вопросов. По сути, это означает, что я выполняю отбраковку/обрезку на процессоре, не так ли? Не будет ли это а) медленнее и б) повторной реализации всей логики, которую обычно делает для меня графический процессор? Что касается октодеревьев, вы предлагаете мне хранить данные о местности в такой структуре данных или я буду использовать ее для обрезки... - person mpen; 06.02.2012
comment
...Только? В этой статье предполагается, что октодеревья на самом деле неэффективны для хранения ландшафта, поскольку каждый доступ теперь имеет накладные расходы на выполнение через m слоев октодерева. Наконец, если убрать большую часть кубов/граней, что произойдет, когда я захочу сделать освещение? Не будут ли отсутствовать некоторые важные данные в GPU/GLSL? т. е. лицо может быть не видно зрителю, но оно все же может блокировать свет? Или мои мечты о красивых тенях такого масштаба изначально нереалистичны? - person mpen; 06.02.2012
comment
Когда дело доходит до света и тени, для которых требуются отдельные преобразованные проходы рендеринга, отсечение экземпляров может применяться для каждого из них. Пока источники света и соответствующие им освещенные кубы не перемещаются/не изменяются, проход карты отбраковки/тени не нужно переделывать. Я не думаю, что это нереально. Чтобы найти, в каком световом конусе или разделе куб изменился (взаимодействия с пользователем), центральный процессор является подходящим местом для этого и инициирует поддерживаемую графическим процессором обработку этого раздела. - person Sam; 06.02.2012
comment
Я предлагаю реализовать экземплярный рендеринг в повторно используемом классе «секций», который может иметь форму, необходимую для любых тестов пересечения, необходимых для кубов в источниках света или разделов. Этот класс раздела должен иметь свой собственный массив экземпляров, содержащий каждый куб, который он пересекает. Это будет заполнено при инициализации и изменено, если куб выходит или входит. - person Sam; 06.02.2012
comment
@Mark, вы удаляете только те лица, которые никогда не видны. Таким образом, вам не нужно повторно загружать их, когда камера вращается. Вы определенно можете перенести проход отбраковки в геометрический шейдер, вам просто нужно найти эффективный способ отправки кубов в GPU. Может 3D текстуры? Вы правы насчет октодеревьев, они хорошо работают, если мир очень неравномерный, чего нет в Minecraft. - person Piotr Praszmo; 06.02.2012
comment
@Banthar: Если я должен обрезать лица, которые никогда не видны на ЦП (я думаю, только те, которые похоронены под землей) ... тогда мне, вероятно, следует преобразовать в какую-то структуру на основе лица, а не на основе куба (используя 3D массив в С# прямо сейчас)? Что касается эффективного способа отправки кубов, что плохого в том, чтобы просто отправить 1 модель куба + данные экземпляра перевода через glVertexAttribDivisor и glDrawElementsInstancedBaseVertex? Насколько я понимаю, они были разработаны для этой цели, а текстуры или юниформ-буферы - это старый способ сделать это? (Я никогда не учился ни тому, ни другому) - person mpen; 06.02.2012
comment
Я только что прочитал большую часть этой статьи, которая говорит об использовании геометрического шейдера для отбраковки, но из этого я понял, что он просто отправляет одну центральную точку + вектор экстентов для каждого экземпляра модели в вершинный шейдер, так что каждая вершина действительно представляет местоположение объекта, затем он вычисляет ограничивающие рамки для каждого из них и отсекает те, которые не вписываются в усеченную пирамиду обзора. Я вижу, как это может помочь с высокополигональными моделями, но мои модели уже настолько просты, насколько это вообще возможно. - person mpen; 06.02.2012
comment
т. е. мои кубы, по сути, уже являются ограничивающими рамками, поэтому я не могу отправить меньше данных, чем уже есть... ну, может быть, немного, но... я не думаю, что получу много извлечь из этого выгоду. Кроме того, в моем примере 90% блоков находятся в пределах усеченной видимости, именно те, которые скрыты, мне нужно отсеять. Хм... если я просто сделаю один проход на стороне процессора и проверю 6 основных сторон, я смогу обрезать его таким образом. Кажется, теперь я вижу. Но обрезка не была целью этого SO-вопроса... Я спрошу об этом позже, если снова застряну. - person mpen; 06.02.2012
comment
@Mark, массивы очень эффективны. Если вы преобразуете массив в отдельные кубы, вам необходимо добавить информацию о его соседях. Это в 6 раз больше информации в виде отдельных кубов и в 24 раза больше информации в виде отдельных граней. Отбор должен быть выполнен как часть этого преобразования, иначе вы в конечном итоге передадите гораздо больше данных, чем необходимо. - person Piotr Praszmo; 06.02.2012
comment
Идея состоит в том, чтобы отправить 3D-текстуру вашего ландшафта и индексы кубов, которые вы хотите нарисовать, на графический процессор. Геометрический шейдер решит, какие стороны видны (т. е. внешние). Если вы отправляете отдельные кубы + 6 соседей на GPU, это в 6 раз больше данных. Вполне вероятно, что стоимость этого переноса будет больше, чем выгода от отбраковки на GPU. - person Piotr Praszmo; 06.02.2012

Я знаю, что этому вопросу почти два года, но могу ли я создать 3D-текстуру, в которой хранятся все отдельные текстуры, где координата текстуры z будет чем-то вроде идентификатора блока. С помощью 3D-текстуры теперь вы можете одновременно связать все свои отдельные текстуры блоков, что означает, что вы можете использовать инстансный рендеринг для передачи преобразований вместе с идентификатором блока, чтобы получить правильную текстуру блока для 3D-сэмплера.

person Nico Cvitak    schedule 10.12.2013

На моей nVidia 8600M GT я обнаружил, что создание экземпляров лучше всего работает «посередине» с умеренным количеством вершин и экземпляров, но в итоге я создал несколько тысяч экземпляров вершин, чтобы исключить избыточные данные и обновить их.

Я бы выбрал 2, используя массив текстур вместе с одним экземпляром куба в массиве вершин, и выбрал бы текстуру лица, используя индексы текстуры вашего «массива экземпляров», где 6 индексов могут даже быть упакованным в несколько целых чисел. Для предоставления атрибутов экземпляра также может использоваться GL_ARB_instanced_arrays, когда не требуется доступ к буферу с использованием gl_InstanceID (предсказуемый и, следовательно, более быстрый в большинстве случаев). Если вам нужны координаты текстуры для конкретного экземпляра, я бы привязал дополнительный массив координат текстур для каждого экземпляра и вершины вместе с соответствующим образом измененным шейдером.

person Sam    schedule 05.02.2012
comment
Я не использовал gl_InstanceID — я полагаю, вы бы использовали это с юниформой, содержащей ваши индексы текстур? Не додумался так сделать. Я использую glDrawElementsInstancedBaseVertex и помещаю данные экземпляра в GL_ARRAY_BUFFER с подсказкой об использовании GL_DYNAMIC_DRAW. Где в игру вступает ARB_instanced_arrays? Не уверен, что это такое. - person mpen; 06.02.2012
comment
Ваш выбор 2 кажется противоречащим вашему первому абзацу. 1 было бы более средним решением, не так ли? 1 использует экземпляры, только с меньшими порциями данных в каждом проходе. Вы бы выбрали 2, даже если на экране 200 тысяч кубов? - person mpen; 06.02.2012
comment
РЕДАКТИРОВАТЬ: Да, я бы сначала сделал тесты... Рано или поздно у вас закончатся юниформ-компоненты, поэтому я бы использовал объекты буфера текстуры. - person Sam; 06.02.2012
comment
Альтернативой использованию объектов буфера текстуры с gl_InstanceID являются вышеупомянутые массивы экземпляров. 8600M GT довольно старая, новые карты лучше подходят для аппаратной поддержки экземпляров, где меньшее количество вершин может быть выгоднее из-за кэширования и т. д. Здесь я не могу дать четкого намека, но я бы отложил это для тестов и оптимизации позже. , так как это не должно нарушать вашу основную концепцию. - person Sam; 06.02.2012
comment
Что ж, я собираюсь пойти по тому или иному пути. Я бы предпочел не тратить слишком много времени на ранний сравнительный анализ, пока он не станет барьером, как вы сказали. Проблема в том, что у меня уже около 60 кадров в секунду и даже ниже (~ 20) в полноэкранном режиме. Это до какой-либо обрезки, хотя... это еще одна проблема, которую я еще не проработал. - person mpen; 06.02.2012

Очень поздний ответ, но кому-то он обязательно понадобится.

Имейте метод в классе основного блока, который возвращает текстуру с параметром для лица. В отдельных классах, которым требуется несколько текстур, переопределите этот метод и используйте случай переключения или серию операторов if/else.

Вот как этот метод будет выглядеть в блочном классе:

public int getBlockTexture(int face){
     if(face = top){
         return grass top
     } else if(face = bottom){
         return grass bottom
     } else {
         return grass side
     }
}

Что касается того, как вы используете это в средстве визуализации, возьмите текстуру перед визуализацией каждого лица. Подобно тому, как вы делаете выбраковку.

person Mr. Ukulele    schedule 20.01.2015