[转载]一步一步教你写OpenGLES之画三角形 - OpenGL - ARSchool

mikel阅读(860)

上一节我们简单的搭建了一个OpenGL的运行窗口,也简单的对这个窗口进行绘制。

来源: [转载]一步一步教你写OpenGLES之画三角形 – OpenGL – ARSchool

上一节我们简单的搭建了一个OpenGL的运行窗口,也简单的对这个窗口进行绘制。
这一节,我们将介绍OpenGL如何将CPU中的内存数据送到GPU的内存中,Shader又是如何找到这些数据,并进行绘制的。
我们将通过绘制三角形这一简单的例子,为大家介绍OpenGL的管线流程,以及如何渲染颜色,颜色渐变动画等知识。

#介绍OpenGL的管线
Opengl 中所有的事物,都是由点来表示的,而这些点又是由3D坐标表示的。但是最终要在屏幕或窗口中显示的是2D画面,这也就是说Opengl一大部分的工作是 如何将3D 坐标变换为2D坐标,Opengl中 有一个 图形管线(graphics pipeline)来专门处理这一过程。 图形流程可以分为两部分:一, 将3d坐标转换为2d坐标;二,将2d坐标变换为实际的彩色像素。

图形流程一般以 一列3D坐标(物体模型的顶点)作为输入,然后将其转为2D坐标,再渲染为2D彩色图形。
    其中将3D的点集渲染为彩色图形,又可分为多个步骤,而每一步骤都是以上一步的输出为输入。 在Opengl中,每一步都被高度定制(简单的由一个API表示)。 这所有的步骤之间可以并行执行。多数显卡都有数千个小的处理单元(processing cores),通过在GPU中运行小的工程从而使得图形流程可以很快处理数据 。这些小的工程被称为着色器(shaders)。

有些着色器是可以供开发者配置,通过编写 这些着色器可以替换opengl里默认的着色器, 从而可以使得开发者细致的掌控流程中一些特定的部分。 OpenGL为我们提供了GLSL语言,该语言除了简单的基本类型(类C)外,都是一些抽象的函数实现,而这些函数实现的算法都集成到了GPU中,从而大 大的节省了CPU的运行时间。


下图简单的描述了图形管线的处理步骤:


如何由顶点再到最后的渲染成像一目了然。 这里面,最重要的两个就是顶点着色(Vertex Shader) 和 片段着色(Fragment Shader), 由于OpenGL 2.0以后,接口编程的开放,这两个就需要用户自己定制,而其他的在一般情况下可保持默认。


# 顶点由CPU到GPU
在MyGLRenderer里, 创建一个构造函数,定义三角形的顶点数据:

复制代码

1
2
3
4
5
6
float[]  verticesWithTriangle =
 {
      0f, 0f,
      5f, 5f,
      10f, 0f  
}

一般在Java中的数据,是由虚拟机为其分配好内存的,因此这一步还不能让CPU真正获取我们所定义的数据,并将数据传递给GPU,
好在Java为我们提供了Buffer这样的对象, 它可以直接在Native层分配内存,以让CPU获取。 在Java中,一个浮点型是4字节,因此,我们可定义BYTES_PER_FLOAT = 4,并有

复制代码

1
2
vertexData = ByteBuffer.allocateDirect(tableVerticesWithTriangles.length*BYTES_PER_FLOAT)
   .order(ByteOrder.nativeOrder()).asFloatBuffer();

在这里,我么将分配好的字节按nativeOrder进行排序,这样在大小端机器上都能适用。

CPU的数据是要送到GPU供Shader使用的,因此,我们需要在Shader中制定顶点的属性
首先,在Android工程目录, res下面创建一个raw文件夹,并创建vertexShader.glsl

复制代码

1
2
3
4
5
6
7
// vertex shader
attribute vec4 a_Position;
              
void main()
{
    gl_Position = a_Position;
}

上面我们只定义了顶点的位置属性, 因此,我们只声明 一个位置属性。 vec4是一个四维数组,如果我们没有为其分配数据,它将自动填充0, 0, 0, 1。 在vertexShader, 最终OpenGL 会将输入的顶点位置赋值给gl_Position 进行输出。


接着,我们定义fragmentShader.glsl

复制代码

1
2
3
4
5
6
7
8
//fragShader
precision mediump float;
uniform vec4 u_Color;
              
void main()
{
    gl_FragColor = u_Color;
}

第一句定义了数据的精度,就像java语言的double, float类型一样。 highp 就像double代表了高精度,因其效率不高,只用于有限的几个操作。我们只需用mediump即可。 然后,定义了一个 uniform颜色变量, 一般uniform变量在shader中不会有太大变化。 最后,将颜色赋值给gl_FragColor, 完成最后的颜色输出。
gl_Position 和 gl_FragColor 都是OpenGL的内置变量。

# 链接着色器到工程中
 为了能够让Opengl能够使用这些着色器,还需要对这些着色器进行编译,以便能够在运行时使用它们。
第一件事,就是创建一个 着色器工程(Shader Program)
首先,我们需要将Shader的代码,读到一个字符串中。在我们package下,创建一个TexResourceRender.java
里面写上如下函数:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static String readTextFromResource(Context context, int resourceId)
    {
        StringBuilder body = new StringBuilder();
              
        try{
            InputStream inputStream = context.getResources().openRawResource(resourceId);
              
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
              
            String nextLine;
              
            while((nextLine = bufferedReader.readLine())!=null)
            {
                body.append(nextLine);
                body.append("\n");
            }
        }catch(IOException e)
        {
            throw new RuntimeException("Could not open resource: "+ resourceId, e);
        }catch(Resources.NotFoundException nfe)
        {
            throw new RuntimeException("Resource not found: "+resourceId, nfe);
        }
        return body.toString();
              
    }

以上代码表示: 我们从res/raw读取了glsl的内容到字符串中,为了覆盖所有的执行可能,我们用try catch包含了文件读取异常和读取不到的情况。
为了,能够获得程序在最后运行的信息,我们需要用Android的Log日志,将信息打印出来,但又不希望只打印我们程序中信息,因此定义一个Logger.java

复制代码

1
2
3
public Logger{
     public static boolean ON = true;
}

这样就可以用ON来判断,是否要打印我们的日志

接着,创建一个ShaderUtils.java,用于完成shader工程的编译,链接:
复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static int compileVertexShader(String shaderCode)
    {
        return compileShader(GL_VERTEX_SHADER,shaderCode); 
    }
              
public static int compileFragShader(String shaderCode)
 {
     return compileShader(GL_FRAGMENT_SHADER, shaderCode);
}
              
              
public static int compileShader(int type, String shaderCode)
{
}

这里,我们将重点介绍compileShader, OpenGL的Shader编译流程大体如下:1 创建一个Shader 2 将Shader源码赋值给Shader 3 编译 Shader

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
final int shaderObjectId = glCreateShader(type); // 1
            
        if(shaderObjectId == 0)
        {
            if(LoggerConfig.ON)
            {
                Log.w(TAG, "Could not create new shader.");
            }
            
            return 0;
        }
            
        glShaderSource(shaderObjectId, shaderCode); // 2
        glCompileShader(shaderObjectId);   // 3
            
        final int[] compileStatus = new int[1]; 
        glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);
            
        if(LoggerConfig.ON)
        {
            Log.v(TAG, "Results of compiling source: "+"\n" + shaderCode +"\n:"
            + glGetShaderInfoLog(shaderObjectId));
        }
            
        if(compileStatus[0] == 0)
        {
            glDeleteShader(shaderObjectId);
            
            if(LoggerConfig.ON)
            {
                Log.w(TAG, "Compliation of shader failed");
            }
            
            return 0;
        }
            
        return shaderObjectId;

为了检测编译的状态,一般在Java平台上,都是创建一个数组,然后获得的编译信息 都是存在该数组的第一个元素中。 如果,编译的状态有错误,删除创建的Shader标识, 如果没错,就返回该标识。

在MyGLRenderer.java的 onSurfaceChanged 下的glClear后读取 glsl的shader文件源码, 之后编译它们

复制代码

1
2
3
4
5
String vertexShaderSource = TextResourceRender.readTextFromResource(mContext, R.raw.simple_vertex_shader);
 String fragShaderSource = TextResourceRender.readTextFromResource(mContext, R.raw.simple_frag_shader);
            
int vertexShader = ShaderUtils.compileVertexShader(vertexShaderSource);
int fragShader = ShaderUtils.compileFragShader(fragShaderSource);

Shader编译以后,需要链接到工程中 ,链接工程的步骤大体如下: 1 创建一个program 2 将Shader依附在工程上 3 链接工程
它的步骤与 Shader的编译相似,在ShaderUtils.java添加如下代码:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static int LinkProgram(int vertexShaderId, int fragShaderId)
  {
      final int programObjectId = glCreateProgram();   //1
      if(programObjectId ==0)
      {
          if(LoggerConfig.ON)
          {
              Log.w("TAG", "Could not create new program");
          }
            
          return 0;
      }
            
      glAttachShader(programObjectId, vertexShaderId);  //2
      glAttachShader(programObjectId, fragShaderId);
            
      glLinkProgram(programObjectId);   //3
            
      //check any error
      final int[] linkStatus = new int[1];
      glGetProgramiv(programObjectId, GL_LINK_STATUS, linkStatus, 0);
            
      if(LoggerConfig.ON)
      {
          Log.v(TAG, "Results of linking program:\n" + glGetProgramInfoLog(programObjectId));
      }
            
      if(linkStatus[0] == 0)
      {
          glDeleteProgram(programObjectId);
          if(LoggerConfig.ON)
          {
              Log.w(TAG, "Linking of program valid");
          }
          return 0;
      }
            
      return programObjectId;
            
  }

然后,在MyGLRenderer.java的 onSurfaceCreated 下创建program 工程
复制代码

1
int program = ShaderUtills.LinkProgram(vertexShader, fragShader);

在使用创建的program之前,我们很想知道,创建的工程是否符合opengl当前上下文的状态, 知道为什么它有时候会运行失效,根据OpenGLES2.0的官方文档, 我们需在ShaderUtils.java下创建如下方法:

复制代码

1
2
3
4
5
6
7
8
9
public static boolean validateProgram(int programObjected)
    {
        glValidateProgram(programObjected);
        final int[] validateStatus = new int[1];
        glGetProgramiv(programObjected, GL_VALIDATE_STATUS, validateStatus, 0 );
        Log.v(TAG, "Results of validating program: "+ validateStatus[0] +"\nLog: "+glGetProgramInfoLog(programObjected));   // 打印日志信息
            
        return validateStatus[0] !=0;
    }

接着,我们就可以使用Shader工程了,

复制代码

1
2
3
4
5
6
if(LoggerConfig.ON)
 {
     ShaderUtils.validateProgram(program);
 }
          
glUseProgram(program);

有了program标识后,我们可以使用它将数据送到Shader里了,首先在MyGLRenderer.java的Filed域中定义

复制代码

1
2
3
4
5
private static final String U_COLOR = “u_Color”;  // 标识fragment shader里 uniform变量
private static final String A_POSITION = "a_Position";
              
private int uColorLocation;  // 存放shader里的变量位置
private int aPositionLocation;

然后,在onSurfaceCreated里

复制代码

1
2
3
4
5
6
uColorLocation = glGetUniformLocation(program, U_COLOR);
aPositionLocation = glGetAttribLocation(program, A_POSITION);
              
vertexData.position(0);
glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 0, vertexData);
glEnableVertexAttribArray(aPositionLocation);

