OpenGL ES初探(一) -- 用OpenGL画一个三角形(1)

OpenGL ES初探(一) – 用OpenGL画一个三角形(1)

目录

初始化OpenGL

创建MainActivity类继承于AppCompatActivity(其他Activity也可以)

初始化GLSurfaceView

package com.yxf.triangle;

import android.opengl.GLSurfaceView;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    //1,定义GLSurfaceView对象,这个View提供了OpenGL ES的显示窗口
    private GLSurfaceView glSurfaceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //2,创建GLSurfaceView对象
        glSurfaceView = new GLSurfaceView(this);
        //3,设置OpenGL ES版本为2.0
        glSurfaceView.setEGLContextClientVersion(2);
        //4,设置渲染器
        glSurfaceView.setRenderer(new MyRenderer(this));
        //5,设置GLSurfaceView为主窗口
        setContentView(glSurfaceView);
    }

    @Override
    protected void onPause() {
        super.onPause();
        //6.1,当Activity暂停时暂停glSurfaceView
        glSurfaceView.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        //6.2,当Activity恢复时恢复glSurfaceView
        glSurfaceView.onResume();
    }
}

onPause()onResume()中的6.1和6.2代码非常重要,千万别忘记加哦

创建MyRenderer

在MainActivity同目录下创建一个MyRenderer

实现android.opengl.GLSurfaceView.Renderer接口(注意Renderer有好几个别搞错了)

package com.yxf.triangle;

import android.content.Context;
import android.opengl.GLSurfaceView;

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

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
//1,静态导入OpenGL ES2.0包
import static android.opengl.GLES20.*;

public class MyRenderer implements GLSurfaceView.Renderer {

    //5,定义顶点坐标所需数字数量,二维图形只需要x,y两个 , 三维需要x,y,z三个.
    //我们先画个二维的三角形,所以顶点坐标所需数字常量设置为2.
    private static final int POSITION_COMPONENT_COUNT = 2;
    //6,设置三角形的三个顶点坐标: 左下(-0.5f,0f),右下(0.5f,0f),中上(0f,0.5f)
    private float[] triangleVertices = {
            -0.5f,0f,
            0.5f,0f,
            0f,0.5f,
    };

    //7,定义Native的浮点数储存所需字节数
    private static final int BYTES_PER_FLOAT = 4;
    //8,定义Native浮点数缓存的引用
    private final FloatBuffer vertexData;


    private Context context;

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

        //9,将三角形顶点数据储存到Native的浮点缓存中
        //注意必须使用ByteBuffer.allocateDirect而不是ByteBuffer.allocate,前者分配的才是不会被垃圾回收的本地内存
        vertexData = ByteBuffer.allocateDirect(triangleVertices.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        vertexData.put(triangleVertices);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //2,设置清空屏幕用的颜色
        glClearColor(0f, 1f, 0f, 0f);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //3,设置视口尺寸
        glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //4,根据步骤2设置的颜色清空屏幕,注意如果不清空会保留之前的绘制,所以这步一般是必要的
        glClear(GL_COLOR_BUFFER_BIT);
    }
}

步骤1,后续的OpenGL ES方法都将以这种方式调用,这将极大节约我们的开发时间.

创建顶点着色器

在res中创建raw目录

在raw目录中添加文件triangle_vertex_shader.glsl

代码如下

attribute vec4 a_Position;

void main() {
    gl_Position = a_Position;
}

模糊的解释下,我们将会把我们在MyRenderer中定义的顶点传进a_Position中,然后着色器将a_Position赋值给gl_Position,gl_Position便是OpenGL所认定的最终顶点

创建片段着色器

在raw目录中添加文件triangle_fragment_shader.glsl

代码如下

precision mediump float;

uniform vec4 u_Color;

void main(){
    gl_FragColor = u_Color;
}

简单解释下precision mediump float定义浮点数精度为中等精度,
后续程序会将颜色值赋值给u_Color,
然后u_Color赋值给gl_FragColor,
OpenGL根据gl_FragColor给片段着色

编译GLSL Shader文件

创建CommonUtils类如下

package com.yxf.triangle;

import android.content.Context;
import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
//1,静态导入导入OpenGL ES 2.0常用方法
import static android.opengl.GLES20.*;

public class CommonUtils {

    private static final String TAG = "CommonUtils";

