[Перевод] Как устроены графика и визуальные артефакты PlayStation

how to make a game engine

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

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

crash bandicoot playstation

Графические артефакты PlayStation

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

Parking Garage Rally Circuit — современная игра с дизайном в стиле 32-битной эпохи

Когда мы говорим о «визуале PS1», то подразумеваем способ растеризации треугольников, поведение текстур, перемещение пикселей и множество других важных графических деталей, связанных с архитектурой консоли и её ограничениями.

Мы рассмотрим в этой статье следующие артефакты и дизайнерские решения:

  • Дёргающиеся текстуры

  • Дрожь полигонов

  • Отсутствие буфера глубины

  • Ближнее усечение

  • Отсутствие FPU

  • Дыры в T-образных соединениях

  • Отсутствие mip-текстурирования

  • Дизеринг

Разумеется, некоторые из перечисленных особенностей связаны друг с другом. Например, мы узнаем о том, почему отсутствие z-буфера связано с дёрганьем текстур, и как отсутствие FPU может объяснить возникновение в полигонах дыр в T-образных соединениях. Мы рассмотрим каждый пункт по отдельности и в процессе будем выявлять эти связи.

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

Аппаратная спецификация PS1

Мы начнём с очень высокоуровневого обзора оборудования PlayStation, относящегося к графике. Изучение этих элементов и некоторых дизайнерских решений, принятых Sony, может дать нам понятие о том, почему всё на PS1 работало именно так.

playstation hardware

CPU

Процессор PlayStation — это R3000A на 33,86 МГц с 32-битной архитектурой набора команд MIPS. В отличие от более старых 16-битных консолей, например, Sega MegaDrive и Super NES, основанных на архитектуре CISC, в PlayStation используется архитектура набора команд MIPS, основанная на RISC.

playstation mips cpu

ОЗУ

PlayStation имеет всего 2 МБ ОЗУ. Все переменные, массивы, буферы, 3D-вершины, меши, враги и вся игровая информация умещена в 2048 КБ. Чтобы преодолеть ограничение в 2 МБ, разработчики некоторых игр находили хитрые способы динамической загрузки блоков данных с CD, но всё равно, в каждый конкретный момент времени доступно было всего 2 МБ.

VRAM

Кроме того, PlayStation имеет 1 МБ видеопамяти. В этих 1 МБ должны храниться буфер кадров, которые будут отображаться на экране, а также текстуры, которые используются в игре.

playstation vram framebuffer

Буфер кадров

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

playstation vram

Текстуры и таблицы поиска цветов (CLUT) тоже хранятся во VRAM

Сопроцессоры

PlayStation содержит два сопроцессора, помогающие основному CPU в выполнении важных задач. Первый сопроцессор — это CP0 (System Control Coprocessor); он контролирует управление памятью, системные прерывания, обработку исключений и контрольные точки. Но в этой статье нас в основном будет интересовать сопроцессор CP2, обычно называемый Geometry Transformation Engine (GTE).

GTE выполняет высокоскоростные векторные и матричные вычисления. Всё это векторное и матричное умножение помогает нам выполнять важные графические преобразования: вращение, перемещение, проецирование и освещение.

GPU

GPU — это чип, отвечающий за 2D-растеризацию. Я хочу подчеркнуть, что это именно 2D, потому что GPU PlayStation может отрисовывать только 2D-объекты. Я делаю на это упор, потому что это позволит нам объяснить некоторые из графических артефактов PS1. Так что помните об этом! GPU PlayStation — движок 2D-растеризации.

triangle rasterization

Отрисовка примитивов

GPU PlayStation знает, как отрисовывать набор нативных примитивов:

  • Полигоны с плоским затенением

  • Полигоны с затенением по Гуро

  • Текстурированные полигоны

  • Линии

  • Спрайты

Полигоны с плоским затенением

Полигоны на PlayStation могут быть или треугольниками (3 вершины), или четырёхугольниками (4 вершины). Полигон с плоским затенением всегда окрашен в один сплошной цвет.