首先找到 shader里,变量的位置, 然后将vertexData的数据用glVertexAttribPointer 传递给Shader, 该函数的6个参数分别代表为:
1 shader中顶点变量的位置
2 顶点的坐标属性分量个数
3 指明了顶点的数据类型
4 顶点是否归一化(是否是整数)
5 代表,每个顶点的总数据大小(包含了所有属性的内存), 由于这里,我们只定义了顶点属性一种,因此可以赋值为0,(后面我们会介绍,顶点有多个属性的情况,再来着重介绍该函数)
6 数据的首地址

最后,用glEnableVertexAttribArray 激活这一顶点属性即可。
到这里,我们将重要的顶点数据由CPU送到GPU,可供VertexShader适用。 关于uniform变量,它相当于图像管线中的全局变量,即vertex shader和fragment shader能对其共享,它传入的值一般不会被管线流程改变。

在onDrawFrame方法的glClear(GL_CLOR_BUFFER_BIT)后面加上:

复制代码

1
glUniform4f(uColorLocation, 01.0f, 0f, 0f); // 第二个参数表示offset, 后面是rgb颜色分量,这里为红色

最后,glDrawArrays(GL_TRIANGLE, 0, 3); //
GL_TRIANGLE 是以三角形的形式去着色,同理还有点(GL_POINT), 线(GL_LINE)的方式。

看一下效果:

哎呦, 哪里不对劲, 为什么是在右上角,没显示全? 这事因为坐标系没有对,在opengl里,坐标系是图像的中心点,即我们定义的(0, 0)点。 而(10, 0)点跑到屏幕外面去了的原因是, opengl的坐标范围一般都在(-1, 1)之间,也就是设备归一化。 所以,我们如果想让自己的三角形显示在中间需要对之前定义的顶点做以下处理:

复制代码

1
2
3
4
5
6
float[]  verticesWithTriangle =
 {
      -1f, -1f,
      0f, 1f,
      1f, 1f 
}

这样就可以显示在中间了。

接下来,通过系统时间来改变每次传入的颜色, 可以实现以下动画效果:

复制代码

1
glUniform4f(aColorPosition, 0f, (float)(Math.abs(Math.sin(System.currentTimeMiilus()/1000)));

由于颜色的最小值为0, 为了不让动画有较长的黑屏现象,可对sin的值去绝对值,这样由黄到黑的不断交替变换的三角形就出来了

GIF图,制作时取得帧较少。  大家凑合着看吧。

记住,虽然OpenGL到目前可以为用户高度定制,但是那也只是限于在Shader中,而Shader 之外只能调用GL的API, 所以OpenGL的调用顺序一定要搞清楚。 而更需记住的是,OpenGL是一个状态机,对于其操作,只需使用简单的int类型,记住其返回的标识以代表我们获取并执行了GL某个状态。
附: 本教程可能讲解的不够深入, 但是我会继续努力的,争取为大家讲清楚每一个概念(概念可以慢慢讲清,API没法讲清, 只有大家多实践了)。 欢迎大家提问,交流

Android 5.0 Lollipop新的摄像头API

mikel阅读(1011)

前记:今天突然发现我刚打开的项目代码在eclipse中打了好几个警告,Camera is deprecated, Camera.Parameters is deprecated……等已经废弃的警告。一看官方文档原来Google本次Android版本更新对Camera这块新出了替代类Android.hardware.camera2。

       这个类把Camera设备封装成多级(包含多个处理阶段)的线性管道,这个管道用来处理输入请求中捕获的每个帧。通过camera2向系统请求拍照时,会返回一个带有一套图像缓冲的输出数据包。如果多个请求的会按顺序处理,多次重复请求只发一次。

使用新API
   1.获取CameraManager实例就可以遍历,查询和打开一个Camera对象。
  2.通过cameraManager对象调研getCameraCharacteristics(String)来获得CameraCharacteristics对象,该对象包含设备的设置信息和输出参数。
   3.通过CameraCaptureSession对象为每个预览对象进行预设置,如大小和format(这些格式必须是设备支持的),可以调用camera.createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)就可获得此对象。
   4.创建SurfaceView orTextureView (via its SurfaceTexture).  渲染图像。系统底层或调用RenderScript或OpenGles,甚至native本地方法来实例传给view的数据。
   5.构造一个CaptureRequest对象,来描述每次捕获图片的具体设置。
   6.最终调用capture()方法完成图像的捕获。
注意事项:
      如果用cameraManager对象打开或获取camera对象失败将,再或者试图连接到一个已经打开的设备抛出异常,记得用CameraAccessException捕获并处理异常。

Android平台下OpenGL初步

mikel阅读(873)

转自网上,网上没找到出处,只看到一些论坛中有这篇文章,组织的有点混乱,这篇文章感觉讲的挺好的。

http://www.bangchui.org/read.php?tid=7572&page=1

本文只关注于如何一步步实现在Android平台下运用OpenGl。

1、GLSurfaceView

GLSurfaceView是Android应用程序中实现OpenGl画图的重要组成部分。GLSurfaceView中封装了一个Surface。而Android平台下关于图像的现实,差不多都是由Surface来实现的。

2、Renderer

有了GLSurfaceView之后,就相当于我们有了画图的纸。现在我们所需要做的就是如何在这张纸上画图。所以我们需要一支笔。
Renderer 是GLSurfaceView的内部静态接口,它就相当于在此GLSurfaceView上作画的笔。我们通过实现这个接口来“作画”。最后通过 GLSurfaceView的setRenderer(GLSurfaceView.Renderer renderer)方法,就可以将纸笔关联起来了。
实现Renderer需要实现它的三个接 口:onSurfaceCreated(GL10 gl, EGLConfig config)、 onSurfaceChanged(GL10 gl, int width, int height)、onDrawFrame(GL10 gl)。下面就这三个接口的具体意义做个简单的介绍。

2.1、onSurfaceCreated

此方法看名字就知道它是在Surface创建的时候被调用的。因此我们可以在这个函数的实现中做一些初始化的工作。例如取出文房四宝、铺好画布、调好颜料之类的。它的函数原
型如下:
public abstract void onSurfaceCreated (GL10 gl, EGLConfig config)
第二个参数在文档中没有关于它的任何public方法和域。因此我们可以不用管它。
第一个参数非常重要。如果说Renderer是画笔的话,那么这个gl参数,就可以说是我们的手了。如何操作这支画笔,都是它说了算!所以我们绝大部分时候都是通过这个叫做gl的手来指挥Renderer画图的。

2.2 onSurfaceChanged

当 GLSurfaceView大小改变时,对应的Surface大小也会改变。值得注意的是,在Surface刚创建的时候,它的size其实是0,也就是 说在画第一次图之前它也会被调用一次的。(而且对于很多时候,Surface的大小是不会改变的,那么此函数就只在创建之初被调用一次而已)

原型如下:
public abstract void onSurfaceChanged (GL10 gl, int width, int height)
同样的,画图的手是必需的。
另外值得注意的是,它告诉了我们这张纸有多高多宽。这点很重要。因为在onSurfaceCreated的时候我们是不知道纸的宽高的,所以有一些和长宽相关的初始化工作还得在此函数中来做。

2.3 onDrawFrame

好了,我们的初始化工作做得差不多了,那么现在就是该真刀真枪画画的时候了!此函数就是真正给你画画用的。每调用一次就画一幅图。可能的疑问是这个函数什么时候会被调
用呢?最开始的时候肯定是会被调用的。以后会有两种模式供你选择:

  1. RENDERMODE_CONTINUOUSLY
  2. RENDERMODE_WHEN_DIRTY

第一种模式(RENDERMODE_CONTINUOUSLY):
连续不断的刷,画完一幅图马上又画下一幅。这种模式很明显是用来画动画的;
第二种模式(RENDERMODE_WHEN_DIRTY):
只有在需要重画的时候才画下一幅。这种模式就比较节约CPU和GPU一些,适合用来画不经常需要刷新的情况。多说一句,系统如何知道需要重画了呢?当然是你要告诉它……
调用GLSurfaceView的requestRender ()方法,告诉它,你脏了。
这两种模式在什么地方设置呢? GLSurfaceView的setRenderMode(int renderMode)方法。可以供你设置你需要的刷新模式。
还是来看看这个函数的原型吧: public abstract void onDrawFrame (GL10 gl) 很简单,只有手。

3、 Android下OpenGL绘图基本流程:

我们从画一个三角形开始说起:

3.1 MyRender

经过前面的介绍,我们应该知道现在需要做的事,就是写好Renderer的三个接口方法。
我们需要重新写一个类实现它,然后重写这三个方法。
class MyRender implements GLSurfaceView.Renderer
OK,笔已经拿好了。“铺好纸”是非常关键的一步。虽然我们说GLSurfaceView就是我们作图的纸,但是“铺”好这张纸,却也非常的重要。

 

下面我们重点讲下,这纸该怎么铺。OpenGL这张纸可不是一般的纸啊。最起码,它是三维的。然而实际的显示屏幕却是一个平面。所以这纸还不好“铺”。
首先,不一定整张纸都用来作画吧,Surface不一定全部都用上(当然,一般情况下我们是全部用上的)。那么我们需要计划好,哪部分区域用来作画。
gl.glViewport(0, 0, width, height);
根据这里的参数width和height,我们可以知道这个宽高需要在onSurfaceChanged里得知。
然后,这一步很关键。如何在平面上画三维坐标的点或图形呢?OpenGL有一个坐标系,如下图:


我们需要将这个坐标系和我们的GLSurfaceView里的Surface做一个映射关系。
glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glFrustumf(-400, 400, -240, 240, 0.3f, 100);

 

glMatrixMode(GL10.GL_PROJECTION); 是说我们现在改变的是坐标系与Surface的映射关系(投影矩阵)。

下一句 gl.glLoadIdentity(); 是将以前的改变都清掉(之前对投影矩阵的任何改变)。

glFrustumf (float left, float right, float bottom, float top, float zNear, float zFar) 这个函数非常Powerful。它实现了Surface和坐标系之间的映射关系。它是以透视投影的方式来进行映射的。
透视投影的意思见下图:


映射说明:
1、 前面一个矩形表示的是我们平面作图的范围。即Surface的作图范围。它有四条边,
我们将它们暂时命名edgeLeft、edgeRight、edgeTop、edgeBottom。
2、 glFrustumf 参数中的left、right、bottom、top指的是作图范围的四条edge在OpenGL x = -400的位置。同理top、right、bottom的值表示的是edgeRight、edgeTop、edgeBottom这几条边在坐标系中的位 置。
3、上面第二点定出了作图范围的x和y的范围。那么对于3D的OpenGL这张纸来说,我们还需要定出z的范围。首先,要想象一下,相机或者眼睛在坐标系的哪个位置?
默认的眼睛的位置在OpenGL坐标的原点处(0,0,0)。视线方向是平行于Z轴往里看。
near表示的是眼睛到作图平面的距离(绝对值哦!),far表示眼睛到最远可见处的平面范围。于是,默认情况下z的作图范围就是在-near到-far的位置。
4、好了,我们已经确定好x、y、z方向的作图范围了。回过头来,就可以发现,这张“立体”的纸,是一个方椎体切去头部的平截头体。我们所画的物体坐标落在这个区域范围内的部分将可以被我们看到(即在屏幕里画出来)。OK,至此,我们把纸终于铺好了。

 

glMatrixMode函数的选项(参数)有后面三种:GL_PROJECTION,GL_MODELVIEW和GL_TEXTURE;

  • GL_PROJECTION,是投影的意思,就是要对投影相关进行操作,也就是把物体投影到一个平面上,就像我们照相一样,把3维物体投到2维的平面上。这样,接下来的语句可以是跟透视相关的函数,比如glFrustum()或gluPerspective();
  • GL_MODELVIEW,是对模型视景的操作,接下来的语句描绘一个以模型为基础的适应,这样来设置参数,接下来用到的就是像gluLookAt()这样的函数;
  • GL_TEXTURE,就是对纹理相关进行操作;

