Opengl es 如何从投影矩阵和ModelView矩阵中获取CATTransform3D?

Opengl es 如何从投影矩阵和ModelView矩阵中获取CATTransform3D?,opengl-es,calayer,3d,catransform3d,projection-matrix,Opengl Es,Calayer,3d,Catransform3d,Projection Matrix,全部, 我有一个iphone项目,它使用OpenGL ES为给定的模型视图矩阵和给定的投影矩阵绘制一个3D模型。我需要用CALayer替换3D模型,所以我将模型视图矩阵的值放入CATTransformM3D结构中,并将其分配给层。transform。它工作得很好,图层是可见的,并按预期在屏幕上移动,但过了一段时间,我意识到我的图层行为不够精确,我应该考虑投影矩阵。然后出现了一个问题:当我简单地连接两个矩阵时,我的层看起来很奇怪(它非常小,大约2个像素,而它应该是300左右,因为它离得很远),或者

全部,

我有一个iphone项目,它使用OpenGL ES为给定的模型视图矩阵和给定的投影矩阵绘制一个3D模型。我需要用CALayer替换3D模型,所以我将模型视图矩阵的值放入CATTransformM3D结构中,并将其分配给
层。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; 
}
-(void)adjustImageObjectWithUserInfo:(NSDictionary*)userInfo
{
NSNumber*objectID=[userInfo-objectForKey:kObjectIDKey];
CALayer*layer=[self.imageLayers objectForKey:objectID];
如果(!layer){return;}
CATTransferorM3D transform=CATTransferorM3D实体;
NSArray*modelViewMatrix=[userInfo objectForKey:KModelViewMatrix];
//获取原始模型视图矩阵;
CGFloat*p=(CGFloat*)&transform;
对于(int i=0;i<16;++i)
{
*p=[[modelViewMatrix对象索引:i]floatValue];
++p;
}
//围绕+z旋转Pi/2
变换=CATTransferM3DCONCAT(变换,CATTransferM3DMakeRotation(M_PI_2,0,0,1));
//投影矩阵投影
transform=CATTransformM3DCONCAT(transform,_projectionMatrix);
layer.transform=转换;
}

任何帮助都将不胜感激。

两个可能的问题:

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

CATransform3DConcat(_projectionMatrix, transform)
,及

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];
}
-(void)调整带有标记ID的图层转换:(NSNumber*)标记ID forModelViewMatrix:(NSArray*)modelViewMatrix
{   
CALayer*layer=[self.imageLayers objectForKey:markerId];
...
CATTransferorM3D transform=CATTransferorM3D实体;
CGFloat*p=(CGFloat*)&transform;
对于(int i=0;i<16;++i){
*p=[[modelViewMatrix对象索引:i]floatValue];
++p;
}
transform.m44=(transform.m43>0)?transform.m43/kzdistance无失真:1;
CGFloat角=-M_PI_2;
如果(self.delegate.interfaceOrientation==UIInterfaceOrientationAndscapeLeft){angle=M_PI;}
如果(self.delegate.interfaceOrientation==UIInterfaceOrientationAndscapeRight){angle=0;}
如果(self.delegate.interfaceOrientation==UIInterfaceOrientationSpatureUpsideDown){angle=M_PI_2;}
变换=CATTransferM3DCONCAT(变换,CATTransferM3DMakeRotation(角度,0,0,-1));
变换=CATTransformM3DCONCAT(CATTransformM3DMakeScale(-1,1,1),变换);
//规范化变换
CGFloat scaleFactor=1.0f/transform.m44;
transform.m41=transform.m41*缩放因子;
transform.m42=transform.m42*缩放因子;
transform.m43=transform.m43*缩放因子;
transform=CATTransformM3DSCALE(变换、缩放因子、缩放因子、缩放因子);
transform.m44=1;
BOOL disableAction=是;
...
[交易开始];
[CATTransaction setDisableActions:disableAction];//禁用动画使层移动更快
layer.transform=转换;
[CATransaction-commit];
}

它不是绝对精确的,但对我来说已经足够精确了。当x或y位移与屏幕大小有关时,偏转变得很明显。

我最近遇到了这个确切的问题(我也在使用ARToolKit),我非常失望地看到您没有找到答案。我想你现在已经离开了,但我发现了这一点,我把它贴在了其他可能遇到同样问题的迷失的灵魂身上

最让我困惑的是,每个人都在谈论通过将m34变量设置为一个小负数来进行CALayer透视变换。虽然这确实有效,但信息量并不大。我最终意识到,该变换的工作原理与其他变换完全相同,它是同构坐标的一个列主变换矩阵。唯一的特殊情况是,它必须组合模型视图和投影矩阵,然后在一个矩阵中缩放到openGL视口的大小。我开始尝试在样式中使用矩阵,其中m34是一个较小的负数,详细解释如下,但最终切换到OpenGL样式的透视变换。事实上,它们是相互关联的,它们只是代表了对转换的不同思考方式

在我们的例子中,我们试图使CALayer变换完全复制开放式GL变换。所需的只是将modelview、投影和缩放矩阵相乘,并翻转y轴,以说明设备屏幕原点位于左上角,OpenGL位于左下角。只要层定位点位于(.5,.5),并且其位置正好位于屏幕的中心,结果将与OpenGL的变换相同

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);
}
此函数采用openGL投影和openGL视图大小,并使用它们为CALayer生成正确的变换。CALayer大小应以OpenGL场景的单位指定。OpenGL视口实际上包含4个变量,[xoffset,yoOffset,x,y],但前两个变量不相关,因为CALayer的原点放在屏幕的中心,与OpenGL 3d原点相对应

只需将矩阵替换为您可以访问的任何通用4x4列主矩阵类。任何事情都可以,只要确保你乘以你的矩阵
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
    }
}