iOS 仿insta360双鱼眼相机全景视频渲染方案

仿insta360,移动端展示球形,小行星,全景类型

鱼眼镜头是什么?
鱼眼镜头是一种极端超广角镜头,其具有两大主要特点:短焦距(16mm或更短),大视场(视场角180度至270度),“鱼眼镜头”是它的俗称。详细请百度

双鱼眼相机


4661684462583_.pic.jpg

两个超180°的广角鱼眼镜头组成同步采集左右两帧画面

方案:还是使用我们熟悉的GPUImage来做渲染
流程:
1.左右两图先渲染到一个纹理上

@interface GPUimageUIImageInput : GPUImageOutput
// 这里用UIImage当做输入源做测试,实际应该是固件传过来的YUV
- (BOOL)processLeftImage:(UIImage *)leftImage rightImage:(UIImage *)rightImage frameTime:(CMTime)frameTime;
@end

left.png 长宽比1
right.png 长宽比1
full.jpeg 这里只是屏幕截图,实际大小是长宽比为2

2.将左右两鱼眼图展开成全景平面图

我们写一个继承GPUImageFilter的filter:CCDoubleFishEyeExpansionFilter
重写他的 FragmentShader。将GPUimageUIImageInput输入的bufferFrame再次渲染

思路是:
a.将平面坐标转换到球面坐标
b.将球面坐标转换为经纬度
c.取经纬度的二维坐标

 void main()
 {
     vec2 uv = mod(abs(textureCoordinate), 1.0);
     float m = blend + 1.0;
     uv = vec2((mod(textureCoordinate.x, 0.5) / (0.5 * m)), textureCoordinate.y);
     vec2 uv0 = ptstp(uv, radius, vec2(textureCoordinate.x > 0.5 ? 0.75 : 0.25, 0.5));
     vec4 col = texture2D(inputImageTexture, mix(textureCoordinate, uv0, lerp));
     
     if (blend > 0.0)
     {
         m = 1.0 - 1.0 / m;
         if (uv.x < m)
         {
             vec2 uv1 = ptstp(uv - vec2(1.0 + m, 0.0), radius, vec2(textureCoordinate.x > 0.5 ? 0.25 : 0.75, 0.5));
             vec4 _col = texture2D(inputImageTexture, mix(textureCoordinate, uv1, lerp));
             col = mix(col, _col, (m - uv.x) / m);
         }
     }
     
     if (hint > 0.0)
     {
         vec2 s0 = uv - vec2(0.5, 0.5);
         if (length(s0) < radius)
             col += vec4(0.5, 0.0, 0.0, 1.0);
     }
     
    lowp vec4 textureColor = col;
    //对比度
    textureColor = vec4((textureColor.rgb + vec3(brightness)), textureColor.w);
    //曝光
    textureColor = vec4(textureColor.rgb * pow(2.0, exposure), textureColor.w);
    //对比度
    textureColor = vec4(((textureColor.rgb - vec3(0.5)) * contrast + vec3(0.5)), textureColor.w);
    //饱和度
    lowp float luminance = dot(textureColor.rgb, luminanceWeighting);
    lowp vec3 greyScaleColor = vec3(luminance);
    textureColor = vec4(mix(greyScaleColor, textureColor.rgb, saturation), textureColor.w);
    
    //色温、色调
    mediump vec3 yiq = RGBtoYIQ * textureColor.rgb; //adjusting tint
    yiq.b = clamp(yiq.b + tint*0.5226*0.1, -0.5226, 0.5226);
    lowp vec3 rgb = YIQtoRGB * yiq;

    lowp vec3 processed = vec3(
        (rgb.r < 0.5 ? (2.0 * rgb.r * warmFilter.r) : (1.0 - 2.0 * (1.0 - rgb.r) * (1.0 - warmFilter.r))), //adjusting temperature
        (rgb.g < 0.5 ? (2.0 * rgb.g * warmFilter.g) : (1.0 - 2.0 * (1.0 - rgb.g) * (1.0 - warmFilter.g))),
        (rgb.b < 0.5 ? (2.0 * rgb.b * warmFilter.b) : (1.0 - 2.0 * (1.0 - rgb.b) * (1.0 - warmFilter.b))));
    textureColor = vec4(mix(rgb, processed, temperature), textureColor.a);
    
    //阴影、高光
    mediump float luminance1 = dot(textureColor.rgb, luminanceWeighting1);
    mediump float shadow = clamp((pow(luminance1, 1.0/(shadows+1.0)) + (-0.76)*pow(luminance1, 2.0/(shadows+1.0))) - luminance1, 0.0, 1.0);
    mediump float highlight = clamp((1.0 - (pow(1.0-luminance1, 1.0/(2.0-highlights)) + (-0.8)*pow(1.0-luminance1, 2.0/(2.0-highlights)))) - luminance1, -1.0, 0.0);
    lowp vec3 result = vec3(0.0, 0.0, 0.0) + ((luminance1 + shadow + highlight) - 0.0) * ((textureColor.rgb - vec3(0.0, 0.0, 0.0))/(luminance1 - 0.0));
    
    gl_FragColor = vec4(result.rgb, textureColor.a);
 }
);