flat triangle playstation

Полигоны с затенением по Гуро

Также мы можем задавать треугольникам или прямоугольникам цвет каждой вершины. Это называется затенением по Гуро; такое затенение активно применялось в играх 90-х и начала 2000-х.

gouraud triangle playstation

После того, как мы зададим цвет каждой вершины треугольника (или четырёхугольника), GPU начинает интерполировать значение цвета для каждого пикселя внутри полигона.

Показанный выше цветной треугольник может показаться некрасивым, однако многие игры для PS1 при помощи этой техники достигали интересных результатов. В играх наподобие Final Fantasy VII,  Crash Bandicoot, и Spyro the Dragon полигоны по Гуро применялись для создания персонажей с плавным затенением без использования текстур.

final fantasy vii chocobo

Чокобо из Final Fantasy VII

Текстурированные полигоны

И, разумеется, PlayStation также могла накладывать изображение текстуры на полигон с использованием UV-координат. Прежде чем текстурировать полигон, изображение текстуры должно быть уже загружено в память VRAM.

texture triangle playstation

Линии

Наряду с полигонами, PS1 также могла растеризировать простые линии (отрезки) между двумя экранными координатами (2D-точками).

line primitive playstation

Спрайты

Ещё один очень популярный примитив, используемый во многих играх для PlayStation — это спрайты; по сути, это текстурированные тайлы с координатами (x, y), шириной и высотой. Так как мы говорим о текстурированном спрайте, перед его использованием изображение текстуры заранее должно быть загружено во VRAM. Как и можно ожидать, этот примитив часто применялся в 2D играх.

sprite playstation

Отправка примитивов в GPU

Чтобы полностью разобраться в потоке обработки графики PlayStation, давайте рассмотрим, как нам заставить GPU отрисовать примитивы в буфер кадров. Упростим свою задачу: попросим GPU отрисовать на экране единичный треугольник с плоским затенением.

VRAM консоли PlayStation не отображается в память, и единственный способ отрисовки примитивов в буфер кадров — это при помощи команд GPU. Для отрисовки треугольника с плоским затенением мы должны отправить GPU команду, сообщающую, какой тип примитива мы хотим отрисовать, а также передающую параметры (координаты, цвет и так далее). Повторюсь, только GPU отвечает за отрисовку (растеризацию) полигона в буфер кадров.

gpu packets psx dev

Мы отправляем значения в GPU при помощи последовательней пакетовПакеты на PlayStation — это слова (32-битные значения), отправляемые последовательно и сообщающие GPU, что нужно отрисовывать. В примере ниже мы отправляем множество пакетов, чтобы сообщить GPU, что хотим отрисовать сиреневый треугольник с плоским затенением с тремя вершинами в указанных координатах экрана x и y. К счастью, такая передача пакетов часто выполняется при помощи быстрого DMA.

gpu packets psx dev

Получающий эти пакеты порт GPU отображён в адрес памяти 0×1F801810. Показанный ниже фрагмент кода на языке ассемблера MIPS даёт нам понять, что отправка этих пакетов в GPU — это, по сути, последовательность команд загрузки и сохранения в отображённый в память порт ввода-вывода GPU.

  GP0 equ 0x1810

SendPacketsToGPU:
  lui $t0, 0x1F80
  li  $t1, 0x20FF00FF
  sw  $t1, GP0($t0)
  li  $t1, 0x00320032
  sw  $t1, GP0($t0)
  li  $t1, 0x001E0064
  sw  $t1, GP0($t0)
  li  $t1, 0x0064006E
  sw  $t1, GP0($t0)

Обратите внимание, что когда мы отправляем параметры полигона для отрисовки, все координаты задаются в 2D. У нас нет доступа к компоненте z (глубине) на этом этапе растеризации. Я говорю об этом потому, что отсутствие информации о глубине на этапе растеризации — одна из одних причин дёрганья текстур PS1.

Дёргающиеся текстуры

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

texture mapping

