iOS_初探ImageView的3D透视变形效果

前言:

探究iOS中ImageView各种3D透视变形效果

用到的正常图片如下:

正常显示效果的图片
正常显示效果的图片

开始尝试各种3D变换

绕X轴旋转60度

下面代码绕X轴,经过60度的旋转之后,如图所示:

- (void)transform3d_1
{
    // 沿X轴,3d变换60度
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 1, 0, 0);
    _colorImgView.layer.transform = rotate;
}

绕X轴,经过60度的旋转后的效果
绕X轴,经过60度的旋转后的效果

不同相机距离下的透视效果

下面使用3D变换,相机位置不同时的效果分别如下 :

- (void)transform3d_2
{    // 下面3张图片,分别演示了相机距离图片锚点为40,80,160的情况
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 1, 0, 0);//角度代表倒下去的程度,即与竖直面的夹角,0度代表不倒下
    _colorImgView.layer.transform = CATransform3DPerspect(rotate, CGPointMake(0, 0), 80);// 40,80,160
}



/*
 center指的是相机的位置,
 相机的位置是相对于要进行变换的CALayer的来说的,
 原点是CALayer的anchorPoint在整个CALayer的位置,
 例如CALayer的大小是(100, 200), anchorPoint值为(0.5, 0.5),
 此时anchorPoint在整个CALayer中的位置就是(50, 100),正中心的位置。
 传入透视变换的相机位置为(0, 0),那么相机所在的位置相对于CALayer就是(50, 100)。
 
 如果希望相机在左上角,则需要传入(-50, -100)。
 
 disZ表示的是相机离z=0平面(也可以理解为屏幕)的距离。
 */
CATransform3D CATransform3DMakePerspective(CGPoint center, float disZ)
{
    CATransform3D transToCenter = CATransform3DMakeTranslation(-center.x, -center.y, 0);
    CATransform3D transBack = CATransform3DMakeTranslation(center.x, center.y, 0);
    CATransform3D scale = CATransform3DIdentity;
    scale.m34 = -1.0f/disZ;
    return CATransform3DConcat(CATransform3DConcat(transToCenter, scale), transBack);
}
CATransform3D CATransform3DPerspect(CATransform3D t, CGPoint center, float disZ)
{
    return CATransform3DConcat(t, CATransform3DMakePerspective(center, disZ));
}


相机距离为40

相机距离为40时的变形效果:

相机距离为40时的变形效果:
相机距离为40时的变形效果:

相机距离为80

相机距离为80时的变形效果:

相机距离为80时的变形效果
相机距离为80时的变形效果

相机距离为160

相机距离为160时的变形效果:

相机距离为160时的变形效果
相机距离为160时的变形效果

透视原理分析

由上面可以知道,相机 距离 ImageView的锚点越近,透视效果越强; 距离越远,透视效果越弱;

如下图所示:

透视原理示意图
透视原理示意图

有bug的底边中点透视

如果要绕底边进行透视,

那么需将图层的锚点移动到底边的中点上,如下图所示

- (void)transform3d_3
{    // 有bug
    // 如果要绕固定ImgeView的边(如底边)旋转,只需要调整 layer 的 anchorPoint 到对应的边上就行了
    CALayer *layer = [_colorImgView layer];
    CGPoint oldAnchorPoint = layer.anchorPoint;
    
    // sg__old anchor point:0.500000,0.500000
    NSLog(@"sg__old anchor point:%f,%f",oldAnchorPoint.x,oldAnchorPoint.y);
    
    // 向下,向右 为正;此时锚点在底边中点
    [layer setAnchorPoint:CGPointMake(0.5, 1.0)];
    
    // 根据新旧锚点,移动图层的坐标位置
    // sg__layer pos old__187.000000,333.000000
    NSLog(@"sg__layer pos old__%f,%f",layer.position.x,layer.position.y);
    CGFloat posX = layer.position.x + layer.bounds.size.width * (layer.anchorPoint.x - oldAnchorPoint.x);
    CGFloat posY = layer.position.y + layer.bounds.size.height * (layer.anchorPoint.y - oldAnchorPoint.y);
    // sg__layer pos new__187.000000,453.000000
    NSLog(@"sg__layer pos new__%f,%f",posX,posY);
    
    [layer setPosition:CGPointMake(posX,posY)];
    
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 1, 0, 0);
    _colorImgView.layer.transform = CATransform3DPerspect(rotate, CGPointMake(0, 0), 160);
}