顺便说下,OpenGL里面的操作,很多是基于对矩阵的操作的,比如位移,旋转,缩放,所以,
这里其实说的规范一点就是glMatrixMode是用来指定哪一个矩阵是当前矩阵,而它的参数代表要操作的目标:

  • GL_PROJECTION是对投影矩阵操作;
  • GL_MODELVIEW是对模型视景矩阵操作;
  • GL_TEXTURE是对纹理矩阵进行随后的操作;

 

 

3.2 画图之前先构图

Android 的 OpenGL 作图,不同于一般的作图,这点我们不得不感慨。它将数据和画完全分开来。例如,我们要画一个三角形。很显然,三角形有三个点。我们在画图之前首先要构图, 比如每个点在哪个地方。我们将这些数据放在一个一个数组缓冲区中,放好这些数据之后,再统一一起画出来。

下面,主要讲下,如何将顶点数据和颜色数据放入符合 Android OpenGL 的数组缓冲区中。

 

首 先我们要明白的是,OpenGL 是一个非常底层的画图接口,它所使用的缓冲区存储结构是和我们的 java 程序中不相同的。Java 是大端字节序(BigEdian),而 OpenGL 所需要的数据是小端字节序(LittleEdian)。所以,我们在将 Java 的缓冲区转化为 OpenGL 可用的缓冲区时需要作一些工作。
byte 数据缓冲区

不管我们的数据是整型的还是浮点型的,为了完成 BigEdian 到 LittleEdian 的转换,我们都首先需要一个 ByteBuffer。我们通过它来得到一个可以改变字节序的缓冲区。

ByteBuffer mBuffer = ByteBuffer.allocateDirect(pointCount*dimension*4);
mBuffer.order(ByteOrder.nativeOrder());

注意,我们应该用“allocateDirect”来分配内存空间,因为这样才可以 order 排序。最后我们就可以通过:
resultBuffer = mBuffer.asFloatBuffer();
resultBuffer = mBuffer.asIntBuffer();
将字节缓冲区转化为整型或者浮点型缓冲区了。

 

 

3.3 终于画图了!

有了前面所有的准备工作之后,有一个好消息可以告诉你,我们终于可以画图了!而且,有了前面这么多工作,真正画图的工作其实比较简单。
3.3.1、清理好你的纸

前面我们说过了,在画图之前,一定要把纸给清理干净喽:

gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

另外,之前我们在映射坐标系的时候,用了glMatrixMode(GL10.GL_PROJECTION);来指定改变的是投影矩阵。那么现在要画图了,所以我们需要指定改变的是“视图矩阵”:

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();

 

3.3.2、启用数组

我们的前面说过,画图的数据都放在数组缓冲区里,最后再一起传过来作画。那么我们首先要告诉 OpenGL,我们需要用到哪些数组。例如我们需要顶点数组和颜色数组:

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

 

3.3.3、指定数组数据

我们前面已经构造好了我们的数据缓冲区,floatBuffer(或 IntBuffer)。现在我们只需要将这个数据缓冲区和对应的功能绑定起来就好了:

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, VertexBuffer);
gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer);

这两句话分别绑定了顶点数据数组和颜色数据数组。其中第一个参数表示的是每个点有几个坐标。例如顶点,有 x、y、z值,所以是 3;而颜色是 r、g、b、a 值,所以是 4。

 

3.3.4、画图!

gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);

第一个参数指明了画图的类型——三角形(android 似乎只支持画三角形、点、线,不支持画多边形)。后面两个参数指明,从哪个顶点开始画,画多少个顶点。

 

OK!至此,我们的第一个三角形就画出来了,来看看效果吧。

版权声明:本文为博主原创文章,未经博主允许不得转载。

[转]Qualcomm AR SDK之替换模型(已解决模型贴图问题)

mikel阅读(948)

原文:http://blog.csdn.net/ggtaas/article/details/8507049

求从事移动应用开发以及基于移动终端的AR应用开发的朋友,一起做一些事!有兴趣可以给我私信,谢谢!

1、将模型转换为.obj格式,这个通过很多三维软件都可以实现,我用的是3Dmax。

2、将obj文件转换为.h文件

因为高通ARsdk识别的是这类的头文件。头文件中包含了这个模型的坐标数据。提取这些坐标数据通过OpenGL进行渲染就可以绘制出图形。这是后话。现 在介绍怎么将obj文件转为头文件。首先从网上下载ActivePerl和obj2opengl.pl。ActivePerl是一个perl的脚本解释 器。obj2opengl.pl就是使用perl语言写的一个脚本程序。顾名思义,就可以知道这个脚本程序的作用就是将obj->opengl能够 使用的头文件。这两个可以在下面这个链接下载:

http://download.csdn.net/detail/ggtaas/4998573

http://download.csdn.net/detail/ggtaas/4998714

ActivePerl解压之后直接安装,一般安装在c盘的perl文件夹下,然后将第二个压缩包解压得到的obj2opengl.pl文件拷贝进bin文 件夹下。这样这个工具就可以用了。很简单吧。下面将你需要转换的obj文件也拷贝进bin文件夹中,例如是banana.obj,再运行下面的dos命令 就可以了,见下图:

这样你会发现你的bin文件夹下就多生成了一个banana.h文件。

3、替换模型,这里我们以ImageTargets为例,将生成的banana.h文件替换程序中的teapot.h。

首先来简单看一下生成的头文件的内容:

  1. <span style=“font-family:SimSun;font-size:14px;”>/*                                                          
  2. created with obj2opengl.pl                                  
  3.                                                             
  4. source file    : .\banana.obj                               
  5. vertices       : 4032                                       
  6. faces          : 8056                                       
  7. normals        : 4032                                       
  8. texture coords : 4420                                       
  9.                                                             
  10.                                                             
  11. // include generated arrays                                 
  12. #import “.\banana.h”                                        
  13.                                                             
  14. // set input data to arrays                                 
  15. glVertexPointer(3, GL_FLOAT, 0, bananaVerts);               
  16. glNormalPointer(GL_FLOAT, 0, bananaNormals);                
  17. glTexCoordPointer(2, GL_FLOAT, 0, bananaTexCoords);         
  18.                                                             
  19. // draw data                                                
  20. glDrawArrays(GL_TRIANGLES, 0, bananaNumVerts);              
  21. */
  22. unsigned int bananaNumVerts = 24168;
  23. float bananaVerts [] = {
  24.   // f 231/242/231 132/142/132 131/141/131                  
  25.   0.172233487787643, -0.0717437751698985, 0.228589675538813,
  26.   0.176742968653347, -0.0680393472738536, 0.2284149434494,
  27.   0.167979223684599, -0.0670168837233226, 0.24286384937854,
  28.   // f 131/141/131 230/240/230 231/242/231                  
  29.   0.167979223684599, -0.0670168837233226, 0.24286384937854,
  30.   0.166391290343292, -0.0686544011752973, 0.241920432968569,
  31. ………………</span>

由于篇幅有限制截取这么一点。其实已经够了,下面的都是类似这样的坐标值。那主要有哪些呢?看下面这些就可以了,在程序里面需要用到的就是这些。opengl的基础知识,这里就不赘述了。

  1. <span style=“font-family:SimSun;font-size:14px;”>// include generated arrays                                 
  2. #import “.\banana.h”                                        
  3. // set input data to arrays                                 
  4. glVertexPointer(3, GL_FLOAT, 0, <span style=“color:#ff0000;”>bananaVerts</span>);
  5. glNormalPointer(GL_FLOAT, 0, <span style=“color:#ff0000;”>bananaNormals</span>);
  6. glTexCoordPointer(2, GL_FLOAT, 0, bananaTexCoords);
  7.                                   <span style=“color:#ff0000;”>               </span>
  8. // draw data                                                
  9. glDrawArrays(GL_TRIANGLES, 0, bananaNumVerts);</span>

切入正题:

准备工作:把banana.h拷贝到jni文件夹下。把banana.jpg拷贝到assets文件下。在ImageTargets文件夹打开Jni文件夹。打开ImageTargets.cpp 

1.#include”Teapot.h” ->#include”banana.h”

2.

glTexCoordPointer(2, GL_FLOAT, 0, (const GLvoid*)&teapotTexCoords[0]);

       glVertexPointer(3, GL_FLOAT, 0, (const GLvoid*) &teapotVertices[0]);

       glNormalPointer(GL_FLOAT, 0, (const GLvoid*) &teapotNormals[0]);

       glDrawElements(GL_TRIANGLES, NUM_TEAPOT_OBJECT_INDEX, GL_UNSIGNED_SHORT,

                      (const GLvoid*)&teapotIndices[0]);

改为:

glTexCoordPointer(2, GL_FLOAT, 0, (constGLvoid*) &bananaTexCoords[0]);

       glVertexPointer(3, GL_FLOAT, 0, (const GLvoid*) & bananaVerts [0]);

       glNormalPointer(GL_FLOAT, 0, (const GLvoid*) &bananaNormals[0]);

       glDrawArrays(GL_TRIANGLES, 0, bananaNumVerts);

3.

glVertexAttribPointer(vertexHandle, 3, GL_FLOAT, GL_FALSE, 0, (constGLvoid*) &teapotVertices[0]);

glVertexAttribPointer(normalHandle, 3, GL_FLOAT, GL_FALSE, 0, (constGLvoid*) &teapotNormals[0]);

glVertexAttribPointer(textureCoordHandle, 2, GL_FLOAT, GL_FALSE, 0, (constGLvoid*) &teapotTexCoords[0]);

改为:

glVertexAttribPointer(vertexHandle,3, GL_FLOAT, GL_FALSE, 0, (const GLvoid*) &bananaVerts[0]);

glVertexAttribPointer(normalHandle,3, GL_FLOAT, GL_FALSE, 0, (const GLvoid*) &bananaNormals[0]);

glVertexAttribPointer(textureCoordHandle,2, GL_FLOAT, GL_FALSE, 0, (const GLvoid*) &bananaTexCoords[0]);

4.

glDrawElements(GL_TRIANGLES,NUM_TEAPOT_OBJECT_INDEX, GL_UNSIGNED_SHORT, (const GLvoid*)&teapotIndices[0]);

改为:

glDrawArrays(GL_TRIANGLES, 0,bananaNumVerts);

5. 设置 kObjectScale= 120.f;

为了使模型的大小适当,否则模型太小了。

6.打开src文件夹,com文件夹,qualcomm文件夹,打开 ImageTargets.java。在private void loadTextures()添 加: mTextures.add(Texture.loadTextureFromApk(“banana.jpg”,getAssets())); 如 下所示:

private voidloadTextures()

{

mTextures.add(Texture.loadTextureFromApk(“banana.jpg”,getAssets()));

//mTextures.add(Texture.loadTextureFromApk(“TextureTeapotBrass.png”, getAssets()));

//mTextures.add(Texture.loadTextureFromApk(“TextureTeapotBlue.png”, getAssets()));

//mTextures.add(Texture.loadTextureFromApk(“TextureTeapotRed.png”, getAssets()));

}

7.在cygwin一直cd到imagetargets目录后NDK-build可得结果。

最后效果图如下:

更新:最近在一个美女程序猿的帮助下解决了模型贴图的问题,现在简单说下具体流程,并附上官方方法:

高通采用的是UV贴图,正常展UV即可,但是坐标有问题,贴上去的就上面的图一样。下面是官方的解决方法:

就是将贴图垂直翻转,注意不是旋转。你可以使用PS,或者画图工具

最后的效果下图所示:

OpenGL快速渲染函数glDrawElements使用说明

mikel阅读(3093)

OpenGL 基本的绘图函数例如glVertex、glNormal等在调试模式下运行时,如果模型的顶点数或者三角面数过大(比如超过一万时),则程序运行速度会非 常慢,根本就无法进行正常的调试。为此查阅了相关资料,找到glArrayElement、glDrawElements这两个个函数。这两个函数都能通 过少数几条语句的调用实现大量数据的绘制,从而节省了函数调用的资源占用。

glArrayElement 函数用法简单直接,只要把所有的顶点、法向量等数据,按照三角形的顺序准备好,就可以直接渲染,但缺点是不支持顶点索引,所以内存占用比较大。举例来说, 如果一个网格有100个顶点,一般大约会有200个三角面,如果使用glArrayElement就需要存储200×3=600个顶点的数据,相比原有的 数据多了5倍。如果是已经条带化的数据,这种冗余数据多的可能就不只5倍了。权衡之下还是决定使用glDrawElements函数。
glDrawElements 函数支持顶点数据列表,更为方便的是它还支持顶点索引,所以就成为了快速渲染的首选。可是我在具体使用过程中,却总是没有任何顶点数据被绘制出来,查了相 关资料,既不是数据错误,也不是硬件不支持,只好暂时搁置一边了。一个偶然的机会,我看到某段示例代码,发现glDrawElements中索引数据类型 的参数是GL_UNSIGNED_INT,而我之前用的参数都是GL_INT(因为算法的需要,有时需要存储负数索引,以表示正反方向的不同)。抱着试试 看的想法,我把参数改成了GL_UNSIGNED_INT,结果竟然绘制出图像来了。赶紧查阅了MSDN,对glDrawElements的说明如下:
void glDrawElements(
GLenum
mode,
  GLsizei count,
  GLenum type,
  const GLvoid *indices
);

Parameters

mode
The kind of primitives to render. It can assume one of the following symbolic values: GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP, GL_QUADS, and GL_POLYGON.
count
The number of elements to be rendered.
type
The type of the values in indices. Must be one of GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT.
indices
A pointer to the location where the indices are stored.
注意其中对type参数的说明,索引数据的类型必须是 GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT、GL_UNSIGNED_INT之一。这一来就解释了为什么 glDrawElements没有绘制出任何元素的问题所在了。可气的是OpenGL竟然没有对这个问题给出任何的提示,不知到是不是微软有意如此淡化 OpenGL的作用,如果改天有机会可以用linux系统下的编译器作作测试。
最后,附上我在渲染时调用的部分代码。
//顶点
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, (float*)m_vDataCoord[0]);
// 法向量
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, 0, (float*)m_vDataNormal[0]);
//顶点颜色
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(3, GL_FLOAT, 0, (float*)m_vDataColor[0]);
//纹理坐标
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, (float*)m_vDataUv[0]);
glDrawElements(GL_TRIANGLES, (GLsizei)m_vIndexCoord.size()*3, GL_UNSIGNED_INT, (GLvoid*)m_vIndexCoord[0]);

