Log4X

链路纵横
数据加载中……

2011年6月13日

Android上OpenGL开发一些经验记录(上)

Android沿用了J2ME的OPENGL ES API.

相比C版本的OpenGL,Opengl ES 没有glu和glut库,而且只能画三角形(多边形需要三角化)。

没有直接的drawXXX 方法,只有通过 glVertiexPointer传入顶点画图。

另外参数上,没有指针和C风格的数组,Java需要用Buffer类来代替这个。

先看android的glVertexPointer :

GL10.glVertexPointer(int size, int type, int stride, Buffer pointer):

这个方法为 后面openGL绘图准备顶点数据。 

size : 代表每个顶点包含几个坐标参数 ,如pointer的buffer中只含有 x,y坐标, 则传2, OpenGL会默认使用0作为z坐标。如果包含 x,y,z 坐标,则传3。其他值在这里都不适用。
type : 是一个枚举值,可以为 GL_FLOAT和 GL_FIXED,浮点数可对应java 的 float,要求pointer为 FloatBuffer.
      GL_FIXED意为定点数,长度4字节,高16位表示整数部分,低16位表示小数部分。传入int值前需要先左移16位(即需扩大 0x10000倍)。要求pointer为IntBuffer.
      
stride :指每个元素之间,间隔多少个值。加入buffer中坐标点数值间紧密相连,那么stride就是0,如果点1,点2间还有不用到的值,则stride就是这种值的个数。
pointer:这是最需要注意的一个参数,也是Java版OpenGL特有的。Buffer类型是一个基类,具体的类型是 FloatBuffer还是IntBuffer要根据前面type参数来决定。
         Java中的 Buffer分两种,一种direct,一种非direct.这里只支持 DirectBuffer,也就是通过ByteBuffer.allocateDirect(int cap) 分配而来的buffer.
         通过allocateDirect 得到的是一个 ByteBuffer,实际使用时,如果要作为FloatBuffer,可以通过 ByteBuffer.asFloatBuffer来得到,IntBuffer也同样。
         做到这些当然还不够,ByteOrder也对这个参数有影响。当的ByteBuffer作为FloatBuffer,则float的4字节默认是按照小端排列在directBuffer中,在
         某些大端的设备中这个buffer的格式就不正确,需要根据设备设置ByteOrder.
         所以以FloatBuffer为例,创建方法如下:
         FloatBuffer buffer = ByteBuffer.allocateDirect(1024).order(ByteOrder.nativeOrder()).asFloatBuffer();
         有了FloatBuffer,我们还需要将实际顶点坐标传入这个buffer。这只需要调用 FloatBuffer.put(float)的方法即可。
         注意放顶点坐标是x,y...的格式,还是x,y,z...的格式要和前面第一个参数 size是2还是3统一。
         所有的值放完以后,需要调用 buffer.rewind()或者 buffer.position(0),将buffer内游标置回0,否则OpenGL会从最后一次put的下一个位置开始读取。
         
关于三角化的算法:
Android的OpenGL API 画多边形目前只支持 triangle_fan,triangles, triangle_strip 三种。如果只有简单无洞的凸多边形,只需要将所有点当作triangle_fan的顶点就可以画出。
如果存在凹多边形,就需要进行三角化(当然凸多边形也可以三角化,保持逻辑上的统一)。
有个最简单的三角化算法叫做 ear clipping三角化。几何上非常容易理解:
一个多边形的一个顶点,和它相邻两个顶点可以组成一个三角形,如果这个三角形内部不存在这个多边形的其他顶点,就可以把这个顶点和相邻点组成的三角形当作一个ear.
这个ear可以被切下来(沿两个相邻顶点切)。每切一次,得到一个三角形和一个少了一个顶点的多边形,然后再对这个多边形做同样操作,直到剩下的这个多边形也只剩下3个顶点。
如此一来,这个多边形被完全分解为3角形。
要把把几何上的操作变成代码,只要解决这几个问题:
#如何判断多边形的一串顶点是逆时针还是顺时针排列的?
 - 从某个顶点指向它下一个顶点,可以得到一个向量,多边形有几条边就得到几个向量,如果所有相邻两个向量的叉积(前一个向量和后一个向量的叉积)之和大于0,则排列是逆时针的,反之是顺时针的
 (根据右手坐标系判断,一系列逆时针向量围成的面积是指向z轴正向的),为0有两种情况,这串点是直线,或者多边形某两条边交叉,这种多变形不符合这个三角化算法的条件。
