OpenGL定义了一个跨编程语言、跨平台的专业图形程序接口。可用于二维或三维图像的处理与渲染,它是一个功能强大、调用方便的底层图形库。对于嵌入式设备,其提供了OpenGL ES(OpenGL for Embedded Systems)版本。
由于OpenGL是跨编程语言、跨平台的设计,所以在每个平台上都要有它的具体实现,负责提供OpenGL的上下文环境以及窗口的管理。在Android平台使用EGL提供本地平台对OpenGL ES的实现。
我们的目标是实现一个小Demo:在Android手机上利用OpenGL绘制一个三角形出来。
当然我们可以直接使用Android提供的GLSurfaceView,通过这种方式使用OpenGL比较简单,不需要我们再去搭建OpenGL上下文环境。本文我们不会去使用GLSurfaceView,而是直接借助EGL的API,在C++环境搭建出OpenGL上下文环境。
首先,我们用Android Studio创建一个Native C++工程出来,编写CMakeList.txt文件,加入EGL库,文件内容如下:
cmake_minimum_required(VERSION 3.10.2)
project("eglrender")
add_library( # Sets the name of the library.
egl-render
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
EglJni.cpp
common/looper.cpp
common/MyLooper.cpp
render/GLRender.cpp
render/Triangle.cpp
gles/EglCore.cpp
gles/GLUtils.cpp )
find_library(log-lib log)
find_library(EGL-lib EGL)
find_library(GLESv2-lib GLESv2)
target_link_libraries(
# Specifies the target library.
egl-render
android
${log-lib}
${EGL-lib}
${GLESv2-lib} )
关注target_link_libraries选项,在链接时,我们添加了android、EGL-lib、GLESv2-lib三个库。在搭建OpenGL上下文环境,以及执行OpenGL程序需要用到这三个库中的方法。
首先,需要获取显示设备,通过eglGetDisplay方法获取
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL_NO_DISPLAY) {
return false;
}
然后,调用eglInitialize()来初始化这个显示设备:
// 不关心版本号,直接传0即可
if (!eglInitialize(mEGLDisplay, 0, 0)) {
return false;
}
EGL有了Display之后,我们需要准备配置选项,比如:色彩格式、像素格式、SurfaceType等。最终通过eglChooseConfig得到配置选项信息。
const EGLint attribList[] = {EGL_BUFFER_SIZE, 32,
EGL_ALPHA_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_NONE };
// 获取config
EGLConfig config;
int numConfigs;
if (!eglChooseConfig(mEGLDisplay, attribList, &config, 1, &numConfigs)) {
return false;
}
有了EGLDisplay和EGLConfig之后,接下来就可以创建上下文环境:EGLContext了,通过eglCreateContext完成。
int attrib2_list[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
EGLContext context = eglCreateContext(mEGLDisplay, config, sharedContext, attrib2_list);
有了上下文EGLContext之后,接下来需要创建EGLSurface,这是用来指定OpenGL渲染输出的目的地。就Android平台来说,它代表SurfaceView的surface,surface对象将从java传到native层。
override fun surfaceCreated(holder: SurfaceHolder) {
onSurfaceCreated(holder.surface)
}
对应的JNI代码如下:
extern "C" JNIEXPORT void JNICALL
Java_com_baidu_eglrender_MainActivity_onSurfaceCreated(
JNIEnv *env, jobject instance, jobject surface) {
if (mWindow) {
ANativeWindow_release(mWindow);
mWindow = NULL;
}
mWindow = ANativeWindow_fromSurface(env, surface);
if (mLooper) {
LOGD("post MsgSurfaceCreated")
mLooper->post(MsgSurfaceCreated, mWindow);
}
}
我们通过ANativeWindow_fromSurface方法从surface中拿到ANativeWindow对象指针。然后再根据ANativeWindow指针创建出EGLSurface对象
EGLSurface EglCore::createWindowSurface(ANativeWindow *window) {
if (window == NULL) {
return NULL;
}
int surfaceAttribs[] = {
EGL_NONE
};
mEglSurface = eglCreateWindowSurface(mEGLDisplay, mEGLConfig, window, surfaceAttribs);
if (mEglSurface == NULL) {
return NULL;
}
return mEglSurface;
}
需要明确一点,OpenGL的渲染操作需要在一个新的线程中执行,而且还必须为该线程绑定显示设备(surface)和上下文环境(Context),这样才可以执行OpenGL指令。
if (!eglMakeCurrent(mEGLDisplay, mEglSurface, mEglSurface, mEGLContext)) {
LOGD("eglMakeCurrent error")
}
我们还需要创建一个线程,用来执行OpenGL指令。我们计划在Native层实现一个Looper 线程,功能类似Android的异步消息机制。这个Looper线程的设计可参考官方native-codec项目,本文就不展开了。
接下来我们可以执行OpenGL指令,绘制一个三角形出来。我们编写一个Triangle.cpp实现具体功能。
#include "Triangle.h"
const GLint COORDS_PER_VERTEX = 3;
const GLint vertexStride = COORDS_PER_VERTEX * 4;
Triangle::Triangle() {}
Triangle::~Triangle() {}
int Triangle::init() {
char vertexShader[] =
"#version 300 es\n"
"layout(location = 0) in vec4 a_position;\n"
"layout(location = 1) in vec4 a_color;\n"
"out vec4 v_color;"
"void main()\n"
"{\n"
" gl_Position = a_position;\n"
" v_color = a_color;\n"
"}\n";
char fragmentShader[] =
"#version 300 es\n"
"precision mediump float;\n"
"in vec4 v_color;\n"
"out vec4 fragColor;\n"
"void main()\n"
"{\n"
" fragColor = v_color;\n"
"}\n";
// 根据着色器,创建GL程序
programHandle = createProgram(vertexShader, fragmentShader);
if (programHandle <= 0) {
LOGD("create program error, programHandle=%d\n", programHandle)
return -1;
}
// 清屏
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
return 0;
}
void Triangle::onDraw(int width, int height) {
// 顶点坐标
GLfloat vertices[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
// 三角形颜色
GLfloat color[] = {
1.0f, 0.0f, 0.0f, 1.0f
};
GLint vertexCount = sizeof(vertices) / (sizeof(vertices[0]) * COORDS_PER_VERTEX);
glViewport(0, 0, width, height);
glClear(GL_COLOR_BUFFER_BIT);
// 使用Program
glUseProgram(programHandle);
// 获取GL程序顶点着色器的a_position变量
GLint positionHandle = glGetAttribLocation(programHandle, "a_position");
// 设置顶点位置信息
glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GL_FLOAT, GL_FALSE, vertexStride,
vertices);
glEnableVertexAttribArray(positionHandle);
// 设置片远着色器的color变量
glVertexAttrib4fv(1, color);
// 执行绘制
glDrawArrays(GL_TRIANGLES, 0, vertexCount);
glDisableVertexAttribArray(positionHandle);
}
void Triangle::destroy() {
if (programHandle > 0) {
glDeleteProgram(programHandle);
}
programHandle = -1;
}
看下init方法,首先创建顶点着色器vertexShader和片元着色器代码fragmentShader,然后根据这两个着色器调用createProgram创建出GL程序。该方法会返回一个programe句柄。接着在onDraw()方法中就是具体的绘制逻辑,最终通过glDrawArrays()方法完成OpenGL绘制。
离屏幕展示渲染结果还差最后一步:调用eglSwapBuffers()方法,完成前后台FrameBuffer交换。代码如下:
void GLRender::surfaceChanged(int width, int height) {
// 执行OpenGL指令,绘制三角形
mTriangle->onDraw(width, height);
// 交换前后台FrameBuffer
mEglCore->swapBuffers();
}
// 调用eglSwapBuffers交换前后台FrameBuffer
bool EglCore::swapBuffers(EGLSurface eglSurface) {
return eglSwapBuffers(mEGLDisplay, eglSurface);
}
这里说明下EGL的双缓冲模式:EGL内部有两个FrameBuffer(帧缓冲区),当EGL将一个FrameBuffer显示到屏幕上的时候,另一个FrameBuffer就在后台等待OpenGL ES进行渲染输出了。直到调用eglSwapBuffers指令,把前台FrameBuffer和后台FrameBuffer进行交换,这样用户就可以在屏幕上看到刚才OpenGL ES渲染输出的结果了。
编译运行程序,就可以看到Demo成功绘制了一个红色的三角形,如下图所示:

我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po