最近在鼓搗OpenGL渲染,這東西确實給顯示帶來很大便利,網上資料也比較多,不過因為這些接口都高度封裝,出問題可不太好查。我在網上看了一些資料,有很多渲染圖的例子,不過貌似渲染圖并重新整理的比較少見,是以把我的一些經驗分享一下,畢竟像我一樣臨時現學現搞的菜鳥應該還是比較多的。期間遇到一些坑,還好有大佬的幫忙。。。
1. 着色器腳本
這算是opengl的基本概念,就不做詳細解釋了
char *vertex_shader_source =
"attribute vec2 vertexCoord;"
"attribute vec2 textureCoord;"
"varying vec2 sampleCoord;"
"void main()"
"{"
" sampleCoord = textureCoord;"
" gl_Position = vec4(vertexCoord, 0.0, 1.0);"
"}";
char *fragment_shader_source =
"precision mediump float;"
"varying vec2 sampleCoord;"
"uniform sampler2D sampleTexture;"
"void main()"
"{"
" gl_FragColor = texture2D(sampleTexture, sampleCoord);"
"}";
2.建構着色器程式
這個也是基本操作,八股文一樣,也不再細說了
static void check_gl_error(char *gl_operation)
{
for (GLint error = glGetError(); error; error = glGetError())
{
g_info("[GL] Operation %s() glError (0x%x)", gl_operation, error);
}
}
static GLuint load_shader(GLenum shader_type, char *shader_source)
{
GLuint shader = glCreateShader(shader_type);
if (shader) {
glShaderSource(shader, 1, &shader_source, 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);
g_info("[GL] Could not compile shader %d:%s", shader_type, buf);
free(buf);
}
glDeleteShader(shader);
shader = 0;
}
}
}
return shader;
}
static GLuint gl_render_create_program(GlRender *render, char *vertex_shader_source, char *fragment_shader_source)
{
g_return_val_if_fail(IS_GL_RENDER(render), 0);
GlRenderPrivate *p = render->priv;
p->vertex_shader = load_shader(GL_VERTEX_SHADER, vertex_shader_source);
if (!p->vertex_shader) {
return 0;
}
p->fragment_shader = load_shader(GL_FRAGMENT_SHADER, fragment_shader_source);
if (!p->fragment_shader) {
return 0;
}
GLuint program = glCreateProgram();
if (program) {
glAttachShader(program, p->vertex_shader);
check_gl_error("glAttachShader");
glAttachShader(program, p->fragment_shader);
check_gl_error("glAttachShader");
glLinkProgram(program);
GLint linkStatus = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
if (linkStatus != GL_TRUE) {
GLint bufLength = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
if (bufLength) {
char* buf = (char*)malloc(bufLength);
if (buf) {
glGetProgramInfoLog(program, bufLength, NULL, buf);
g_info("[GL] Could not link program:%s", buf);
free(buf);
}
}
glDeleteProgram(program);
program = 0;
}
}
return program;
}
3.坐标系初始化
定點坐标及紋理坐标初始化後可以直接使用了,不需要再對每一幅圖作坐标映射,因為圖像是局部重新整理,每次顯示還是全圖
static void gl_render_get_handles(GlRender *render)
{
g_return_if_fail(IS_GL_RENDER(render));
GlRenderPrivate *p = render->priv;
glUseProgram(p->program);
p->vertex_coord_handle = glGetAttribLocation(p->program, "vertexCoord");
p->texture_coord_handle = glGetAttribLocation(p->program, "textureCoord");
p->sample_texture_handle = glGetUniformLocation(p->program, "sampleTexture");
g_info("[GL] vertex_coord_handle = %d", p->vertex_coord_handle);
g_info("[GL] texture_coord_handle = %d", p->texture_coord_handle);
g_info("[GL] sample_texture_handle = %d", p->sample_texture_handle);
glEnableVertexAttribArray(p->vertex_coord_handle);
glEnableVertexAttribArray(p->texture_coord_handle);
}
void gl_render_open(GlRender *render)
{
g_return_if_fail(IS_GL_RENDER(render));
GlRenderPrivate *p = render->priv;
p->texture_id = 0;
p->texture_width = 0;
p->texture_height = 0;
p->surface_width = 0;
p->surface_height = 0;
p->vertex_coord[0] = -1.0f;
p->vertex_coord[1] = -1.0f;
p->vertex_coord[2] = 1.0f;
p->vertex_coord[3] = -1.0f;
p->vertex_coord[4] = -1.0f;
p->vertex_coord[5] = 1.0f;
p->vertex_coord[6] = 1.0f;
p->vertex_coord[7] = 1.0f;
p->texture_coord[0] = 0.0f;
p->texture_coord[1] = 1.0f;
p->texture_coord[2] = 1.0f;
p->texture_coord[3] = 1.0f;
p->texture_coord[4] = 0.0f;
p->texture_coord[5] = 0.0f;
p->texture_coord[6] = 1.0f;
p->texture_coord[7] = 0.0f;
p->program = gl_render_create_program(render, vertex_shader_source, fragment_shader_source);
if (!p->program)
{
g_info("[GL] init render failed");
}
gl_render_get_handles(render);
}
4.加載紋理到GPU
考慮切換分辨率的情況,當view的尺寸或紋理的尺寸出現變化時,通過設定glTexImage2D最後一個參數為NULL配置設定一個全圖的緩存,尺寸不變的時候通過glTexSubImage2D往緩沖區寫入局部的圖像資料,達到局部重新整理的效果
GLuint gl_render_load_texture(GlRender *render, GlContext *context,
void* data, int x, int y, int width, int height, int max_width, int max_height)
{
g_return_val_if_fail(IS_GL_RENDER(render), 0);
GlRenderPrivate *p = render->priv;
int surface_width = 0;
int surface_height = 0;
gl_context_get_width_height(context, &surface_width, &surface_height);
if (p->texture_width != max_width || p->texture_height != max_height
|| p->surface_width != surface_width || p->surface_height != surface_height)
{
p->texture_width = max_width;
p->texture_height = max_height;
p->surface_width = surface_width;
p->surface_height = surface_height;
glViewport(0, 0, surface_width, surface_height);
g_info("[GL] set opengl view width %d, height %d, texture width %d, texture height %d",
surface_width, surface_height, max_width, max_height);
if (p->texture_id)
{
glDeleteTextures(1, &p->texture_id);
p->texture_id = 0;
}
glGenTextures(1, &p->texture_id);
check_gl_error("glGenTextures");
glBindTexture(GL_TEXTURE_2D, p->texture_id);
check_gl_error("glBindTexture");
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA_IMG, max_width, max_height, 0, GL_BGRA_IMG, GL_UNSIGNED_BYTE, NULL);
check_gl_error("glTexImage2D");
}
glBindTexture(GL_TEXTURE_2D, p->texture_id);
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_BGRA_IMG, GL_UNSIGNED_BYTE, data);
check_gl_error("glTexSubImage2D");
glBindTexture(GL_TEXTURE_2D, 0);
return p->texture_id;
}
5.繪制
void gl_render_draw(GlRender *render, GlContext *context)
{
g_return_if_fail(IS_GL_RENDER(render));
GlRenderPrivate *p = render->priv;
glUseProgram(p->program);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, p->texture_id);
glUniform1i(p->sample_texture_handle, 0);
glVertexAttribPointer(p->vertex_coord_handle, 2, GL_FLOAT, GL_FALSE, 0, p->vertex_coord);
glVertexAttribPointer(p->texture_coord_handle, 2, GL_FLOAT, GL_FALSE, 0, p->texture_coord);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
check_gl_error("glDrawArrays");
gl_context_swap_buffers(context);
}
6.用完後記得釋放資源
if (p->texture_id) {
glDeleteTextures(1, &p->texture_id);
}
if (p->fragment_shader) {
glDeleteShader(p->fragment_shader);
glDetachShader(p->program, p->fragment_shader);
}
if (p->vertex_shader) {
glDeleteShader(p->vertex_shader);
glDetachShader(p->program, p->vertex_shader);
}
if (p->program) {
glDeleteProgram(p->program);
}
7.小結
用法:先調用gl_render_load_texture加載紋理(第一次調用最好傳一幅全圖),然後調用gl_render_draw繪制,不斷循環這個過程。關鍵點是頂點坐标及紋理坐标,以及圖像紋理的加載方式。本人用的是gobiect,跟c++的class是類似的東西,這裡的GlContext其實是另一個抽象類,裡面有egl的實作,主要是建立surface還有egl的swapbuffers,這個實作網上很多,這裡就不再細述了