存在bug的底边中点透视效果:

存在bug的底边中点透视效果
存在bug的底边中点透视效果

分析bug

上面的有一个bug,现加以解释说明并将其修正

为什么只是绕底边旋转了60度,会看起来这么扁,按照勾股定理应该

高只有长的1/2呀,而现在是长480,高只有100,理论上变形后的高度应该有240的

原因如下图所示:

 

Bug产生原因分析示意图
Bug产生原因分析示意图

这是因为移动了imgView的anchorPoint,从0.5,0.5向下移动到了0.5,1.0,

也就是相机(观察点)现在在 eye1处了。

以 yz 面上的点为例,在 eye2 处,红色矩形在 xy 面上的投影为比较靠下的 A 点,

这也就是为什么旋转之后,矩形的高度比较小的原因了(不足宽度的1/2)

 

ok,知道了错误原因, 那就好办了!

现在只需要把 eye1 往上移动到视图中心即可。即往上移动240/2=120


解决bug

修正后的代码及无bug的底边中点透视效果图如下:

- (void)transform3d_4
{
    // 如果要绕固定ImgeView的边(如底边)旋转,只需要调整 layer 的 anchorPoint 到对应的边上就行了
    CALayer *layer = [_colorImgView layer];
    CGPoint oldAnchorPoint = layer.anchorPoint;
    
    // sg__old anchor point:0.500000,0.500000
    NSLog(@"sg__old anchor point:%f,%f",oldAnchorPoint.x,oldAnchorPoint.y);
    
    // 向下,向右 为正;此时锚点在底边中点
    [layer setAnchorPoint:CGPointMake(0.5, 1.0)];
    
    // 根据新旧锚点,移动图层的坐标位置
    // sg__layer pos old__187.000000,333.000000
    NSLog(@"sg__layer pos old__%f,%f",layer.position.x,layer.position.y);
    CGFloat posX = layer.position.x + layer.bounds.size.width * (layer.anchorPoint.x - oldAnchorPoint.x);
    CGFloat posY = layer.position.y + layer.bounds.size.height * (layer.anchorPoint.y - oldAnchorPoint.y);
    // sg__layer pos new__187.000000,453.000000
    NSLog(@"sg__layer pos new__%f,%f",posX,posY);
    
    [layer setPosition:CGPointMake(posX,posY)];
    
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 1, 0, 0);
    
    // 由于上面整个图层向底边移动了,所以此时,这个相机位置的Y值,应该上移一段距离,到中心位置;240为图片的高
    _colorImgView.layer.transform = CATransform3DPerspect(rotate, CGPointMake(0, -240/2.0), 160);
}


无bug底边中点透视

修正后的底边中点透视效果图:

修正后的底边中点透视效果图
修正后的底边中点透视效果图

绕Y轴3D变换

那么新的需求又来了,

如果要绕左边,类似于开门关门进行透视呢?

等价于, 让相机 沿Y轴方向3D变换,

那么只要修改一下参数即可

照猫画虎做一遍

#pragma mark - 绕Y轴变换
- (void)transform3d_Y_1
{
    // 沿Y轴,3d变换60度
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 0, 1, 0);
    _colorImgView.layer.transform = rotate;
}

绕Y轴变换60度效果图
绕Y轴变换60度效果图

 


不同相机距离时, 绕Y轴3D透视效果

下面分别演示由于相机距离的不同, 导致的绕Y轴旋转效果的差异:

- (void)transform3d_Y_2
{
    // 下面3张图片,分别演示了相机距离图片锚点为40,80,160的情况
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 0, 1, 0);//角度代表倒下去的程度,即与竖直面的夹角,0度代表不倒下
    _colorImgView.layer.transform = CATransform3DPerspect(rotate, CGPointMake(0, 0), 160);// 40,80,160
}


相机距离40时

相机距离40时,绕Y轴的变形效果:

相机距离40时,绕Y轴的变形效果相机距离40时,绕Y轴的变形效果
相机距离40时,绕Y轴的变形效果

