Android利用OpenGLES繪制天空盒實(shí)例教程
前言
天空盒這個(gè)效果最早是在騰訊的實(shí)景地圖里看到的,當(dāng)時(shí)覺得很牛逼,但是沒有想過自己去實(shí)現(xiàn)以下。最近這段時(shí)間對(duì)opengl很有興趣,順便就搞了這個(gè)天空盒,話不多說,先上效果。

天空盒的原理就是在三維空間中放置一個(gè)正方體,然后將我們的相機(jī)放置在正方體內(nèi),當(dāng)我們的視點(diǎn)轉(zhuǎn)動(dòng),相機(jī)跟著轉(zhuǎn)動(dòng)。我們就可以看到相應(yīng)的景色的變換了,天空盒本質(zhì)上是一個(gè)立方體。
OpenGL
關(guān)于什么是OpenGL,什么是OpenGLES就不細(xì)說了,不了解的就自行百度吧,我們主要是關(guān)注代碼。整個(gè)項(xiàng)目采用了Kotlin + Ndk的形式進(jìn)行的開發(fā)?,F(xiàn)在NDK的環(huán)境搭建比以前容易了,而且現(xiàn)在是使用CMakeList來構(gòu)建C++代碼的,不熟悉的可以去查看一下。整個(gè)項(xiàng)目就兩個(gè)關(guān)鍵類,SkyBoxView和SkyBoxRender。下面分別來看一下。
第一步
SkyBoxView繼承了GLSurfaceView,為什么要繼承GLSurfaceView,因?yàn)樵谑褂肙penGLES需要建立一個(gè)窗口和一個(gè)上下文,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方法里,設(shè)置了render為SkyBoxRender,真正的繪制是在這里進(jìn)行的。在initSensity方法里設(shè)置了旋轉(zhuǎn)精度, horSensity和verSensity,水平和數(shù)值旋轉(zhuǎn)時(shí)的精度,就像你玩fps游戲設(shè)置的鼠標(biāo)靈敏度一樣。在onTouchEvent則根據(jù)手指滑動(dòng)的距離設(shè)置俯仰角pitch和偏移腳yaw,調(diào)用skyBoxRender進(jìn)行相機(jī)的旋轉(zhuǎn)。另外如果你看github可能發(fā)現(xiàn)我注釋掉了很多代碼,那是用傳感器旋轉(zhuǎn)的嘗試,但是覺得麻煩,也沒繼續(xù)做,有興趣的讀者可以自己搞一下。
第二步
SkyboxRender的主要工作就是加載貼在正方體表面的6個(gè)圖片紋理,從文件讀取著色器語言,而真正創(chuàng)建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主要是用來產(chǎn)生opengl es的program的,如果對(duì)這個(gè)概念不太理解請(qǐng)參考C++編譯過程。
preparePos是將java層頂點(diǎn)位置數(shù)組傳入進(jìn)來并寫入頂點(diǎn)著色器。
prepareTexture用來生成紋理。
draw用來進(jìn)行繪制。
旋轉(zhuǎn)的時(shí)候就是通過改變cameraFront的單位向量的方向來做到的。
源碼下載
最后附上
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
android實(shí)用工具類分享(獲取內(nèi)存/檢查網(wǎng)絡(luò)/屏幕高度/手機(jī)分辨率)
這篇文章主要介紹了android實(shí)用工具類,包括獲取內(nèi)存、檢查網(wǎng)絡(luò)、屏幕高度、手機(jī)分辨率、獲取版本號(hào)等功能,需要的朋友可以參考下2014-03-03
Android之AppWidget(桌面小部件)開發(fā)淺析
這篇文章主要介紹了Android之AppWidget(桌面小部件)開發(fā)淺析,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02
Androidstudio調(diào)用攝像頭拍照并保存照片
這篇文章主要為大家詳細(xì)介紹了Androidstudio調(diào)用攝像頭拍照并保存照片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
flutter實(shí)現(xiàn)底部導(dǎo)航欄切換
這篇文章主要為大家詳細(xì)介紹了flutter實(shí)現(xiàn)底部導(dǎo)航欄切換,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07
Android實(shí)現(xiàn)雙層ViewPager嵌套
這篇文章主要介紹了Android實(shí)現(xiàn)雙層ViewPager嵌套,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04
Android Internet應(yīng)用實(shí)現(xiàn)獲取天氣預(yù)報(bào)的示例代碼
這篇文章主要介紹了Android網(wǎng)絡(luò)編程及Internet應(yīng)用-獲取天氣,小編覺得挺不錯(cuò)的,一起跟隨小編過來看看吧2018-05-05

