OpenglES2.0 for Android:来画个三角形吧

OpenglES2.0 for Android:来画个三角形吧

先看看我们的整个流程:

理解坐标系:

左侧是Opengl默认的坐标系,右边是典型的android设备屏幕的坐标系。左侧的瘦瘦的三角形映射到android屏幕上就变成了胖胖的三角形(屏幕横向的时候),我们可以使用

camera和投影解决这个问题,具体怎么解决这里就先不累述了。这里我们只需要知道屏幕的左上角是(-1.0,1.0)横向向右为X轴正向,纵向向下为Y轴

负向,其范围都是从 -1到 +1。

定义三角形顶点:

我们在第一个android的小demo的工程里新建一个包shape,然后新建一个类 Triangle 。然后我们在该类中定义三角形的三个顶点的数据:

此时该类如下(Triangle.java):

<span style="font-size:14px;">package com.cumt.shape;

import android.content.Context;

public class Triangle {

	private Context context;
	// 数组中每个顶点的坐标数
    static final int COORDS_PER_VERTEX = 2;
	// 每个顶点的坐标数  	X ,  Y
    static float triangleCoords[] = { 0.0f,  0.5f ,   // top
                                     -0.5f, -0.5f ,   // bottom left
                                      0.5f, -0.5f };   // bottom right

    public Triangle(Context context){
    	this.context = context;
    }
}</span>

我们在该类中定义的float类型的数据并不能直接被opengl使用,float[ ]是属于虚拟机环境的,而Opengl作为本地系统库直接运行在硬件上,所以我们需要将float[ ] 转化为

FloatBuffer以使数据可以被opengl使用,此时该类如下(Triangle.java ):

<span style="font-size:14px;">package com.cumt.shape;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import android.content.Context;

public class Triangle {

	private Context context;

	private static final int BYTES_PER_FLOAT = 4;
	private FloatBuffer vertexBuffer;

	// 数组中每个顶点的坐标数
    static final int COORDS_PER_VERTEX = 2;
	// 每个顶点的坐标数  	X ,  Y
    static float triangleCoords[] = { 0.0f,  0.5f ,   // top
                                     -0.5f, -0.5f ,   // bottom left
                                      0.5f, -0.5f };   // bottom right

    public Triangle(Context context){
    	this.context = context;

    	vertexBuffer = ByteBuffer
    			.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)
    			.order(ByteOrder.nativeOrder())
    			.asFloatBuffer();
    	// 把坐标们加入FloatBuffer中
        vertexBuffer.put(triangleCoords);
        // 设置buffer,从第一个坐标开始读
        vertexBuffer.position(0);
    }
}</span>

编写着色器:

着色器语言(shading language)是opengl es2.0 比opengl es 1.X 新增的内容,使我们更加自由的实现自己想要的效果,该语言基于C/C++。该语言的具体类型等等这里不详细说明。

我们需要两个着色器——顶点着色器和片段着色器,顾名思义顶点着色器是用于处理顶点坐标的(不要与屏幕的像素点混淆),而片段着色器用于处理片段,所谓片段简单理解就是顶点着色器处理后的一堆顶点形成的片段。

现在我们来编写这两个顶点着色器:在 res 目录下新建一个文件夹 raw 在该文件夹下新建一个文件simple_vertex_shader.glsl ,其内容如下:

<span style="font-size:14px;">attribute vec4 a_Position;   

void main()
{
    gl_Position = a_Position;
}   </span>

gl_Position即opengl定义的顶点的坐标,我们目的就是通过这个来告诉opengl我们的顶点数据。vec4是着色器语言中的向量类型的一种,包含了四个浮点数的向量。

接下来在raw文件夹内新建文件 simple_fragment_shader.glsl ,其内容如下:

<span style="font-size:14px;">precision mediump float; 

uniform vec4 u_Color;     							

void main()
{
    gl_FragColor = u_Color;
}</span>