При наложении текстур в исходном изображении обычно ищется цвет каждого пикселя полигона.

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

uv texture mapping

Каждой вершине полигона присваивается UV-координата текстуры

После того, как мы получили UV-координаты каждой вершины, GPU выполняет интерполяцию этих значений внутри полигона для каждого пикселя. Именно так GPU узнаёт, как нужно раскрасить каждый пиксель треугольной грани на основании UV каждой вершины; он интерполирует значения UV и получает нужный цвет из исходного изображения текстуры.

Проблема возникает, если к треугольнику применяется перспектива. Если вершины треугольника имеют разные значения глубин, то GPU не знает, как выполнять интерполяцию с соблюдением перспективы и может использовать только аффинное наложение текстур.

polygon quad perspective

Человеческий мозг понимает, что показанное выше изображение изначально было 3D-четырёхугольником, который повернули, но для GPU PlayStation это всего лишь 2D-геометрия без информации о глубинах.

После проецирования полигона из 3D в 2D PlayStation теряет значения глубин для каждой вершины. Без компоненты z вершин GPU может интерполировать текстуру внутри 2D-геометрии треугольника только линейно, без учёта перспективы.

affine texture mapping playstation

Аффинное наложение текстур

Вы заметили, что интерполяция треугольников справа выполнена линейно? Повторюсь, мы знаем, что оба треугольника изначально были четырёхугольником, который повернули, но для GPU PlayStation эти треугольники — лишь 2D-геометрия на экране без глубины. Именно так PlayStation выполняет наложение текстур, и поэтому при перемещении текстур игровых объектов они кажутся искажёнными и дёргающимися.

Наложение текстур с соблюдением перспективы

Наш мозг в этом случае ожидает увидеть интерполяцию, учитывающую различия в глубинах вершин. Такое наложение текстур называется «с соблюдением перспективы», но GPU PlayStation попросту неспособен его выполнять.

perspective correct texture mapping playstation

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

Забавный факт: для выполнения интерполяции с соблюдением перспективы на самом деле нужна не z, а обратная ей величина 1/z. После вычисления перспективной проекции значение z нелинейно на экране, зато линейно значение 1/z.

perspective correct texture mapping playstation reciprocal z

GTE и перспективная проекция

Теперь, когда мы узнали, что у GPU нет информации о глубине, и что из-за этого он не может выполнять наложение текстур с соблюдением перспективы, давайте немного вернёмся назад и поговорим об этапе проецирования в потоке обработки графики PlayStation.

Проецирование, по сути, преобразует 3D-вершины мирового пространства в 2D-вершины экранного пространства. 3D-мир до проецирования, по сути, концептуален и существует только в нашем сознании, лишь после проецирования в 2D мы получаем реальное описание этого концептуального мира. Существует множество типов проекций, но в разработке игр чаще всего используется перспективная проекция.

perspective projection

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

Очень важная часть перспективного проецирования — это перспективное деление (perspective divide), при котором мы делим исходные компоненты x и y на глубину (z).

c4c9742705496d6416414eed835a2a08.png

Получившийся спроецированный на экран x равен исходному значению x, поделённому на значение z, а получившийся спроецированный y равен исходному y точки, поделённому на её значение z.

Обратно пропорциональное соотношение: посмотрите на показанную выше формулу перспективного деления. Понятна ли она вам? Дробь создаёт обратно пропорциональное соотношение между спроецированными на экран значениями x и y и глубиной точки.

  • Чем больше z, тем больше делитель и тем меньше получившиеся x, y.

  • Чем меньше z, тем меньше делитель и тем больше получившиеся x, y.

То, что находится дальше, кажется меньше, а то, что ближе — больше!

В PlayStation это вычисление перспективной проекции выполняется сопроцессором GTE! Мы просто загружаем в регистры GTE три вершины (x, y, z) треугольника и вызываем специальную команду GTE под названием RTPT (rotation-translation-projection-three). Эта команда RTPT выполняет вращение, перемещение и перспективное проецирование для трёх вершин. После завершения работы RTPT мы можем сохранить результаты этих вычислений в другие регистры GTE и двигаться дальше.

