OpenGLES开发基础(二、创建和使用着色器)

在上一节的文章中,我们对着色器的基础有一些基础的认识。学习到这里,下面我们挽起裤管就是干,看看着色器在开发中的应用

有不对的地方希望大佬拍板 ヽ(´з`)ノ

创建着色器(shader)

这里先贴上安卓代码,然后在做进一步的讲解(笔者一直在使用这个,感觉非常的好用)

 public int loadshader(int shaderType, String source) {
        int shader = GLES20.glCreateShader(shaderType);
        if (shader != 0) {
            //加载shader的源代码
            GLES20.glShaderSource(shader, source);
            //编译shader
            GLES20.glCompileShader(shader);
            //声明一个数组存放着色器
            int[] shaderNum = new int[1];
            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, shaderNum, 0);
            if (shaderNum[0] != 0) {
                Log.e("ES20_ERROR", "Could not compile shader " + shaderType + ":");
                Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
                GLES20.glDeleteShader(shader);
                shader = 0;
            }
        }
        return shader;
    }
    public int CreateProgram(String vertexSource, String fragmentSource) {
        //加载顶点着色器
        int vertexProgram = loadshader(GLES20.GL_VERTEX_SHADER, vertexSource);
        if (vertexProgram == 0) {
            return 0;
        }
        //加载片元着色器
        int fragmentProgram = loadshader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
        if (fragmentProgram == 0) {
            return 0;
        }
        //创建程序
        int program = GLES20.glCreateProgram();
        //若程序创建成功则向程序中加入顶点着色器与片元着色器
        if (program != 0) {
            //绑定顶点着色器
            GLES20.glAttachShader(program, vertexProgram);
            checkGlError("glAttachShader");//检测错误
            //绑定片元着色器
            GLES20.glAttachShader(program, fragmentProgram);
            checkGlError("fragmentProgram");//检测错误
            GLES20.glLinkProgram(program);//链接程序
            //---------实际这一步都已经成功了,只是要检测错误的着色器
            //存放连接错误的数据
            int[] linkstatus = new int[1];
            //获取program的连接情况
            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkstatus, 0);
            if (linkstatus[0] != GLES20.GL_TRUE) {
                Log.e("ES20_ERROR", "Could not link program: ");
                Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(program));
                GLES20.glDeleteProgram(program);
                program = 0;
            }
        }
        return program;
    }
    //检测错误
    public void checkGlError(String op) {
        int error;
        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            Log.e("ES20_ERROR", op + ": glError " + error);
            throw new RuntimeException(op + ": glError " + error);
        }
    }

好了,这就是全部的生成顶点着色器和片元着色器的全部代码了,是不是很简单。哈哈......学到这里,笔者来给大家讲解一些方法在着色器中的作用,方面大家理解,记忆

glCreateProgram

函数原型:int glCreateProgram ()
在连接着色器之前,应该先创建着色器接收程序的容器,该方法相当于就是创建一个容器。
如果创建成功,返回一个正整数作为该着色器程序的id。

glAttachShader

函数原型:void glAttachShader (int program, int shader)
绑定顶点着色器着色器和片元着色器,绑定到容器中。它们都是同一个方法。

glLinkProgram

函数原型:void glLinkProgram (int program)
链接程序

glGetProgramiv

函数原型:void glGetProgramiv(int var0, int var1, int[] var2, int var3)
获取program的连接情况

在这里创建着色器程序的代码基本介绍完毕了,在CreateProgram方法中,返回一个int类型的变量,我暂时把这个int类型的变量叫做变量A,在使用时,还会用到glUseProgram。

glUseProgram

函数原型:void glUseProgram(int var0);
它的作用就是使用某套share程序


使用着色器(shader)

同样,我们先上代码,从代码中讲解

    //初始化着色器
    public void initShader(FGLView view) {
        //加载顶点着色器的脚本内容
        String mVertexShader = loadFromAssetsFile("vertex.glsl", view.getResources());
        //加载片元着色器的脚本内容
        String mFragmentShader = loadFromAssetsFile("frag.glsl", view.getResources());
        //基于顶点着色器与片元着色器创建程序
        mProgram = CreateProgram(mVertexShader, mFragmentShader);
        //获取程序中顶点位置属性引用id
        maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
        //获取程序中顶点纹理坐标属性引用id
        maTexCoorHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoor");
        //获取程序中总变换矩阵引用id
        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
    }
    //流文件读取assets下面的着色器文件
    public String loadFromAssetsFile(String frame, Resources r) {
        String result = null;
        try {
            InputStream is = r.getAssets().open(frame);
            int ch = 0;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while ((ch = is.read()) != -1) {
                baos.write(ch);
            }
            byte[] buff = baos.toByteArray();
            baos.close();
            is.close();
            result = new String(buff, "UTF-8");
            result = result.replaceAll("\\r\\n", "\n");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

在这个代码中,loadFromAssetsFile方法我们不用多说,就是在代码中通过流的形式,加载着色器文件

下面为大家讲解用到的方法

glGetAttribLocation

函数原型:int glGetAttribLocation(int var0, String var1)
这个方法主要是用来绑定属性的id,这里的id是顶点着色器中的id(位置,纹理,变换矩阵名称),如果有读者不明白顶点着色器,请参考第一章(https://www.jianshu.com/p/ca4c54b2b3db

 public void drawSelf(int texId) {
        //制定使用某套share程序
        GLES20.glUseProgram(mProgram);
        MatrixState.setInitStack();
        //位移操作
        MatrixState.transtate(0, 0, 1);
        MatrixState.rotate(xAngle, 1, 0, 0);
        MatrixState.rotate(yAngle, 0, 1, 0);
        MatrixState.rotate(zAngle, 0, 0, 1);
        //给着色器赋值
        GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1,
                false, MatrixState.getFinalMatrix(), 0);
        //画笔设置顶点数据
        GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT,
                false, 0, mVertexBuffer);
        //画笔给纹理设置数据
        GLES20.glVertexAttribPointer(maTexCoorHandle, 2, GLES20.GL_FLOAT,
                false, 0, mTexCoorBuffer);
        //开启顶点和纹理绘制
        GLES20.glEnableVertexAttribArray(maPositionHandle);
        GLES20.glEnableVertexAttribArray(maTexCoorHandle);
        //绑定纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
        //绘制
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);
    }
glVertexAttribPointer

画笔设置顶点、颜色、纹理数据
函数原型:void glVertexAttribPointer(int indx, int size, int type, boolean normalized, int stride, Buffer ptr)
indx:指定要修改的顶点属性的索引值
size:指定每个顶点属性的组件数量。必须为1、2、3或者4。初始值为4。(如position是由3个(x,y,z)组成,而颜色是4个(r,g,b,a))
type:指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值为GL_FLOAT。
normalized:指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE)。
stride:指定连续顶点属性之间的偏移量。如果为0,那么顶点属性会被理解为:它们是紧密排列在一起的。初始值为0。
ptr:指定一个指针,指向数组中第一个顶点属性的第一个组件。初始值为0。

glEnableVertexAttribArray

开启顶点和纹理绘制(允许顶点着色器读取GPU(服务器端)数据。)
函数原型:void glEnableVertexAttribArray(int var0)
var0:着色器顶点数据

glActiveTexture

该函数选择一个纹理单元,线面的纹理函数将作用于该纹理单元上,参数为符号常量GL_TEXTUREi ,i的取值范围为0~K-1,K是OpenGL实现支持的最大纹理单元数,可以使用GL_MAX_TEXTURE_UNITS来调用函数glGetIntegerv()获取该值。

glBindTexture

绑定纹理数据
函数原型:glBindTexture(int var0, int var1);
var0:纹理类型
var1:纹理数据

glDrawArrays和glDrawElements

glDrawArrays

函数原型:GL_APICALL void GL_APIENTRY glDrawArrays (GLenum mode, GLint first, GLsizei count);
mode:绘制方式,OpenGL2.0以后提供以下参数:GL_POINTS、GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN。
first:从数组缓存中的哪一位开始绘制,一般为0。
count:数组中顶点的数量。

glDrawElements

函数原型:void glDrawElements( GLenum mode, GLsizei count,
GLenum type, const GLvoid *indices);
mode指定绘制图元的类型,它应该是下列值之一,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为绘制图元的数量乘上一个图元的顶点数。
type为索引值的类型,只能是下列值之一:GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT。
indices:指向索引存贮位置的指针

在这里着重讲解一下这几个方法:
GL_LINES_STRIP(条带线):按照顶点顺序连接顶点
GL_TRIANGLES(循环线):按照顶点顺序连接顶点,最后一个点连接第一点
GL_TRIANGLES(三角形):三个点一组,如果不够三个点就会舍弃 多余的点
GL_TRIANGLE_STRIP(三角形带):顶点按照顺序依次 组成三角形绘制,最后实际形成的是一个三角型带
GL_TRIANGLE_FAN(三角形扇面):将第一个点作为中心点,其他点作为边缘点,绘制一系列的组成扇形的三角形

相同点:它们的作用都是矩阵数据渲染图元、进行绘制
异同:(这是笔者自己的理解,文采有限...哈哈哈)在移动平台中,glDrawArrays绘制通过每三个点绘制一个三角形,在面点数据中,不会重用顶点数据。glDrawElements绘制也是通过每三个点绘制一个三角形,但是会重用顶点数据,也就是绘制相同的结果。glDrawElements需要的顶点数据会更少,看下面图更容易理解:
glDrawArray和glDrawElements.png

在MatrixState方法中,封装了对变换矩阵的常用方法(这套结构读者可以自己封装,方法名也可以自行命名)。在代码中解释也很清楚。请读者自己查看理解

public class MatrixState {
    private static float[] mProjMatrix = new float[16];//4x4矩阵 投影用
    private static float[] mVMatrix = new float[16];//摄像机位置朝向9参数矩阵
    private static float[] mMVPMatrix;//最后起作用的总变换矩阵
    static float[] mMMatrix = new float[16];//具体物体的移动旋转矩阵

    //获取不变换初始矩阵
    public static void setInitStack() {
        Matrix.setRotateM(mMMatrix, 0, 0, 1, 0, 0);
    }

    //设置沿xyz轴移动
    public static void transtate(float x, float y, float z) {
        Matrix.translateM(mMMatrix, 0, x, y, z);
    }

    //设置绕xyz轴转动
    public static void rotate(float angle, float x, float y, float z) {
        Matrix.rotateM(mMMatrix, 0, angle, x, y, z);
    }

    /**
     * 作用:设置摄像机
     * 前三个:摄像机位置x y z
     * 中间三个:摄像机目标点x y z
     * 后三个:摄像机UP向量X分量,摄像机UP向量Y分量,摄像机UP向量Z分量
     */
    public static void setCamera(float cx, float cy, float cz, float tx, float ty, float tz, float upx, float upy, float upz) {
        Matrix.setLookAtM(mVMatrix, 0,
                cx, cy, cz,
                tx, ty, tz,
                upx, upy, upz);
    }
    /**
     * 作用:设置透视投影参数
     * 前四个:near面的left,right,bottom,top
     * 后两个:near面距离,far面距离
     */
    public static void setProject(float left, float right, float bottom, float top, float near, float far) {
        Matrix.frustumM(mProjMatrix, 0, left, right, bottom, top, near, far);
    }

    //获取具体物体的总变换矩阵( // 合并投影和视口矩阵 )
    public static float[] getFinalMatrix() {
        mMVPMatrix = new float[16];
        Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
        return mMVPMatrix;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342