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

全景顾名思义就是给人以三维立体感觉的实景360度全方位图像~

此图像最大的三个特点是:

1、全:全方位,全面的展示了360度球型范围内的所有景致;可在例子中用鼠标左键按住拖动,观看场景的各个方向;

2、景:实景,真实的场景,三维实景大多是在照片基础之上拼合得到的图像,最大限度的保留了场景的真实性;

3、360:360度环视的效果,虽然照片都是平面的,但是通过软件处理之后得到的360度实景,却能给人以三维立体的空间感觉,使观者犹如身在其中。全景由于它给人们带来全新的真实现场感和交互式的感受。它可广泛应用于三维电子商务,如在线的房地产楼盘展示、虚拟旅游、虚拟教育等领域。

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

看效果图:

b33b06a745a3

GIF_.gif

我们用连续的6张天空图片,拼接成了一个无缝的立方体。想想一下我们站在这个立方体的中心,这个时候我们的前后左右上下都充满了天空的图片,不管你的头转向哪边,都能够看见天空。

理论上我们把眼睛旋转360度观察,图上的三个光束会先消失在出现,这就像是我们把立方体旋转了360度又回到了原位置一样。就像下图:

b33b06a745a3

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

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