Ниже показан пример работы процедуры RTPT на языке ассемблера MIPS. Для общего понимания происходящего нам необязательно знать MIPS.

RotTransPers3:
  lwc2 VXY0, ($a0)   ; Вводим x,y для вершины 1
  lwc2 VZ0, 4($a0)   ; Вводим z для вершины 1
  lwc2 VXY1, ($a1)   ; Вводим x,y для вершины 2
  lwc2 VZ1, 4($a1)   ; Вводим z для вершины 2
  lwc2 VXY2, ($a2)   ; Вводим x,y для вершины 3
  lwc2 VZ2, 4($a2)   ; Вводим z для вершины 3
  nop
  rtpt
  lw $t0, 16($sp)
  lw $t1, 20($sp)
  lw $t2, 24($sp)
  lw $t3, 28($sp)
  swc2 SXY0, ($a3)   ; Выводим экранные x,y для вершины 1
  swc2 SXY1, ($t0)   ; Выводим экранные x,y для вершины 2
  swc2 SXY2, ($t1)   ; Выводим экранные x,y для вершины 3
  swc2 IR0,  ($t2)
  cfc2 $v1,FLAG0
  mfc2 $v0,SZ2
  sw $v1,($t3)
  j $ra
  sra $v0,$v0,2

Обратите внимание, что мы сначала запрашиваем последовательность команд lwc2 (load word coprocessor 2 — «загрузить слово в сопроцессор 2»), чтобы загрузить значения компонент x, y и z всех трёх вершин треугольника. Так мы загружаем в регистры GTE значений (x0, y0, z0), (x1, y1, z1) и (x2, y2, z2).

Затем мы отправляем GTE команду RTPT. Вскоре после этого мы используем команду swc2 (store word coprocessor 2 — «сохранить слово сопроцессора 2») для вывода значений экранных 2D-координат (x, y) трёх вершин. Значения SXY0, SXY1 и SXY2 — это экранные координаты x и y после вращения, перемещения и перспективного проецирования.

Видите, как ввод 3D-координатов (x, y, z) и вывод GTE даёт нам 2D-координаты (x, y) в экранном пространстве? После этого этапа мы не храним исходное значение глубины 3D-вершин, и GPU только имеет доступ к экранным 2D-координатам для растеризации в буфере кадров.

Сортировка по глубине и таблицы упорядочивания

Ещё один очень характерный для PS1 визуальный артефакт — это скачки полигонов и их резкое появление на основании их порядка глубины.

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

t rex demo playstation

Обратите внимание, как полигоны ноги тираннозавра становятся видимыми и резко появляются над полигонами живота. Этот артефакт — непосредственный результат того, как PlayStation вычисляет порядок рендеринга полигонов по глубине.

Такой рендеринг по глубине обычно называется алгоритмом художника. При такой методике мы сначала отрисовываем далёкие от нас объекты, а близкие к камере объекты после.

В мире PlayStation сортировка примитивов в правильном порядке отрисовки выполняется при помощи структуры данных на основе связанного списка, называемой таблицей упорядочивания (Ordering Table). Мы добавляем примитивы в OT в том порядке, в котором нужно отрисовывать их в буфере кадров.

playstation ordering table

OT PlayStation — это связанный список, содержащий указатели на примитивы в том порядке, в котором они будут отрисовываться.

Так как в большинстве игр используются полигоны, а порядок из отображения зависит от их глубины в мировом пространстве, сопроцессор GTE помогает нам и с этим! У GTE есть специальная команда, вычисляющая средние значения z для вершин полигона. Сопроцессор GTE возвращает среднее значение глубины, которое затем можно использовать как «индекс» для помещения этого примитива в OT.

playstation average z ordering table