OpenGL OBJ模型加载.

mikel阅读(1341)

复制代码
  1 type ArrayList<'T> = System.Collections.Generic.List<'T>
  2 
  3 type ObjMaterialItem() =
  4     member val Name = "" with get,set
  5     member val Ambient = [|0.f;0.f;0.f;0.f|] with get,set
  6     member val Diffuse = [|0.f;0.f;0.f;0.f|] with get,set
  7     member val Specular = [|0.f;0.f;0.f;0.f|] with get,set
  8     member val Shiness = 0.f with get,set
  9     member val DiffuseMap = "" with get,set
 10     member val SpecularMap = "" with get,set
 11     member val BumpMap = "" with get,set
 12     member val DiffuseID = 0 with get,set
 13     member val SpecularID = 0 with get,set
 14     member val BumpID = 0 with get,set
 15 
 16 type ObjMaterial() =
 17     member val Name = "" with get,set
 18     member val Items = new ArrayList<ObjMaterialItem>() with get,set
 19     member val currentItem = new ObjMaterialItem() with get,set
 20 
 21 type VertexAttribute() =
 22     let strToInt str =
 23         let (ok,f) = System.Int32.TryParse(str)
 24         if ok then f else -1
 25     member val Position= Vector3.Zero with get,set
 26     member val Texcoord=Vector2.Zero with get,set
 27     member val Normal= Vector3.Zero with get,set
 28     member val PositionIndex = -1 with get,set
 29     member val TexcoordIndex = -1 with get,set
 30     member val NormalIndex = -1 with get,set
 31     //各个值的索引信息
 32     member this.SetValue(line:string) =
 33         let ls = line.Split('/')
 34         match ls.Length with
 35         | 1 -> 
 36             this.PositionIndex <- strToInt ls.[0]
 37         | 2 -> 
 38             this.PositionIndex <- strToInt ls.[0]
 39             this.TexcoordIndex <- strToInt ls.[1]
 40         | 3 -> 
 41             this.PositionIndex <- strToInt ls.[0]
 42             this.NormalIndex <- strToInt ls.[2]
 43             if not (ls.[1] = "" || ls.[1] = null) then  
 44                 this.TexcoordIndex <- strToInt ls.[1]
 45         | _ -> ()
 46     //组织格式用T2fV3f/N3fV3f/T2fN3fV3f/V3f成float32[]
 47     member this.PointArray 
 48         with get() =
 49             let mutable ps = Array.create 0 0.0f
 50             if this.TexcoordIndex > 0 then  ps <- Array.append ps [|this.Texcoord.X;1.0f - this.Texcoord.Y|]
 51             if this.NormalIndex > 0 then  ps <- Array.append ps [|this.Normal.X;this.Normal.Y;this.Normal.Z|]
 52             if this.PositionIndex > 0 then  ps <- Array.append ps [|this.Position.X;this.Position.Y;this.Position.Z|]
 53             ps
 54 
 55 type ObjFace() =
 56     let mutable vectexs = [||] : VertexAttribute array 
 57     //每个面的顶点,一个是三角形,如果是矩形,为了兼容性,应该化为成二个三角形.
 58     member this.Vectexs 
 59         with get() =
 60             let mutable result = vectexs.[0..]
 61             if vectexs.Length = 4 then
 62                 let newvxs = [|vectexs.[0];vectexs.[2]|]
 63                 result <- Array.append result newvxs
 64             result
 65     //在读取文件时,得到当前面包含的顶点索引信息.(此时对应顶点只有索引,没有真实数据)
 66     member this.AddVectex (line:string) =
 67         let ls = line.TrimEnd(' ').Split(' ')
 68         let vs =
 69             ls |> Array.map(fun p -> 
 70                 let va = new VertexAttribute()
 71                 va.SetValue(p)
 72                 va) 
 73         vectexs <- vs
 74     member this.VertexCount with get() = this.Vectexs.Length
 75 
 76 type ObjGroup() =
 77     //得到数组里所有面的对应所有顶点属性
 78     let mutable vectexs = new ArrayList<VertexAttribute>()
 79     let mutable points = Array2D.create 0 0 0.f
 80     let mutable vbo,ebo = 0,0
 81     member val Faces = new ArrayList<ObjFace>() with get,set
 82     member val Mtllib = "" with get,set
 83     member val Usemtl = "" with get,set
 84     member val Name = "" with get,set
 85     member val Material = new ObjMaterialItem() with get,set
 86     member val IsHaveMaterial = false with get,set
 87     member val Path = "" with get,set
 88     member this.VBO with get() = vbo
 89     member this.EBO with get() = ebo
 90     //读取文件,读取当前group里的面的信息,并且会在读面信息时读取到这个面所有顶点索引
 91     member this.AddFace (line:string) =
 92         let face = new ObjFace()
 93         face.AddVectex(line)
 94         this.Faces.Add(face)
 95         vectexs.AddRange(face.Vectexs)
 96     //组织一个规则二维数组,一维表示每面上的每个顶点,二维表示每个顶点是如何组织,包含法向量,纹理坐标不
 97     member this.DataArray 
 98         with get() =  
 99             if points.Length < 1 then
