OpenGL笔记(五) 着色器渲染(以Android为例)

时间:2022-03-03 15:24:27

一、Android平台上下文环境的创建及初始化

1. 首先实例化Android上下文环境,即EGL的初始化。

bool EGLCore::init(EGLContext sharedContext) {
EGLint numConfigs;
EGLint width;
EGLint height; const EGLint attribs[] = { EGL_BUFFER_SIZE, , EGL_ALPHA_SIZE, , EGL_BLUE_SIZE, , EGL_GREEN_SIZE, , EGL_RED_SIZE, , EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE };
//eglGetDisplay来返回OpenGL ES渲染的目标,每个厂商都会返回默认的显示设备
if ((display = eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY) {
LOGE("eglGetDisplay() returned error %d", eglGetError());
return false;
}
// 初始化显示设备
if (!eglInitialize(display, , )) {
LOGE("eglInitialize() returned error %d", eglGetError());
return false;
}
// 得到配置选项信息
if (!eglChooseConfig(display, attribs, &config, , &numConfigs)) {
LOGE("eglChooseConfig() returned error %d", eglGetError());
release();
return false;
}
// 创建OpenGL的上下文环境EGLContext
EGLint eglContextAttributes[] = { EGL_CONTEXT_CLIENT_VERSION, , EGL_NONE };
if (!(context = eglCreateContext(display, config, NULL == sharedContext ? EGL_NO_CONTEXT : sharedContext, eglContextAttributes))) {
LOGE("eglCreateContext() returned error %d", eglGetError());
release();
return false;
} pfneglPresentationTimeANDROID = (PFNEGLPRESENTATIONTIMEANDROIDPROC)eglGetProcAddress("eglPresentationTimeANDROID");
if (!pfneglPresentationTimeANDROID) {
LOGE("eglPresentationTimeANDROID is not available!");
} return true;
}

2. 将EGL和设备的屏幕连接起来。使用EGLSurface,通过EGL库提供的eglCreateWindowSurface可以创建一个可实际显示的Surface,通过EGL库提供的eglCreatePbufferSurface可以创建一个OffScreen的Surface。_window就是通过Java层的Surface对象创建出的ANativeWindow类型的对象,即本地设备屏幕的表示。也就是说真实显示的Surface还是通过Java层创建好的, 然后OpenGL只是绘制到了这个target上边?

EGLSurface EGLCore::createWindowSurface(ANativeWindow* _window) {
EGLSurface surface = NULL;
EGLint format;
if (!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format)) {
LOGE("eglGetConfigAttrib() returned error %d", eglGetError());
release();
return surface;
}
ANativeWindow_setBuffersGeometry(_window, , , format);
if (!(surface = eglCreateWindowSurface(display, config, _window, ))) {
LOGE("eglCreateWindowSurface() returned error %d", eglGetError());
}
return surface;
}

通过Java层的Surface对象创建ANativeWindow类型的对象方法如下:

JNIEXPORT void JNICALL Java_com_phuket_tour_opengl_renderer_PngPreviewController_setSurface
(JNIEnv * env, jobject obj, jobject surface) {
if (surface != && NULL != controller) {
window = ANativeWindow_fromSurface(env, surface);
LOGI("Got window %p", window);
controller->setWindow(window);
} else if (window != ) {
LOGI("Releasing window");
ANativeWindow_release(window);
window = ;
}
}

3. 开发者需要开辟一个新的线程,来执行OpenGL ES的渲染操作,而且还必须为该线程绑定显示设备(surface)与上下文环境(Context)。EGL是双缓冲模式,内部有两个FrameBuffer,当EGL将一个FrameBuffer显示到屏幕上的时候,另外一个FrameBuffer就在后台等待OpenGL ES进行渲染输出了。直到调用函数eglSwapBuffers这条指令的时候,才会把前台的FrameBuffer和后台的FrameBuffer进行交换。

bool EGLCore::makeCurrent(EGLSurface eglSurface) {
return eglMakeCurrent(display, eglSurface, eglSurface, context);
}

二、Texture/Shader/Program

1. 创建Texture(纹理)

int PicPreviewTexture::initTexture() {
glGenTextures(, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return ;
}

2. 创建/初始化/编译Shader

GLuint PicPreviewRender::compileShader(GLenum type, const char *sources) {
GLint status;
GLuint shader = glCreateShader(type);
if (shader == || shader == GL_INVALID_ENUM) {
LOGI("Failed to create shader %d", type);
return ;
}
glShaderSource(shader, , &sources, NULL);
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
glDeleteShader(shader);
LOGI("Failed to compile shader : %s\n", sources);
return ;
}
return shader;
}

3. 创建并使用Program(显卡可执行程序)

int PicPreviewRender::useProgram() {
program = glCreateProgram();
glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
glBindAttribLocation(program, ATTRIBUTE_VERTEX, "position");
glBindAttribLocation(program, ATTRIBUTE_TEXCOORD, "texcoord");
glLinkProgram(program);
GLint status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE) {
LOGI("Failed to link program %d", program);
return -;
}
glUseProgram(program); uniformSampler = glGetUniformLocation(program, "yuvTexSampler"); return ;
}

三、渲染操作

void PicPreviewRender::render(){
glViewport(_backingLeft, _backingTop, _backingWidth, _backingHeight);
glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glUseProgram(program);
static const GLfloat _vertices[] = { -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f };
glVertexAttribPointer(ATTRIBUTE_VERTEX, , GL_FLOAT, , , _vertices);
glEnableVertexAttribArray(ATTRIBUTE_VERTEX);
static const GLfloat texCoords[] = { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f };
glVertexAttribPointer(ATTRIBUTE_TEXCOORD, , GL_FLOAT, , , texCoords);
glEnableVertexAttribArray(ATTRIBUTE_TEXCOORD);
picPreviewTexture->bindTexture(uniformSampler);
glDrawArrays(GL_TRIANGLE_STRIP, , );
}

首先,_vertices有4个顶点,每个顶点都会调用一次顶点着色器程序。按照规则,一共有四个点,每个点为vec2,但是顶点着色器的in输入变量为vec3,在GLSL语法中,vec2传递给vec3,其中扩充的维度的值默认为0,这也可以接受因为所在的是二维平面,不然也不会2个float代表一个点,所以,第三维自动为0。gl_Position 的第四维是和裁剪以及变换有关的,没有相关的变换时候是1.0。

顶点坐标的归一化区间是[-1, 1],纹理坐标的归一化区间是[0, 1]。所以代码是将纹理整体完整铺在显示区域,如果想把纹理的一部分铺上去,那就在[0, 1]区间内部分写进去。

执行完渲染操作后,调用eglSwapBuffers即可显示。至此,一次渲染操作完成。