OpenGL ES 学习教程(2):可编程管线 Shader 三角形
下面给大家介绍OpenGL可编程管线,真正开始用 OpenGL绘制图形时,使用的是下面的API:
glBegin(GL_TRIANGLES); // 绘制三角形 glVertex3f( 0.0f, 1.0f, 0.0f); // 上顶点 glVertex3f(-1.0f,-1.0f, 0.0f); // 左下 glVertex3f( 1.0f,-1.0f, 0.0f); // 右下 glEnd();
这就是已经被抛弃的固定管线编程 。
暂时忽略固定管线编程的OpenGL API,利用图形数学知识学习可编程管线。
上图中虚线是 固定管线,黑色粗实线 是 可编程管线 。
可以看到这里 用了 顶点程序、片元程序字样,就是说,这两个过程是可编程的,要我们自己写代码去控制。
顶点程序就是常说的 顶点 Shader ,后缀一般是vsh , 片元程序 就是常说的 片段Shader,后缀一般是 fsh 。
好。
我们编写的 Shader 代码是可以运行在GPU上的,既然是作为一段程序,那么和我们平时写的C++代码一样,想要最后运行起来也要经过一个流程。
1、创建一个项目 glCreateProgram
2、创建一个代码文件 glCreateShader
3、输入代码 glShaderSource
4、编译代码 glCompileShader
5、查看编译是否错误 glGetShaderiv
6、把Shader添加到项目中 glAttachShader
7、链接每个Shader glLinkProgram
8、获取链接状态 glGetProgramiv
9、运行这个项目 glUseProgram
下面是一个Shader 的完整例子,包含上面的流程
#pragma once #include<assert.h> #include<egl\egl.h> #include<gles2\gl2.h> class ShaderID { public: ShaderID() { m_shaderId = -1; } int m_shaderId; }; //封装OpenGL 的glProgram的一些信息; class GLProgram { public: int m_programId; ShaderID m_vertexShader; ShaderID m_fragmentShader; public: GLProgram() { m_programId = -1; } public: //加载shader并且创建glProgram; bool createProgram(const char *vertex, const char *fragment) { bool error = false; do { if (vertex) { //创建Shader; m_vertexShader.m_shaderId = glCreateShader(GL_VERTEX_SHADER); //源码; glShaderSource(m_vertexShader.m_shaderId, 1, &vertex, 0); //编译; glCompileShader(m_vertexShader.m_shaderId); GLint compileStatus; glGetShaderiv(m_vertexShader.m_shaderId, GL_COMPILE_STATUS, &compileStatus); error = (compileStatus == GL_FALSE); if (error) { GLchar message[256]; glGetShaderInfoLog(m_vertexShader.m_shaderId, sizeof(message), 0, message); assert((message && 0) != 0); break; } } if (fragment) { //创建Shader; m_fragmentShader.m_shaderId = glCreateShader(GL_FRAGMENT_SHADER); //源码; glShaderSource(m_fragmentShader.m_shaderId, 1, &fragment, 0); //编译; glCompileShader(m_fragmentShader.m_shaderId); GLint compileStatus; glGetShaderiv(m_fragmentShader.m_shaderId, GL_COMPILE_STATUS, &compileStatus); error = (compileStatus == GL_FALSE); if (error) { GLchar message[256]; glGetShaderInfoLog(m_fragmentShader.m_shaderId, sizeof(message), 0, message); assert((message && 0) != 0); break; } } //创建gl程序; m_programId = glCreateProgram(); //代码添加到program中; if (m_vertexShader.m_shaderId) { glAttachShader(m_programId, m_vertexShader.m_shaderId); } if (m_fragmentShader.m_shaderId) { glAttachShader(m_programId, m_fragmentShader.m_shaderId); } //链接; glLinkProgram(m_programId); GLint linkStatus; glGetProgramiv(m_programId, GL_LINK_STATUS, &linkStatus); if (linkStatus == GL_FALSE) { GLchar message[256]; glGetProgramInfoLog(m_programId, sizeof(message), 0, message); assert((message && 0) != 0); break; } //使用program; //glUseProgram(m_programId); } while (false); if (error) { if (m_fragmentShader.m_shaderId) { glDeleteShader(m_fragmentShader.m_shaderId); m_fragmentShader.m_shaderId = 0; } if (m_vertexShader.m_shaderId) { glDeleteShader(m_vertexShader.m_shaderId); m_vertexShader.m_shaderId = 0; } if (m_programId) { glDeleteProgram(m_programId); m_programId = 0; } } return true; } virtual void begin() { glUseProgram(m_programId); } virtual void end() { glUseProgram(0); } };</gles2\gl2.h></egl\egl.h></assert.h>
一对简单的 Vertex Shader 和 Fragment Shader 可能长的像下面
const char* vertexShader= { "precision lowp float;" "uniform mat4 m_mvp;" "attribute vec3 m_position;" "attribute vec4 m_color;" "varying vec4 m_outColor;" "void main()" "{" " vec4 pos=vec4(m_position,1);" " gl_Position=m_mvp*pos;" " m_outColor=m_color;" "}" }; const char* fragmentShader = { "precision lowp float;" "varying vec4 m_outColor;" "void main()" "{" " gl_FragColor=m_outColor;" "}" };
uniform 、attribute 都是变量修饰符。这两个修饰符修饰的变量 ,我们可以在 C++ 代码中获取到然后给他们赋值。
varying 修饰符修饰的变量代表在 Vertex 和 Fragment 之间传递。不能从C++代码中操作。
uniform mat4 m_mvp
我们从C++中传入 MVP 矩阵
attribute vec3 m_position
我们从C++中传入 顶点位置
attribute vec4 m_color
我们从C++中传入 顶点颜色
好了。下面就让我们来设置这些变量。
这里继承了上面的类
#pragma once #include"GLProgram.h" class GLProgram_Color :public GLProgram { public: //传入shader中的值; GLint m_position; GLint m_color; GLint m_mvp; public: GLProgram_Color() { m_position = -1; m_color = -1; m_mvp = -1; } ~GLProgram_Color() {} GLint getPositionAttribute() const { return m_position; } GLint getColorAttribute() const { return m_color; } GLint getMVP() const { return m_mvp; } //attribute:只能在Vertexshader中使用; //Unifrom:在Vertex和Fragment中共享使用,且不能被修改; //Varying:从Vertex传递数据到Fragment中使用; virtual bool Initialize() { const char* vertexShader= { "precision lowp float;" "uniform mat4 m_mvp;" "attribute vec3 m_position;" "attribute vec4 m_color;" "varying vec4 m_outColor;" "void main()" "{" " vec4 pos=vec4(m_position,1);" " gl_Position=m_mvp*pos;" " m_outColor=m_color;" "}" }; const char* fragmentShader = { "precision lowp float;" "varying vec4 m_outColor;" "void main()" "{" " gl_FragColor=m_outColor;" "}" }; bool ret = createProgram(vertexShader, fragmentShader); if (ret) { m_position = glGetAttribLocation(m_programId, "m_position"); m_color = glGetAttribLocation(m_programId, "m_color"); m_mvp = glGetUniformLocation(m_programId, "m_mvp"); } return ret; } virtual void begin() { glUseProgram(m_programId); glEnableVertexAttribArray(m_position); glEnableVertexAttribArray(m_color); } virtual void end() { glDisableVertexAttribArray(m_position); glDisableVertexAttribArray(m_color); glUseProgram(0); } };
可以看到,在创建Shader 并且编译成功之后,就可以获取 Shader 中的变量了。
bool ret = createProgram(vertexShader, fragmentShader); if (ret) { m_position = glGetAttribLocation(m_programId, "m_position"); m_color = glGetAttribLocation(m_programId, "m_color"); m_mvp = glGetUniformLocation(m_programId, "m_mvp"); }
然后像下面这样去传入值 到这几个变量
glUniformMatrix4fv(m_program.m_mvp, 1, false, &proj[0][0]); glVertexAttribPointer(m_program.m_position, 2, GL_FLOAT, false, sizeof(glm::vec3), pos); glVertexAttribPointer(m_program.m_color, 4, GL_FLOAT, false, sizeof(glm::vec4), color);
MVP是一个矩阵,是由 模型矩阵 乘以 视图矩阵 乘以 投影矩阵 得来的,代表对世界坐标系中的一个点执行的一系列 操作。
想详细了解的请百度相关资料,关键字:OpenGL 矩阵变换
好了,我们在教程一的基础上添加了 一些修改,创建了Shader、给Shader设置了值,并且引入了一个三方库 GLM 来进行矩阵的计算。
示例程序下载:http://pan.baidu.com/s/1eQjW5O2