Как получить CATransform3D из матриц проекции и ModelView? - PullRequest
6 голосов
/ 18 мая 2011

все,

У меня есть проект iphone, который рисует 3D-модель, используя OpenGL-ES для данной матрицы вида модели и данной матрицы проекции.Мне нужно было заменить 3D-модель на CALayer, поэтому я поместил значения матрицы вида модели в структуру CATransform3D и присвоил ей layer.transform.Это работало хорошо, слой был виден и перемещался на экране, как и ожидалось, но через некоторое время я понял, что поведение моих слоев недостаточно точное, и я должен принять во внимание матрицу проекции.И тогда возникла проблема: когда я просто объединяю две матрицы, мой слой выглядит странно (он очень маленький, около 2 пикселей, в то время как он должен быть около 300, так как он очень далеко) или вообще не виден.Как я могу решить это?

Вот кусок кода:

- (void)adjustImageObjectWithUserInfo:(NSDictionary *)userInfo
{
    NSNumber *objectID = [userInfo objectForKey:kObjectIDKey];
    CALayer *layer = [self.imageLayers objectForKey:objectID];
    if (!layer) { return; }

    CATransform3D transform = CATransform3DIdentity;
    NSArray *modelViewMatrix = [userInfo objectForKey:kModelViewMatrixKey];

      // Get raw model view matrix;
    CGFloat *p = (CGFloat *)&transform;
    for (int i = 0; i < 16; ++i)
    {
        *p = [[modelViewMatrix objectAtIndex:i] floatValue];
        ++p;
    }

      // Rotate around +z for Pi/2 
    transform = CATransform3DConcat(transform, CATransform3DMakeRotation(M_PI_2, 0, 0, 1));

      // Project with projection matrix
    transform = CATransform3DConcat(transform, _projectionMatrix);

    layer.transform = transform; 
}

Любая помощь будет оценена.

Ответы [ 5 ]

10 голосов
/ 08 июня 2012

Недавно я столкнулся с этой проблемой (я также использовал ARToolKit), и я был очень разочарован, увидев, что вы не нашли ответ.Я полагаю, что вы пошли дальше, но я понял это и выкладываю это для любой другой потерянной души, которая может столкнуться с той же проблемой.

Самым запутанным для меня было то, что все говорят о том, чтобы преобразовать перспективу CALayer, установив для переменной m34 небольшое отрицательное число.Хотя это работает, это не очень информативно.В конце концов я понял, что преобразование работает точно так же, как любое другое преобразование, это матрица преобразования основных столбцов для однородных координат.Единственный особый случай - это то, что он должен объединить матрицу вида модели и проекции, а затем масштабировать до размера окна просмотра openGL, все в одной матрице.Я начал с попытки использовать матрицу в стиле, где m34 - это небольшое отрицательное число, как объяснено более подробно здесь , но в конечном итоге переключился на открытое преобразование в стиле GL, как объяснено здесь .На самом деле они эквивалентны друг другу, они просто представляют разные способы мышления о преобразовании.

В нашем случае мы пытаемся сделать так, чтобы преобразование CALayer точно повторяло открытое преобразование GL.Все, что для этого требуется, - это умножить вместе матрицы моделей, проекций и масштабирования и перевернуть ось y, чтобы учесть тот факт, что источник экрана устройства находится вверху слева, а открытый GL - внизу слева.Пока якорь слоя находится в точке (.5, .5) и его позиция точно в центре экрана, результат будет идентичен преобразованию открытого GL

void attach_CALayer_to_marker(CATransform3D* transform, Matrix4 modelView, Matrix4 openGL_projection, Vector2 GLViewportSize)
{
//Important: This function assumes that the CA layer has its origin in the 
//exact center of the screen.

Matrix4 flipY = {   1, 0,0,0,
                    0,-1,0,0,
                    0, 0,1,0,
                    0, 0,0,1}; 

//instead of -1 to 1 we want our result to go from -width/2 to width/2, same 
//for height
CGFloat ScreenScale = [[UIScreen mainScreen] scale];
float xscl = GLViewportSize.x/ScreenScale/2;
float yscl = GLViewportSize.y/ScreenScale/2;

//The open GL perspective matrix projects onto a 2x2x2 cube.  To get it onto the
    //device screen it needs to be scaled to the correct size but
//maintaining the aspect ratio specified by the open GL window.
    Matrix4 scalingMatrix = {xscl,0   ,0,0,
                               0,   yscl,0,0,
                               0,   0   ,1,0,
                               0,   0   ,0,1};

//Open GL measures y from the bottom and CALayers measure from the top so at the
//end the entire projection must be flipped over the xz plane.
//When that happens the contents of the CALayer will get flipped upside down.
//To correct for that they are flipped updside down at the very beginning,
//they will then be flipped right side up at the end.

Matrix flipped = MatrixMakeFromProduct(modelView, flipY);
Matrix unscaled = MatrixMakeFromProduct(openGL_projection, flipped);
Matrix scaled = MatrixMakeFromProduct(scalingMatrix, unscaled);

//flip over xz plane to move origin to bottom instead of top
Matrix Final = SiMatrix4MakeFromProduct(flipY, scaled);
*transform = convert_your_matrix_object_to_CATransform3D(Final);
}