    /**
     * 用于读取GLSL Shader文件内容
     * @param context
     * @param resId
     * @return
     */
    public static String readTextFromResource(Context context, int resId) {
        StringBuilder builder = new StringBuilder();
        InputStream inputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader reader = null;
        try {
            inputStream = context.getResources().openRawResource(resId);
            inputStreamReader = new InputStreamReader(inputStream);
            reader = new BufferedReader(inputStreamReader);

            String nextLine;
            while ((nextLine = reader.readLine()) != null) {
                builder.append(nextLine);
                builder.append("\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
                if (inputStreamReader != null) {
                    inputStreamReader.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return builder.toString();
    }

    /**
     * 编译着色器
     * @param type
     * @param source
     * @return
     */
    public static int compileShader(int type, String source) {
        final int shaderId = glCreateShader(type);
        if (shaderId == 0) {
            //2,如果着色器创建失败则会返回0
            Log.w(TAG, "Could not create new shader");
            return 0;
        }
        //3,将Shader源文件加载进ID为shaderId的shader中
        glShaderSource(shaderId, source);
        //4,编译这个shader
        glCompileShader(shaderId);
        final int[] status = new int[1];
        //5,获取编译状态储存于status[0]
        glGetShaderiv(shaderId, GL_COMPILE_STATUS, status, 0);

        Log.v(TAG, "compile source : \n" + source + "\n" +
                "info log : " + glGetShaderInfoLog(shaderId));
        if (status[0] == 0) {
            //6,检查状态是否正常,0为不正常
            Log.w(TAG, "Compilation of shader failed.");
            return 0;
        }
        return shaderId;
    }

    /**
     * 编译顶点着色器
     * @param source
     * @return
     */
    public static int compileVertexShader(String source) {
        return compileShader(GL_VERTEX_SHADER, source);
    }

    /**
     *编译片段着色器
     * @param source
     * @return
     */
    public static int compileFragmentShader(String source) {
        return compileShader(GL_FRAGMENT_SHADER, source);
    }
}

更新MyRendereronSurfaceCreated如下

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    //2,设置清空屏幕用的颜色
    glClearColor(0f, 1f, 0f, 0f);
    //9,读取shader源文件
    String vertexShaderSource = CommonUtils.readTextFromResource(context, R.raw.triangle_vertex_shader);
    String fragmentShaderSource = CommonUtils.readTextFromResource(context, R.raw.triangle_fragment_shader);

    //10,编译shader 源文件
    int vertexShader = CommonUtils.compileVertexShader(vertexShaderSource);
    int fragmentShader = CommonUtils.compileFragmentShader(fragmentShaderSource);
}

把着色器链接进OpenGL程序

CommonUtils添加如下方法

    /**
     * 创建OpenGL对象,并添加着色器,返回OpenGL对象Id
     * @param vertexShaderId
     * @param fragmentShaderId
     * @return
     */
    public static int linkProgram(int vertexShaderId, int fragmentShaderId) {
        //7,创建OpenGL对象
        final int programId = glCreateProgram();
        if (programId == 0) {
            Log.w(TAG, "Create OpenGL program failed");
            return 0;
        }

        //8,在program上附上着色器
        glAttachShader(programId, vertexShaderId);
        glAttachShader(programId, fragmentShaderId);
        //9,链接程序
        glLinkProgram(programId);
        final int[] status = new int[1];
        glGetProgramiv(programId, GL_LINK_STATUS, status, 0);
        Log.v(TAG, "Results of linking program : \n" + glGetProgramInfoLog(programId));
        if (status[0] == 0) {
            Log.w(TAG, "Link program failed");
            return 0;
        }
        return programId;
    }

这个方法主要是创建OpenGL的Program对象,然后将着色器链接进Program中,并返回Program的Id;

更新MyRenderer,在类中添加全局变量

//11,添加program对象Id定义
private int program;

更新MyRenderer.onSurfaceCreated,在onSurfaceCreated方法末尾添加如下代码

//12,将shader添加进program,并获得Program的Id
program = CommonUtils.linkProgram(vertexShader, fragmentShader);
//13,使用这个program
glUseProgram(program);

关联Shader数据

首先获得Shader中u_Colora_Position的位置

MyRenderer类中添加全局变量

//14,定义u_Color和a_Position相关变量
private static final String U_COLOR = "u_Color";    private int uColorLocation;
private static final String A_POSITION = "a_Position";
private int aPositionLocation;

然后在MyRenderer.onSurfaceCreated最后添加

//15,获得u_Color和a_Position的位置
//一个Uniform,一个Attribute别搞错了
uColorLocation = glGetUniformLocation(program, U_COLOR);
aPositionLocation = glGetAttribLocation(program, A_POSITION);

现在我们已经获得u_Colora_Position的位置

接下来将数据写入其中吧

MyRenderer.onSurfaceCreated最后添加

//16,根据a_Position的位置将顶点数据传给a_Position
vertexData.position(0);
glVertexAttribPointer(aPositionLocation, POSITION_COMPONENT_COUNT, GL_FLOAT, false, 0, vertexData);
//17,激活顶点数组
glEnableVertexAttribArray(aPositionLocation);

绘制三角形

终于可以画三角形喽

MyRenderer.onDrawFrame最后添加

//18,更新着色器中u_Color的值(r,g,b,a) = (255,255,255,255)
glUniform4f(uColorLocation, 1f, 1f, 1f, 1f);
//19,告诉OpenGL,画三角形,从开头读顶点数据开始读,读三个顶点
glDrawArrays(GL_TRIANGLES, 0, 3);

运行程序,会得到如下效果

screenshot

思考

想想是不是哪里不对劲?

记得我们的三个顶点吗?

(-0.5f,0f),(0.5f,0),(0,0.5f)

这根据数学计算不应该是一个直角三角形吗?可是为什么最终的图形却不是直角三角形呢?

原来OpenGL的坐标系并不是按长度算的,而是按比例算的,也就是说OpenGL取屏幕中间为(0f,0f),x坐标是从[-1,1],y坐标范围也是从[-1,1],这样的话在竖屏模式下,以我手机(1080*1920)为例,明显y的1个单位长度(960px)就比x轴的1单位长度((540px)长多了,这就导致了最终不是直角三角形了.

那么如果遇到需要明确的设置长度的时候怎么办呢?

请听下回分解

示例源码

OpenGL-Triangle-1

参考

OpenGL ES应用开发实践指南 Android卷

相关链接

GLSL Shader常见属性说明

Android OpenGL ES 部分方法说明

OpenGL ES初探(二) – 用OpenGL画一个三角形(2)

猜你喜欢

转载自blog.csdn.net/dqh147258/article/details/79796379