100                 let length1 = vectexs.Count
101                 if length1 > 0 then
102                     let length2 = vectexs.[0].PointArray.Length
103                     if length2 > 0 then
104                         points <- Array2D.init length1 length2 (fun i j -> vectexs.[i].PointArray.[j])
105             points
106     member this.CreateVBO() = 
107         if this.ElementLength > 0 then
108             vbo <- GL.GenBuffers(1)
109             GL.BindBuffer(BufferTarget.ArrayBuffer,vbo)
110             GL.BufferData(BufferTarget.ArrayBuffer,IntPtr (4 *this.ElementLength*this.VectorLength ),this.DataArray,BufferUsageHint.StaticDraw)
111             let len = this.ElementLength - 1
112             let eboData = [|0..len|]
113             ebo <- GL.GenBuffers(1)
114             GL.BindBuffer(BufferTarget.ElementArrayBuffer,ebo)
115             GL.BufferData(BufferTarget.ElementArrayBuffer,IntPtr (4 * this.ElementLength),eboData,BufferUsageHint.StaticDraw)
116         if this.IsHaveMaterial then
117             let kdPath = Path.Combine(this.Path,this.Material.DiffuseMap)
118             if File.Exists kdPath then
119                 this.Material.DiffuseID <- TexTure.Load(kdPath)
120     member this.DrawVBO() = 
121         if this.VBO >0 && this.EBO >0 then
122             GL.BindBuffer(BufferTarget.ArrayBuffer,this.VBO)
123             GL.BindBuffer(BufferTarget.ElementArrayBuffer,this.EBO)
124             if this.IsHaveMaterial then
125                 GL.Enable(EnableCap.Texture2D)
126                 GL.BindTexture(TextureTarget.Texture2D,this.Material.DiffuseID)
127             GL.InterleavedArrays(this.InterFormat,0,IntPtr.Zero)
128             GL.DrawElements(BeginMode.Triangles,this.ElementLength,DrawElementsType.UnsignedInt,IntPtr.Zero)
129             GL.Disable(EnableCap.Texture2D)
130     //多少个顶点
131     member this.ElementLength with get() = Array2D.length1 this.DataArray
132     //顶点组织形式长度T2fV3f/N3fV3f/T2fN3fV3f/V3f
133     member this.VectorLength with get() = Array2D.length2 this.DataArray
134     //顶点组织形式
135     member this.InterFormat 
136         with get()= 
137             let mutable result = InterleavedArrayFormat.T2fN3fV3f
138             if this.VectorLength = 3 then  result <- InterleavedArrayFormat.V3f
139             if this.VectorLength = 5 then  result <- InterleavedArrayFormat.T2fV3f
140             if this.VectorLength = 6 then  result <- InterleavedArrayFormat.N3fV3f
141             result
142 
143 type ObjModel(fileName:string) =      
144     let mutable groupName = "default"
145     let mutable groups = [] : ObjGroup list
146     let addGroup group = groups <- (group :: groups)   
147     //得到每行数组去掉标识符后的数据如 v 1.0 2.0 3.0 -> 1.0 2.0 3.0     
148     let getLineValue (line:string) =
149         let fs = line.Split(' ')
150         let len = fs.Length - 1
151         if fs.Length > 1 then (fs.[1..len] |> Array.filter (fun p -> p <> null && p<> " " && p <> ""))
152         else [|line|]
153     //数组转化成float32
154     let strToFloat str =
155       let (ok,f) = System.Single.TryParse(str)
156       if ok then f else System.Single.NaN
157     let mutable group = ObjGroup()
158     let mutable mtllib = ""
159     member val Positions = new ArrayList<Vector3>() with get,set
160     member val Normals = new ArrayList<Vector3>() with get,set
161     member val Texcoords = new ArrayList<Vector2>() with get,set
162     member val Materials = new ArrayList<ObjMaterial>() with get,set
163     member this.Path 
164         with get() = System.IO.Path.GetDirectoryName(fileName)
165     member this.GetLineFloatArray (line:string) =
166         let fs = getLineValue(line)
167         fs |> Array.map (fun p -> strToFloat p) 
168     member this.GetLineValue (line:string,?sep) =
169         let dsep = defaultArg sep " "
170         let fs = getLineValue(line)
171         String.concat dsep fs          
172     member this.CurrentGroup 
173         with get() =
174             let bExist = groups |> List.exists(fun p -> p.Name = groupName)
175             if not bExist then 
176                 let objGroup = new ObjGroup()
177                 objGroup.Name <- groupName
178                 objGroup.Mtllib <-  mtllib
179                 addGroup objGroup
180             group <- groups |> List.find(fun p -> p.Name = groupName)
181             group
182     member this.Groups
183         with get() =
184             groups
185     //主要有二步,首先读取文件信息,然后把顶点,法线,纹理坐标根据索引来赋值
186     member this.LoadObjModel(?bCreateVBO) =
187         let bCreate = defaultArg bCreateVBO false
188         let file = new StreamReader(fileName)
189         let mutable beforeFace = false
190         let (|StartsWith|) suffix (s:string) = s.TrimStart(' ','\t').StartsWith(suffix,StringComparison.OrdinalIgnoreCase)
191         //首先读取文件信息,此时顶点只有索引信息.
192         while not file.EndOfStream  do
193             let str = file.ReadLine()
194             match str with
195             | StartsWith "mtllib " true  ->
196                 mtllib <- this.GetLineValue(str)
197                 //#region 读纹理
198                 let material = new ObjMaterial()
199                 material.Name <- mtllib
200                 let mtlFile = new StreamReader(Path.Combine(this.Path,mtllib))
201                 while not mtlFile.EndOfStream do
202                     let str = mtlFile.ReadLine()
203                     match str with
204                     | null -> ()
205                     | StartsWith "newmtl " true ->
206                         material.currentItem <- new ObjMaterialItem()
207                         material.currentItem.Name <- this.GetLineValue(str)
208                         material.Items.Add(material.currentItem)
209                     | StartsWith "ka " true -> material.currentItem.Ambient <- this.GetLineFloatArray(str)  
210                     | StartsWith "kd " true -> material.currentItem.Diffuse <- this.GetLineFloatArray(str)    
211                     | StartsWith "ks " true -> material.currentItem.Specular <- this.GetLineFloatArray(str) 
212                     | StartsWith "map_Kd " true -> material.currentItem.DiffuseMap <- this.GetLineValue(str)    
213                     | StartsWith "map_Ks " true -> material.currentItem.SpecularMap <- this.GetLineValue(str)   
214                     | StartsWith "map_bump " true -> material.currentItem.BumpMap <- this.GetLineValue(str)  
215                     | StartsWith "Ns " true ->
216                          let ns = this.GetLineFloatArray(str).[0]  
217                          material.currentItem.Shiness <- ns * 0.128f                                     
218                     | _ -> ()
219                 mtlFile.Close()
220                 this.Materials.Add(material)
221                 //#endregion
222             | null -> ()
223             | StartsWith "usemtl " true -> this.CurrentGroup.Usemtl <- this.GetLineValue(str)
224             | StartsWith "g " true ->
225                 groupName <- this.GetLineValue(str)
226                 beforeFace <- false
227             | StartsWith "vn " true ->
228                 let fs = this.GetLineFloatArray(str)
229                 this.Normals.Add(Vector3(fs.[0],fs.[1],fs.[2]))
230             | StartsWith "vt " true ->
231                 let fs = this.GetLineFloatArray(str)
232                 this.Texcoords.Add(Vector2(fs.[0],fs.[1]))
233             | StartsWith "v " true ->
234                 let fs = this.GetLineFloatArray(str)
235                 this.Positions.Add(Vector3(fs.[0],fs.[1],fs.[2]))
236             | StartsWith "f " true ->
237                 if beforeFace then
238                     group.AddFace(this.GetLineValue(str))
239                 else 
240                     this.CurrentGroup.AddFace(this.GetLineValue(str))
241                 beforeFace <- true
242             | _ -> printfn "%s" ("---------"+str)     
243         file.Close()
244         //根据索引信息来给对应的顶点,法线,纹理坐标赋值
245         groups |>List.iter (fun p -> 
246             p.Faces.ForEach(fun face -> 
247                 face.Vectexs |> Array.iter(fun vect ->
248                     if vect.PositionIndex > 0 then vect.Position <-this.Positions.[vect.PositionIndex-1]
249                     if vect.TexcoordIndex > 0 then  vect.Texcoord <- this.Texcoords.[vect.TexcoordIndex-1]
250                     if vect.NormalIndex > 0 then  vect.Normal <- this.Normals.[vect.NormalIndex-1]                      
251                     )
252                 )
253             let mater = this.Materials.Find(fun m -> m.Name = p.Mtllib)
254             if box(mater) <> null then
255                 let mitem = mater.Items.Find(fun i -> i.Name = p.Usemtl)
256                 if box(mitem) <> null then 
257                     p.Material <- mitem
258                     p.Path <- this.Path
259                     p.IsHaveMaterial <- true
260             )
261         //释放空间
262         this.Positions.Clear()
263         this.Normals.Clear()
264         this.Texcoords.Clear()
265         if bCreate then this.CreateVbo()
266     //生成VBO信息
267     member this.CreateVbo() =
268         this.Groups |> List.iter (fun p -> p.CreateVBO())
269     member this.DrawVbo() =
270         this.Groups |> List.iter (fun p -> p.DrawVBO())
复制代码

其中ObjMode主要是加载文件,主要方法在LoadObjModel里,这个方法主要有二个主要作用.

一是在file与file.close这节,主要是读取OBJ文件里所有的信息,当读到mtllib时,会尝试打开关联的材质文件,然后读取材 质里的信息,根据每读一个newmtl,来添加一个ObjMaterialItem.然后就是读到g就会生成一个group,然后读到usemtl与 f(面)时,分别为前面生成的group,来分别对应group当前所用材质以及添加f(面)信息到group中,f(面)一般包含3个顶点(三角形)与 四个顶点(方形)的v/vt/vn(可能只包含v,也可能全包含)的顶点索引信息.而f中vn(法向量),v(顶点),vt(纹理向量)中索引指向全局的 对应值,就是说,当f中索引v可能已经到100了,而这时,我们读到的顶点数据可能只有10个.

Face中通过读到的如下结构,v,v/vt,v//vn,v/vt/vn这四种结构,然后通过AddVectex里分别解析成对应的 VertexAttribute结构.在VertexAttribute中,记住属性PointArray,这个把上面的v,v/vt,v//vn,v /vt/vn这四种结构按照顺序会组装成一个float[],里的数据分别对应Opengl中的InterleavedArrayFormat中的V3f,T2fV3f,N3fV3f,T2N3fV3f.与后面在Group里组装VBO要用到.(前面Opengl绘制我们的小屋(一)球体,立方体绘制有讲解)其类还有一个作用,如果检查到4个顶点,则分成六个顶点,索引如果为1,2,3,4,分成1,2,3,4,1,3,意思就是一个方形分成二个三角形,保持逆时针顺序不变,一是为了只生成一个VBO,二是为了兼容性.

二是把对应的VertexAttribute里的v/vt/vn的索引,变成ObjMode里所读到的对应v/vt/vn里的真实数据.为什么 分成二步做,上面其实有说,f中的v/vt/vn的索引值是全局的.这个索引可能大于你读到的相关索引数据.并且把对应group里用到的材质关联上去.

上面的完成后,下面的才能开始,VertexAttribute中的PointArray就能组装到对应值.Group里的DataArray 根据其中的Face中的VertexAttribute中的PointArray来组装数据生成VBO,PointArray的组装是一个规则二维数组 [x,y],x等于Group里的顶点个数,y就是V3f/T2fV3f/N3fV3f/T2fN3fV3f所对应的数据长度,分别是3,5,6,8.创 建VBO与显示VBO也是group来完成的,在OBJ里,就是根据每组数据来绘制显示的数据.

创建VBO与绘制的代码因为有了上面数据的组装,所以显示的很简单,其中还是注意 GL.InterleavedArrays(this.InterFormat,0,IntPtr.Zero)这句使用,这句能帮我们节省很多代码,会自 动根据InterleavedArrayFormat来给我们关闭打开相应状态,自动给对应顶点结构如 VectorPointer,TexcoordPointer,NormalPointer赋值.

在材质方面,我只对我们最平常的贴图map_Kd做了处理,还有对应的是法线纹理会在后面说明.

在网上下载了一些OBJ模型,然后用这个来加载,开始会发现纹理是上下反的,在网上查找了下,有种说法,纹理是用窗口坐标系,而Opengl是用的笛卡尔坐标系.对这种说法我表示怀疑,但是又不知从何解释,不过把纹理坐标经过y经过变换1-y后表示确实显示正常.

通过这次OBJ模型的加载,也解决了长久以来我心中的一个疑问,我以前老是在想,如果一个顶点,有几个纹理坐标或者几个法向量,那是如何用VBO的,原来就是通过最简单,最粗暴的方法复制几分数据来处理的.

代码全是通过F#写的,以前也没说F#的东东,因为我自己也是在摸索,通过这个模型加载,我发现有些东东可以说下.大家可以发现,在F# 里,ObjGroup里的顶点数组,法线数组,面数组相关数据量大的全是用的ArrayList<‘T>这个结构,这个我们可以看到定义 type ArrayList<‘T> = System.Collections.Generic.List<‘T>,就是C#的List<T>,大家可能会问这和F#中 的List,Array有什么不同?以及为什么不用这二个数据结构,下面是我的实践.

从这次来看,F#的array为了函数式不变性,在需要一点一点添加上万元素时,很坑爹.因为每次添加一个元素,就相当于重新生成一个数组.而 F#中的List也不同于C#中的List(本质是个数组),用下标找值是O(n).当时打开一个3M的文件,加载需要我20S,主要是因为 ReadObjFile里读ObjGroup里.我用表示多面元素用的F#中的array,导致每添加一个元素就需要重新生成.然后根据元素对应索引找到 对应的值,这个都需要十秒左右,主要是因为我在ReadObjFile后,读到的点,法线等数据全是用F#的List保存,而在后面根据下标来得到对应的 数据是,这就是个大杯具.

如果要求又能快速添加,又能快速根据下标找元素,应该还是用到C#中包装数组的List结构.上面提到的一些操作换成C#中的list,总共原来30S的时间到现在不到2S的时间,不能不说,坑爹啊.

不过我能肯定的是,在objgroup中的DataArray,这个是用的F#的Array2D,里面数据是超大量的.但是这个不会有前面说的问题,因为在组织这个Array2D时,我们已知其中这个二维数组的长度,和各个对应元素值.

下面给出效果图:

image

下面给出附件:源代码 可执行文件

和前面一样,其中EDSF上下左右移动,鼠标右键加移动鼠标控制方向,空格上升,空格在SHIFT下降。

Unity3d导出Android的apk文件时相关问题的解决办法

mikel阅读(1173)

今天上午着手将一个unity3d开发的小游戏build到Android手机上运行,结果遇到了不少问题。

首先遇到的第一个问题是在build到一半的时候,弹出如下报错:

Error building Player: UnityException: No platforms found

Android SDK does not include any platforms! Did you run Android SDK setup to install the platform(s)?
Minimum platform required for build is Android 4.0 (API level 14)

问题解决办法:用android的sdk manager更新android的sdk