上面我还将调色的参数的相关参数加在着色器中了

这gif我做了好多张了,就是不动啊

展开后,调整参数

3.将全景平面图渲染到球体上
我们写一个继承GPUImageFilter的filter:CCPanoramaBallFilter
这里我们需要用顶点绘一个圆:

/// 画球顶点
/// - Parameters:
///   - slice: 将 360 均分slice块
///   - radius: 半径1
- (void)generateSphereVerticesSlice:(int)slice radius:(float)radius{
    int parallelsNum = slice / 2; // 这里可以看做是 一方向的180°
    int verticesNum = (parallelsNum + 1) * (slice + 1); // 顶点坐标个数
    int indicesNum = parallelsNum * slice * 6; // 纹理坐标个数
    float angleStep = (2 * M_PI) / (float)(slice); // 将360°均分slice个小角度
    
    // 顶点坐标和纹理坐标
    GLfloat vertexArray[verticesNum * 5];
    // 顶点坐标索引数组
    GLushort vertexIndexArray[indicesNum];
    
    /* 顶点坐标公式
     x = r * sin α * sin β
     y = r * cos α
     z = r * sin α * cos β
     */
    _verticesSize = 0;
    for (int i = 0; i < parallelsNum + 1; i++) {
        for (int j = 0; j < slice + 1; j++) {
            int vertexIndex = (i * (slice + 1) + j) * 5;
            vertexArray[vertexIndex + 0] = (radius * sinf(angleStep * (CGFloat)(i)) * sinf(angleStep * (CGFloat)(j)));
            vertexArray[vertexIndex + 1] = (radius * cosf(angleStep * (CGFloat)(i)));
            vertexArray[vertexIndex + 2] = (radius * sinf(angleStep * (CGFloat)(i)) * cosf(angleStep * (CGFloat)(j)));
            
            vertexArray[vertexIndex + 3] = (CGFloat)(j) / (CGFloat)(slice);
            vertexArray[vertexIndex + 4] = (CGFloat)(1.0) - ((CGFloat)(i) / (CGFloat)(parallelsNum));
            
            _verticesSize += 5;
        }
    }
    
    int vertexIndexTemp = 0;
    for (int i = 0; i < parallelsNum; i++) {
        for (int j = 0; j < slice; j++) {
            vertexIndexArray[0 + vertexIndexTemp] = (GLushort)(i * (slice + 1) + j);
            vertexIndexArray[1 + vertexIndexTemp] = (GLushort)((i + 1) * (slice + 1) + j);
            vertexIndexArray[2 + vertexIndexTemp] = (GLushort)((i + 1) * (slice + 1) + (j + 1));
            
            vertexIndexArray[3 + vertexIndexTemp] = (GLushort)(i * (slice + 1) + j);
            vertexIndexArray[4 + vertexIndexTemp] = (GLushort)((i + 1) * (slice + 1) + (j + 1));
            vertexIndexArray[5 + vertexIndexTemp] = (GLushort)(i * (slice + 1) + (j + 1));
            
            vertexIndexTemp += 6;
        }
    }
    _indicesSize = vertexIndexTemp;
    
    _vertices = vertexArray;
    _indices = vertexIndexArray;

    //设置VBO
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexArray), vertexArray, GL_STATIC_DRAW);
    
    //设置EBO
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(vertexIndexArray), vertexIndexArray, GL_STATIC_DRAW);
}

顶点坐标中加入mvp变换矩阵

NSString *const kPanoramaBallMVPVertexShaderString = SHADER_STRING
(
 uniform mat4 mvpMatrix;   // 最终的 MVP 变换矩阵
 attribute vec4 position;
 attribute vec4 inputTextureCoordinate;
 
 varying vec2 textureCoordinate;
 
 void main()
 {
    gl_Position = mvpMatrix * position;
    textureCoordinate = inputTextureCoordinate.xy;
 }
);

定义球体视角类型

typedef NS_ENUM(NSInteger,PanoramaType) {
    ///球体
    PanoramaType_Sphere,
    ///全景
    PanoramaType_Pano,
    ///小行星
    PanoramaType_Asteroid,
};

投影矩阵

