Android平台openGL ES实现全景图片

全景又被称为3D实景,是一种新兴的富媒体技术,其与视频,声音,图片等传统的流媒体最大的区别是“可操作,可交互”。 全景分为虚拟现实和3D实景两种。虚拟现实是利用maya等软件,制作出来的模拟现实的场景,代表有虚拟紫禁城等;3D实景是利用单反相机或街景车拍摄实景照片,经过特殊的拼合,处理,让作者立于画境中,让最美的一面展现出来。

全景顾名思义就是给人以三维立体感觉的实景360度全方位图像~
此图像最大的三个特点是:
1、全:全方位,全面的展示了360度球型范围内的所有景致;可在例子中用鼠标左键按住拖动,观看场景的各个方向;  
2、景:实景,真实的场景,三维实景大多是在照片基础之上拼合得到的图像,最大限度的保留了场景的真实性;  
3、360:360度环视的效果,虽然照片都是平面的,但是通过软件处理之后得到的360度实景,却能给人以三维立体的空间感觉,使观者犹如身在其中。全景由于它给人们带来全新的真实现场感和交互式的感受。它可广泛应用于三维电子商务,如在线的房地产楼盘展示、虚拟旅游、虚拟教育等领域。

本篇我们基于上一篇粒子光束 的基础上实现全景背景图

看效果图:

GIF_.gif

我们用连续的6张天空图片,拼接成了一个无缝的立方体。想想一下我们站在这个立方体的中心,这个时候我们的前后左右上下都充满了天空的图片,不管你的头转向哪边,都能够看见天空。
理论上我们把眼睛旋转360度观察,图上的三个光束会先消失在出现,这就像是我们把立方体旋转了360度又回到了原位置一样。就像下图:

GIF5_.gif

之所以能实现360度旋转,是因为我们用了6张图片并把他们加载成一个立方体。

我们先创建一个模型对象类,即立方体模型。

public class Skybox {
    private static final int POSITION_COMPONENT_COUNT = 3;
    private final VertexArray vertexArray;
    private final ByteBuffer indexArray;

    public Skybox() {        
        // Create a unit cube.
        vertexArray = new VertexArray(new float[] {
            -1,  1,  1,     // (0) Top-left near
             1,  1,  1,     // (1) Top-right near
            -1, -1,  1,     // (2) Bottom-left near
             1, -1,  1,     // (3) Bottom-right near
            -1,  1, -1,     // (4) Top-left far
             1,  1, -1,     // (5) Top-right far
            -1, -1, -1,     // (6) Bottom-left far
             1, -1, -1      // (7) Bottom-right far                        
        });

        // 6 indices per cube side
        indexArray =  ByteBuffer.allocateDirect(6 * 6)
            .put(new byte[] {
                // Front
                1, 3, 0,
                0, 3, 2,

                // Back
                4, 6, 5,
                5, 6, 7,

                // Left
                0, 2, 4,
                4, 2, 6,

                // Right
                5, 7, 1,
                1, 7, 3,

                // Top
                5, 1, 4,
                4, 1, 0,

                // Bottom
                6, 2, 7,
                7, 2, 3
            });
        indexArray.position(0);        
    }
    public void bindData(SkyboxShaderProgram skyboxProgram) {
        vertexArray.setVertexAttribPointer(0,
            skyboxProgram.getPositionAttributeLocation(),
            POSITION_COMPONENT_COUNT, 0);               
    }

    public void draw() {
        glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, indexArray);
    }
}

我们用VertexArray储存立方体的8个顶点。用indexArray 这个索引数组的索引指向每个顶点,把所有顶点分别绑定成三角形组,每个组有立方体上每个面的2个三角形。

bindData方法从内存中加载数据绑定,然后通过 glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, indexArray);绘制立方体。

下面我们添加着色器

顶点着色器

uniform mat4 u_Matrix;
attribute vec3 a_Position;  
varying vec3 v_Position;