Эта функция принимает открытоеGLprojection и openGL view size и использует их для генерации правильного преобразования для CALayer.Размер CALayer должен быть указан в единицах открытой сцены GL.Окно просмотра OpenGL на самом деле содержит 4 переменные [xoffset, yoffset, x, y], но первые две не имеют значения, потому что Origin of CALayer помещен в центр экрана, чтобы соответствовать OpenGL 3d Origin.

Просто замените Matrix любым классом основной матрицы 4x4, к которому у вас есть доступ.Все будет работать, просто убедитесь, что вы умножаете свои матрицы в правильном порядке.Все, что по сути дела, это репликация конвейера OpenGL (за исключением отсечения).

2 голосов
/ 02 августа 2011

Я только что избавился от матрицы проекции, и это лучший вариант, который у меня есть:

- (void)adjustTransformationOfLayerWithMarkerId:(NSNumber *)markerId forModelViewMatrix:(NSArray *)modelViewMatrix
{   
    CALayer *layer = [self.imageLayers objectForKey:markerId];

            ...

    CATransform3D transform = CATransform3DIdentity;        
    CGFloat *p = (CGFloat *)&transform;
    for (int i = 0; i < 16; ++i) {
        *p = [[modelViewMatrix objectAtIndex:i] floatValue];
        ++p;
    }

    transform.m44 = (transform.m43 > 0) ? transform.m43/kZDistanceWithoutDistortion : 1;

    CGFloat angle = -M_PI_2;
    if (self.delegate.interfaceOrientation == UIInterfaceOrientationLandscapeLeft)      { angle = M_PI; }
    if (self.delegate.interfaceOrientation == UIInterfaceOrientationLandscapeRight)     { angle = 0; }
    if (self.delegate.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) { angle = M_PI_2; }
    transform = CATransform3DConcat(transform, CATransform3DMakeRotation(angle, 0, 0, -1));

    transform = CATransform3DConcat(CATransform3DMakeScale(-1, 1, 1), transform);

        // Normalize transformation
    CGFloat scaleFactor = 1.0f / transform.m44;
    transform.m41 = transform.m41 * scaleFactor;
    transform.m42 = transform.m42 * scaleFactor;
    transform.m43 = transform.m43 * scaleFactor;
    transform = CATransform3DScale(transform, scaleFactor, scaleFactor, scaleFactor);
    transform.m44 = 1;

    BOOL disableAction = YES;

            ...

    [CATransaction begin];
    [CATransaction setDisableActions:disableAction]; // Disable animation for layer to move faster
    layer.transform = transform;                
    [CATransaction commit];
}

Это было не совсем точно, но это было достаточно точно для моих целей.Отклонение становится заметным, когда смещение x или y было размером с экран.

1 голос
/ 01 августа 2011

Две возможные проблемы:

1) Порядок объединения. Классическая матричная математика справа налево. Так что попробуйте

CATransform3DConcat(_projectionMatrix, transform)

и

2) Неверное значение коэффициента проекции. Какие значения вы используете?

Надеюсь, это поможет.

0 голосов
/ 02 февраля 2018

Как прямой ответ на вопрос: матрицы проекций предназначены для вывода чего-либо в диапазоне -1 ... 1, тогда как CoreAnimation обычно работает с пикселями.Отсюда и неприятности.Таким образом, чтобы решить проблему, вы ДОЛЖНЫ умножить вид модели на матрицу проекции, уменьшите свою модель, чтобы она хорошо вписывалась в диапазон -1 ... 1 (в зависимости от вашего мира, вы можете разделить ее на bounds.size) и после умножения матрицы проекции выможет вернуться к пикселям.

