《OpenGL从入门到放弃06》纹理图片显示

之前的文章:
《OpenGL从入门到放弃01 》一些基本概念
《OpenGL从入门到放弃02 》GLSurfaceView和Renderer
《OpenGL从入门到放弃03 》相机和视图
《OpenGL从入门到放弃04 》画一个长方形
《OpenGL从入门到放弃05》着色器语言

这一节我们来学习一下如何用OpenGL显示一张图片,先上图


在OpenGL中显示一张图片,需要使用到纹理采样函数 texture2D,整个步骤大概如下

我们在《OpenGL从入门到放弃04 》画一个长方形的基础上修改

1、修改着色器

    // 顶点着色器的脚本
    String vertexShaderCode =
            "uniform mat4 uMVPMatrix;" +         //接收传入的转换矩阵
            "attribute vec4 vPosition;" +      //接收传入的顶点
            "attribute vec2 aTexCoord;" +       //接收传入的顶点纹理位置
            "varying vec2 vTextureCoord;" +     //增加用于传递给片元着色器的纹理位置变量
            "void main() {" +
                    "gl_Position = uMVPMatrix * vPosition;" +  //矩阵变换计算之后的位置
                    "vTextureCoord = aTexCoord;" +
            " }";

    // 片元着色器的脚本
    String fragmentShaderCode =
            " precision mediump float;" +  // 声明float类型的精度为中等(精度越高越耗资源)
              "varying vec2 vTextureCoord;" +  //纹理坐标
              "uniform sampler2D sTexture;" + //纹理采样器,代表一副纹理
              " void main() {" +
                     "gl_FragColor = texture2D(sTexture,vTextureCoord);" +//进行纹理采样
               " }";

片元的颜色不再是简单的单色,而是通过texture2D进行纹理采样,得到的颜色。

纹理采样器 sampler2D sTexture,变量名sTexture是我们自己取的,我们不用为sTexture赋值,着色器会自动处理sTexture,指向我们待会儿要传过去的纹理图片,纹理坐标由顶点着色器传过来。

2、准备纹理坐标数据

先说一下纹理坐标系

纹理贴图:把一个纹理(对于2D贴图,可以简单的理解为图片),按照所期望的方式显示在图形的表面。

如果想把一幅纹理映射到相应的几何图元,就必须告诉GPU如何进行纹理映射,也就是为图元的顶点指定恰当的纹理坐标。纹理坐标用浮点数来表示,范围一般从0.0到1.0,左上角坐标为(0.0,0.0),右上角坐标为(1.0,0.0),左下角坐标为(0.0,1.0),右下角坐标为(1.0,1.0)

纹理坐标

顶点坐标

看上面两张图就明白了,假如长方形的顶点顺序是V1、v2、v4、v3,那么纹理坐标对应V1、v2、v4、v3,才能正常显示

了解了纹理坐标之后,开始写代码了~

根据长方形的顶点坐标,定义对应的纹理坐标

private void initVertext() {
        float vertices[] = new float[]{
                -1, 1, 0,
                -1, -1, 0,
                1, 1, 0,
                1, -1, 0,
        };//顶点位置

        float[] colors = new float[]{
                0, 0,
                0, 1,
                1, 0,
                1, 1,
        };//纹理顶点数组
        //转换成 FloatBuffer
        mVertexBuffer = GLUtil.floatArray2FloatBuffer(vertices);
        mTexCoordBuffer = GLUtil.floatArray2FloatBuffer(colors);
    }

这一步非常简单了,顶点坐标跟纹理坐标一一对应,比如顶点在左上角[-1,1],纹理坐标对应[0,0],此处忽略z轴。不明白再看看上面两张图

3、获取纹理坐标句柄

顶点着色器中增加了纹理坐标变量 vTextureCoord,
需要给他赋值,所以先获取句柄,然后在draw方法进行设置数据

        //纹理位置句柄
        maTexCoordHandle = GLES20.glGetAttribLocation(mProgram,"aTexCoord");

光有纹理坐标,没有纹理数据也不行,所以下一步是设置纹理数据

4、设置纹理数据

先上代码

    private void initTexture(){
        int textures[] = new int[1]; //生成纹理id

        GLES20.glGenTextures(  //创建纹理对象
                1, //产生纹理id的数量
                textures, //纹理id的数组
                0  //偏移量
        );
        mTextureId = textures[0];

        //绑定纹理id,将对象绑定到环境的纹理单元
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);

        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);//设置MIN 采样方式
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);//设置MAG采样方式
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);//设置S轴拉伸方式
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);//设置T轴拉伸方式

        if (mBitmap == null){
            Log.e("lxb", "initTexture: mBitmap == null");
            return;
        }
        //加载图片
        GLUtils.texImage2D( //实际加载纹理进显存
                GLES20.GL_TEXTURE_2D, //纹理类型
                0, //纹理的层次,0表示基本图像层,可以理解为直接贴图
                mBitmap, //纹理图像
                0 //纹理边框尺寸
        );
    }

设置纹理数据有几个步骤

  1. 创建纹理对象 GLES20.glGenTextures,可以拿到纹理id
  2. 绑定纹理id GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);
  3. 设置采样方法 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
    GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST)
  4. 实际加载纹理进显存 GLUtils.texImage2D,需要先准备Bitmap

4、纹理坐标赋值再绘制

纹理数据设置好了,纹理句柄有了,只差最后一步,把纹理坐标传给着色器

        //把纹理坐标传给着色器
        GLES20.glEnableVertexAttribArray(maTexCoordHandle);
        GLES20.glVertexAttribPointer(maTexCoordHandle, 2,
                GLES20.GL_FLOAT, false,
                2*4, mTexCoordBuffer);