- (void)setPanoramaType:(PanoramaType)panoramaType{
    _panoramaType = panoramaType;
    
    switch (panoramaType) {
        case PanoramaType_Sphere: //球体
        {
            _projectionFov = 65;
            _camEyeX = 0;
            _camEyeY = 0;
            _camEyeZ = 4;
            _camCenterX = 0;
            _camCenterY = 0;
            _camCenterZ = 0;
            _camUpX = 0;
            _camUpY = 1;
            _camUpZ = 0;
            
            _cullFaceEnable = YES;
        }
            break;
        case PanoramaType_Pano: //全景
        {
            _projectionFov = 65;
            _camEyeX = 0;
            _camEyeY = 0;
            _camEyeZ = 0.5;
            _camCenterX = 0;
            _camCenterY = 0;
            _camCenterZ = 0;
            _camUpX = 0;
            _camUpY = 1;
            _camUpZ = 0;
            
            _cullFaceEnable = NO;
        }
            break;
        case PanoramaType_Asteroid: //小行星
        {
            _projectionFov = 140;
            _camEyeX = 0;
            _camEyeY = 0;
            _camEyeZ = 1;
            _camCenterX = 0;
            _camCenterY = 0;
            _camCenterZ = 0;
            _camUpX = 0;
            _camUpY = 1;
            _camUpZ = 0;
            
            _cullFaceEnable = NO;
        }
            break;
            
        default:
            break;
    }
}

#pragma mark - public

- (GLKMatrix4)getTransformMatrix{
    //手势拖拽
    GLKMatrix4 modelViewMatrix = self.gestureTransformMatrix;
    //陀螺仪运动
    modelViewMatrix = GLKMatrix4Multiply(modelViewMatrix, self.deviceMotionMatrix);
    
    switch (_panoramaType) {
        case PanoramaType_Asteroid:
        {
            GLKMatrix4 matrix = GLKMatrix4RotateX(GLKMatrix4Identity, 1.2690004);
            matrix = GLKMatrix4RotateY(matrix, -0.138);
            GLKMatrix4 viewMatrix = GLKMatrix4MakeLookAt(_camEyeX, _camEyeY, _camEyeZ, _camCenterX, _camCenterY, _camCenterZ, _camUpX, _camUpY, _camUpZ);
            viewMatrix = GLKMatrix4Multiply(viewMatrix, matrix);
            modelViewMatrix = GLKMatrix4Multiply(viewMatrix, modelViewMatrix);
        }
            break;
        case PanoramaType_Pano:
        case PanoramaType_Sphere:
        {
            GLKMatrix4 viewMatrix = GLKMatrix4MakeLookAt(_camEyeX, _camEyeY, _camEyeZ, _camCenterX, _camCenterY, _camCenterZ, _camUpX, _camUpY, _camUpZ);
            modelViewMatrix = GLKMatrix4Multiply(viewMatrix, modelViewMatrix);
        }
            break;
        default:
            break;
    }
    
    GLKMatrix4 projectionMatrix = [self getProjectionMatrix];
    return GLKMatrix4Multiply(projectionMatrix,modelViewMatrix);
}

- (GLKMatrix4)getProjectionMatrix{
    
    float width = self.panoramaDisplaySize.width * UIScreen.mainScreen.scale;
    float height = self.panoramaDisplaySize.height * UIScreen.mainScreen.scale;
    float aspect = (float)(width / height);
    float nearZ = 0.1;
    float farZ = 100.0;
    
    return GLKMatrix4MakePerspective(GLKMathDegreesToRadians(self.projectionFov), aspect, nearZ, farZ);
}

看效果吧


球.PNG

全景.PNG

小行星.PNG

我们把拖拽手势给加上:

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    if (!_gestureEnable){
        return;
    }
    
    if (!_panoramaTransformObj){
        NSLog(@"%@ plase config transfromObj",self);
        return;
    }
    
    UITouch *touch = [touches anyObject];
    CGPoint location = [touch locationInView:self];
    CGPoint previousLocation = [touch previousLocationInView:self];
    
    GLKVector3 previousTouchVector = [self vectorFromScreenLocation:previousLocation inAttitude:_gestureTransformMatrix];
    GLKVector3 nowVector = [self vectorFromScreenLocation:location inAttitude:_gestureTransformMatrix];
    GLKQuaternion q = GLKQuaternionFromTwoVectors(previousTouchVector, nowVector);
    _gestureTransformMatrix = GLKMatrix4Multiply(_gestureTransformMatrix, GLKMatrix4MakeWithQuaternion(q));
    [_panoramaTransformObj updateGestureTransformMatrix:_gestureTransformMatrix];
}
IMG_4414.GIF

总结一下:
参考了很多资料,总算是弄出了效果。
但是还是有很多地方要优化:比如拼接处的色差,性能等问题。待以后继续研究。
这里只是学习记录,也是参考网上各位大佬实现的,没有上具体代码。
需要请email: 13048914897@163.com

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容