【问题标题】:How to get CATransform3D from Projection and ModelView matrices?如何从 Projection 和 ModelView 矩阵中获取 CATransform3D?
【发布时间】:2011-08-28 01:54:28
【问题描述】:

所有,

我有一个 iphone 项目,它使用 OpenGL-ES 为给定的模型视图矩阵和给定的投影矩阵绘制 3D 模型。我需要用 CALayer 替换 3D 模型,因此我将模型视图矩阵的值放入 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; 
}

任何帮助将不胜感激。

【问题讨论】:

  • 你有没有设法让它工作?我正在尝试做同样的事情——从 OpenGL 投影和 modelView 转换为 CATransform3D 以附加到 CATransformLayer

标签: opengl-es calayer 3d catransform3d projection-matrix


【解决方案1】:

我最近遇到了这个确切的问题(我也在使用 ARToolKit),看到您没有找到答案,我感到非常失望。我想你现在已经继续前进了,但我想通了,我将它发布给任何其他可能遇到同样问题的迷失灵魂。

对我来说最令人困惑的是,每个人都在谈论通过将 m34 变量设置为一个小的负数来进行 CALayer 透视变换。尽管这确实有效,但它的信息量并不大。我最终意识到,变换的工作原理与其他变换完全一样,它是齐次坐标的列主变换矩阵。唯一的特殊情况是它必须结合模型视图和投影矩阵,然后在一个矩阵中缩放到 openGL 视口的大小。我首先尝试使用 m34 是一个小的负数的样式的矩阵,如更详细的解释here,但最终切换到打开的 GL 样式透视变换,如here 解释的那样。它们实际上是彼此之间的equivalent,它们只是代表了对转换的不同思考方式。

在我们的例子中,我们试图让 CALayer 变换完全复制一个开放的 GL 变换。所需要的只是将模型视图、投影和缩放矩阵相乘并翻转 y 轴以说明设备屏幕原点位于左上角而 open GL 位于左下角的事实。只要图层锚点在 (.5,.5) 并且它的位置正好在屏幕的中心,结果将与 open 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 视图大小,并使用它们为 CALayer 生成正确的变换。 CALayer 大小应以 open GL 场景的单位指定。 OpenGL 视口实际上包含 4 个变量,[xoffset,yoffset,x,y] 但前两个无关紧要,因为 CALayer 的 Origin 位于屏幕中心以对应 OpenGL 3d Origin。

只需将 Matrix 替换为您可以访问的任何通用 4x4 列主要矩阵类。只要确保你以正确的顺序乘以你的矩阵,任何事情都会奏效。所有这一切本质上都是在复制 OpenGL 管道(减去裁剪)。

【讨论】:

  • 谢谢你的回答,这个问题很困扰我。我会尝试你的方法并做出回应。
  • 我也在使用 ARToolkit,但我不知道你是如何设置 Matrix4 和 Vector2 的。这是我需要的结构定义吗?另外,这应该放在 ARView 中还是可以留在 ARViewController 中?我一直在努力完成这项工作:) 看起来你已经掌握了,但如果没有更多信息我无法重现。
  • @JimTrue 这些只是我项目使用的自定义矩阵库中的通用矩阵结构。如果您的项目还没有使用矩阵库,我建议您找到一个现有的而不是滚动您自己的,我相信那里有很多选择,矩阵乘法是一个非常常见的用例
【解决方案2】:

我刚刚摆脱了投影矩阵,这是我得到的最好的变体:

- (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 位移大约为屏幕尺寸时,偏转变得明显。

【讨论】:

  • 谢谢@Zapko。我用我的标记转换尝试了你的代码(你在使用 String SDK 吗?),但我没有得到任何有用的东西。我想我需要坐下来弄清楚我正在通过的转换实际上是手动进行的。
  • 嗨,托尼!不,我正在使用 ARToolKit+。我现在没有链接,但我稍后会在这里写,关于我作为基础的项目。如果您告诉我您将获得的结果,那就太好了。
  • 这是那个增强现实项目的link
  • 据我所知,与 OpenGL 变换矩阵相比,CATransform3D 矩阵实际上是转置的。
  • 非常感谢它帮了我很多:-)但是我没有得到这个概念,但是帮了我很多,你能解释一下这4个失真值和fx,fy,cx cy
【解决方案3】:

两个可能的问题:

1) 连接顺序。经典矩阵数学是从右到左。所以试试

CATransform3DConcat(_projectionMatrix, transform)

,和

2) 投影系数的值是错误的。您使用的值是什么?

希望这会有所帮助。

【讨论】:

  • 嗨,埃里克!是的,我尝试了不同的连接顺序,其中很多)你所说的投影系数是什么意思,我想我错过了什么?
  • 尽管如此,该函数的文档正确顺序是从左到右的。即 CATransform3DConcat(CATransform3DConcat(model, view), projection) 将按预期工作。逆向工程。
【解决方案4】:

您是否考虑到图层渲染到已经应用了正射投影矩阵的 GL 上下文中?

在 Mac 上查看介绍性评论;这个类在 iPhone 上是私有的,但是原理是一样的。

此外,与 CATransform3D 相比,OpenGL 矩阵在内存中进行了转置。也要考虑到这一点;虽然大多数结果看起来都一样,但有些则不然。

【讨论】:

    【解决方案5】:

    作为问题的直接答案:投影矩阵旨在输出 -1 ... 1 范围内的内容,而 CoreAnimation 通常适用于像素。烦恼由此而来。所以要解决这个问题,你应该在将模型视图乘以投影矩阵之前,减少你的模型以很好地适应 -1 ... 1 范围(取决于你的世界是你可以将它划分为 bounds.size)和投影矩阵乘法之后你可以返回像素。

    所以下面是一个Swift中的sn-p代码(我个人很讨厌Swift,但是我的客户喜欢它)相信可以看懂。它是为 CoreAnimation 和 ARKit 编写的,并测试了它的工作原理。我希望 ARKit 矩阵可以被认为是 opengl 的

    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
        }
    }
    

    附:我认为对于使用左右坐标系、Y 和 Z 轴方向、列主要矩阵、浮点和 CGFloat 不兼容以及缺乏 CA 和 ARKit 集成创建所有可能混合的开发人员来说,地狱中的一个地方将特别热门。 .

    【讨论】:

      猜你喜欢
      • 2012-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多