更新完成后还是报错,打开sdk目录C:\sdk\platforms,发现文件夹的名字是android-4.2,将文件夹名字改成android-17该问题解决

继续发布,接着产生了另外一个问题

Error building Player: UnityException: Couldn’t find Android device
No Android devices found. If you are sure that device is attached then it might be USB driver problem, for details please check Android SDK Setup section in Unity Manual.

问题解决办法:启动android虚拟机 AVD Manager或者连接上手机调试设备

解决了上面的两个小问题后,apk安装包终于生成了。

接下来开始安装生成的apk到android虚拟机运行,安装时出现了如下报错:

android Installation error: INSTALL_FAILED_CONTAINER_ERROR

解决办法:

在untiy3d的PlayerSetting中,将Install Location设置由原来的PreferExternal改成Automatic。

Image[3]

终于安装好了apk文件,然后在android的模拟器中运行时,却出现了如下错误

Image(1)[3]

解决办法:

在模拟器的参数设置中选中Use Host GPU这个选项

Image(2)[3]

终于,游戏在模拟器下运行起来了,是不是很酷呢?

Image(3)[3]

[转载]OpenGL_Qt学习笔记之_03(平面图形的着色和旋转) - tornadomeet - 博客园

mikel阅读(912)

来源: [转载]OpenGL_Qt学习笔记之_03(平面图形的着色和旋转) – tornadomeet – 博客园

  在这一节中主要简单介绍下怎样给平面几何着色,以及怎样让绘制出来的几何图形旋转起来。在上一节OpenGL_Qt学习笔记之_02(绘制简单平面几何图形) 中已经介绍了如何利用opengl画一些简单的图像,那么这一节就在上面的基础上给它着色,且让他旋转。

 

实验基础

首先来看着色,其实着色在上篇文章中已经用过,使用的是函数glColor3f()。一旦我们使用这个函数着色后且不更改颜色,则后面所绘的图形都是这 个颜色了。我们给多边形着色时,表面上看都是给它的顶点着色,其实因为我们在initializeGL()函数中已经设置过 glShapeModel(GL_SMOOTH),即颜色阴影是平滑模式。所以当我们给顶点设置后颜色后,其所构成的多边形内部就会按照一定规律自动填充 颜色,且是平滑过渡。

下面来看看旋转,首先是要了解opengl的一个三维空间,如下图所示:

 

如果是按照X轴旋转的话,就是图中的指向视点那个轴了,NeHe教程中把沿x轴旋转比作成一台台钜,锯片中心的轴从左至右摆放,尖利的锯齿绕着X轴狂 转,看起来要么向上转,要么向下转。把Y轴旋转比喻成巨大的龙卷风过程,龙卷风的中心从地面指向天空,垃圾和碎片围着Y轴从左向右或是从右向左狂转不止。 把z轴旋转比喻成从正前方看着一台工作着的风扇。风扇的叶片绕着Z轴顺时针或逆时针狂转。这个比喻很形象。

其实大家凭想象都能想出来是怎么转的,这里不再罗嗦了。Opengl中关于旋转使用的是函数:

glRotatef( Angle, Xvector, Yvector, Zvector )

Angle 通常是个变量代表对象转过的角度。 Xvector , Yvector 和 Zvector 三个参数则共同决定旋转轴的方向。比如(1,0,0)所描述的矢量经过X坐标轴的1个单位处并且方向向右。(-1,0,0)所描述的矢量经过X坐标轴的1 个单位处,但方向向左。

 

实验说明

我在给圆着色时,每次给一个顶点赋值一种颜色,因为画圆是用三角形来逼近的,所以有很多顶点,且我这里给的顶点的颜色是随机赋值的,随机函数用的是Qt中自带的qrand(),

qrand()%10表示产生0~9之间的整数,因此(GLfloat)(qrand()%10)/10则表示0.0~0.9之间的小数。

在我这次试验中同样要注意,因为屏幕大小本身就2个单位的长和宽,所以设置旋转的轴时取值要适当,否则很难观察到旋转的效果。

 

实验结果

着色结果:

 

旋转时截取的一张图效果:

旋转的时候为了看到效果,可以不断改变窗口的大小,让GLWidget这个类不断执行paintGL()函数,实现重绘来看旋转效果。

 

实验主要部分代码及解释(附录有工程code下载链接地址)。

glwidget.cpp:

复制代码
#include "glwidget.h"
#include "ui_glwidget.h"

#include <QtGui>
#include <QtCore>
#include <QtOpenGL>

#define GL_PI 3.1415926
#define GL_RADIUX  0.2f

GLWidget::GLWidget(QGLWidget *parent) :
    QGLWidget(parent),
    ui(new Ui::GLWidget)
{
  //  setWindowTitle("The Opengl for Qt Framework");
    ui->setupUi(this);
    fullscreen = false;
    triangle_rotate = 0.0;
    quads_rotate = 0.0;
    circle_rotate = 0.0;
}

//这是对虚函数,这里是重写该函数
void GLWidget::initializeGL()
{
    setGeometry(300, 150, 500, 500);//设置窗口初始位置和大小
    glShadeModel(GL_SMOOTH);//设置阴影平滑模式
    glClearColor(0.0, 0.0, 0.0, 0.0);//改变窗口的背景颜色,不过我这里貌似设置后并没有什么效果
    glClearDepth(1.0);//设置深度缓存
    glEnable(GL_DEPTH_TEST);//允许深度测试
    glDepthFunc(GL_LEQUAL);//设置深度测试类型
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);//进行透视校正
}

void GLWidget::paintGL()
{

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //glClear()函数在这里就是对initializeGL()函数
                                                        //中设置的颜色和缓存深度等起作用
    glLoadIdentity();//重置当前的模型观察矩阵,该句执行完后,将焦点移动到了屏幕的中心

    /*下面几句代码是用来画三角形的,以glBegin()开始,glEnd()结束;glVertex3f为给定一个3维的顶点,坐标值为浮点型*/
    glTranslatef(-0.3, 0.3, -0.6);
    glRotatef(triangle_rotate, 0.2, 0.2, 0.0);//设置旋转的角度,这里为了观察,同时沿x轴和y轴做了旋转
    glBegin(GL_TRIANGLES);//GL_TRIANGLES代表为画三角形
    glVertex3f(0.0f, 0.2f, 0.0f);//上顶点坐标
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex3f(-0.2f, -0.2f, 0.0f);//左下角坐标
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex3f(0.2f, -0.2f, 0.0f);//右下角坐标
    glColor3f(0.0f, 0.0f, 1.0f);
    glEnd();//结束画完
    triangle_rotate += 0.05;  //为了看到动态效果,这里每次刷新后旋转的角度都加0.05

    glLoadIdentity();//重新焦点定位,同样是屏幕的中心
    glTranslatef(0.3f,0.3f,0.0f); // 向x轴正方向移动0.3个单位
    glRotatef(quads_rotate, 0.0, 0.2, 0.2);
    glColor3f(0.0f,1.0f,0.0f);//颜色设置放在这个地方,对下面的顶点设置都是有效的

    /*下面开始绘制四边形*/
    glBegin(GL_QUADS);
    glVertex3f(-0.2f, 0.2f, 0.0f); // 左上顶点
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex3f( 0.2f, 0.2f, 0.0f); // 右上顶点
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex3f( 0.2f, -0.2f, 0.0f); // 右下顶点
    glColor3f(0.0f, 0.0f, 1.0f);
    glVertex3f(-0.2f, -0.2f, 0.0f); // 左下顶点
    glColor3f(1.0f, 0.2f, 0.8f);
    glEnd(); // 四边形绘制结束
    quads_rotate += 0.05;

    glLoadIdentity();
    glTranslatef(0.0f, -0.3f, 0.0f);
    glColor3f(0.0f, 0.0f, 1.0f);
    glRotatef(circle_rotate, 0.2, 0.0, 0.2);

    /*这里用连续的三角形面积来逼近圆的面积实现圆周的画法*/
    GLint circle_points = 100, i = 0;
    glBegin(GL_TRIANGLE_FAN);
    for(int i = 0; i < circle_points; i++ )
    {
        double angle = 2*GL_PI*i/circle_points;
        //qrand()%10为产生0~10的整数,这里的颜色是随机产生的
        glColor3f((GLfloat)(qrand()%10)/10, (GLfloat)(qrand()%10)/10, (GLfloat)(qrand()%10)/10);
        glVertex3f(GL_RADIUX*cos(angle), GL_RADIUX*sin(angle), 0);
    }
    glEnd();
    circle_rotate += 0.05;

}

//该程序是设置opengl场景透视图,程序中至少被执行一次(程序启动时).
void GLWidget::resizeGL(int width, int height)
{
    if(0 == height)
        height = 1;//防止一条边为0
    glViewport(0, 0, (GLint)width, (GLint)height);//重置当前视口,本身不是重置窗口的,只不过是这里被Qt给封装好了
    glMatrixMode(GL_PROJECTION);//选择投影矩阵
    glLoadIdentity();//重置选择好的投影矩阵
   // gluPerspective(45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0);//建立透视投影矩阵
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();


}
void GLWidget::keyPressEvent(QKeyEvent *e)
{
    switch(e->key())
    {
        //F1键为全屏和普通屏显示切换键
        case Qt::Key_F1:
            fullscreen = !fullscreen;
            if(fullscreen)
                showFullScreen();
            else
            {
                setGeometry(300, 150, 500, 500);
                showNormal();
            }
            updateGL();
            break;
        //Ese为退出程序键
        case Qt::Key_Escape:
            close();
    }
}

GLWidget::~GLWidget()
{
    delete ui;
}
复制代码

 

 

总结:opengl中实现平面图形的着色和旋转,实现起来比较简单,只需要对对应的函数赋即可。

 

   参考资料:

   http://nehe.gamedev.net/

   http://www.owlei.com/DancingWind/

   http://blog.csdn.net/qp120291570/article/details/7853513

 

     附录:

实验工程code下载

 

 

 

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。 (新浪微博:tornadomeet,欢迎交流!)

[转载]OpenGL_Qt学习笔记之_04(3D图形的绘制和旋转) - tornadomeet - 博客园

mikel阅读(774)

来源: [转载]OpenGL_Qt学习笔记之_04(3D图形的绘制和旋转) – tornadomeet – 博客园

 

绘制四棱锥

四棱锥由5个面构成一个封闭的立体图,其中4个共顶点的侧面是三角形,底面是个四边形。如果我们要绘制一个3D的四棱锥只需要绘制这5个面即可,绘制的方法和前一篇文章OpenGL_Qt学习笔记之_03(平面图形的着色和旋转)的相同。只不过这里的顶点坐标是3维的,所以图像深度那一维不一定为0。因此我们可以事先计算好四棱锥各个顶点的坐标,这对学过立体几何的人来说应该是小case了。然后绘制每个面就可以。

注意,在opengl中绘制每个面时,所有面给出的顶点的顺序都要按照逆时针或者顺时针(我这里采用的是逆时针),这样才能保证所绘制出来的图像时正确的。

现在我们在paintGL中开始绘制四棱锥,如果按照NeHe的教程,它只是绘制了个金字塔,并没有底面,只有4个侧面,这里,我采用它的方法,代码如下:

复制代码
/*下面开始画四棱锥*/
    glLoadIdentity();//重置当前的模型观察矩阵

    glTranslatef(-0.5, 0.0, -0.5);//将绘制平面移动到屏幕的左半平面和里面

    glRotatef(x_rotate, 0.2, 0.2, 0.0);

    glBegin(GL_TRIANGLES);

    /*前正面的绘制*/

    glColor3f(1.0, 0.0, 0.0);//上顶点红色

    glVertex3f(0.0, 0.3, 0.0);

    glColor3f(0.0, 0.0, 1.0);//左下点蓝色

    glVertex3f(-0.3, -0.3, 0.3);

    glColor3f(0.0, 1.0, 0.0);//右下角绿色

    glVertex3f(0.3, -0.3, 0.3);

    /*右侧面的绘制*/

    glColor3f(1.0, 0.0, 0.0);//上顶点红色

    glVertex3f(0.0, 0.3, 0.0);

    glColor3f(0.0, 0.0, 1.0);//左下点蓝色

    glVertex3f(0.3, -0.3, 0.3);

    glColor3f(0.0, 1.0, 0.0);//右下角绿色

    glVertex3f(0.3, -0.3, -0.3);

    /*后侧面的绘制*/

    glColor3f(1.0, 0.0, 0.0);//上顶点红色

    glVertex3f(0.0, 0.3, 0.0);

    glColor3f(0.0, 0.0, 1.0);//左下点蓝色

    glVertex3f(0.3, -0.3, -0.3);

    glColor3f(0.0, 1.0, 0.0);//右下角绿色

    glVertex3f(-0.3, -0.3, -0.3);

    /*左侧面的绘制*/

    glColor3f(1.0, 0.0, 0.0);//上顶点红色

    glVertex3f(0.0, 0.3, 0.0);

    glColor3f(0.0, 0.0, 1.0);//左下点蓝色

    glVertex3f(-0.3, -0.3, -0.3);

    glColor3f(0.0, 1.0, 0.0);//右下角绿色

    glVertex3f(-0.3, -0.3, 0.3);

    x_rotate_angle1 += 3.0;

glEnd();
复制代码

 

在绘制完金子塔后,把它沿某一个方向旋转后如下图所示:

 

如果我们在后面加上代码,把底面补全,画上一个四边形,此时加入的代码如下:

复制代码
 /*底面四边形的绘制,使四棱锥封闭起来*/

    glBegin(GL_QUADS);

    glColor3f(0.0, 0.0, 1.0);//上顶点红色

    glVertex3f(-0.3, -0.3, 0.3);

    glColor3f(0.0, 1.0, 0.0);//左下点蓝色

    glVertex3f(0.3, -0.3, 0.3);

    glColor3f(0.0, 0.0, 1.0);//右下角绿色

    glVertex3f(0.3, -0.3, -0.3);

    glColor3f(0.0, 1.0, 0.0);

    glVertex3f(-0.3, -0.3, -0.3);

glEnd();
复制代码

 

这时候的结果如下:

 

绘制立方体

绘制立方体的方法和四棱锥的方法类似,只不过这里是由6个正方形构成的封闭体,我们依次绘制出每个面即可,同样要注意的是绘制每个面时给出点的顺序要一致,绘制每个面的顺序倒不需要按照什么逆时针或者顺时针,什么顺序都行。

计算好正方体的8个顶点坐标后就开始写代码了,代码如下:

复制代码
/*下面开始画立方体*/

    glLoadIdentity();

    glTranslated(0.5, 0, 0.5);//将绘制平面移动到屏幕的右半平面和外面

    glRotatef(rotate_angle2, -0.2, 0.2, -0.3);

    glBegin(GL_QUADS);

    //上顶面

    glColor3f(0.0, 1.0, 0.0);

    glVertex3f(-0.3, 0.3, -0.3);

    glVertex3f(-0.3, 0.3, 0.3);

    glVertex3f(0.3, 0.3, 0.3);

    glVertex3f(0.3, 0.3, -0.3);

    //下顶面

    glColor3f(0.0, 1.0, 0.0);

    glVertex3f(-0.3, -0.3, -0.3);

    glVertex3f(-0.3, -0.3, 0.3);

    glVertex3f(0.3, -0.3, 0.3);

    glVertex3f(0.3, -0.3, -0.3);

    //正前面

    glColor3f(1.0, 0.0, 0.0);

    glVertex3f(-0.3, 0.3, 0.3);

    glVertex3f(-0.3, -0.3, 0.3);

    glVertex3f(0.3, -0.3, 0.3);

    glVertex3f(0.3, 0.3, 0.3);

    //右侧面

    glColor3f(1.0, 1.0, 0.0);

    glVertex3f(0.3, 0.3, 0.3);

    glVertex3f(0.3, -0.3, 0.3);

    glVertex3f(0.3, -0.3, -0.3);

    glVertex3f(0.3, 0.3, -0.3);

    //背后面

    glColor3f(0.0, 1.0, 1.0);

    glVertex3f(-0.3, 0.3, -0.3);

    glVertex3f(0.3, 0.3, -0.3);

    glVertex3f(0.3, -0.3, -0.3);

    glVertex3f(-0.3, -0.3, -0.3);

    //左侧面

    glColor3f(1.0, 0.0, 1.0);

    glVertex3f(-0.3, 0.3, -0.3);

    glVertex3f(-0.3, -0.3, -0.3);

    glVertex3f(-0.3, -0.3, 0.3);

    glVertex3f(-0.3, 0.3, 0.3);

    rotate_angle2 -= 3;

glEnd();
复制代码

 

其效果如下:

 

当两者放在一起,且经过不同轴的旋转后图像如下:

 

实验主要部分代码如下(附录有工程code下载地址):

复制代码
#include "glwidget.h"
#include "ui_glwidget.h"

#include <QtGui>
#include <QtCore>
#include <QtOpenGL>

GLWidget::GLWidget(QGLWidget *parent) :
    QGLWidget(parent),
    ui(new Ui::GLWidget)
{
  //  setCaption("The Opengl for Qt Framework");
    ui->setupUi(this);
    fullscreen = false;
    rotate_angle1 = 0.0;
    rotate_angle2 = 0.0;
}

//这是对虚函数,这里是重写该函数
void GLWidget::initializeGL()
{
    setGeometry(300, 150, 640, 480);//设置窗口初始位置和大小
    glShadeModel(GL_SMOOTH);//设置阴影平滑模式
    glClearColor(0.0, 0.0, 0.0, 0);//改变窗口的背景颜色,不过我这里貌似设置后并没有什么效果
    glClearDepth(1.0);//设置深度缓存
    glEnable(GL_DEPTH_TEST);//允许深度测试
    glDepthFunc(GL_LEQUAL);//设置深度测试类型
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);//进行透视校正
}

void GLWidget::paintGL()
{
    //glClear()函数在这里就是对initializeGL()函数中设置的颜色和缓存深度等起作用
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /*下面开始画四棱锥*/
    glLoadIdentity();//重置当前的模型观察矩阵
    glTranslatef(-0.5, 0.0, -0.5);//将绘制平面移动到屏幕的左半平面和里面
    glRotatef(rotate_angle1, 0.2, 0.2, 0.0);
    glBegin(GL_TRIANGLES);
    /*前正面的绘制*/
    glColor3f(1.0, 0.0, 0.0);//上顶点红色
    glVertex3f(0.0, 0.3, 0.0);
    glColor3f(0.0, 0.0, 1.0);//左下点蓝色
    glVertex3f(-0.3, -0.3, 0.3);
    glColor3f(0.0, 1.0, 0.0);//右下角绿色
    glVertex3f(0.3, -0.3, 0.3);
    /*右侧面的绘制*/
    glColor3f(1.0, 0.0, 0.0);//上顶点红色
    glVertex3f(0.0, 0.3, 0.0);
    glColor3f(0.0, 0.0, 1.0);//左下点蓝色
    glVertex3f(0.3, -0.3, 0.3);
    glColor3f(0.0, 1.0, 0.0);//右下角绿色
    glVertex3f(0.3, -0.3, -0.3);
    /*后侧面的绘制*/
    glColor3f(1.0, 0.0, 0.0);//上顶点红色
    glVertex3f(0.0, 0.3, 0.0);
    glColor3f(0.0, 0.0, 1.0);//左下点蓝色
    glVertex3f(0.3, -0.3, -0.3);
    glColor3f(0.0, 1.0, 0.0);//右下角绿色
    glVertex3f(-0.3, -0.3, -0.3);
    /*左侧面的绘制*/
    glColor3f(1.0, 0.0, 0.0);//上顶点红色
    glVertex3f(0.0, 0.3, 0.0);
    glColor3f(0.0, 0.0, 1.0);//左下点蓝色
    glVertex3f(-0.3, -0.3, -0.3);
    glColor3f(0.0, 1.0, 0.0);//右下角绿色
    glVertex3f(-0.3, -0.3, 0.3);
    rotate_angle1 += 3.0;
    glEnd();
    /*底面四边形的绘制,使四棱锥封闭起来*/
    glBegin(GL_QUADS);
    glColor3f(0.0, 0.0, 1.0);//上顶点红色
    glVertex3f(-0.3, -0.3, 0.3);
    glColor3f(0.0, 1.0, 0.0);//左下点蓝色
    glVertex3f(0.3, -0.3, 0.3);
    glColor3f(0.0, 0.0, 1.0);//右下角绿色
    glVertex3f(0.3, -0.3, -0.3);
    glColor3f(0.0, 1.0, 0.0);
    glVertex3f(-0.3, -0.3, -0.3);
    glEnd();

    /*下面开始画立方体*/
    glLoadIdentity();
    glTranslated(0.5, 0, 0.5);//将绘制平面移动到屏幕的右半平面和外面
    glRotatef(rotate_angle2, -0.2, 0.2, -0.3);
    glBegin(GL_QUADS);
    //上顶面
    glColor3f(0.0, 1.0, 0.0);
    glVertex3f(-0.3, 0.3, -0.3);
    glVertex3f(-0.3, 0.3, 0.3);
    glVertex3f(0.3, 0.3, 0.3);
    glVertex3f(0.3, 0.3, -0.3);
    //下顶面
    glColor3f(0.0, 1.0, 0.0);
    glVertex3f(-0.3, -0.3, -0.3);
    glVertex3f(-0.3, -0.3, 0.3);
    glVertex3f(0.3, -0.3, 0.3);
    glVertex3f(0.3, -0.3, -0.3);
    //正前面
    glColor3f(1.0, 0.0, 0.0);
    glVertex3f(-0.3, 0.3, 0.3);
    glVertex3f(-0.3, -0.3, 0.3);
    glVertex3f(0.3, -0.3, 0.3);
    glVertex3f(0.3, 0.3, 0.3);
    //右侧面
    glColor3f(1.0, 1.0, 0.0);
    glVertex3f(0.3, 0.3, 0.3);
    glVertex3f(0.3, -0.3, 0.3);
    glVertex3f(0.3, -0.3, -0.3);
    glVertex3f(0.3, 0.3, -0.3);
    //背后面
    glColor3f(0.0, 1.0, 1.0);
    glVertex3f(-0.3, 0.3, -0.3);
    glVertex3f(0.3, 0.3, -0.3);
    glVertex3f(0.3, -0.3, -0.3);
    glVertex3f(-0.3, -0.3, -0.3);
    //左侧面
    glColor3f(1.0, 0.0, 1.0);
    glVertex3f(-0.3, 0.3, -0.3);
    glVertex3f(-0.3, -0.3, -0.3);
    glVertex3f(-0.3, -0.3, 0.3);
    glVertex3f(-0.3, 0.3, 0.3);
    rotate_angle2 -= 3;
    glEnd();
}