Обратите внимание, что глубина, определяющая порядок отрисовки, указывается не попиксельно. У нас есть одна глубина на каждый полигон. И это основная причина возникновения таких проблем с z-конфликтами, при которых полигоны иногда выскакивают вперёд в OT. В зависимости от конфигурации вершин средняя глубина одного треугольника помещает его перед другим треугольником в OT.

Дрожь полигонов

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

playstation metal gear solid

Если понаблюдать, как полигоны перемещаются по экрану, то можно заметить, что вершины скачут. Многие ресурсы обвиняют в этом погрешности точности, вызванные отсутствием у PS1 сопроцессора для вычислений с плавающей точкой. Хоть у PS1 действительно нет FPU, основная причина дрожания полигонов заключается не в отсутствии хранения данных с плавающей точкой, а в том, что растеризатор не обеспечивает субпиксельной точности.

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

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

35b20897d1bdc03b942e183783722899.gif

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

1e464df692524adc64eb907a5dce7675.gif

Отсутствие FPU

Так как мы упомянули отсутствие у PlayStation FPU (Floating Point Unit), думаю, можно вкратце поговорить о том, как игры для PS1 обрабатывали дробные числа наподобие 10,56 или 3,14159. Играм ведь всё равно как-то нужно описывать дробные количества для таких параметров, как ускорение, скорость и так далее.

Представление чисел с плавающей точкой

Если вы современный программист, есть вероятность того, что машина или игровая консоль, для которой вы пишете код, имеет нативную аппаратную поддержку чисел с плавающей запятой. Мы привыкли объявлять переменные в виде float или double. Такое описание основано на стандарте, использующем очень умную формулу, которая позволяет представлять большое количество разных значений при помощи лишь 32 битов (float) или 64 битов (double).

floating point

Самый распространённый стандарт арифметики с плавающей запятой — это IEEE745, установленный IEEE в 1985 году:

floating point representation

Давайте остановимся и подумаем о том, что здесь происходит. У нас есть всего 32 бита для представления максимально возможного количества «вещественных чисел». На самом деле, это довольно сюрреалистичная задача! При помощи 32 битов мы можем представить 232 уникальных целых чисел, что лишь чуть больше 4 миллиардов! При помощи показанной выше формулы мы можем описать не только большой диапазон целых чисел, но и их дробную часть с достаточно высокой точностью.

К сожалению, PlayStation не использует эту возможность, поскольку Sony не установила в свою консоль сопроцессор FPU. Поэтому нам нужно найти альтернативу для описания дробных чисел на PS1.

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

Представление чисел с фиксированной запятой

Альтернатива, лучше подходящая для системы, не имеющей FPU — это использование системы чисел с дробями на основе целых чисел, известной как fixed-point.

Запятая не плавает по числу, а находится на одном и том же месте! То есть мы резервируем фиксированное количество битов под целую часть и фиксированное количество битов под дробную часть числа.

И мы даже можем выбирать нужный нам тип описания. Ниже представлены примеры описания с фиксированной точкой формата 16.16 и 20.12.

fixed point math

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

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

fixed point subpixel rasterizer

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

subpixel rasterization

Помните, что растеризатор PS1 работает не так. GPU принимает только целочисленные координаты (x, y), а вершины привязываются к целочисленной сетке, приводя к дрожанию полигонов.

Дыры в T-образных соединениях и ближнее усечение

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

Чтобы полнее понять, что такое T-образные соединения, рассмотрим пример, используемый в нашем курсе программирования для PlayStation. В наших лекциях мы совместно программируем очень простой проект в стиле Wipeout для PS1, и одна из самых его важных частей — это рендеринг трассы, на которой проходят гонки.

wipeout playstation

Одна из первых проблем, с которой мы сталкиваемся при первой попытке рендеринга трассы — это проблема ближнего усечения (near-clipping). Это ближнее усечение происходит, когда полигоны находятся слишком близко к камере и отбрасываются рендерером. Такое отсечение полигонов приводит к большим зазорам при перемещении по трассе.

near clipping

