Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Форум / Z-интерполяция

Z-интерполяция

Поделиться

Страницы: 1 2 Следующая

eDmkПользовательwww4 ноя. 201720:56#0
Всем привет!
Может кто занимался написанием z-интерполяции в треугольнике?
Очень хочется избавиться от деления единицы на произведение векторов.
+ Показать

Pt.X/Y/Z - барицентрические координаты точки в треугольнике.
Z.X/Y/Z - вектор глубины треугольника, т.е. единица поделенная на Z вершин.

Есть идеи или рекомендации по этому поводу?
Лучше одно доп. умножение, чем деление.
Убрав единицу в итоге неплохо поднимается ФПС в софтрендере.

foxesПостоялецwww4 ноя. 201723:53#1
Можно попробовать заменить деление на поиск приближенного обратного числа. Ни чего другого тут не изменишь. Максимум надо смотреть всю реализацию, возможно что то переставить, чтобы делений было не так много.
eDmk
> произведение векторов.
Это скалярное произведение, используется для проекции вектора на ось Z.

Функция пересчета трехмерных координат в проекцию у меня тоже имеет одно деление и я особенно не парился. Только это деление используется для нормализации значений под размеры проекционного куба, а не для расчета глубины. То есть если убрать деление то, остальные расчеты нужно будет производить уже с учетом домножения на r[3] и последующим делением на r[3]. А иначе картинка будет просто ортогональной, а не перспективной.

+ Показать

Правка: 5 ноя. 2017 1:39

eDmkПользовательwww5 ноя. 20171:44#2
Да у меня 2 формулы перспективной интерполяции и везде надо единицу делить на выражение.
Видимо из-за перспективной матрицы, которая тоже приведена к единице.
Жаль. Это деление тормозит расчеты довольно сильно.
foxesПостоялецwww5 ноя. 20172:00#3
Обратное число это тоже самое число только в минус первой степени. Если посмотреть на вычисление быстрого обратного корня то
i  = 0x7F800000 - ( i );
Будет первым приближением обратного числа. А дальше можно уточнить его по такой формуле:
y  = y * ( 1.5 - ( x2 * y * y ) );
Или просто написать на асме с использованием соответствующей команды для вычисления обратного значения. Хотя на сколько я помню это делается автоматически компилятором, если используется формат float.

У меня софтверный треугольник рисуется так

+ Показать

Как видишь тут полно делений.

Правка: 5 ноя. 2017 2:05

FordPerfectПостоялецwww5 ноя. 20172:07#4
eDmk
XYZ - это индексы вершин? При этом начальная Z - это именно координата?

Не так страшно деление, как его малюют. Инструкция приблизительного деления (rcpss) примерно в ту же скорость что и умножение. Даже полноценное деление не такое уж долгое. А ещё можно 4 деления одновременно, с SIMD.

В кваке вроде делили раз в несколько пикселей, в посередине использовали результат от соседей.

Pt.X * Z.X + Pt.Y * Z.Y + Pt.Z * Z.Z можно без умножений, т. к. результат между соседними пикселями в строке отличается на константу.
Не обязательно использовать 3 барицентрические координаты: Pt.Z=1-(Pt.X+Pt.Y). Подставить, преобразовать.

Если у тебя паскаль - хз как там с оптимизациями (и 32-битными флоатами и SIMD). Можешь поспрашивать у тех, кто с этим больше работает.

eDmkПользовательwww5 ноя. 20172:58#5
Просто если заменить на умножение, то получается быстрее на 5 fps на ядро.
В совокупности на 4-х ядернике это почти + ~15 fps. На моем 10-и ядернике + ~30 fps.
Для софтрендера это хороший прирост. Вот и заморочился вопросом.
Да на асме все и написано. Все работает достаточно быстро, но без деления еще быстрее.
Правда результат пока неправильный.
+ Показать

>Не обязательно использовать 3 барицентрические координаты
К сожалению обязательно. Иначе треугольник неправильный.