这里我们只传入一个颜色信息。这里注意一下 上面顶点着色器的 限定符 attribute 和 uniform 。attribute 一般用于每个顶点各不相同的量,如顶点位置等,后者一般用于

同一组顶点组成的相同的量,如光源位置,一组颜色等。

编译着色器及绘制:

接下来我们进行最后一步,编译着色器以及绘制三角形,这里我们来写一个工具类用于加载着色器程序以及编译着色器。

首先新建一个包utils ,此时目录结构如下图所示:

在utils包下新建类 LoggerConfig 用于管理我们的日志(是否输出Logcat的信息),代码如下(LoggerConfig.java):

<span style="font-size:14px;">package com.cumt.utils;

public class LoggerConfig {
    public static final boolean ON = true;
}</span>

再在该包下新建类ShaderHelper 用于加载着色器程序以及编译着色器:

<span style="font-size:14px;">package com.cumt.utils;

import static android.opengl.GLES20.GL_COMPILE_STATUS;
import static android.opengl.GLES20.GL_FRAGMENT_SHADER;
import static android.opengl.GLES20.GL_LINK_STATUS;
import static android.opengl.GLES20.GL_VALIDATE_STATUS;
import static android.opengl.GLES20.GL_VERTEX_SHADER;
import static android.opengl.GLES20.glAttachShader;
import static android.opengl.GLES20.glCompileShader;
import static android.opengl.GLES20.glCreateProgram;
import static android.opengl.GLES20.glCreateShader;
import static android.opengl.GLES20.glDeleteProgram;
import static android.opengl.GLES20.glDeleteShader;
import static android.opengl.GLES20.glGetProgramInfoLog;
import static android.opengl.GLES20.glGetProgramiv;
import static android.opengl.GLES20.glGetShaderInfoLog;
import static android.opengl.GLES20.glGetShaderiv;
import static android.opengl.GLES20.glLinkProgram;
import static android.opengl.GLES20.glShaderSource;
import static android.opengl.GLES20.glValidateProgram;
import android.util.Log;

public class ShaderHelper {

    private static final String TAG = "ShaderHelper";

    /**
     * 加载并编译顶点着色器,返回得到的opengl id
     * @param shaderCode
     * @return
     */
    public static int compileVertexShader(String shaderCode) {
        return compileShader(GL_VERTEX_SHADER, shaderCode);
    }

    /**
     * 加载并编译片段着色器,返回opengl id
     * @param shaderCode
     * @return
     */
    public static int compileFragmentShader(String shaderCode) {
        return compileShader(GL_FRAGMENT_SHADER, shaderCode);
    }

    /**
     * 加载并编译着色器,返回opengl id
     * @param type
     * @param shaderCode
     * @return
     */
    private static int compileShader(int type, String shaderCode) {
        // 建立新的着色器对象
        final int shaderObjectId = glCreateShader(type);

        if (shaderObjectId == 0) {
            if (LoggerConfig.ON) {
                Log.w(TAG, "不能创建新的着色器.");
            }

            return 0;
        }

        // 传递着色器资源代码.
        glShaderSource(shaderObjectId, shaderCode);

        //编译着色器
        glCompileShader(shaderObjectId);

        // 获取编译的状态
        final int[] compileStatus = new int[1];
        glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS,
            compileStatus, 0);

        if (LoggerConfig.ON) {
        	//打印log
            Log.v(TAG, "代码编译结果:" + "\n" + shaderCode
                + "\n:" + glGetShaderInfoLog(shaderObjectId));
        }

        // 确认编译的状态
        if (compileStatus[0] == 0) {
            // 如果编译失败,则删除该对象
            glDeleteShader(shaderObjectId);

            if (LoggerConfig.ON) {
                Log.w(TAG, "编译失败!.");
            }

            return 0;
        }