Итак, ниже приведен фрагмент кода в Swift (лично я ненавижу Swift, но моему клиенту это нравится), я верю, что его можно понять.Он написан для CoreAnimation и ARKit и проверен на работоспособность.Я надеюсь, что матрицы ARKit можно рассматривать как открытые

func initCATransform3D(_ t_ : float4x4) -> CATransform3D
{
    let t = t_.transpose //surprise m43 means 4th column 3rd row, thanks to Apple for amazing documenation for CATransform3D and total disregarding standard math with Mij notation
    //surprise: at Apple didn't care about CA & ARKit compatibility so no any easier way to do this
    return CATransform3D(
        m11: CGFloat(t[0][0]), m12: CGFloat(t[1][0]), m13: CGFloat(t[2][0]), m14: CGFloat(t[3][0]),
        m21: CGFloat(t[0][1]), m22: CGFloat(t[1][1]), m23: CGFloat(t[2][1]), m24: CGFloat(t[3][1]),
        m31: CGFloat(t[0][2]), m32: CGFloat(t[1][2]), m33: CGFloat(t[2][2]), m34: CGFloat(t[3][2]),
        m41: CGFloat(t[0][3]), m42: CGFloat(t[1][3]), m43: CGFloat(t[2][3]), m44: CGFloat(t[3][3])
    )
}

override func updateAnchors(frame: ARFrame) {
    for animView in stickerViews {
        guard let anchor = animView.anchor else { continue }

        //100 here to make object smaller... on input it's in pixels, but we want it to be more real.
        //we work in meters at this place
        //surprise: nevertheless the matrices are column-major they are inited in "transposed" form because arrays/columns are written in horizontal direction
        let mx = float4x4(
            [1/Float(100), 0, 0, 0],
            [0, -1/Float(100), 0, 0],    //flip Y axis; it's directed up in 3d world while for CA on iOS it's directed down
            [0, 0, 1, 0],
            [0, 0, 0, 1]
        )

        let simd_atr = anchor.transform * mx //* matrix_scale(1/Float(bounds.size.height), matrix_identity_float4x4)
        var atr = initCATransform3D(simd_atr)   //atr = anchor transform

        let camera = frame.camera
        let view = initCATransform3D(camera.viewMatrix(for: .landscapeRight))
        let proj = initCATransform3D(camera.projectionMatrix(for: .landscapeRight, viewportSize: camera.imageResolution, zNear: 0.01, zFar: 1000))

        //surprise: CATransform3DConcat(a,b) is equal to mathematical b*a, documentation in apple is wrong
        //for fun it's distinct from glsl or opengl multiplication order
        atr = CATransform3DConcat(CATransform3DConcat(atr, view), proj)
        let norm = CATransform3DMakeScale(0.5, -0.5, 1)     //on iOS Y axis is directed down, but we flipped it formerly, so return back!
        let shift = CATransform3DMakeTranslation(1, -1, 0)  //we should shift it to another end of projection matrix output before flipping
        let screen_scale = CATransform3DMakeScale(bounds.size.width, bounds.size.height, 1)
        atr = CATransform3DConcat(CATransform3DConcat(atr, shift), norm)
        atr = CATransform3DConcat(atr, CATransform3DMakeAffineTransform(frame.displayTransform(for: self.orientation(), viewportSize: bounds.size)));
        atr = CATransform3DConcat(atr, screen_scale)    //scale back to pixels

        //printCATransform(atr)
        //we assume center is in 0,0 i.e. formerly there was animView.layer.center = CGPoint(x:0, y:0)
        animView.layer.transform = atr
    }
}

PS. Я думаю, что для разработчиков, которые создали все возможное сочетание с системами координат левой и правой руки, направлением оси Y и Z, основными матрицами столбцов, плавающей точкой& CGFloat несовместимость и отсутствие CA и ARKit интеграции место в аду будет особенно жарко ...

0 голосов
/ 26 июня 2012

Учли ли вы, что слои визуализируются в контекст GL, к которому уже применена матрица ортопроекции?

См. Вступительный комментарий в Mac;этот класс является закрытым для iPhone, но принципы те же.

Кроме того, матрицы OpenGL транспонируются в памяти по сравнению с CATransform3D.Учитывайте это тоже;хотя большинство результатов кажутся одинаковыми, некоторые не будут.

...