主要修改以上几点,其它的保存不变,整理后的代码如下

/**
 *
 * 增加纹理
 */
public class Square02 {

    // 顶点着色器的脚本
    String vertexShaderCode =
            "uniform mat4 uMVPMatrix;" +         //接收传入的转换矩阵
            "attribute vec4 vPosition;" +      //接收传入的顶点
            "attribute vec2 aTexCoord;" +       //接收传入的顶点纹理位置
            "varying vec2 vTextureCoord;" +     //增加用于传递给片元着色器的纹理位置变量
            "void main() {" +
                    "gl_Position = uMVPMatrix * vPosition;" +  //矩阵变换计算之后的位置
                    "vTextureCoord = aTexCoord;" +
            " }";


    // 片元着色器的脚本
    String fragmentShaderCode =
            " precision mediump float;" +  // 声明float类型的精度为中等(精度越高越耗资源)
              "varying vec2 vTextureCoord;" +
              "uniform sampler2D sTexture;" + //纹理采样器,代表一副纹理
              " void main() {" +
                     "gl_FragColor = texture2D(sTexture,vTextureCoord);" +//进行纹理采样
               " }";

    private FloatBuffer mVertexBuffer;  //顶点坐标数据要转化成FloatBuffer格式
    private FloatBuffer mTexCoordBuffer;//顶点纹理坐标缓存


    // 数组中每3个值作为一个坐标点
    static final int COORDS_PER_VERTEX = 3;

    //一个顶点有3个float,一个float是4个字节,所以一个顶点要12字节
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per mVertex

    //当前绘制的顶点位置句柄
    private int vPositionHandle;
    //变换矩阵句柄
    private int mMVPMatrixHandle;
    //这个可以理解为一个OpenGL程序句柄
    private  int mProgram;
    //纹理坐标句柄
    private int maTexCoordHandle;

    //变换矩阵,提供set方法
    private float[] mvpMatrix = new float[16];
    private int mTextureId;

    public void setMvpMatrix(float[] mvpMatrix) {
        this.mvpMatrix = mvpMatrix;
    }
    private Bitmap mBitmap;

    public Square02(Bitmap bitmap) {
        this.mBitmap = bitmap;
        initVertext();
        initShder();
        initTexture();
    }

    private void initVertext() {
        float vertices[] = new float[]{
                -1, 1, 0,
                -1, -1, 0,
                1, 1, 0,
                1, -1, 0,
        };//顶点位置

        float[] colors = new float[]{
                0, 0,
                0, 1,
                1, 0,
                1, 1,
        };//纹理顶点数组
        mVertexBuffer = GLUtil.floatArray2FloatBuffer(vertices);
        mTexCoordBuffer = GLUtil.floatArray2FloatBuffer(colors);
    }

    private void initShder() {
        //获取程序,封装了加载、链接等操作
        mProgram = GLUtil.createProgram(vertexShaderCode,fragmentShaderCode);
        /***1.获取句柄*/
        // 获取顶点着色器的位置的句柄(这里可以理解为当前绘制的顶点位置)
        vPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        // 获取变换矩阵的句柄
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        //纹理位置句柄
        maTexCoordHandle = GLES20.glGetAttribLocation(mProgram,"aTexCoord");
    }

    private void initTexture(){
        int textures[] = new int[1]; //生成纹理id

        GLES20.glGenTextures(  //创建纹理对象
                1, //产生纹理id的数量
                textures, //纹理id的数组
                0  //偏移量
        );
        mTextureId = textures[0];

        //绑定纹理id,将对象绑定到环境的纹理单元
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);

        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
                GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);//设置MIN 采样方式
//        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
//                GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);//设置MAG采样方式
//        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
//                GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);//设置S轴拉伸方式
//        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
//                GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);//设置T轴拉伸方式

        if (mBitmap == null){
            Log.e("lxb", "initTexture: mBitmap == null");
            return;
        }
        //加载图片
        GLUtils.texImage2D( //实际加载纹理进显存
                GLES20.GL_TEXTURE_2D, //纹理类型
                0, //纹理的层次,0表示基本图像层,可以理解为直接贴图
                mBitmap, //纹理图像
                0 //纹理边框尺寸
        );
    }

    public void draw() {
        // 将程序添加到OpenGL ES环境
        GLES20.glUseProgram(mProgram);

        /**设置数据*/
        // 启用顶点属性,最后对应禁用
        GLES20.glEnableVertexAttribArray(vPositionHandle);
        GLES20.glEnableVertexAttribArray(maTexCoordHandle);

        //设置三角形坐标数据(一个顶点三个坐标)
        GLES20.glVertexAttribPointer(vPositionHandle, 3,
                GLES20.GL_FLOAT, false,
                3 * 4, mVertexBuffer);
        //设置纹理坐标数据
        GLES20.glVertexAttribPointer(maTexCoordHandle, 2,
                GLES20.GL_FLOAT, false,
                2*4, mTexCoordBuffer);

        // 将投影和视图转换传递给着色器,可以理解为给uMVPMatrix这个变量赋值为mvpMatrix
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

//        //设置使用的纹理编号
//        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

        /** 绘制三角形,三个顶点*/
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

        // 禁用顶点数组(好像不禁用也没啥问题)
        GLES20.glDisableVertexAttribArray(vPositionHandle);
        GLES20.glDisableVertexAttribArray(maTexCoordHandle);
    }
}

源码已经给出,大家可以先动手试试,有问题欢迎留言。

下一篇文章比较有意思了,关于滤镜的,实现黑白图片、冷暖色调、四分镜等等,敬请期待。

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

推荐阅读更多精彩内容