#根据顶点顺逆情况,调整顶点排列,保证逆时针排序,则可以得到如下结论:
- 如果相邻两个向量,前一个向量和后一个向量叉积,值为正,则两个向量连接点这个顶点是凸点,值为负则为凹点。
- 如果有相邻3个顶点 p1,p2,p3 组成三角形,有另外一个顶点 P,如何判断P是否在三角形内?
分别做如下几个叉积p2P X p1p2, p3P X p2p3 , p1P X p3p1 ,如果这3个叉积同为正或同为负,则这个顶点在三角形内部。
 
关于 glEnableClientState, glDisableClientState,
glEnableClientState(int cap);
cap : GL_COLOR_ARRAY
GL_EDGE_FLAG_ARRAY
GL_FOG_COORD_ARRAY
GL_INDEX_ARRAY
GL_NORMAL_ARRAY
GL_SECONDARY_COLOR_ARRAY
GL_TEXTURE_COORD_ARRAY
GL_VERTEX_ARRAY
是用来启用或者关闭和这些数组功能。
如同前面的glVertexPointer ,还有别的诸如 glColorPointer, glEdgeFlagPointer等等接口,
glVertexPointer描述顶点位置,其他的描述顶点或者平面的属性。
这些数组默认都是disable状态,如果用这两个接口enable了某个数组,那么当glDrawArrays 调用之前必须把相应的数组准备好。否则draw不能成功。
所以如果不准备传入某个数组,draw之前要先 disable这个数组(如果前面enable过)。
反之如果要使用某个数组,也要事先enable。

 还有Java中缺少的API,比如没有glPerspective的时候如何用glFrustm实现glPerspective,以及没有gluLookAt的时候如何用modelView来得到同样效果。将在之后文章里面解说。

posted @ 2011-06-13 14:43 YYX 阅读(1058) 评论(0) 编辑

2010年2月15日

windows下opengl开发的准备工作

本来准备写一些opengl的入门和概念性的文章,谁知道上班的事情一下子变得很忙,人就变懒了...
不过终于在春节长假有了一些空闲时间。就继续一下未竞的事业吧。
Windows下做OpenGL开发需要的什么:
如果是用VS来开发OpenGl,首先看一下VS目录下的 VC/include/下有没有GL这个文件夹。如果有了,那什么也不需要。
如果没有这个目录,就需要自己创建,并且从网上找gl.h和glu.h两个头文件放到该目录,同时找gl.dll,glu.dll放到windows/system32/下或者放到和项

目编译所得exe文件同目录。
另外,如果想简化一下一些硬件和平台相关的函数调用,可以找glut.h和glut.dll分别放到以上两个目录。
然后VS中可以建立空白工程,在头文件引用路径上加上VC/include/GL。
复制如下代码,可以画一个白色正方形。
代码
 1         #include <glut.h>  
 2         void myDisplay(void
 3         { 
 4             glClear(GL_COLOR_BUFFER_BIT);
 5             glColor3f(1.0f,1.0f,1.0f);
 6             glRectf(-0.5f-0.5f0.5f0.5f); 
 7             glFlush(); 
 8         } 
 9         int main(int argc, char *argv[]) 
10         { 
11             glutInit(&argc, argv); 
12             glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); 
13             glutInitWindowPosition(100100); 
14             glutInitWindowSize(500500); 
15             glutCreateWindow("sample"); 
16             glutDisplayFunc(&myDisplay); 
17             glutMainLoop(); 
18             return 0
19         }

 

其中gl开头都是opengl基础实现的函数,glut开头是glut工具库函数。
gl开头的函数实现都是平台无关的,而glut每个平台都会有不同实现。
关于代码中每个函数的作用,在以后的文章中会做一些解释。

posted @ 2010-02-15 00:02 YYX 阅读(144) 评论(0) 编辑

2009年12月19日

基本的2D图形变换&变换矩阵推导

平时开发程序,免不了要对图像做各种变换处理。有的时候变换可能比较复杂,比如平移之后又旋转,旋转之后又平移,又缩放。

直接用公式计算,不但复杂,而且效率低下。这时可以借助变换矩阵和矩阵乘法,将多个变换合成一个。 最后只要用一个矩阵对每个点做一次处理就可以得到想要的结果。

 另外,矩阵乘法一般有硬件支持,比如3D 图形加速卡,处理3D变换中的大量矩阵运算,比普通CPU 要快上1000倍。

