Android OpenGLES绘制天空盒

作者: a49f87ef5d4f | 来源:发表于2018-07-03 18:32 被阅读74次

    0.

    天空盒这个效果最早是在腾讯的实景地图里看到的,当时觉得很牛逼,但是没有想过自己去实现以下。最近这段时间对opengl很有兴趣,顺便就搞了这个天空盒,话不多说,先上效果。

    image

    天空盒的原理就是在三维空间中放置一个正方体,然后将我们的相机放置在正方体内,当我们的视点转动,相机跟着转动。我们就可以看到相应的景色的变换了,天空盒本质上是一个立方体。

    1.

    关于什么是OpenGL,什么是OpenGLES就不细说了,不了解的就自行百度吧,我们主要是关注代码。整个项目采用了Kotlin + Ndk的形式进行的开发。现在NDK的环境搭建比以前容易了,而且现在是使用CMakeList来构建C++代码的,不熟悉的可以去查看一下。整个项目就两个关键类,SkyBoxView和SkyBoxRender。下面分别来看一下。

    2.

    SkyBoxView继承了GLSurfaceView,为什么要继承GLSurfaceView,因为在使用OpenGLES需要建立一个窗口和一个上下文,GLSurfaceView帮我们做了这些工作。下面是SkyBoxView的主要代码:

    class SkyBoxView(context: Context, attributeSet: AttributeSet?) : GLSurfaceView(context, attributeSet)
    {
    
    private lateinit var skyBoxRender: SkyBoxRender
    
    private var lastX=0F
    
    private var lastY=0F
    
    private var yaw=0f
    
    private var pitch=0f
    
    private var screenWidth=0
    
    private var screenHeight=0
    
    private var horSensity=0.03f
    
    private var verSensity=0.03f
    
    constructor(context: Context) : this(context, null)
    
    init
    {
    //  initSensor()
        initSensity()
        initConfig()
    }
    
    private fun initSensity()
    {
        screenWidth=resources.displayMetrics.widthPixels
        screenHeight=resources.displayMetrics.heightPixels
        horSensity= 360.0f/screenWidth
        verSensity=180.0f/screenHeight
    }
    
    
    private fun rotate(pitch:Float,yaw:Float)
    {
        queueEvent {
    
            skyBoxRender.rotate(pitch,yaw)
        }
    }
    private fun initConfig()
    {
        setEGLContextClientVersion(3)
        skyBoxRender=SkyBoxRender(context)
        setRenderer(skyBoxRender)
        renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
    
    }
    
    override fun onTouchEvent(event: MotionEvent?): Boolean
    {
        when(event?.action)
        {
            MotionEvent.ACTION_DOWN->
            {
                lastX=event.x
                lastY=event.y
                return true
            }
    
            MotionEvent.ACTION_MOVE->
            {
                val offsetX=event.x-lastX
                val offsetY=lastY-event.y
                yaw+=offsetX*horSensity
                pitch+=offsetY*verSensity
                lastX=event.x
                lastY=event.y
                skyBoxRender.rotate(pitch,yaw)
            }
        }
    
        return true
    }
    
    }
    

    在initConfig方法里,设置了render为SkyBoxRender,真正的绘制是在这里进行的。在initSensity方法里设置了旋转精度, horSensity和verSensity,水平和数值旋转时的精度,就像你玩fps游戏设置的鼠标灵敏度一样。在onTouchEvent则根据手指滑动的距离设置俯仰角pitch和偏移脚yaw,调用skyBoxRender进行相机的旋转。另外如果你看github可能发现我注释掉了很多代码,那是用传感器旋转的尝试,但是觉得麻烦,也没继续做,有兴趣的读者可以自己搞一下。

    3.

    SkyboxRender的主要工作就是加载贴在正方体表面的6个图片纹理,从文件读取着色器语言,而真正创建opengles program和绘制是用C++代码来写的,所以主要看一下这里。

    #include <jni.h>
    #include <string>
    #include <GLUtils/GLUtils.h>
    #include <glm/glm.hpp>
    #include <glm/gtc/type_ptr.hpp>
    #include <glm/gtc/matrix_transform.hpp>
    
    extern "C" {
    
    JNIEXPORT jint JNICALL
    Java_com_skateboard_skybox_SkyBoxRender_genProgram(JNIEnv *env, jobject thiz, jstring vertexPath,
                                                   jstring fragmentPath) {
    //load program
    const char *cVertexPath = env->GetStringUTFChars(vertexPath, nullptr);
    const char *cFragmentPath = env->GetStringUTFChars(fragmentPath, nullptr);
    int program = glutils::loadProgram(cVertexPath, cFragmentPath);
    return program;
    
    }
    
    JNIEXPORT jint JNICALL
    Java_com_skateboard_skybox_SkyBoxRender_preparePos(JNIEnv *env, jobject thiz, jfloatArray pos) {
    //gen vao vbo
    
    unsigned int VAO, VBO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    int posSize = env->GetArrayLength(pos);
    float* p=env->GetFloatArrayElements(pos, nullptr);
    glBufferData(GL_ARRAY_BUFFER, posSize* sizeof(float), p,
                 GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
    glBindVertexArray(0);
    return VAO;
    }
    
    JNIEXPORT jint JNICALL
    Java_com_skateboard_skybox_SkyBoxRender_prepareTexture(JNIEnv *env, jobject thiz) {
    //gen texture
    unsigned int TEXTURE;
    glGenTextures(1, &TEXTURE);
    glBindTexture(GL_TEXTURE_CUBE_MAP, TEXTURE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    return 1;
    }
    
    glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 0.0f);
    glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
    
     JNIEXPORT void JNICALL
    Java_com_skateboard_skybox_SkyBoxRender_draw(JNIEnv *env, jobject thiz, jint program, jint VAO,
                                             jint texture,jfloat width,jfloat height) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glClearColor(0.0, 1.0, 0.0, 1.0);
    glUseProgram(program);
    
    glEnable(GL_DEPTH_TEST);
    glm::mat4 viewMatrix = glm::mat4(1.0f);
    glm::mat4 projectionMatrix = glm::mat4(1.0f);
    glm::vec3 v = glm::vec3(cameraFront.x - cameraPos.x, cameraFront.y - cameraPos.y,
                            cameraFront.z - cameraPos.z);
    viewMatrix = glm::lookAt(cameraPos, v, glm::vec3(0.0f, 1.0f, 0.0f));
    projectionMatrix = glm::perspective(glm::radians(45.0f), width / height, 0.1f,
                                        100.0f);
    int viewMatrixLocation = glGetUniformLocation(program, "view");
    int projectMatrixLocation = glGetUniformLocation(program, "projection");
    glUniformMatrix4fv(viewMatrixLocation, 1, GL_FALSE, &viewMatrix[0][0]);
    glUniformMatrix4fv(projectMatrixLocation, 1, GL_FALSE, &projectionMatrix[0][0]);
    
    glBindVertexArray(VAO);
    glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    

    }

    JNIEXPORT void JNICALL
    Java_com_skateboard_skybox_SkyBoxRender_rotate(JNIEnv *env, jobject thiz,jfloat pitch,jfloat yaw) {
    
    if(pitch>89)
    {
        pitch=89.0;
    }
    if(pitch<-89)
    {
        pitch=-89.0;
    }
    cameraFront.x=glm::cos(glm::radians(pitch))*glm::cos(glm::radians(yaw));
    cameraFront.y=glm::sin(glm::radians(pitch));
    cameraFront.z=glm::cos(glm::radians(pitch))*glm::sin(glm::radians(yaw));
    cameraFront=glm::normalize(cameraFront);
    }
    
    }
    

    genProgram主要是用来产生opengl es的program的,如果对这个概念不太理解请参考C++编译过程。
    preparePos是将java层顶点位置数组传入进来并写入顶点着色器。
    prepareTexture用来生成纹理。
    draw用来进行绘制。
    旋转的时候就是通过改变cameraFront的单位向量的方向来做到的。

    4.

    最后附上
    github

    相关文章

      网友评论

        本文标题:Android OpenGLES绘制天空盒

        本文链接:https://www.haomeiwen.com/subject/vhmjuftx.html