一、序言
1. 着色器是用来实现图形渲染的、代替固定渲染管线的可编程程序,着色器替代了传统的固定渲染管线,可以实现2D、3D图形学计算中的相关计算,由于其可编程性,可以实现各种各样的图像效果。
2. 着色器语言专门用来为着色器编程的编程语言,着色器语言有 HLSL、GLSL等等语言,
HLSL是高阶着色器语言(High Level Shader Language)的简称,HLSL独立的工作在 Windows 平台上,只能供微软的Direct3D使用;
GLSL是OpenGL着色语言(OpenGL Shading Language)的简称,是一种类C语言的高阶着色器语言,可以跨平台运行,存在于windows、android、Mac OS等系统。
3. OpenGL(Open Graphics Library,开放图形库)是用于渲染2D、3D矢量图形的跨平台的应用程序编程接口(API)。OpenGL家族主要包括桌面OpenGL和OpenGL ES,OpenGL ES(OpenGL for Embedded Systems)是以和嵌入式设备为目标的图形应用程序编程接口。
4. OpenGL ES 1.0 支持固定功能渲染管线,OpenGL ES 2.0开始采用可编程着色功能的图形管线,OpenGL ES 3.0向后兼容2.0,但是
OpenGL ES 2.0/3.0不支持OpenGL ES 1.0的固定功能管线。OpenGL ES 3.0由OpenGL ES着色语言3.0规范和OpenGL ES 3.0 API规范组成。
二、示例代码
本例子介绍如何使用着色器渲染一个彩色三角形的程序,我们需要创建一个顶点着色器(vertex shader)和一个片段着色器(fragment shader),
一个着色器需要创建两个基本的对象:着色器对象和程序对象,可以类比C语言的编程器和链接程序,C编译器为一段源代码生成.o目标文件,然后
C链接程序将目标文件链接成最后的可执行程序。OpenGL ES也是类似的,着色器源码代码提供给着色器对象,着色器对象被编译成一个目标文件,
然后着色器对象可以链接到一个程序对象,一般过程包括7个步骤:
* 1. 创建一个顶点着色器对象和一个片段着色器对象
* 2. 将源代码提供给着色器对象
* 3. 编译着色器对象
* 4. 创建一个程序对象
* 5. 程序对象关联编译后的着色器对象
* 6. 链接程序对象,生成可以执行的硬件指令
* 7. 设置程序对象为活动程序
main.cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <EGL/egl.h>
#include <GLES3/gl31.h>
#include "WindowSurface.h"
EGLDisplay eglDisplay;
EGLSurface eglSurface;
GLuint programObject;
EGLBoolean egl_init(){
eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if(eglDisplay == EGL_NO_DISPLAY){
return EGL_FALSE;
}
EGLint major, minor;
if(!eglInitialize(eglDisplay, &major, &minor)){
return EGL_FALSE;
}
printf("[%s +%d]: major-%d minor-%d\n", __func__, __LINE__, major, minor); //major-1 minor-5
EGLConfig eglConfig;
EGLint numConfigs;
EGLint eglConfigAttribList[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, //EGL_WINDOW_BIT
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_NONE
};
if(!eglChooseConfig(eglDisplay, eglConfigAttribList, &eglConfig, 1, &numConfigs)){
return EGL_FALSE;
}
printf("[%s +%d]: numConfigs-%d\n", __func__, __LINE__, numConfigs); //numConfigs-1
EGLNativeWindowType window;
window = getWindowSurface();
eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, window/* 需要先获取到NativeWindow*/, NULL);
if(eglSurface == EGL_NO_SURFACE){
EGLint eglError= eglGetError();
printf("eglCreateWindowSurface failed, eglError-0x%x\n", eglError);
return EGL_FALSE;
}
EGLContext eglContext;
EGLint eglContextAttribList[] = {
EGL_CONTEXT_CLIENT_VERSION, 3,
EGL_NONE
};
eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, eglContextAttribList);
if(eglContext == EGL_NO_CONTEXT){
return EGL_FALSE;
}
if(!eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)){
printf("eglMakeCurrent failed\n");
return EGL_FALSE;
}
glViewport( 0, 0, 1080 , 2440);
return EGL_TRUE;
}
GLuint loadShader(GLenum type, const char *shaderSrc){
GLuint shader;
GLint compiled;
/* 1. 创建着色器对象,根据type创建顶点着色器或者片段着色器,返回值是指向着色器对象的句柄 */
shader = glCreateShader(type);
if(shader == 0){
return 0;
}
/* 2. 给着色器对象提供源代码 */
glShaderSource(shader, 1, &shaderSrc, NULL);
/* 3. 编译已经存在着色器对象的着色器源代码。和常规的语言编译器一样,你需要知道是否编译报错,可以glGetShaderiv查询 */
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); //查询是否编译报错
if(!compiled){
GLint infoLen = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); //查询编译错误log的长度
if(infoLen > 1){
char *infoLog = (char *)malloc(sizeof(char) * infoLen);
glGetShaderInfoLog(shader, infoLen, NULL, infoLog); //获取编译错误log
printf("Error compiling shader: %s\n", infoLog);
free(infoLog);
}
glDeleteShader(shader);
return 0;
}
return shader;
}
/* 加载顶点着色器 */
GLuint loadVertexShader(){
/* vShaderStr是顶点着色器源代码,第一行声明采用OpenGL ES着色语言3.0版本(GL ES SL 3.0)。
* 顶点着色器输入(或者称作顶点属性):vPosition和aColor,分别对于后面代码传入的顶点数组的vVertices和vColor。
* 顶点着色器输出(或者称作可变变量):vColor,在图元光栅化阶段,为每个生成的片段计算顶点着色器的输出值,并作为片段着色器的输入
*/
char vShaderStr[] =
"#version 300 es \n
layout(location = 0) in vec4 vPosition; \n
layout(location = 1) in vec3 aColor; \n
out vec3 vColor; \n
void main() \n
{ \n
gl_Position = vPosition; \n
vColor = aColor; \n
} \n ";
return loadShader(GL_VERTEX_SHADER, vShaderStr);
}
/* 加载片段着色器 */
GLuint loadFragmentShaer(){
/* fShaderStr是片段着色器源代码,第一行声明采用OpenGL ES着色语言3.0版本(GL ES SL 3.0)。
* 片段着色器输入变量(或者称作可变变量):vColor,在图元光栅化阶段,为每个生成的片段计算顶点着色器的输出值,作为片段着色器的输入
* 片段着色器输出颜色:fragColor,传递到管线的逐片段操作部分。
*/
char fShaderStr[] =
"#version 300 es \n
precision mediump float; \n
in vec3 vColor; \n
out vec3 fragColor; \n
void main() \n
{ \n
fragColor = vColor; \n
} \n ";
return loadShader(GL_FRAGMENT_SHADER, fShaderStr);
}
GLuint loadProgram(){
GLuint vertexShader;
GLuint fragmentShader;
GLint linked;
vertexShader = loadVertexShader();
fragmentShader = loadFragmentShaer();
/* 4. 创建程序对象,返回值是指向程序对象的句柄 */
programObject = glCreateProgram();
if(programObject == 0){
return 0;
}
/* 5. 程序对象关联编译好的着色器对象 */
glAttachShader(programObject, vertexShader);
glAttachShader(programObject, fragmentShader);
/* 6. 程序对象链接,生成可以执行的硬件指令,和常规的链接一样,你需要知道是否链接成功,可以glGetProgramiv查询 */
glLinkProgram(programObject);
glGetProgramiv(programObject, GL_LINK_STATUS, &linked); //查询是否链接报错
if(!linked){
GLint infoLen = 0;
glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &infoLen); //查询链接错误log的长度
if(infoLen > 1){
char *infoLog = (char *)malloc(sizeof(char) *infoLen);
glGetProgramInfoLog(programObject, infoLen, NULL, infoLog); //获取链接错误log
printf("Error linking program: %s\n", infoLog);
free(infoLog);
}
glDeleteProgram(programObject);
return GL_FALSE;
}
/* 7. 设置程序对象为活动程序 */
glUseProgram(programObject);
return GL_TRUE;
}
void drawTriangle(){
GLfloat vVertices[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
GLfloat vColor[] = {
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f
};
//glViewport(0, 0, 1080, 2400);
glClear(GL_COLOR_BUFFER_BIT);
/* 顶点数组指定每个顶点的属性,是保存在应用程序地址空间(OpenGL ES客户空间)的缓冲区 */
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices); //顶点位置数组,数据类型为浮点型
glEnableVertexAttribArray(0); //启用索引为0的顶点数组
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, vColor);//顶点颜色数组,数据类型为浮点型
glEnableVertexAttribArray(1); //启用索引为1的顶点数组
/* 绘制图元,OpenGL ES 3.0可以绘制点、直线和三角形图元,这里绘制三角形 */
glDrawArrays(GL_TRIANGLES, 0, 3);
}
int main(){
EGLint ret;
ret = egl_init();
if(ret == EGL_FALSE){
printf("[%s +%d]: egl_init failed\n", __func__, __LINE__);
return -1;
}
ret = loadProgram();
if(ret == GL_FALSE){
printf("[%s +%d]: loadProgram failed\n", __func__, __LINE__);
return -1;
}
while(1){
drawTriangle();
eglSwapBuffers(eglDisplay, eglSurface);
sleep(1);
}
return 0;
}
将代码放在frameworks/native目录下进行编译,生成opengl-es-triangle,然后push到/system/bin/目录下执行。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := opengl-es-triangle
LOCAL_MODULE_TAGS := tests
LOCAL_SHARED_LIBRARIES := libEGL libGLESv2 libGLESv3 libutils liblog libgui libui
LOCAL_SRC_FILES := main.cpp WindowSurface.cpp
include $(BUILD_EXECUTABLE)