//该程序是设置opengl场景透视图,程序中至少被执行一次(程序启动时).
void GLWidget::resizeGL(int width, int height)
{
    if(0 == height)
        height = 1;//防止一条边为0
    glViewport(0, 0, (GLint)width, (GLint)height);//重置当前视口,本身不是重置窗口的,只不过是这里被Qt给封装好了
    glMatrixMode(GL_PROJECTION);//选择投影矩阵
    glLoadIdentity();//重置选择好的投影矩阵
   // gluPerspective(45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0);//建立透视投影矩阵
    glMatrixMode(GL_MODELVIEW);//以下2句和上面出现的解释一样

    glLoadIdentity();


}
void GLWidget::keyPressEvent(QKeyEvent *e)
{
    switch(e->key())
    {
        //F1键为全屏和普通屏显示切换键
        case Qt::Key_F1:
            fullscreen = !fullscreen;
            if(fullscreen)
                showFullScreen();
            else
            {
                setGeometry(300, 150, 640, 480);
                showNormal();
            }
            updateGL();
            break;
        //Ese为退出程序键
        case Qt::Key_Escape:
            close();
    }
}

GLWidget::~GLWidget()
{
    delete ui;
}
复制代码

 

总结:本文在前面文章绘制2D图像和旋转的基础上,增加一维的坐标就可以绘制出3D图形即旋转了。在画3D 图时,必须将OpenGL屏幕想象成一张很大的画纸,后面还带着许多透明的层。差不多就是个由大量的点组成的立方体。这些点从左至右、从上至下、从前到后 的布满了这个3D图的表面。

 

参考资料:

http://nehe.gamedev.net/

http://www.owlei.com/DancingWind/

http://www.qiliang.net/old/nehe_qt/

 

附录:

实验工程code下载

 

 

 

 

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。 (新浪微博:tornadomeet,欢迎交流!)

OpenGL_Qt学习笔记之_05(纹理映射)

mikel阅读(1213)

 纹理映射基础知识

什么叫纹理映射,一开始我也不明白,感觉这个词好专业(毕竟没有学过图形学),后面经过网上查找资料和这次实验稍微理解了点。纹理映射简单的 讲,就是把一个纹理(其实说白了,纹理可以理解为一幅图像)映射到空间物体的表面上,因此纹理映射也叫贴图,这个表明不一定是矩形,比如说我可以是球面, 或者是任意曲面。在上一篇文章OpenGL_Qt学习笔记之_04(3D图形的绘制和旋转)中,我们绘制的空间体的表面都是一些光滑的颜色,如果要在其表面采用那种方法绘制图像的话,则必须利用微分的思想,一个一个的绘制小图像,然后拼接起来,可想而知,这个过程是多么的复杂,opengl的纹理映射就可以很好的解决这一问题。

下面来看看纹理映射的一个示意图片(百度百科上的):

这个示意图说明,将中间的纹理(即图片)映射到左边的茶壶曲面上,就形成了右边的图了。在右边的图中,可以看到茶壶的表面上布满了图片,这就像中国古代的陶器绘图一样。

纹理映射另外一个好处是能够保证在变换多边形时,多边形上的纹理也会随之变化。

 

纹理映射相关函数

要进行纹理映射,得先了解下opengl中有关纹理映射的一些相关函数。

void glGenTextures(GLsizei n, GLuint *textures);

该函数的作用是开辟存储纹理的内存空间,其中参数n为开辟纹理内存的个数,texture为存储纹理的地址索引。

 

void glBindTexture(GLenum target,   GLuint texture);

该函数的作用是把存储纹理的对象texture绑定到纹理目标target上,在opengl中纹理目标分为GL_TEXTURE_1D和GL_TEXTURE_2D。该句代码运行完后,对target的操作也对应于对texture指向的内容的操作。

 

void glTexImage2D(GLenum target,GLint level,GLint components,GLsizei width, glsizei height,GLint border,GLenum format,GLenum type, const GLvoid *pixels);

参数1为纹理目标;参数2为目标的层次,即目标的详细程度,一般情况采用0即可;参数3表示的是数据成分的个数,如果数据由RGB构成,则将该 参数设置为3;参数4和5分别为创建纹理数据的长和宽;参数6为边框的值,一般也设为0;参数8为数据的通道格式;参数9为纹理的数据元素类型;参数10 为纹理的数据内容。

这个函数的功能是创建一个纹理,并为该纹理分配了数据。

 

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

该函数表示的是当所显示的纹理比加载进来的纹理小时,采用GL_LINEAR的方法来处理。

 

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

和上面的函数类似,注意该函数的参数2为GL_TEXTURE_MAG_FILTER,不要误认为是GL_TEXTURE_MAG_FILTER。

 

glTexCoord2f(x, y);

  第一个参数是X坐标。 0.0f 是纹理的左侧。 0.5f 是纹理的中点, 1.0f 是纹理的右侧。第二个参数是Y坐标。0.0f 是纹理的底部。0.5f 是纹理的中点,1.0f 是纹理的顶部。

  给定一张纹理图,它的坐标示意图如下:

  

  其中纹理的中心点坐标为(0.5, 0.5).

 

实验说明:

这个实验室在上一篇博文OpenGL_Qt学习笔记之_04(3D图形的绘制和旋转) 绘制的立方体上贴每个面上贴上一张纹理(即图片)。

我们需要在GLWidget这个类中添加一个函数来加载纹理数据,该函数为loadTextures().

  另外,在QGLWidget这个类中,initializeGL(), paintGL(), resizeGL()这3个函数的执行顺序是该类启动时就执行initializeGL(),主要用于初始化opengl,通常用来设置一些前期的背景 色,光照参数等等。paintGL()用于渲染整个场景;resizeGL()用于在widget大小变化的时候产生合理view。

  注意,本实验给的图片长和宽必须是2的n次方,最大不要超过256,最小也不要小于64。我这里采用的是256*256像素的图片。

  实验结果:

  加载纹理的图像为:

  

 

  纹理映射后的效果如下:

   

 

  实验主要部分代码及注释(附录有工程code下载地址):

复制代码
#include "glwidget.h"
#include "ui_glwidget.h"

#include <QtGui>
#include <QtCore>
#include <QtOpenGL>

GLWidget::GLWidget(QGLWidget *parent) :
    QGLWidget(parent),
    ui(new Ui::GLWidget)
{
  //  setCaption("The Opengl for Qt Framework");
    ui->setupUi(this);
    fullscreen = false;
    rotate_angle = 0.0;
}

//这是对虚函数,这里是重写该函数
void GLWidget::initializeGL()
{
    setGeometry(300, 150, 500, 500);//设置窗口初始位置和大小
    loadTextures();
    glEnable(GL_TEXTURE_2D);//允许采用2D纹理技术
    glShadeModel(GL_SMOOTH);//设置阴影平滑模式
    glClearColor(0.0, 0.0, 0.0, 0);//改变窗口的背景颜色,不过我这里貌似设置后并没有什么效果
    glClearDepth(1.0);//设置深度缓存
    glEnable(GL_DEPTH_TEST);//允许深度测试
    glDepthFunc(GL_LEQUAL);//设置深度测试类型
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);//进行透视校正
}

void GLWidget::paintGL()
{
    //glClear()函数在这里就是对initializeGL()函数中设置的颜色和缓存深度等起作用
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /*下面开始画立方体,并对其进行纹理映射*/
    glLoadIdentity();
    glRotatef(rotate_angle, -0.2f, 0.2f, -0.3f);
    glBegin(GL_QUADS);
    //上顶面
    glTexCoord2f(0.0, 1.0);//将2D的纹理坐标映射到3D的空间物体表面上
    glVertex3f(-0.3f, 0.3f, -0.3f);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-0.3f, 0.3f, 0.3f);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(0.3f, 0.3f, 0.3f);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(0.3f, 0.3f, -0.3f);
    //下顶面
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-0.3f, -0.3f, -0.3f);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-0.3f, -0.3f, 0.3f);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(0.3f, -0.3f, 0.3f);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(0.3f, -0.3f, -0.3f);
    //正前面
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-0.3f, 0.3f, 0.3f);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-0.3f, -0.3f, 0.3f);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(0.3f, -0.3f, 0.3f);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(0.3f, 0.3f, 0.3f);
    //右侧面
    glTexCoord2f(0.0, 1.0);
    glVertex3f(0.3f, 0.3f, 0.3f);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(0.3f, -0.3f, 0.3f);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(0.3f, -0.3f, -0.3f);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(0.3f, 0.3f, -0.3f);
    //背后面
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-0.3f, 0.3f, -0.3f);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(0.3f, 0.3f, -0.3f);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(0.3f, -0.3f, -0.3f);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(-0.3f, -0.3f, -0.3f);
    //左侧面
    glTexCoord2f(0.0, 1.0);
    glVertex3f(-0.3f, 0.3f, -0.3f);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-0.3f, -0.3f, -0.3f);
    glTexCoord2f(1.0, 0.0);
    glVertex3f(-0.3f, -0.3f, 0.3f);
    glTexCoord2f(1.0, 1.0);
    glVertex3f(-0.3f, 0.3f, 0.3f);
    rotate_angle -= 3;
    glEnd();
}

//该程序是设置opengl场景透视图,程序中至少被执行一次(程序启动时).
void GLWidget::resizeGL(int width, int height)
{
    if(0 == height)
        height = 1;//防止一条边为0
    glViewport(0, 0, (GLint)width, (GLint)height);//重置当前视口,本身不是重置窗口的,只不过是这里被Qt给封装好了
    glMatrixMode(GL_PROJECTION);//选择投影矩阵
    glLoadIdentity();//重置选择好的投影矩阵
   // gluPerspective(45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0);//建立透视投影矩阵
    glMatrixMode(GL_MODELVIEW);//以下2句和上面出现的解释一样
    glLoadIdentity();

}
void GLWidget::keyPressEvent(QKeyEvent *e)
{
    switch(e->key())
    {
        //F1键为全屏和普通屏显示切换键
        case Qt::Key_F1:
            fullscreen = !fullscreen;
            if(fullscreen)
                showFullScreen();
            else
            {
                setGeometry(300, 150, 500, 500);
                showNormal();
            }
            updateGL();
            break;
        //Ese为退出程序键
        case Qt::Key_Escape:
            close();
    }
}

/*装载纹理*/
void GLWidget::loadTextures()
{
    QImage tex, buf;
    if(!buf.load("../opengl_nehe_05/cherry.jpg"))
    {
        qWarning("Cannot open the image...");
        QImage dummy(128, 128, QImage::Format_RGB32);//当没找到所需打开的图片时,创建一副128*128大小,深度为32位的位图
        dummy.fill(Qt::green);
        buf = dummy;
    }
    tex = convertToGLFormat(buf);//将Qt图片的格式buf转换成opengl的图片格式tex
    glGenTextures(1, &texture[0]);//开辟一个纹理内存,内存指向texture[0]
    glBindTexture(GL_TEXTURE_2D, texture[0]);//将创建的纹理内存指向的内容绑定到纹理对象GL_TEXTURE_2D上,经过这句代码后,以后对
                                            //GL_TEXTURE_2D的操作的任何操作都同时对应与它所绑定的纹理对象
    glTexImage2D(GL_TEXTURE_2D, 0, 3, tex.width(), tex.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex.bits());//开始真正创建纹理数据
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);//当所显示的纹理比加载进来的纹理小时,采用GL_LINEAR的方法来处理
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);//当所显示的纹理比加载进来的纹理大时,采用GL_LINEAR的方法来处理
}

GLWidget::~GLWidget()
{
    delete ui;
}
复制代码

 

 

  总结:

  了解了opengl中的纹理映射机制后,对空间体的表面进行贴图还是比较方便的。

   参考资料:

   http://nehe.gamedev.net/

   http://www.owlei.com/DancingWind/

   http://blog.csdn.net/qp120291570/article/details/7853513

     http://www.qiliang.net/old/nehe_qt/

  附录:

  实验工程code下载

作者:tornadomeet 出处:http://www.cnblogs.com/tornadomeet 欢迎转载或分享,但请务必声明文章出处。 (新浪微博:tornadomeet,欢迎交流!)