下面是3类基本的2D图形变换。 

平移:

设某点向x方向移动 dx, y方向移动 dy ,[x,y]为变换前坐标, [X,Y]为变换后坐标。

则 X = x+dx;  Y = y+dy;

以矩阵表示:

                              1    0    0

[X, Y, 1] = [x, y, 1][ 0    1    0  ] ; 

                              dx  dy   1

  1    0    0

  0    1    0   即平移变换矩阵。 

  dx  dy   1 

 

 旋转

 旋转相比平移稍稍复杂:

 设某点与原点连线和X轴夹角为b度,以原点为圆心,逆时针转过a度  , 原点与该点连线长度为R, [x,y]为变换前坐标, [X,Y]为变换后坐标。

  x = Rcos(b) ; y = Rsin(b);

  X = Rcos(a+b) = Rcosacosb - Rsinasinb = xcosa - ysina; (合角公式)

  Y = Rsin(a+b) = Rsinacosb + Rcosasinb = xsina + ycosa ;


  用矩阵表示:

                              cosa   sina  0

 [X, Y, 1] = [x, y, 1][-sina  cosa  0  ] 

                               0        0     1

  cosa   sina  0

 -sina  cosa  0  为旋转变换矩阵。

   0       0     1 

 

 缩放

 设某点坐标,在x轴方向扩大 sx倍,y轴方向扩大 sy倍,[x,y]为变换前坐标, [X,Y]为变换后坐标。

 X = sx*x; Y = sy*y;

则用矩阵表示:

                              sx    0    0

[X, Y, 1] = [x, y, 1][ 0    sy    0  ] ; 

                              0     0     1

 sx    0    0

 0    sy    0  即为缩放矩阵。 

 0     0     1

 

 2D基本的模型视图变换,就只有上面这3种,所有的复杂2D模型视图变换,都可以分解成上述3个。

比如某个变换,先经过平移,对应平移矩阵A, 再旋转, 对应旋转矩阵B,再经过缩放,对应缩放矩阵C.

则最终变换矩阵 T = ABC. 即3个矩阵按变换先后顺序依次相乘(矩阵乘法不满足交换律,因此先后顺序一定要讲究)。


最近在学习OpenGL,并准备深入学习一下计算机图形学,接下去,会写一些OpenGL的入门和Tips。 

posted @ 2009-12-19 01:59 YYX 阅读(714) 评论(0) 编辑

2009年10月15日

如何在android native编程中使用logCat

Android NDK发布后,java+C的编程方式成为android上性能编程的首选。
但在C中调试困难,因此能使用logcat成为必须的要求。

关于在Native代码中使用logcat,网上有很多说法,大部分有所欠缺,有的根本是错的。

要使用logcat,首先在代码中要引入 log的头文件。
#include <android/log.h>

然后你可以简单的通过
__android_log_write(ANDROID_LOG_ERROR,"Tag","Message"); 方法向logcat输出。
log 级别有很多  :
    ANDROID_LOG_UNKNOWN,
    ANDROID_LOG_DEFAULT,   
    ANDROID_LOG_VERBOSE,
    ANDROID_LOG_DEBUG,
    ANDROID_LOG_INFO,
    ANDROID_LOG_WARN,
    ANDROID_LOG_ERROR,
    ANDROID_LOG_FATAL,
    ANDROID_LOG_SILENT,

这样写完以后,如果直接编译,就会报 __android_log_write 方法undefined.
怎么回事呢?关键是在设置编译选项上面。
在Android.mk文件里,可以指定一个LOCAL_LDLIBS的参数。如果不指定,那么编译的时候,只会引入默认的几个重要的lib,比如libc之类的。
如果要用log,那就要把 liblog给引进来。
网上很多的写法是 LOCAL_LDLIBS := -llog ,这在build static lib的时候没什么问题。如果是build shared lib,就会报个 cannot find -llog的错误。意思是找不到liblog.so这个库文件。
因此需要改成 LOCAL_LDLIBS :=  -L$(SYSROOT)/usr/lib -llog 才可以正常编译。
其中-L参数是指定了搜索lib的路径。
下面是一个android.mk的内容的例子:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := TestNdkNetwork
LOCAL_SRC_FILES := HttpConnection.cpp
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
include $(BUILD_SHARED_LIBRARY)

posted @ 2009-10-15 20:28 YYX 阅读(976) 评论(0) 编辑