        // 返回着色器的opengl id
        return shaderObjectId;
    }

    /**
     * 链接顶点着色器和片段着色器成一个program
     * 并返回这个pragram的opengl id
     * @param vertexShaderId
     * @param fragmentShaderId
     * @return
     */
    public static int linkProgram(int vertexShaderId, int fragmentShaderId) {

        // 新建一个program对象
        final int programObjectId = glCreateProgram();

        if (programObjectId == 0) {
            if (LoggerConfig.ON) {
                Log.w(TAG, "不能新建一个 program");
            }

            return 0;
        }

        // Attach the vertex shader to the program.
        glAttachShader(programObjectId, vertexShaderId);

        // Attach the fragment shader to the program.
        glAttachShader(programObjectId, fragmentShaderId);

        // 将两个着色器连接成一个program
        glLinkProgram(programObjectId);

        // 获取连接状态
        final int[] linkStatus = new int[1];
        glGetProgramiv(programObjectId, GL_LINK_STATUS,
            linkStatus, 0);

        if (LoggerConfig.ON) {
            // Print the program info log to the Android log output.
            Log.v(
                TAG,
                "Results of linking program:\n"
                    + glGetProgramInfoLog(programObjectId));
        }

        // 验证连接状态
        if (linkStatus[0] == 0) {
            // If it failed, delete the program object.
            glDeleteProgram(programObjectId);

            if (LoggerConfig.ON) {
                Log.w(TAG, "连接 program 失败!.");
            }

            return 0;
        }

        // Return the program object ID.
        return programObjectId;
    }

    /**
     * Validates an OpenGL program. Should only be called when developing the
     * application.
     */
    public static boolean validateProgram(int programObjectId) {
        glValidateProgram(programObjectId);
        final int[] validateStatus = new int[1];
        glGetProgramiv(programObjectId, GL_VALIDATE_STATUS,
            validateStatus, 0);
        Log.v(TAG, "Results of validating program: " + validateStatus[0]
            + "\nLog:" + glGetProgramInfoLog(programObjectId));

        return validateStatus[0] != 0;
    }

    /**
     * /**
     * 编译,连接 ,返回 program 的 ID
     * @param vertexShaderSource
     * @param fragmentShaderSource
     * @return
     */
    public static int buildProgram(String vertexShaderSource,
        String fragmentShaderSource) {
        int program;

        // Compile the shaders.
        int vertexShader = compileVertexShader(vertexShaderSource);
        int fragmentShader = compileFragmentShader(fragmentShaderSource);

        // Link them into a shader program.
        program = linkProgram(vertexShader, fragmentShader);

        if (LoggerConfig.ON) {
            validateProgram(program);
        }
        return program;
    }
}</span>

上面整个过程如图所示:

以后我们就可以很方便的一直使用该工具类来加载编译着色器了。只需要调用buildProgram,传入两个着色器的String文本就可以获得program的id是不是很方便。

我们似乎遗忘了什么,对的我们的两个着色器的文本内容该怎么活的呢,我们再在该目录下定义一个工具类TextResourceReader类来获得文本的内容,返回String类型

代码如下(TextResourceReader.java):

<span style="font-size:14px;">package com.cumt.utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import android.content.Context;
import android.content.res.Resources;

public class TextResourceReader {
    /**
     * Reads in text from a resource file and returns a String containing the
     * text.
     */
    public static String readTextFileFromResource(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();
    }
}</span>

OK ,接下来就让我们来使用者两个工具类获得program的id吧,先回到 前面定义的 Triangle 类:

我们来定义一个私有方法getProgram 并在构造函数中调用 ,此时代码如下 (Triangle.java):

<span style="font-size:14px;">package com.cumt.shape;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import com.cumt.openglestwo_test_one.R;
import com.cumt.utils.ShaderHelper;
import com.cumt.utils.TextResourceReader;
import android.content.Context;
import android.opengl.GLES20;

public class Triangle {

	private Context context;

