Android 上的 OpenGL ES:如何旋转相机以响应触摸事件?

问题描述 投票:0回答:1

有关 OpenGL ES 的 Android 开发人员指南描述了如何应用基于触摸输入的相机视图转换(请参阅OpenGL ES:响应触摸事件)。

这基本上是通过根据触摸的 x 和 y 移动设置

angle
属性来实现的……

override fun onTouchEvent(e: MotionEvent): Boolean {
    /* … */
    renderer.angle += (dx + dy) * TOUCH_SCALE_FACTOR
    /* … */
}

...并根据它应用旋转:

override fun onDrawFrame(gl: GL10) {
    /* … */
    Matrix.setRotateM(rotationMatrix, 0, angle, 0f, 0f, -1.0f)
    /* … */
}

但是,在这种方法中,触摸事件的 x 坐标和 y 坐标都组合为一个角度,确定绕 z 轴的旋转。

我尝试分离角度......

override fun onTouchEvent(e: MotionEvent): Boolean {
    /* … */
    renderer.angleX += dx * TOUCH_SCALE_FACTOR
    renderer.angleY += dy * TOUCH_SCALE_FACTOR
    /* … */
}

...执行不同的旋转:

override fun onDrawFrame(gl: GL10) {
    /* … */
    Matrix.setRotateM(rotationMatrix, 0, angleX, 0f, 1f, 0f)
    Matrix.setRotateM(rotationMatrix, 0, angleY, 1f, 0f, 0f)
    /* … */
}

但是,沿 x 维度拖动时,结果似乎不太正确。

我如何获得这种非常基本的摄像机运动,就像您在不同平台上的几乎所有其他 3D 程序中看到的那样?

(在 iOS SceneKit 中,您可以使用一行来获取此(和其他手势):

sceneView.allowsCameraControl = true
。)

android kotlin opengl-es
1个回答
0
投票

相机可以被认为是在围绕原点的球体表面上移动。因此,球坐标非常适合描述其运动。

为了定义空间中点的位置,球坐标系使用点到原点(球体半径)的距离

r
和两个角度
theta
phi
的组合。对于我们的用例,半径将保持固定。 x 方向上的触摸移动将对应于
phi
的变化,y 方向上的移动将对应于
theta
的变化。

互换 OpenGL ES 风格坐标系的轴,球坐标

(r, theta, phi)
按以下方式转换为常规笛卡尔坐标
(x, y, z)

x = r * sin(theta) * sin(phi)
y = r * cos(theta)
z = r * sin(theta) * cos(phi)

相机的位置和方向将仅使用

Matrix.setLookAtM()
方法设置。除了输出矩阵(及其内部的偏移量)之外,此方法还需要三个输入向量。
eye
向量定义了相机在三维空间中的位置,而
center
向量指定了相机将要观察的点(在我们的例子中,该点始终是原点)。这为相机留下了一个自由度,即它围绕连接眼睛和中心的线旋转。该旋转由定义相机视场中向上方向的
up
矢量确定。只有该向量的方向有任何相关性,而不是其长度。

在代码中,再次使用

onTouchEvent()
子类的
GLSurfaceView
方法来捕获触摸输入(围绕中点的方向反转可能会也可能不会被删除):

override fun onTouchEvent(e: MotionEvent): Boolean {
    /* … */
    renderer.phi -= dx * 0.01f
    renderer.theta -= dy * 0.01f
    /* … */
}

GLSurfaceView.Renderer
子类声明两个角度和固定半径,并执行相机变换。最初,角度设置为
theta = 90°
phi = 0°
,对应于
(x, y, z) = (0, 0, r)
:

class CubeRenderer : GLSurfaceView.Renderer {
    /* … */

    @Volatile
    var theta: Double = PI / 2.0

    @Volatile
    var phi: Double = 0.0

    override fun onDrawFrame(unused: GL10) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)

        val x = RADIUS * sin(theta) * sin(phi)
        val y = RADIUS * cos(theta)
        val z = RADIUS * sin(theta) * cos(phi)

        val ux = -cos(theta) * sin(phi)
        val uy = sin(theta)
        val uz = -cos(theta) * cos(phi)

        Matrix.setLookAtM(
            viewMatrix, 0,
            x.toFloat(), y.toFloat(), z.toFloat(),   // eye
            0.0f, 0.0f, 0.0f,                        // center (= origin)
            ux.toFloat(), uy.toFloat(), uz.toFloat() // up
        )

        Matrix.multiplyMM(vPMatrix, 0, projectionMatrix, 0, viewMatrix, 0)
        triangle.draw(vPMatrix)
    }

    private companion object {
        const val RADIUS = 5.0
    }
}

up
向量设置为与连接
eye
center
向量的线正交。给定
(r, theta, phi)
向量的球面坐标
eye
,这可以通过将
up
向量设置为
(1, theta - pi/2, phi)
来实现。也就是说,
up
向量的θ角比
eye
向量的θ角小90°,并且由于其长度并不重要,因此将其设置为1(以简化计算)。

鉴于

sin(x - pi/2) = -cos(x)
cos(x - pi/2) = sin(x)
对于任何实数
x
,我们因此得到:

ux = sin(theta - pi/2) * sin(phi) = -cos(theta) * sin(phi)
uy = cos(theta - pi/2) = sin(theta)
uz = sin(theta - pi/2) * cos(phi) = -cos(theta) * cos(phi)

eye
向量初始设置为
(r, theta, phi) = (5, pi/2, 0)
up
向量最初将为
(x, y, z) = (0, 1, 0)
,即指向 y 轴正方向。

© www.soinside.com 2019 - 2024. All rights reserved.