相机距离80时

相机距离80时,绕Y轴的变形效果:

相机距离80时,绕Y轴的变形效果
相机距离80时,绕Y轴的变形效果

相机距离160时

相机距离160时,绕Y轴的变形效果:

相机距离160时,绕Y轴的变形效果
相机距离160时,绕Y轴的变形效果

有bug的绕左边中点透视

下面正式绕Y轴, 实现开门关门的变形效果:

跟前文一样, 我们先来一段实现功能,但是依旧存在bug (压缩太过了)的代码:

// 有bug
- (void)transform3d_Y_3
{
    // 如果要绕固定ImgeView的边(如左边)旋转,只需要调整 layer 的 anchorPoint 到对应的边上就行了
    CALayer *layer = [_colorImgView layer];
    CGPoint oldAnchorPoint = layer.anchorPoint;
    
    // sg__old anchor point: 0.5  ,  0.5
    NSLog(@"sg__old anchor point:%f,%f",oldAnchorPoint.x,oldAnchorPoint.y);
    
    // 向下,向右 为正;此时锚点在左边中点
    [layer setAnchorPoint:CGPointMake(0, 0.5)];
    
    // 根据新旧锚点,移动图层的坐标位置
    // sg__layer pos old__187.000000,333.000000
    NSLog(@"sg__layer pos old__%f,%f",layer.position.x,layer.position.y);
    CGFloat posX = layer.position.x + layer.bounds.size.width * (layer.anchorPoint.x - oldAnchorPoint.x);
    CGFloat posY = layer.position.y + layer.bounds.size.height * (layer.anchorPoint.y - oldAnchorPoint.y);
    // sg__layer pos new__67.000000,333.000000
    NSLog(@"sg__layer pos new__%f,%f",posX,posY);
    
    [layer setPosition:CGPointMake(posX,posY)];
    
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 0, 1, 0);
    _colorImgView.layer.transform = CATransform3DPerspect(rotate, CGPointMake(0, 0), 160);// 这儿有bug
}

绕Y轴开关门效果,但是存在bug
绕Y轴开关门效果,但是存在bug

解决bug

下面照旧来解决上述bug,

bug产生原因是:

由于图层整个左移了,所以相机要向右移动一段距离,从而对齐到视图的中心

- (void)transform3d_Y_4
{// 如果要绕固定ImgeView的边(如底边)旋转,只需要调整 layer 的 anchorPoint 到对应的边上就行了
    CALayer *layer = [_colorImgView layer];
    CGPoint oldAnchorPoint = layer.anchorPoint;
    
    // sg__old anchor point:0.500000,0.500000
    NSLog(@"sg__old anchor point:%f,%f",oldAnchorPoint.x,oldAnchorPoint.y);
    
    // 向下,向右 为正;此时锚点在左边中点
    [layer setAnchorPoint:CGPointMake(0, 0.5)];
    
    // 根据新旧锚点,移动图层的坐标位置
    // sg__layer pos old__187.000000,333.000000
    NSLog(@"sg__layer pos old__%f,%f",layer.position.x,layer.position.y);
    CGFloat posX = layer.position.x + layer.bounds.size.width * (layer.anchorPoint.x - oldAnchorPoint.x);
    CGFloat posY = layer.position.y + layer.bounds.size.height * (layer.anchorPoint.y - oldAnchorPoint.y);
    // sg__layer pos new__67.000000,333.000000
    NSLog(@"sg__layer pos new__%f,%f",posX,posY);
    
    [layer setPosition:CGPointMake(posX,posY)];
    
    CATransform3D rotate = CATransform3DMakeRotation(M_PI/3, 0, 1, 0);
    
    // 由于上面整个图层向左边移动了,所以此时,这个相机位置的X值,应该右移一段距离,到中心位置;240为图片的宽
    _colorImgView.layer.transform = CATransform3DPerspect(rotate, CGPointMake(240/2.0, 0), 160);
}

 

修正后的绕Y轴开关门效果
修正后的绕Y轴开关门效果

至此,我们就可以通过根据用户手势操作, 动态修改参数, 从而实现3D变换动画了


附录:

github下载地址

 

未完待续,下一章节,つづく