	private static final int BYTES_PER_FLOAT = 4;
	private FloatBuffer vertexBuffer;

	// 数组中每个顶点的坐标数
    static final int COORDS_PER_VERTEX = 2;
	// 每个顶点的坐标数  						X ,  Y
    static float triangleCoords[] = { 0.0f,  0.5f ,   // top
                                     -0.5f, -0.5f ,   // bottom left
                                      0.5f, -0.5f };   // bottom right

    private int program;

    public Triangle(Context context){
    	this.context = context;

    	vertexBuffer = ByteBuffer
    			.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)
    			.order(ByteOrder.nativeOrder())
    			.asFloatBuffer();
    	// 把坐标们加入FloatBuffer中
        vertexBuffer.put(triangleCoords);
        // 设置buffer,从第一个坐标开始读
        vertexBuffer.position(0);

        getProgram();

    }

    //获取program的id
    private void getProgram(){
    	//获取顶点着色器文本
    	String vertexShaderSource = TextResourceReader
				.readTextFileFromResource(context, R.raw.simple_vertex_shader);
    	//获取片段着色器文本
		String fragmentShaderSource = TextResourceReader
				.readTextFileFromResource(context, R.raw.simple_fragment_shader);
		//获取program的id
		program = ShaderHelper.buildProgram(vertexShaderSource, fragmentShaderSource);
		GLES20.glUseProgram(program);
    }
}</span>

至此我们已经胜利在望了,接下来思考一下,我们应该做哪些工作?显然我们需要将定义的数据传入着色器中来使用。详细的步骤见下面的代码(Triangle.java):

<span style="font-size:14px;">package com.cumt.shape;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import com.cumt.openglestwo_test_one.R;
import com.cumt.utils.ShaderHelper;
import com.cumt.utils.TextResourceReader;
import android.content.Context;
import android.opengl.GLES20;

public class Triangle {

	private Context context;

	private static final int BYTES_PER_FLOAT = 4;
	private FloatBuffer vertexBuffer;

	//---------第四步:定义坐标元素的个数,这里有三个顶点
	private static final int POSITION_COMPONENT_COUNT = 3;

	// 数组中每个顶点的坐标数
    static final int COORDS_PER_VERTEX = 2;
	// 每个顶点的坐标数  						X ,  Y
    static float triangleCoords[] = { 0.0f,  0.5f ,   // top
                                     -0.5f, -0.5f ,   // bottom left
                                      0.5f, -0.5f };   // bottom right

    private int program;

    //------------第一步 : 定义两个标签,分别于着色器代码中的变量名相同,
    //------------第一个是顶点着色器的变量名,第二个是片段着色器的变量名
	private static final String A_POSITION = "a_Position";
	private static final String U_COLOR = "u_Color";

	//------------第二步: 定义两个ID,我们就是通ID来实现数据的传递的,这个与前面
	//------------获得program的ID的含义类似的
	private int uColorLocation;
	private int aPositionLocation;

    public Triangle(Context context){
    	this.context = context;

    	vertexBuffer = ByteBuffer
    			.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)
    			.order(ByteOrder.nativeOrder())
    			.asFloatBuffer();
    	// 把坐标们加入FloatBuffer中
        vertexBuffer.put(triangleCoords);
        // 设置buffer,从第一个坐标开始读
        vertexBuffer.position(0);

        getProgram();

        //----------第三步: 获取这两个ID ,是通过前面定义的标签获得的
        uColorLocation = GLES20.glGetUniformLocation(program, U_COLOR);
		aPositionLocation = GLES20.glGetAttribLocation(program, A_POSITION);

