Android OpenGLES繪製天空盒

滑板上的老砒霜發表於2018-07-03

0.

天空盒這個效果最早是在騰訊的實景地圖裡看到的,當時覺得很牛逼,但是沒有想過自己去實現以下。最近這段時間對opengl很有興趣,順便就搞了這個天空盒,話不多說,先上效果。

Android OpenGLES繪製天空盒
天空盒的原理就是在三維空間中放置一個正方體,然後將我們的相機放置在正方體內,當我們的視點轉動,相機跟著轉動。我們就可以看到相應的景色的變換了,天空盒本質上是一個立方體。

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繪製天空盒
公眾號:滑板上的老砒霜,定期分享技術原創文章。

相關文章