eDmk
> Я по книжке делал:
И в данном случае Inverse избыточен
procedure LookAtMatrix(Eye, Target, Up: TVertex; var RM: TMatrix4); var OM, TM: TMatrix4; begin // Положение и ориентация Translation( Eye, -TM); Orientation( Eye, Target, Up, OM); Transpose( OM); // Умножаем и инвертируем MulMatrixC( TM, OM, RM); end;
Вот тебе тоже самое но без Inverse
eDmk
> Более универсальное решение.
Ну так тебе две страницы и говорят что универсальное решение здесь не нужно.
Если забыть про преобразование MVP - инверсия в геймдеве нужна крайне редко.
MrShoor
Да, действительно без инверсии работает :) Только вот так:
procedure LookAtMatrix(Eye, Target, Up: TVertex; var RM: TMatrix4); var OM, TM: TMatrix4; begin // Положение и ориентация Translation( -Eye, TM); Orientation( Eye, Target, Up, OM); Transpose( OM); // Умножаем MulMatrixC( TM, OM, RM); end;
@!!ex
> инверсия в геймдеве нужна крайне редко
Согласен, но на всякий случай лучше иметь рабочий код.
@!!ex
> Если забыть про преобразование MVP - инверсия в геймдеве нужна крайне редко.
Я бы не сказал, что инверсия нужна крайне редко. Мне лично она нужна часто, но нужна она часто в коде, а не в рендере на каждый кадр. Например делаешь ты что-то в редакторе с моделью, и нужно это сделать в пространстве модели, а не мира. И оп, обратная матрица. Или берешь два объекта, и говоришь, что один из них будет чайлдом другого, и оп, опять нужна обратная матрица.
eDmk
> Только вот так:
Да, я минус зачем-то перед матрицей впихнул, когда хотел перед Eye.
А если для D3DXVec3Unproject(внутри эта функция считает инверсию матрицы) ?
Или тоже избыточно ?
ronniko
> А если для D3DXVec3Unproject(внутри эта функция считает инверсию матрицы) ?
> Или тоже избыточно ?
Для конкретной проекционной матрицы обратную получить еще проще, т.к. проекционная матрица - это как правило только скейл + оффсет по Z. Но т.к. проекционные матрицы бывают разные - то проще не заморачиватья и считать через Inverse
ronniko
> внутри эта функция считает инверсию матрицы
Это же обычная пропорция. Зачем тут инверсия?
function TCamera.UnProject(X, Y: Integer): TVertex; var fX, fY, K: Single; R: TVertex absolute Result; begin // Нормализованные координаты экрана fX := ( X / FHalf.X) + FR.l; fY := ( Y / FHalf.Y) - FR.t; // Глубина if ( FTarget.ZBufferType = ZT_SINGLE) then R.Z := FTarget.ZDepth32( X, Y) else R.Z := FTarget.ZDepth64( X, Y); // Коэффициент положения плоскости ZFar по глубине K := ( R.Z / FR.f); // Позиция в координатах фрустума R.X := fX * ( FV.FarSize.X * K) / FR.w; R.Y := fY * ( FV.FarSize.Y * K) / FR.h; R.W := 1.0; end;
Мой вариант переводит из ScreenSpace во ViewSpace. Не факт, что подойдет для DX.
Начертите фрустум и найдите нужные пропорции и коэффициенты. Будет вам счастье.
Мы пропорции в 4-м классе проходили :)
MrShoor
То что ты привел - это же тоже MV матрица.
Я про то, что универсальное решение очень редко нужно. 99% задач - это преобразование модельно видовой матрицы.
eDmk
> Pascal-версия — 1 167 024 вызовов в секунду (~2570 тактов).
То есть больше млн полноценных инверсий полностью произвольных матриц? Много что-то, надо будет проверить.
Mikle
Да не. На самом деле достаточно медленно. Умножение, например, 176 млн в секунду на невыровненных данных и 200 млн на выровненных. 800 тактов инверсия и 15 тактов умножение — получается дикая разница (в ~55 раз быстрее).
Хотя если инверсию раз на кадр вычислять, то для большинства задач вполне хватает даже такой версии. У меня SIMD-версия быстрее — 12,4 млн в секунду. Правда сравнить пока не с чем. На просторах интернета жуткий код бродит.
Можно сделать операцию обратного модельного умножения. Это будет короче чем вычислять обратную матрицу и потом умножать.
eDmk
> Хотя если инверсию раз на кадр вычислять, то для большинства задач вполне
> хватает даже такой версии. У меня SIMD-версия быстрее — 3,7 млн в секунду.
> Правда сравнить пока не с чем. На просторах интернета жуткий код бродит.
Да хоть вот, вариант простой как топор.
inline FMat inverse(FMat const& m) { float mc[12]; float rc[12]; m.disassemble( mc); /* mc[0], mc[1], mc[2], mc[3], mc[4], mc[5], mc[6], mc[7], mc[8], mc[9], mc[10], mc[11], */ float det = mc[0] * mc[5] * mc[10] + mc[1] * mc[6] * mc[8] + mc[2] * mc[4] * mc[9] - mc[0] * mc[6] * mc[9] - mc[1] * mc[4] * mc[10] - mc[2] * mc[5] * mc[8]; if ( fabsf( det) < 1e-9) { return FMat{}; } float invdet = 1.0f / det; rc[0] = invdet * ( mc[5] * mc[10] - mc[6] * mc[9]); rc[1] = invdet * ( mc[2] * mc[9] - mc[1] * mc[10]); rc[2] = invdet * ( mc[1] * mc[6] - mc[2] * mc[5]); rc[3] = invdet * ( mc[1] * mc[7] * mc[10] + mc[2] * mc[5] * mc[11] + mc[3] * mc[6] * mc[9] - mc[1] * mc[6] * mc[11] - mc[2] * mc[7] * mc[9] - mc[3] * mc[5] * mc[10]); rc[4] = invdet * ( mc[6] * mc[8] - mc[4] * mc[10]); rc[5] = invdet * ( mc[0] * mc[10] - mc[2] * mc[8]); rc[6] = invdet * ( mc[2] * mc[4] - mc[0] * mc[6]); rc[7] = invdet * ( mc[0] * mc[6] * mc[11] + mc[2] * mc[7] * mc[8] + mc[3] * mc[4] * mc[10] - mc[0] * mc[7] * mc[10] - mc[2] * mc[4] * mc[11] - mc[3] * mc[6] * mc[8]); rc[8] = invdet * ( mc[4] * mc[9] - mc[5] * mc[8]); rc[9] = invdet * ( mc[1] * mc[8] - mc[0] * mc[9]); rc[10] = invdet * ( mc[0] * mc[5] - mc[1] * mc[4]); rc[11] = invdet * ( mc[0] * mc[7] * mc[9] + mc[1] * mc[4] * mc[11] + mc[3] * mc[5] * mc[8] - mc[0] * mc[5] * mc[11] - mc[1] * mc[7] * mc[8] - mc[3] * mc[4] * mc[9]); return FMat::assemble( rc); }
Вычисляется исключительно при инициализации, поэтому на оптимизацию вообще забил. Зато работает на произвольных аффинных матрицах.
У меня рейтрейсер, который в виде матриц хранит положение треугольников сцены, а через обратные матрицы считает барицентрические координаты пересечения с лучом. Поскольку треугольники стационарны, то и матрицы рассчитываются единожды при загрузке геометрии и затем тупо умножаются на координаты лучей.
С численной стабильностью тоже не церемонимся - сразу после обращения, вычисляется mwt * inverse(mwt), и, если получившая матрица слишком далеко отходит от единичной, весь треугольник считается вырожденным и тупо выкидывается.
Тема в архиве.