FordPerfectПостоялецwww5 ноя. 20174:40#6
eDmk
>К сожалению обязательно. Иначе треугольник неправильный.
Можно подробнее?
Pt.X * Z.X + Pt.Y * Z.Y + Pt.Z * Z.Z = Pt.X * Z.X + Pt.Y * Z.Y + (1.0-(Pt.X+Pt.Y)) * Z.Z = Z.Z + Pt.X * (Z.X - Z.Z) + Pt.Y * (Z.Y - Z.Z).
(Z.X - Z.Z) и (Z.Y - Z.Z) - константы.
Или у тебя Pt.X + Pt.Y + Pt.Z = 1.0 не выполняется? А почему?
  //Делим единицу на выражение
  divsd xmm0, xmm1

  //Конвертируем в single
  cvtsd2ss xmm0, xmm0
Нафига? Деление float32 заметно быстрее, чем деление float64.
Можно
  //Конвертируем в single
  cvtsd2ss xmm0, xmm0

  //Делим единицу на выражение
  divss xmm0, xmm1
или
  //Конвертируем в single
  cvtsd2ss xmm0, xmm0

  //Делим единицу на выражение
  rcpss xmm0, xmm0
если точности 14 бит достаточно.

И вообще нафига тебе double?

И можно считать 4 за раз (divps/rcpps).

Выражение Pt.X * Z.X + Pt.Y * Z.Y + Pt.Z * Z.Z меняется линейно вдоль строчки пикселей. Инкремент можно посчитать один раз, и дальше складывать с предыдущим результатом. Одно сложение, а не 3 умножения ж.

foxesПостоялецwww5 ноя. 201710:15#7
Тут правда без глубины.
+ Показать

С глубиной так

+ Показать

_S4AFDLSdrawDEF и _S4AFSdrawDEF рисуют горизонтальную линию для разных режимов, с тестом глубины и без.
Тут интерполяция представлена в целых числах как числа с фиксированной точкой. Для цвета 8 бит после запятой, для глубины 15 бит после запятой. Буфер глубины 16 битный. Глубина для следующей точки как и цвет вычисляется одним сложением.
((S4AFDSdata*)data)->z+=((S4AFDSdata*)data)->za;

triangle_MultyColor_8888_loop2_deff - собирает часть треугольника из этих линий.

Правка: 5 ноя. 2017 10:45

eDmkПользовательwww5 ноя. 201713:05#8
>Можно подробнее?
Константа только вектор Z. Это глубина треугольника по вершинам в барицентрических координатах.
+ Показать

Вектор точки Pt у меня не константа. Это интерполируемый край. AX+BY+C=0.
Он приращается простым сложением. Pt := Pt + Delta; Получается следующий пиксел.
Если пиксел <= 0, то точка внутри треугольника. (У меня отрицательная площадь).
Потом идет приведение пиксела к барицентрическим координатам в треугольнике, т.е. умножаем на площадь.
Потом вычисляем Z (формула в первом посте) и сравниваем с Z-Buffer'ом.
У меня приложение x64, поэтому double. Он быстрее обрабатывается в x64 чем single.
Кроме того у single глюки. Z-Fighting краев полигона. Видно если прозрачность включить.
С даблом меньше проблем.

Правка: 5 ноя. 2017 13:10

eDmkПользовательwww5 ноя. 201713:07#9
>foxes
Это неинтерполируемый треугольник видимо.
Таких алгоритмов у меня куча. Они не правильные.
Интерполяция идет только по формуле:  AX+BY+C=0.
Остальное неправильно. Хотя на треугольник похоже.

