=============================== 前序================================
AndroidLearnOpenGL是本博主自己实现的LearnOpenGL练习集合:
Github地址:https://github.com/wangyongyao1989/AndroidLearnOpenGL
系列文章:
1、LearnOpenGL之入门基础
2、LearnOpenGL之3D显示
===================================================================
============================== 显示效果 ===============================
===================================================================
根据上一篇文章的LearnOpenGL入门基础的学习,我们已经了解了OpenGL一些基础概念,接我们一起下来实现3D效果。
一、3D概序:
1、模型矩阵(Model Matrix):
在开始进行3D绘图时,我们首先创建一个模型矩阵。这个模型矩阵包含了位移、缩放与旋转操作,它们会被应用到所有物体的顶点上,以变化它们到全局的世界空间。让我们变换一下我们的平面,将其绕着x轴旋转,使它看起来像放在地上一样。这个模型矩阵看起来是这样的
glm::mat4 model;
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
通过将顶点坐标乘以这个模型矩阵,我们将该顶点坐标变换到世界坐标。我们的平面看起来就是在地板上,代表全局世界里的平面。
2、观察矩阵(View Matrix):
我们想要在场景里面稍微往后移动,以使得物体变成可见的(当在世界空间时,我们位于原点(0,0,0))。摄像机向后移动,和将整个场景向前移动是一样的。
glm::mat4 projection;
projection = glm::perspective(glm::radians(45.0f),
screenWidth / screenHeight, 0.1f, 100.0f);
这正是观察矩阵所做的,我们以相反于摄像机移动的方向移动整个场景。因为我们想要往后移动,并且OpenGL是一个右手坐标系(Right-handed System),所以我们需要沿着z轴的正方向移动。我们会通过将场景沿着z轴负方向平移来实现。它会给我们一种我们在往后移动的感觉。
3、投影矩阵(Projection Matrix):
定义一个投影矩阵。我们希望在场景中使用透视投影,所以像这样声明一个投影矩阵:
glm::mat4 projection;
projection = glm::perspective(glm::radians(45.0f)
, screenWidth / screenHeight, 0.1f, 100.0f);
4、变换矩阵结合,从顶点glsl程序传入着色:
我们应该将它们传入着色器。首先,让我们在顶点着色器中声明一个uniform变换矩阵然后将它乘以顶点坐标:
#version 320 es
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0
layout (location = 1) in vec3 aTexCoord;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
// 注意乘法要从右向左读
gl_Position = projection * view * model * vec4(aPos, 1.0f);
TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}
将矩阵传入着色器(这通常在每次的渲染迭代中进行,因为变换矩阵会经常变动。
// create transformations
// make sure to initialize matrix to identity matrix first
glm::mat4 model = glm::mat4(1.0f); //模型矩阵(Model Matrix)
glm::mat4 view = glm::mat4(1.0f); //观察矩阵(View Matrix)
glm::mat4 projection = glm::mat4(1.0f); //投影矩阵(Projection Matrix)
double timeValue = clock() * 10 / CLOCKS_PER_SEC;
model = glm::rotate(model, (float) timeValue,
glm::vec3(0.5f, 1.0f, 0.0f));
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
projection = glm::perspective(glm::radians(45.0f)
, (float) screenW / (float) screenH, 0.1f, 100.0f);
// retrieve the matrix uniform locations
unsigned int modelLoc = glGetUniformLocation(shaderProgram, "model");
unsigned int viewLoc = glGetUniformLocation(shaderProgram, "view");
// pass them to the shaders (3 different ways)
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, &view[0][0]);
setMat4("projection", projection);
5、Z缓冲:
OpenGL存储它的所有深度信息于一个Z缓冲(Z-buffer)中,也被称为深度缓冲(Depth Buffer)。GLFW会自动为你生成这样一个缓冲(就像它也有一个颜色缓冲来存储输出图像的颜色)。深度值存储在每个片段里面(作为片段的z值),当片段想要输出它的颜色时,OpenGL会将它的深度值和z缓冲进行比较,如果当前的片段在其它片段之后,它将会被丢弃,否则将会覆盖。这个过程称为深度测试(Depth Testing),它是由OpenGL自动完成的。
然而,如果我们想要确定OpenGL真的执行了深度测试,首先我们要告诉OpenGL我们想要启用深度测试;它默认是关闭的。我们可以通过glEnable函数来开启深度测试。glEnable和glDisable函数允许我们启用或禁用某个OpenGL功能。这个功能会一直保持启用/禁用状态,直到另一个调用来禁用/启用它。现在我们想启用深度测试,需要开启GL_DEPTH_TEST:
glEnable(GL_DEPTH_TEST);
因为我们使用了深度测试,我们也想要在每次渲染迭代之前清除深度缓冲(否则前一帧的深度信息仍然保存在缓冲中)。就像清除颜色缓冲一样,我们可以通过在glClear函数中指定DEPTH_BUFFER_BIT位来清除深度缓冲:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
二、代码实现概览:
1、代码地址:
Github地址:https://github.com/wangyongyao1989/AndroidLearnOpenGL
2、 调用流程图:
3、代码调用的过程:
代码从三个方法setMultiCube3DGLSLPath()、initMultiCube3DOpengl()、MultiCube3DOpenGLRenderFrame()对native层代码进行赋值、初始化、渲染的操作。
- setMultiCube3DGLSLPath():把glsl文件的顶点和片段程序的路径地址传入C++层
- initMultiCube3DOpengl():初始OpenGL的相关操作,包括“获取着色器的string代码”,“加载着色器”,“链接编译顶点、片段程序”。
- MultiCube3DOpenGLRenderFrame():渲染frame。
三、代码源码分析:
1、顶点着色器程序:
#version 320 es
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为0
layout (location = 1) in vec3 aTexCoord;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
// 注意乘法要从右向左读
gl_Position = projection * view * model * vec4(aPos, 1.0f);
TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}
2、片段着色器程序:
#version 320 es
precision mediump float;
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
// texture sampler
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
// linearly interpolate between both textures (80% container, 20% awesomeface)
FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}
3、链接编译顶点、片段程序:
从1和2中的顶点着色器程序、片段着色器程序的地址传入到OpenGLBase的C++类中进行解析、加载着色器、链接编译顶点和片段程序的操作。
a.文件解析:
把顶点着色器程序、片段着色器程序解析成const char*
bool OpenGLBase::getSharderPath(const char *vertexPath, const char *fragmentPath) {
ifstream vShaderFile;
ifstream fShaderFile;
// ensure ifstream objects can throw exceptions:
vShaderFile.exceptions(ifstream::failbit | ifstream::badbit);
fShaderFile.exceptions(ifstream::failbit | ifstream::badbit);
try {
// open files
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
stringstream vShaderStream, fShaderStream;
// read file's buffer contents into streams
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// close file handlers
vShaderFile.close();
fShaderFile.close();
// convert stream into string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch (ifstream::failure &e) {
LOGE("Could not getSharderPath error :%s", e.what());
return false;
}
gVertexShaderCode = vertexCode.c_str();
gFragmentShaderCode = fragmentCode.c_str();
return true;
}
b.加载着色器:
顶点着色器程序、片段着色器程序都可以通过这个方法进行加载:
/**
* 加载着色器
* @param shaderType
* @param pSource
* @return
*/
GLuint OpenGLBase::loadShader(GLenum shaderType, const char *pSource) {
GLuint shader = glCreateShader(shaderType); //创建着色器
if (shader) {
glShaderSource(shader, 1, &pSource, NULL); //着色器源码附加到着色器对象上
glCompileShader(shader); //编译着着色器
GLint compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint infoLen = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen) {
char *buf = (char *) malloc(infoLen);
if (buf) {
glGetShaderInfoLog(shader, infoLen, NULL, buf);
LOGE("Could not compile shader %d:\n%s\n",
shaderType, buf);
free(buf);
}
glDeleteShader(shader); //删除着色器对象
shader = 0;
}
}
}
return shader;
}
c.链接编译顶点和片段程序的操作:
/**
* 连接编译顶点和片元程序
* @param pVertexSource 顶点程序
* @param pFragmentSource 片元程序
* @return
*/
GLuint OpenGLBase::createProgram(const char *pVertexSource
, const char *pFragmentSource) {
vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource);
if (!vertexShader) {
return 0;
}
fraShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource);
if (!fraShader) {
return 0;
}
shaderProgram = glCreateProgram(); //创建一个着色程序对象
if (shaderProgram) {
//把着色器附加到了程序对象上
glAttachShader(shaderProgram, vertexShader);
checkGlError("glAttachShader");
glAttachShader(shaderProgram, fraShader);
checkGlError("glAttachShader");
glLinkProgram(shaderProgram); //链接程序对象
GLint linkStatus = GL_FALSE;
//检测链接着色器程序是否失败
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &linkStatus);
if (linkStatus != GL_TRUE) {
GLint bufLength = 0;
glGetProgramiv(shaderProgram, GL_INFO_LOG_LENGTH, &bufLength);
if (bufLength) {
char *buf = (char *) malloc(bufLength);
if (buf) {
glGetProgramInfoLog(shaderProgram, bufLength, NULL, buf);
LOGE("Could not link shaderProgram:\n%s\n", buf);
free(buf);
}
}
glDeleteProgram(shaderProgram); //
shaderProgram = 0;
}
}
return shaderProgram;
}
4、初始化OpenGL相关操作:
从Android获取GLSurfaceView承载窗口获取width和height,设置glViewport显示窗口。绑定VAO/VBO/EBO——加载创建texture(纹理)——使用着色器程序。
//
// Created by MMM on 2024/7/30.
//
#include "OpenglesMultiCube3D.h"
#include <iostream>
bool OpenglesMultiCube3D::setupGraphics(int w, int h) {
screenW = w;
screenH = h;
LOGI("setupGraphics(%d, %d)", w, h);
LOGI("gVertexShaderCode :%s", gVertexShaderCode);
LOGI("gFragmentShaderCode :%s", gFragmentShaderCode);
gProgram = createProgram(gVertexShaderCode, gFragmentShaderCode);
if (!gProgram) {
LOGE("Could not create shaderProgram.");
return false;
}
gvPositionHandle = glGetAttribLocation(gProgram, "vPosition");
checkGlError("glGetAttribLocation");
LOGI("glGetAttribLocation(\"vPosition\") = %d\n",
gvPositionHandle);
glViewport(0, 0, w, h);
checkGlError("glViewport");
LOGI("glViewport successed!");
//清屏
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
checkGlError("glClear");
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
//绑定VAO
glBindVertexArray(VAO);
//把顶点数组复制到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices)
, cubeVertices, GL_STATIC_DRAW);
// 1. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE
, 5 * sizeof(float), (void *) 0);
glEnableVertexAttribArray(0);
// texture coord attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float),
(void *) (3 * sizeof(float)));
glEnableVertexAttribArray(1);
// load and create a texture
glGenTextures(1, &texture1);
glBindTexture(GL_TEXTURE_2D, texture1);
// set texture wrapping to GL_REPEAT (default wrapping method)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// set texture filtering parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER
, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// tell stb_image.h to flip loaded texture's on the y-axis.
stbi_set_flip_vertically_on_load(true);
if (data1) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB
, width1, height1
, 0, GL_RGB
, GL_UNSIGNED_BYTE, data1);
glGenerateMipmap(GL_TEXTURE_2D);
}
stbi_image_free(data1);
// tell opengl for each sampler to which
// texture unit it belongs to (only has to be done once)
// don't forget to activate/use the shader before setting uniforms!
glUseProgram(shaderProgram);
glUniform1i(glGetUniformLocation(shaderProgram, "texture1"), 0);
return true;
}
5、帧的渲染:
绑定texture(纹理)——使用程序——开启深度测试—— 模型矩阵(Model Matrix)观察矩阵(View Matrix)、投影矩阵(Projection Matrix)的初始化创建并与顶点着色器中model/view/projection 的uniform进行数据交换。模型矩阵(Model Matrix)观察矩阵(View Matrix)、投影矩阵(Projection Matrix)组合矩阵传入到gl_position做对应的顶点数据的位置变换。
void OpenglesMultiCube3D::renderFrame() {
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
// also clear the depth buffer now!
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// bind Texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
//2、使用程序
glUseProgram(gProgram);
checkGlError("glUseProgram");
//开启深度测试
glEnable(GL_DEPTH_TEST);
// create transformations
glm::mat4 view = glm::mat4(1.0f); //观察矩阵(View Matrix)
glm::mat4 projection = glm::mat4(1.0f); //投影矩阵(Projection Matrix)
//观察矩阵(View Matrix)平移,类似于相机移动
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -6.0f));
projection = glm::perspective(glm::radians(45.0f)
, (float) screenW / (float) screenH, 0.1f,100.0f);
// pass them to the shaders (3 different ways)
setMat4("projection", projection);
setMat4("view", view);
// render boxes
glBindVertexArray(VAO);
for (unsigned int i = 0; i < 10; i++) {
// calculate the model matrix for each object
//and pass it to shader before drawing
glm::mat4 model = glm::mat4(1.0f); //模型矩阵(Model Matrix)
//对获取到的模型移动到对应位置
model = glm::translate(model, cubePositions[i]);
float angle = 20.0f * i;
if (i < 6) {
double timeValue = clock() * 10 / CLOCKS_PER_SEC;
angle = timeValue * 25.0f;
}
//让模型经过旋转矩阵的变化
model = glm::rotate(model, glm::radians(angle)
, glm::vec3(1.0f, 0.3f, 0.5f));
setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
checkGlError("glDrawArrays");
}
四、效果展示:
MultiCube