void main()                    
{                                                 
    v_Position = a_Position;    //把顶点位置传给片段着色器

    v_Position.z = -v_Position.z; //反转Z分量。把右手坐标系转化为左手坐标系

    gl_Position = u_Matrix * vec4(a_Position, 1.0);//成u_Matrix即用投影~
    gl_Position = gl_Position.xyww;//把Z值变成W,这样透视除法之后为1,即Z始终在1的远平面上。Z=1最远,即在别的物体的后面,就像是背景。
}    

片段着色器:

precision mediump float; 

uniform samplerCube u_TextureUnit;//立方体纹理
varying vec3 v_Position;

void main()                         
{
    gl_FragColor = textureCube(u_TextureUnit, v_Position);    
}

然后用Java代码封装着色器程序
这里用java代码映射到着色器上

  uMatrixLocation = glGetUniformLocation(program, U_MATRIX);
    uTextureUnitLocation = glGetUniformLocation(program, U_TEXTURE_UNIT);
    aPositionLocation = glGetAttribLocation(program, A_POSITION);
  }


  public void setUniforms(float[] matrix, int textureId) {
    glUniformMatrix4fv(uMatrixLocation, 1, false, matrix, 0);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureId);
    glUniform1i(uTextureUnitLocation, 0);
  }

有了关系映射,就可以绑定数据进行绘制了
@onDrawFrame(GL10 gl10)中

skyboxProgram.useProgram();
    skyboxProgram.setUniforms(viewProjectionMatrix, skyboxTexture);//映射
    skybox.bindData(skyboxProgram);//绑定数据
    skybox.draw();//绘制

下面看手势操作代码
在Activity中监听glSurfaceView

glSurfaceView.setOnTouchListener(new View.OnTouchListener() {
      float previousX, previousY;

      @Override public boolean onTouch(View v, MotionEvent event) {
        if (event != null) {
          if (event.getAction() == MotionEvent.ACTION_DOWN) {
            previousX = event.getX();
            previousY = event.getY();
          } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            final float deltaX = event.getX() - previousX;
            final float deltaY = event.getY() - previousY;

            previousX = event.getX();
            previousY = event.getY();

            glSurfaceView.queueEvent(new Runnable() {
              @Override public void run() {
                particlesRenderer.handleTouchDrag(deltaX, deltaY);
              }
            });
          }

          return true;
        } else {
          return false;
        }
      }
    });

因为openGL是在一个单独的线程中的,所以需要 glSurfaceView.queueEvent发送事件

把deltaX, deltaY传递到了Renderer类中

public void handleTouchDrag(float deltaX, float deltaY) {
    xRotation += deltaX / 16f; //除以16是缩减拖动效果的
    yRotation += deltaY / 16f;

然后我们根据这个滑动值,用矩阵去操作立方体变化。
@onDrawFrame

//以 0 0 0为中心绘制,我们站在中心观察
  private void drawSkybox() {
    setIdentityM(viewMatrix, 0);
    rotateM(viewMatrix, 0, -yRotation, 1f, 0f, 0f);//沿着Y轴旋转
    rotateM(viewMatrix, 0, -xRotation, 0f, 1f, 0f);//沿着x轴旋转  FPS模型
    multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
    skyboxProgram.useProgram();
    skyboxProgram.setUniforms(viewProjectionMatrix, skyboxTexture);
    skybox.bindData(skyboxProgram);
    skybox.draw();
  }

在这之前我们要在onSurfaceCreated里面初始化立方体

skyboxProgram = new SkyboxShaderProgram(context);
    skybox = new Skybox();

@Override public void onSurfaceChanged(GL10 gl10, int width, int height) {
    GLES20.glViewport(0, 0, width, height);
    MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width / (float) height, 1f, 10f);
  }

ps:

Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行,最近发布了Kotlin/Native能把Kotlin编译成机器码,也就是C/C++一样的能力。本专题专注Kotlin,Kotlin/Native,KotlinJS与Kotlin_Android的那些事,让我们共同学习Kotlin壮大Kotlin~
加入专题吧

Kotlin-Android-KotlinJS-Kotlin/Native:http://www.jianshu.com/c/e88f0f9356a8

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

推荐阅读更多精彩内容