Получается идеально, но медленно :(

+ Показать

Правка: 5 ноя. 2017 13:22

DeamonПостоялецwww5 ноя. 201714:13#10
eDmk
Как вариант, если вектора нормализированные и скалярное произведение меньше 1, то можно воспользоваться разложением в ряд тейлора и считать до скажем 4-го члена ряда:
Изображение
при
Изображение

foxesПостоялецwww5 ноя. 201714:42#11
eDmk
> Это неинтерполируемый треугольник видимо.
Что значит неинтерполируемый?
foxes
> интерполяция представлена в целых числах как числа с фиксированной точкой. Для
> цвета 8 бит после запятой, для глубины 15 бит после запятой.

Правка: 5 ноя. 2017 14:42

}:+()___ [Smile]Постоялецwww5 ноя. 201718:24#12
eDmk
> Потом вычисляем Z (формула в первом посте) и сравниваем с Z-Buffer'ом.
Для целей Z-буффера деление нафиг не нужно, 1/Z работает так же хорошо, как и Z.
Перспективное деление требуется для корректной интерполяции текстурных координат и прочих атрибутов.

> У меня приложение x64, поэтому double. Он быстрее обрабатывается в x64 чем single.
Полный бред. SSE/AVX позволяют работать с float-ами в два (и больше в случае комплексных команд) раза быстрее.
Растеризаторы ложаться под SIMD идеально, поэтому никаких оправданий использованию double нет.
Собственно, то что GPU используют преимущественно float является подтверждению этого тезиса.

> Кроме того у single глюки. Z-Fighting краев полигона. Видно если прозрачность включить.
Вот поэтому все нормальные растеризаторы работают с целыми числами для границ треугольников.
Заодно отсекаются узкие сверхтонкие треугольники, которые являются проблемой.

FordPerfectПостоялецwww5 ноя. 201719:43#13
}:+()___ [Smile]
>Полный бред. SSE/AVX позволяют работать с float-ами в два (и больше в случае комплексных команд) раза быстрее.
Комплексных? Даже несчастные умножение/деление - скалярные - быстрее (умножение - только по latency).
InstructionLatencyReciprocal throughput
MULSS MULPS41
MULSD MULPD51
DIVSS DIVPS7-147-14
DIVSD DIVPD7-227-22
RCPSS/PS32

Это Агнер Фог, для i7.

eDmk
>Константа только вектор Z.
>Вектор точки Pt у меня не константа.
И что?
Как это противоречит тому, что
Pt.X*Z.X+Pt.Y*Z.Y+Pt.Z*Z.Z=S*Z.Z+Pt.X*(Z.X-Z.Z)+Pt.Y*(Z.Y-Z.Z)
где Pt.X+Pt.Y+Pt.Z=S (которое константа что по смыслу барицентрических координат)?
Ну и в любом случае, если
>Константа только вектор Z.
>Он приращается простым сложением. Pt := Pt + Delta;
то InvZ=Pt.X*Z.X+Pt.Y*Z.Y+Pt.Z*Z.Z тоже инкрементируется простым сложением: InvZ:=InvZ+DeltaInvZ, где DeltaInvZ=Delta.X*Z.X+Delta.Y*Z.Y+Delta.Z*Z.Z.

>У меня приложение x64, поэтому double. Он быстрее обрабатывается в x64 чем single.
Очень странное утверждение. Походу неправда. }:+()___ [Smile] дело говорит.

>Кроме того у single глюки. Z-Fighting краев полигона. Видно если прозрачность включить.
Опять же, }:+()___ [Smile] в теме: обычно pixel coverage делают в целых числах с субпиксельной точностью.

Правка: 5 ноя. 2017 19:52

}:+()___ [Smile]Постоялецwww5 ноя. 201721:37#14
FordPerfect
> Комплексных? Даже несчастные умножение/деление - скалярные - быстрее (умножение - только по latency).
Комплексные — это те, которые не имеют специализированного железа и выполняются софтварно микрокодом.
Отличить их можно по плавающей производительности — это деление, корень и все что сложнее.

Умножение же — это чисто хардварная операция (возможно, за исключением NaN-ов и денормалов).
Скорее всего, выполняется на общем железе для float/double (а возможно и для целых), непонятно откуда там дополнительная задержка.

Кстати, хорошо видно, что "скалярные" операции на самом деле векторные с отключенной записью результата.

Страницы: 1 2 Следующая

/ Форум / Программирование игр / Графика

2001—2017 © GameDev.ru — Разработка игр