Это очень распространённая на PS1 проблема; чтобы решить её, мы можем или выполнять правильное усечение полигонов (polygon clipping), при котором применяется алгоритм усечения и мы получаем разрезанную геометрию, усечённую по ближней плоскости, или просто подразделить такие большие полигоны на меньшие, что снизит отсечение рядом с ближней плоскостью.

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

playstation polygon subdivision

Как видите, мы используем систему LOD (level-of-detail, уровня деталей), при которой чем ближе полигоны к игроку, тем на большее количество частей они подразделяются. В примере выше показаны три разных уровня LOD, при которых мы в зависимости от расстояния до камеры подразделяем четырёхугольники ноль, один или два раза.

И вы уже можете заметить, как образуются T-образные соединения! Переход от нуля до одного подразделения создаёт в меше T-образное соединение. То же самое происходит, когда мы переходим от одного подразделения до двух. Такой наивный способ подразделения четырёхугольников пополам создаёт T-образные соединения при каждой смене уровня LOD.

playstation t junctions

Но почему T-образные соединения считаются чем-то плохим? Из-за проблем с точностью точка, находящаяся между исходными двумя, может выйти за пределы отрезка, соединяющего эти первые две точки.

t junction artefact

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

Эти зазоры видны во многих играх. Wipeout — это не единственный пример. Можно также увидеть мерцание в T-образных соединениях в Rage Racer каждый раз, когда в полигонах возникают зазоры.

rage racer polygon gaps

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

quake 2 playstation subdivision

Quake 2 для Playstation

Дизеринг

Очень важная визуальная деталь, которую часто игнорируют в других ресурсах — это дизеринг. Консоль Sony реализует особый стиль дизеринга, составляющий важную часть картинки PS1.

dithering

По сути, дизеринг — это способ симуляции дополнительных цветов применением паттерна сочетания двух цветов.

dithering

Хотя PlayStation и способна использовать 24-битные цвета (обеспечивающие нам более 16 миллионов уникальных цветов), практически в каждом кадре применяется большое количество дизеринга.

playstation dithering

Стоит помнить, что программисту не нужно реализовывать собственный алгоритм дизеринга, потому что у консоли есть специальный аппаратный атрибут для обработки 24-битного или 15-битного дизеринга. Иными словами, это аппаратная функция, встроенная в GPU консоли PlayStation и применяемая ко всем текстурированным полигонам.

Некоторым игрокам не нравится эффект дизеринга на PS1, и моддеры нашли способы его отключения.

Отсутствие mip-текстурирования

Я также хочу упомянуть, что консоль PlayStation нативно не поддерживает mip-текстурирование. Mip-текстуры в компьютерной графике — это заранее вычисленные оптимизированные последовательности текстур, каждая из которых имеет прогрессивно более низкополигональное представление предыдущей. Очень распространённый подход —использование разных разрешений для разных значений степеней двойки.

mip mapping

В игре Wipeout разработчики попытались реализовать это при помощи кода. Игра хранит на CD одну и ту же текстуру в разных разрешениях, и решает, какую из них использовать на основании того, насколько далеко четырёхугольник трассы находится от камеры. При этом применяется та же логика подразделения четырёхугольников трассы, о которой мы говорили выше. Четырёхугольники, подразделённые один раз, используют текстуру среднего разрешения, а четырёхугольники, подразделённые дважды — текстуру высокого разрешения.

mip mapping

Заключение и дополнительные ресурсы

Уместить всю информацию о PlayStation и её внутреннем техническом устройстве в один пост попросту невозможно. Но я надеюсь, что этот обзор позволил вам выработать понимание того, что происходит за кулисами и как возникают эти артефакты в стиле PS1.

Можно и не говорить о том, что это лишь вершина айсберга сведений о работе PlayStation и всех красивых подробностей устройства консоли Sony. В качестве отличного ресурса для начинающих можно упомянуть сообщество PSXDEV. Youtube-канал DisplacedGamers — увлекательный способ узнать больше о PS1 и понять, как программировались некоторые из игр для неё.

Habrahabr.ru прочитано 12782 раза