仿insta360,移动端展示球形,小行星,全景类型
鱼眼镜头是什么?
鱼眼镜头是一种极端超广角镜头,其具有两大主要特点:短焦距(16mm或更短),大视场(视场角180度至270度),“鱼眼镜头”是它的俗称。详细请百度
双鱼眼相机
两个超180°的广角鱼眼镜头组成同步采集左右两帧画面
方案:还是使用我们熟悉的GPUImage来做渲染
流程:
1.左右两图先渲染到一个纹理上
@interface GPUimageUIImageInput : GPUImageOutput
// 这里用UIImage当做输入源做测试,实际应该是固件传过来的YUV
- (BOOL)processLeftImage:(UIImage *)leftImage rightImage:(UIImage *)rightImage frameTime:(CMTime)frameTime;
@end
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);
}
);
上面我还将调色的参数的相关参数加在着色器中了
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);
}
看效果吧
我们把拖拽手势给加上:
- (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];
}
总结一下:
参考了很多资料,总算是弄出了效果。
但是还是有很多地方要优化:比如拼接处的色差,性能等问题。待以后继续研究。
这里只是学习记录,也是参考网上各位大佬实现的,没有上具体代码。
需要请email: 13048914897@163.com