一:openGl 基础知识
opengl是什么 ?openGl能干什么
OpenGL(英语:Open Graphics Library,译名:开放图形库或者“开放式图形库”)是用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口(API)。这个接口由近350个不同的函数调用组成,用来从简单的图形位元绘制复杂的三维景象。而另一种程式介面系统是仅用于Microsoft Windows上的Direct3D。OpenGL常用于CAD、虚拟实境、科学视觉化程式和电子游戏开发。
OpenGL的高效实现(利用图形加速硬件)存在于Windows,部分UNIX平台和Mac OS。这些实现一般由显示装置厂商提供,而且非常依赖于该厂商提供的硬体。开放原始码函式库Mesa是一个纯基于软体的图形API,它的代码兼容于OpenGL。但是,由于许可证的原因,它只声称是一个“非常相似”的API。
OpenGL规范由1992年成立的OpenGL架构评审委员会(ARB)维护。ARB由一些对建立一个统一的、普遍可用的API特别感兴趣的公司组成。根据OpenGL官方网站,2002年6月的ARB投票成员包括3Dlabs、Apple Computer、ATI Technologies、Dell Computer、Evans & Sutherland(英语:Evans & Sutherland)、Hewlett-Packard、IBM、Intel、Matrox、NVIDIA、SGI和Sun Microsystems,Microsoft曾是创立成员之一,但已于2003年3月退出。
----摘自维基百科
是什么?
简单来说 opengl是一种规范,这种规范用在大部分你能看到的电子设备中,如笔记本电脑,显示器,移动端设备等
做什么?
能用来绘制你所能见到的电子设备上需要显示的所有东西,如加载一张图,进行动画的显示等
怎么做?
openGl因为是跨平台的,因此你在Window上,android上均能使用,只要下载一些基本组件库,本系列后续会进行使用openGl 在Android 平台上实现代码和Mac上使用C++实现opengl的一些常见入门用法,高级用法学习和使用庆参考教程网站:
二:openGl环境搭建
2.1:Mac上我使用的Clion IDE,同时需要你安装glew 和glfw3 ,具体详细搭建过程件请自行百度,提供一篇参考
2.2:Android 上openGl 环境搭建比较简单,搭建方法就是在 AndroidManifest.xml中配置上即可使用,如下:
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
2.3:Window上搭建opengl环境,我在Window上使用的是Clion +(glad+glfw)来实现调用使用opengl的,具体详细过程参考如下:
三:你好 三角形
1:window +clion + (glad+ glfw) + C++ 使用 版本
3.1:先上代码,讲解在注释,Clion中实现绘制三角形的代码如下:
#include <iostream>
#include "glew.h"
#include "glfw3.h"
using namespace std;
void GlClearError();
bool GlLogCall(const char* fun,const char* file,int line);
#define ASSERT(x) if(!(x) ) cout<<"file:"<<__FILE__<<",LINE:"<<__LINE__<<",FUNC:"<<__FUNCTION__ <<endl;
//检测错误的宏定义
# define GLCall(x) GlClearError(); \
x; \
ASSERT(GlLogCall(#x,__FILE__,__LINE__))
void GlClearError()
{
while (glGetError() != GL_NO_ERROR);
}
bool GlLogCall(const char *fun, const char *file, int line) {
while (GLenum error = glGetError()){
cout<<"OpenGl Error:" <<error<<endl;
cout<<"file:"<<file<<",LINE:"<<line<<",FUNC:"<<fun <<endl;
return false;
}
return true;
}
//顶点着色器脚本
const char *vertexShaderSource =
"#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
//片元着色器脚本
const char *fragmentShaderSource =
"#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
void processInput(GLFWwindow *window)
{
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
void key_callback(GLFWwindow * window, int key, int scancode, int action, int mode)
{
//如果按下ESC,把windowShouldClose设置为True,外面的循环会关闭应用
if(key==GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
std::cout<<"ESC"<<mode;
}
const int GLSL_TYPE_VERTEX = 0;
const int GLSL_TYPE_FRAGMENT = 1;
static unsigned int VAO = 0;
static unsigned int VBO = 0;
static unsigned int EBO = 0;
bool createAndCompileShader(const char* glslStr,int glslType,int &shaderId )
{
unsigned int shaderType = -1;
if(glslType == GLSL_TYPE_VERTEX ){
shaderType =GL_VERTEX_SHADER;
} else if(glslType == GLSL_TYPE_FRAGMENT){
shaderType = GL_FRAGMENT_SHADER;
} else{
cout << "please input right glslType\n" <<endl;
return false;
}
GLCall(shaderId = glCreateShader(shaderType));
GLCall( glShaderSource(shaderId, 1, &glslStr, NULL));
GLCall( glCompileShader(shaderId));
int success;
char infoLog[512];
GLCall( glGetShaderiv(shaderId, GL_COMPILE_STATUS, &success));
cout << "createAndCompileShader success is " << success<< std::endl;
if (!success)
{
GLCall( glGetShaderInfoLog(shaderId, 512, NULL, infoLog));
if(glslType == GLSL_TYPE_VERTEX){
cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
return false;
} else{
cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
return false;
}
}
cout << "success::SHADER::VERTEX::COMPILATION " << std::endl;
return true;
}
int linkProgram(int &vertexId,int &fragmentId )
{
//shaderProgram为opengl运行时 GPU上跑的程序Id, 此程序id 会被用来查找顶点和片元的着色器中定义的变量
unsigned int shaderProgram;
GLCall( shaderProgram = glCreateProgram());
GLCall( glAttachShader(shaderProgram, vertexId));
GLCall( glAttachShader(shaderProgram, fragmentId));
GLCall( glLinkProgram(shaderProgram));
// check for linking errors
int success = -1;
string infoLog = "";
GLCall( glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success));
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, const_cast<char *>(infoLog.c_str()));
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
std::cout << "linkProgram success result" << shaderProgram << std::endl;
glDeleteShader(vertexId);
glDeleteShader(fragmentId);
return shaderProgram;
}
void initVertexConfiguration(float * vertexArray,int vertexArrayLength, int * index,int indexLength){
//创建顶点缓存数组 VAO 顶点数组VBO 和元素缓存数组(元素缓存)EBO
GLCall( glGenVertexArrays(1, &VAO));
GLCall( glGenBuffers(1, &VBO));
GLCall( glGenBuffers(1, &EBO));
//绑定VAO VBO 和EBO
GLCall( glBindVertexArray(VAO));
std::cout << "initVertexConfiguration sizeof(vertexArray) : " << sizeof(vertexArray) << std::endl;
GLCall(glBindBuffer(GL_ARRAY_BUFFER, VBO));
// GLCall(glBufferData(GL_ARRAY_BUFFER, sizeof(vertexArray) * sizeof(float), (void *)vertexArray, GL_STATIC_DRAW));
//glBufferData 函数第二个参数 指的是 顶点坐标的长度
GLCall(glBufferData(GL_ARRAY_BUFFER, vertexArrayLength, (void *)vertexArray, GL_STATIC_DRAW));
std::cout << "initVertexConfiguration sizeof(index : " << sizeof(index) << std::endl;
GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO));
GLCall(glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexLength, index, GL_STATIC_DRAW));
//关键步骤,指定传递出去的数据之间的排列方式和数据大小 数据类型等
GLCall(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (void*)0));
GLCall(glEnableVertexAttribArray(0));
//解绑
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
int main() {
std::cout << "Hello, World!" << std::endl;
if(!glfwInit())
return -1;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
glfwWindowHint(GLFW_OPENGL_PROFILE ,GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);//
//创建窗口以及上下文,窗口大小为960 * 540,这个值可以随便更改,电脑会根据你配置的值生成对应大小的窗口显示器
GLFWwindow* window = glfwCreateWindow(960, 540, "hello opengl", NULL, NULL);
if(!window)
{
//创建失败会返回NULL
glfwTerminate();
return -1;
}
glfwMakeContextCurrent (window);
// Set the required callback functions
glfwSetKeyCallback(window, key_callback);
// Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions
glewExperimental = GL_TRUE;
int result = glewInit();
cout << "result : "<<result<<endl;
cout << "gl version "<<glGetString(GL_VERSION)<<endl;
/*****************标准opengl 代码 在不同平台上 使用opengl 均需要以下代码*********************/
//设置opengl窗口显示的原点和宽高,opengl的显示视图窗口和 我们上面创建的 window窗口一样大小
glViewport(0,0,960,540);
//设置刷新频率
glfwSwapInterval(1);
//第一步,创建glsl脚本,即顶点着色器和片元着色器 并编译
int vertexId = -1;
int fragmentId = -1;
cout << "first step "<<endl;
createAndCompileShader(vertexShaderSource,GLSL_TYPE_VERTEX,vertexId);
createAndCompileShader(fragmentShaderSource,GLSL_TYPE_FRAGMENT,fragmentId);
cout << "first step end,vertexId"<<vertexId<<endl;
cout << "first step end,fragmentId"<<fragmentId<<endl;
cout << "Second step "<<endl;
//第二步:生成使用顶点和片元着色器的opengl 程序 ID
unsigned int programId = linkProgram(vertexId,fragmentId);
cout << "Second step result programId is "<< programId <<endl;
//顶点坐标
float vertices[] = {
0.5f, 0.5f, 0.0f, // top right //0
0.5f, -0.5f, 0.0f, // bottom right //1
-0.5f, -0.5f, 0.0f, // bottom left //2
-0.5f, 0.5f, 0.0f // top left //3
};
//顶点坐标对应的索引坐标
int indices[] = {
// note that we start from 0!
0, 1, 3, // first Triangle
1, 2, 3 // second Triangle
};
//第三步:初始化顶坐标相关环境
initVertexConfiguration(vertices,sizeof(vertices),indices,sizeof(indices));
while (!glfwWindowShouldClose(window))
{
processInput(window);
//初始化背景颜色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// draw our first triangle
GLCall(glUseProgram(programId));
GLCall(glBindVertexArray(VAO)); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0));
// glBindVertexArray(0); // no need to unbind it every time
// -------------------------------------------------------------------------------
GLCall(glfwSwapBuffers(window));
GLCall(glfwPollEvents());
}
//删除
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glDeleteProgram(programId);
/*****************标准opengl 代码 在不同平台上 使用opengl 均需要以上代码*********************/
// glfw: terminate, clearing all previously allocated GLFW resources.
// ------------------------------------------------------------------
glfwTerminate();
return 0;
}
CmakeList配置脚本如下:
cmake_minimum_required(VERSION 3.20)
project(OpenGlLearn)
set(CMAKE_CXX_STANDARD 14)
# 添加头文件
set(GLEW_H /usr/local/Cellar/glew/2.2.0_1/include/GL)
set(GLFW_H /usr/local/Cellar/glfw/3.3.4/include/GLFW)
include_directories(${GLEW_H} ${GLFW_H})
set(GLM /Users/didi/code/Clion/openGlTest/glm/)
include_directories(${GLM} ${GLM})
set(GLM_GTC /Users/didi/code/Clion/openGlTest/glm/gtc)
include_directories(${GLM_GTC} ${GLM_GTC})
set(GLM_DETAIL /Users/didi/code/Clion/openGlTest/glm/detail)
include_directories(${GLM_DETAIL} ${GLM_DETAIL})
set(GLM_EXT /Users/didi/code/Clion/openGlTest/glm/ext)
include_directories(${GLM_EXT} ${GLM_EXT})
set(GLM_GTX /Users/didi/code/Clion/openGlTest/glm/gtx)
include_directories(${GLM_GTX} ${GLM_GTX})
set(GLM_SIMD /Users/didi/code/Clion/openGlTest/glm/simd)
include_directories(${GLM_SIMD} ${GLM_SIMD})
# 添加目标链接
set(GLEW_LINK /usr/local/Cellar/glew/2.2.0_1/lib/libGLEW.2.2.dylib)
set(GLFW_LINK /usr/local/Cellar/glfw/3.3.4/lib/libglfw.3.dylib)
#link_libraries(${OPENGL} ${GLEW_LINK} ${GLFW_LINK})
link_libraries("-framework OpenGL" ${GLEW_LINK} ${GLFW_LINK})
# 执行编译命令
set(SOURCE_FILES
src/main.cpp)
add_executable(openGlTest ${SOURCE_FILES})
if (APPLE)
target_link_libraries(openGlTest "-framework OpenGL")
target_link_libraries(openGlTest "-framework GLUT")
endif()
执行之后电脑上显示的窗口如下:
具体代码细节讲解:参考注释
2:Android studio +java + openglES 实现 版本
android 实现使用opengl 绘制三角形 其实主要是3步:
- 创建一个activity,layout文件中设置一个glsurview组件即可,并且制定opengl的版本(不指定版本无法执行后面的代码),为glsurview指定一个实现 GLSurfaceView.Renderer 的render的实例
- 实例化要绘制界面的的render实例,并重写 onSurfaceCreated,onSurfaceChanged,onDrawFrame三个方法
- 指定定点着色器和 片元着色器,指定定点坐标,在onDrawFrame中开始绘制
public class SimpleRenderActivity extends Activity {
GLSurfaceView glSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple_render);
initView();
}
void initView(){
glSurfaceView = findViewById(R.id.surfaceview_opengles);
// glSurfaceView.setElevation(2.0f);
//设置onengl的版本
glSurfaceView.setEGLContextClientVersion(2);
//设置渲染器 自实现 背景颜色改变
//glSurfaceView.setRenderer(new BackGroundRender());
//设置渲染器 自实现 画三角形
glSurfaceView.setRenderer(new TriagenRender());
//设置渲染模式
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
}
public class TriagenRender implements GLSurfaceView.Renderer {
FloatBuffer floatBuffer ;
int progrem;
private int vPosition;
int colorHandle;
//设置颜色,依次为红绿蓝和透明通道
float color[] = { 1.0f, 0f, 0f, 1.0f };
//三角形定点坐标
float titagenrender [] = {
0.5f, 0.5f, 0f,
-0.5f,-0.5f,0f,
0.5f,-0.5f,0f
};
//定点着色器
private final String vertexShaderCode =
"attribute vec4 vPosition; " +
"void main() {" +
"gl_Position = vPosition;" +
"}";
//片元着色器
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
private String TAG = "TriagenRender";
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.i(TAG,"onSurfaceCreated 1");
//1: 申请CPU内存空间
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(titagenrender.length *4);
byteBuffer.order(ByteOrder.nativeOrder());
floatBuffer = byteBuffer.asFloatBuffer();
Log.i(TAG,"onSurfaceCreated 2");
// 2:将所要做的三角形的坐标输入floatbytebuffer
floatBuffer.put(titagenrender);
floatBuffer.position(0);
Log.i(TAG,"onSurfaceCreated 3");
//3:创建定点着色器和片元着色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
Log.i(TAG,"onSurfaceCreated 4");
// 4:创建承载着色器的opengl 方法,并将点点着色器和片元着色器加入
progrem = GLES20.glCreateProgram();
GLES20.glAttachShader(progrem,vertexShader);
GLES20.glAttachShader(progrem,fragmentShader);
Log.i(TAG,"onSurfaceCreated 5");
//5:连接程序
GLES20.glLinkProgram(progrem);
}
//创建着色器 (本方法主要为定点着色器和 片元着色器)
private int loadShader(int type,String shaderCode){
//根据类型创建着色器
int shade = GLES20.glCreateShader(type);
//加载资源
GLES20.glShaderSource(shade,shaderCode);
GLES20.glCompileShader(shade);
return shade;
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
}
@Override
public void onDrawFrame(GL10 gl) {
Log.i(TAG,"onDrawFrame 1");
//1:获取程序中的句柄并启用
GLES20.glUseProgram(progrem);
vPosition = GLES20.glGetAttribLocation(progrem, "vPosition");
GLES20.glEnableVertexAttribArray(vPosition);
// GLES20.glEnable(vPosition);
Log.i(TAG,"onDrawFrame 2");
// 2:准备三角形的定点坐标数据
GLES20.glVertexAttribPointer(vPosition,3,GLES20.GL_FLOAT, false,3*4,floatBuffer);
Log.i(TAG,"onDrawFrame 3");
//3:准备着色器片元
colorHandle = GLES20.glGetUniformLocation(progrem, "vColor");
GLES20.glUniform4fv(colorHandle,1,color,0);
Log.i(TAG,"onDrawFrame 4");
// 4:绘制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,3);
GLES20.glDisableVertexAttribArray(vPosition);
}
}
绘制结果展示:
四:2种opengl实现方式对比
经过上述代码C++实现版本和android实现版本,opengl的核心不变,主要变化的是其在不同平台上已经不同语言的一些区别,但是基本流程是一样的,主要概括如下:
- 初始化opengl环境,指定opengl版本,如
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
glfwWindowHint(GLFW_OPENGL_PROFILE ,GLFW_OPENGL_CORE_PROFILE);
glSurfaceView.setEGLContextClientVersion(2);
//设置渲染器 自实现 画三角形
glSurfaceView.setRenderer(new TriagenRender());
//设置渲染模式
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
- 指定顶点和片元着色器,并且编译,得到gpu opengl运行ID,根据得到的这个ID 获取在定点和片元着色器中定义的各种变量,如顶点坐标和颜色坐标 纹理坐标等,常见的就是这三个坐标
- 指定运行时的一些参数,关键参数就是告诉opengl 各种定点之间坐标的stride,最后启动绘制。在这个步骤上,C++ 在PC上实现和 Android上实现的代码API 都基本一致
//关键步骤,指定传递出去的数据之间的排列方式和数据大小 数据类型等
GLCall(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (void*)0));
GLCall(glEnableVertexAttribArray(0));
//最后一步启动绘制 C++绘制的是EBO,如绘制VAO 则api 是glDrawArrays
GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0));
// 2:准备三角形的定点坐标数据
GLES20.glVertexAttribPointer(vPosition,3,GLES20.GL_FLOAT, false,3*4,floatBuffer);
//启动绘制 java启动绘制的是VAO
GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,3);
五:结语
经过上面代码细节的对比 ,我们基本了解了什么是opengl,以及opengl在PC上和android上进行入门绘制的基本流程。那么opengl的核心元素就是glsl语言 ,定点着色器 片元着色器 ,VAO EBO等基本元素在不同平台上的绘制都是一样的,那么这些核心元素是什么?如何理解?在何种场合使用?一系列问题的答案我们下回再见。