		//---------第五步: 传入数据
		GLES20.glVertexAttribPointer(aPositionLocation, COORDS_PER_VERTEX,
				GLES20.GL_FLOAT, false, 0, vertexBuffer);
		GLES20.glEnableVertexAttribArray(aPositionLocation);

    }

    //获取program
    private void getProgram(){
    	//获取顶点着色器文本
    	String vertexShaderSource = TextResourceReader
				.readTextFileFromResource(context, R.raw.simple_vertex_shader);
    	//获取片段着色器文本
		String fragmentShaderSource = TextResourceReader
				.readTextFileFromResource(context, R.raw.simple_fragment_shader);
		//获取program的id
		program = ShaderHelper.buildProgram(vertexShaderSource, fragmentShaderSource);
		GLES20.glUseProgram(program);
    }

    //----------第七步:绘制
    public void draw(){
    	GLES20.glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
		GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, POSITION_COMPONENT_COUNT);
    }
}</span>

最后我们只需要在我们前面定义的MyRender中使用即可,此时MyRender代码(这里在构造函数中引入了Context上下文环境,因为需要我们的Triangle对象的构造函数需要Context),

如下(MyRender.java ):

<span style="font-size:14px;">package com.cumt.render;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import com.cumt.shape.Triangle;

import android.content.Context;
import android.opengl.GLSurfaceView.Renderer;
import android.util.Log;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glViewport;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;

public class MyRender implements Renderer {

	private Context context;

	public MyRender(Context context){
		this.context = context;
	}

	//定义三角形对象
	Triangle triangle;

	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		Log.w("MyRender","onSurfaceCreated");
		// TODO Auto-generated method stub
		//First:设置清空屏幕用的颜色,前三个参数对应红绿蓝,最后一个对应alpha
		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		triangle = new Triangle(context);
	}

	public void onSurfaceChanged(GL10 gl, int width, int height) {
		Log.w("MyRender","onSurfaceChanged");
		// TODO Auto-generated method stub
		//Second:设置视口尺寸,即告诉opengl可以用来渲染的surface大小
		glViewport(0,0,width,height);
	}

	public void onDrawFrame(GL10 gl) {
		Log.w("MyRender","onDrawFrame");
		// TODO Auto-generated method stub
		//Third:清空屏幕,擦除屏幕上所有的颜色,并用之前glClearColor定义的颜色填充整个屏幕
		glClear(GL_COLOR_BUFFER_BIT);
		//绘制三角形
		triangle.draw();
	}
}</span>

运行结果如下:

时间: 2024-08-29 00:03:17

OpenglES2.0 for Android:来画个三角形吧的相关文章

OpenglES2.0 for Android:来画个立方体吧

OpenglES2.0 for Android:来画个立方体吧 前言: 前面一直在说OpenglES2.0二维图形的绘制,接下来我们步入三维的世界 ,三维世界远比二维要有趣的多,与此同时复杂性也要高得多,在unity3D中我们可以很容易的就创建 一个立方体,而在OpenglES2.0中这个过程要复杂得多,但是更加有趣 .先来看下我们的整个流程: 摄像机的设置: 想想你的摄像头,它的位置不同,朝向不同,对同一个事物拍摄得到的画面肯定是不同的,Opengl中的摄像头和我们日常生活中的摄像头是一样的道

OpenglES2.0 for Android:来画个球吧

OpenglES2.0 for Android:来画个球吧 理解球坐标系 首先看下球的坐标系 ,如图 : (图来自百度百科 ) 设球上有一点 A ,球心为O ,OA在 xOy上的投影与X轴夹角为 φ (范围为 0 到360 ,单位 :度), OA在与Z的夹角为 θ (范围为 0 到 180  ,单位:度 ),球的半径为r,则有 ; r * sin θ = y / sin φ    r * sinθ  = x / cos φ   z = r * cos θ 由此可得 X,Y,Z坐标 我们前面已经知

OpenglES2.0 for Android:来画个矩形吧

