Metal渲染:实现画面比例功能
Posted csutanyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Metal渲染:实现画面比例功能相关的知识,希望对你有一定的参考价值。
如果我们使用AVPlayer及AVPlayerLayer进行视频播放的话,那们我们可以使用AVPlayerLayer.videoGravity来控件画面的显示比例(Resize, ResizeAspect, ResizeAspectFill)。但是如果我们使用Metal进行视频渲染的放要如何实现画面比例呢?
其实我们可以通过设置Metal的View Point来实现:
画面比例如果使用AVSampleBufferDisplayLayer有一个videoGravity属性:
@property(copy) AVLayerVideoGravity videoGravity;
使用系统播放器时,AVPlayerLayer也有一个videoGravity属性:
@property(copy) AVLayerVideoGravity videoGravity;
但在使用Metal进行渲染时MTLLayer并没有类似的属性,只有在父类CALayer有一个contentsGravity属性:
@property(copy) CALayerContentsGravity contentsGravity;
但CALayer的contentsGravity属性并不能让MTLLayer上渲染的视频产生画面比例的效果。我们需要使用MTLRenderCommandEncoder的以下设置View point的接口来实现:
- (void)setViewport:(MTLViewport)viewport;
MTLViewport定义:
typedef struct { double originX, originY, width, height, znear, zfar; } MTLViewport;
Metal是可以进行3D渲染的,在我们的视频渲染中只使用到了2D,因此这个znear和zfar直接取-1和1就好,对于2D渲染,默认MTViewpoint为:
(MTLViewport){0.0, 0.0, drawableSize.width, drawableSize.height, -1.0, 1.0 }];
以上view point会使视频纹理缩放填满整个画布,而不保持视频原分辨率比例 。
在我们下面通过设置view point方法来实现画面比例前,有如下条件:
// 视频宽高 CGFloat videoWidth = textureSize.width; CGFloat videoHeight = textureSize.height; // 视频高宽比 CGFloat videoScale = videoHeight/videoWidth; // 画面同宽比 CGFloat canvaseScale = (CGFloat)(drawableSize.height) / (CGFloat)(drawableSize.width); // 视频实际显示的宽高,即在view point中使用的宽高 CGFloat videoDisplayWidth = drawableSize.width; CGFloat videoDisplayHeight = drawableSize.height;
视频的画面比例通常我们需要实现三种情况:
kCAGravityResize:
视频宽高随画布宽高进行缩放,将画布填满,不考虑视频本身的比例,图像会变形。
Resize的实现很简单,不用特殊设置ViewPoint默认视频就是按Layer进行拉伸变形填满整个Metal Layer的。由于要实现其它两种画面比例,设置viewPoint如下:
[encoder setViewport:(MTLViewport){0.0, 0.0, videoDisplayWidth, videoDisplayHeight, -1.0, 1.0 }];
kCAGravityResizeAspect:
将视频进行等比缩放,当视频相对画布较大的边刚好和画布一样时不再缩放,如果视频比例与画面比例不相等时,上下或者左右会出现黑边。
分两种情况。
1. 如果canvaseScale > videoScale
即画布相比例对视频比例,更高,那么当视频等比缩放到和画布的宽度一样时,画面上下就会有黑边,我们使上下的黑边一样,如图:
视频显示宽高分别为:
videoDisplayWidth = drawableSize.width; videoDisplayHeight = drawableSize.width * (videoHeight/videoWidth);
此时的高度没有画布高,MTLViewport的originX为0,originY应该比0大,视频向上偏移,使下面有黑边,偏移的量正好是画布高度减去缩放后视频显示高度的差再除以2,因此viewPoint设置如下:
[encoder setViewport:(MTLViewport){0.0, (drawableSize.height - videoDisplayHeight)/2, videoDisplayWidth, videoDisplayHeight, -1.0, 1.0 }];
2. 如果canvaseScale <= videoScale
即画布相对视频比例,更宽,那么当视频等比缩放到与画布高度一样时,画面的左右就会有黑边,我们使左右黑边一样,如图:
视频显示宽高分别为:
videoDisplayWidth = drawableSize.height * (videoWidth/videoHeight); videoDisplayHeight = drawableSize.height;
此时的宽没有画布宽,MTLViewpoint的originY为0,orignX应该比0大,视频向右偏移,使左边有黑边,偏移的量正好是画布宽减去缩放后视频显示宽的差再除以2,因此viewPoint设置如下:
[encoder setViewport:(MTLViewport){(drawableSize.width - videoDisplayWidth)/2, 0, videoDisplayWidth, videoDisplayHeight, -1.0, 1.0 }];
kCAGravityResizeAspectFill
将视频在kCAGravityResizeAspect的基础上进行等比放大,当上下或者左右的黑边刚好消失时停止。这样上下或者左右的画面会有一部分在画布外面看不见。
同样分两种情况讨论。
3. 如果canvaseScale > videoScale
即画布相对视频比例,更高,那么当视频等比缩放到和画布的高一样时,视频的左右就会有一部分跑到画布外面去,我们使视频居中,左右跑出画布的视频宽度一样,如图:
视频显示宽高分别为:
videoDisplayWidth = (videoWidth/videoHeight) * drawableSize.height; videoDisplayHeight = drawableSize.height;
此时视频的宽videoDisplayWidth已经比画布的宽drawableSize.width要大了,为了居中,MTLViewpoint的orignX应该比0小,使视频画面向画布左偏移移。因此viewPoint设置如下:
[encoder setViewport:(MTLViewport){(drawableSize.width - videoDisplayWidth)/2, 0, videoDisplayWidth, videoDisplayHeight, -1.0, 1.0 }];
4. 如果canvaseScale <= videoScale
即画布相对视频比例,更宽,那么当视频等比缩放到和画面一样宽时,视频的上下就会有一部分跑到画布的外面去,我们要使视频居中,上下移出画布的视频高度应该一样,如下图:
视频显示宽高分别为:
videoDisplayWidth = drawableSize.width; videoDisplayHeight = (videoHeight/videoWidth)*drawableSize.width;
此时视频的高videoDisplayHeitht已经比画面的高drawableSize.height要大了,为了居中,MTLViewpoint的originY应该比0小,使视频的画面向画布下偏移。因此viewPoint设置如下:
[encoder setViewport:(MTLViewport){0, (drawableSize.height - videoDisplayHeight)/2, videoDisplayWidth, videoDisplayHeight, -1.0, 1.0 }];
以上是关于Metal渲染:实现画面比例功能的主要内容,如果未能解决你的问题,请参考以下文章