2009年7月7日

JNI调用Java方法

JNI 调用java类的方法与反射代码类似。

先得到object的类。
-->jobject obj;
jclass cls = env->GetObjectClass(obj);

然后查找方法:
jmethodID mgetZoom = env->GetMethodID(cls,"getZoom","()I");

jmethodID getFocus = env->GetMethodID(cls,"getDirectionalArrows","()Ljava/util/Vector;");

然后用这个方法id去执行obj的方法。
jint i = env->CallIntMethod(obj,mgetZoom);

jobject vec = env->CallObjectMethod(obj,getFocus);

需要注意的是 GetMethodID方法的格式。
jmethodID GetMethodID(JNIEnv *env, jclass clazz, 
const char *name, const char *sig); 

JNIEnv这个参数C++中不需要。clazz就是前面得到的jclass.
name则是方法名称,sig是方法签名。
方法签名有特定的格式:(param-type)ret-type,括号内表示该方法传入参数类型,后面的是返回类型
其中param-type和ret-type都是由特定符号组成,各java primitive type都有各自对应符号如下表。
Table 3-2 Java VM Type Signatures 
Type Signature  Java Type  
Z  boolean  
B  byte  
C  char  
S  short  
I  int  
J  long  
F  float  
D  double  
L fully-qualified-class ;  fully-qualified-class  
[ type  type[]  
( arg-types ) ret-type  method type  

比如long f (int n, String s, int[] arr);
的signature就是(ILjava/lang/String;[I)J
另外千万别忘记L fully-qualified-class ; 这个格式后面的那个;号。

PS: 方法签名可以通过 javap -s <CLASS> 查看。

posted @ 2009-07-07 10:52 YYX 阅读(847) 评论(0) 编辑

2009年1月9日

How to add Chinese support to an English only BB emulator

摘要: Before start , you must have: - An emulator already supports Chinese (can be any model). - BlackBerry Desktop Manager (can get from official site) - A bat file attached (add “.bat” by your...阅读全文

posted @ 2009-01-09 19:17 YYX 阅读(51) 评论(0) 编辑

2008年12月24日

j2me 的一些小tips

摘要: j2me 的一些小tipsInputStream.available()方法返回还有多少字节,在真机上可能有错。所以还是选择传统方式的读写流。使用Alert控件要先设定好要跳转的界面,再show Alert,而不是倒过来。在Canvas的paint方法中的g 的drawRGB的 processalpha半透明设置才有用,对于从某个Image得到的Graphics没用Image.createImag...阅读全文

posted @ 2008-12-24 12:19 YYX 阅读(58) 评论(0) 编辑

2008年10月17日

在www.blogjava.net上看到的有趣题目

摘要: http://www.blogjava.net/Jack2007/archive/2008/10/16/234742.html原文作者是Jack.Wang题目描述:给定一个十进制数N,写下从1开始,到N的所有整数,然后数一下其中出现的所有"1"的个数。例如:N=2,写下1,2。这样只出现了1个"1"N=12,写下 1,2,3,4,5,6,7,8,9,10,11,12。这样"1"的个数是5请写出一个...阅读全文

posted @ 2008-10-17 00:56 YYX 阅读(1247) 评论(13) 编辑

2008年10月7日

JAVA程序员看C#的精华与糟粕

摘要: C#和java是号称90%的相同加上10%的不同。因此当时我学习C#,阅读两种代码完全没有什么阻碍。对C#了解得深入以后,来发表下对这两种语言各自特性的一些看法比较起java和C#大相径庭的那10%,会发现C#五花八门的特性要多很多。比较知名的有:委托,属性,真正的泛型,索引器,类初始化器,分部类,操作符重载,struct,unsafe代码,IDisposable等,另外.net framewor...阅读全文

posted @ 2008-10-07 07:48 YYX 阅读(6972) 评论(80) 编辑
一个细小,难以察觉的错误

摘要: 这是一个控制Java动态代理执行的类(附C#实现),target域是他的代理目标,动态代理会生成一个可以替代target的类,在调用target类的某方法时,实际上是调用被改造过的方法(会多执行代码块中标记custom code的那些代码)。不过这些不是重点。重点是,ignorePrefixList中包含了一些字符串,而要求是以这些字符串开头的方法,不执行//custom code中省略的逻辑,而...阅读全文

posted @ 2008-10-07 00:03 YYX 阅读(2001) 评论(5) 编辑