OpenglES2.0 for Android:来画个矩形吧 上一节中我们绘制了一个三角形,我们在上一节的基础上来完成矩形的绘制 . OK,开始动手做吧,首先在上一节的项目中的shape目录下新建一个类--Square (Square.java),然后定义矩形的四个顶点的坐标,此时代码如下(Square.java): <span style="font-size:14px;">package com.cumt.shape; public class Square { //f

OpenglES2.0 for Android:再谈纹理映射

OpenglES2.0 for Android:再谈纹理映射 前言 上一节我们实现了一个简单的纹理映射的例子--一个简单的贴图,这节我们来做一些稍微复杂一点的例子,最后再给我们前面的立方体做一个纹理. 纹理拉伸 重复拉伸方式 这种是经常使用的一张纹理拉伸方式,常用于绘制一些重复的元素,比如我们在游戏绘制一幅方格式的地图时.使用重复拉伸方式使得纹理能够根据目标平 面的大小自动重复,这样既不会失去纹理图的效果,也可以节省内存.如下图所示: 实现起来很简单,我们回到上节的项目,找到我们纹理的工具类Te

OpenglES2.0 for Android:纹理映射

OpenglES2.0 for Android:纹理映射 前言 纹理映射又叫做纹理贴图,是将纹理空间中的纹理像素映射到屏幕空间中的像素的过程.就是把一幅图像贴到三维物体的表面上来增强真实感, 可以和光照计算.图像混合等技术结合起来形成许多非常漂亮的效果 (百度百科).简单来说,纹理就是一个图形或者照片,我们可以将它们 加载到Opengl中用以美化我们绘制的物体. 前期准备 我们现在准备实现这样一个功能:将一张图片贴到一个正方形中 .我们在以前画矩形的那节代码的基础上进行实现纹理贴图.这里我们新建

OpenglES2.0 for Android:各种变换来一波

OpenglES2.0 for Android:各种变换来一波 监听屏幕事件 在进行各种变换之前,我们先来了解一下如何监听屏幕的事件.我们下面的变换都需要用立方体来演示,所以我们继续使用上一节的绘制立方体的内容 首先新建一个项目 OpengESChange ,将上一节中关于绘制立方体的代码复制过来 .在前面我们一直在使用 android.opengl.GLSurfaceView 在第一篇中我们已经知道了这个类的作用,为了监听屏幕事件,我们创建一个类继承自该类,重写其onTouchEvent方法.

Android上使用OpenglES2.0遇到的一点问题

按照教程开发OpenglES2.0应用,遇到Logcat报错“Called unimplemented OpenGL ES API” 在论坛和stackoverflow上找到了答案. 1.manifest里面加上 <uses-feature android:glEsVersion="0x00020000" android:required="true" /> 2.surfaceView要设置 mGLSurfaceView.setEGLContextCli

基于Cocos2d-x学习OpenGL ES 2.0系列——你的第一个三角形(1)

[本系列转自]http://cn.cocos2d-x.org/tutorial/lists?id=79 前言 在本系列教程中,我会以当下最流行的2D引擎Cocos2d-x为基础,介绍OpenGL ES 2.0的一些基本用法.本系列教程的宗旨是OpenGL扫盲,让大家在使用Cocos2d-x过程中,知其然,更知其所以然.本系列教程不会涉及非常底层的数学原理,同时也不会过多地提及OpenGL本身的一些细节知识.但是我会在每篇文章的最后给出一些参考链接,大家可以顺藤摸瓜,一举Get OpenGL这个新

Effective前端3:用CSS画一个三角形

三角形的场景很常见,打开一个页面可以看到各种各样的三角形: 由于div一般是四边形,要画个三角形并不是那么直观.你可以贴一张png,但是这种办法有点low,或者是用svg的形式,但是太麻烦.三角形其实可以用CSS画出来.如上图提到,可以分为两种三角形,一种是纯色的三角形,第二种是有边框色的三角形,先介绍最简单的纯色三角形. 1. 三角形的画法 三角形可以用border画出来,首先一个有四个border的div应该是这样的: 然后把它的高度和宽度去掉,剩下四个border,就变成了: 再把bord