Android OpenGl Es 学习(七):使用纹理

概述

这是一个新的系列,学习OpengGl Es,其实是《OpenGl Es 应用开发实践指南 Android卷》的学习笔记,感兴趣的可以直接看这本书,当然这个会记录自己的理解,以下只作为笔记,以防以后忘记

之后会对本书的前九章依次分析记录

Android OpenGl Es 学习(一):创建一个OpenGl es程序

Android OpenGl Es 学习(二):定义顶点和着色器

Android OpenGl Es 学习(三):编译着色器

Android OpenGl Es 学习(四):增填颜色

Android OpenGl Es 学习(五):调整宽高比

Android OpenGl Es 学习(六):进入三维

Android OpenGl Es 学习(七):使用纹理

Android OpenGl Es 学习(八):构建简单物体

Android OpenGl Es 学习(九):增添触摸反馈

最终是要实现一个曲棍球的简单游戏,类似这样的

理解纹理

Opengl中的纹理可以用来表示,图像,照片甚至是由一个图像算法生成的分行数据,每个二维的纹理都由许多小的纹理(texel)元素组成,他们是小块的数据,类似于前面讨论过的片段和像素,要使用纹理通常的方式就是直接加载一个图像数据

每一个二维纹理都有自己的坐标空间,其范围是从(0,0)到(1,1),按照惯例,一个维度叫做S,一个维度叫做T,当我们需要把纹理用于一个三角形或者一组三角形时,我们要为每一个顶点指定一组ST纹理坐标,以便让Opengl知道使用,纹理的那个部分绘制到三角形上,这些纹理坐标有时也被称为UV纹理坐标

对于一个Opengl纹理来说,他没有内在的方向性,因此我们可以用不同的坐标把他定向到任何我们喜欢的方向,但是大多数计算机图形都有一个默认的方向,他们通常规定为y轴向下为正,y的值随着向图像的底部移动而增加,我们需要记住,如果想用正确的方向观看图像,我们必须要考虑这一点

在Opengl2.0中,纹理不必是正方形,但是没个维度必须是2的次幂(POT),这样规定的原因是,非POT纹理可以使用的场合非常有限,POT纹理适用于各种情况

纹理的尺寸也有个最大值,他根据不同的实现而变化,但是它通常都是比较大的,比如 2048 * 2048

把纹理加载到opengl中

public static int loadTexture(Context context, int resourceid) {
    
    
        int[] textureObjectId = new int[1];
        GLES20.glGenTextures(1, textureObjectId, 0);
        if (textureObjectId[0] == 0) {
    
    
            Log.d("mmm", "纹理加载失败");
            return 0;
        }

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inScaled=false;

        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceid, options);
        if (bitmap==null){
    
    
            Log.d("mmm","加载图片为空");
            GLES20.glDeleteTextures(1,textureObjectId,0);
            return 0;
        }


        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureObjectId[0]);

        
        return textureObjectId[0];
    }
方法 描述
GLES20.glGenTextures(1, textureObjectId, 0) 创建一个纹理对象,opengl会把生成的纹理id储存到textureObjectId中,如果返回值是0就表示失败,如果返回值不为0就是成功
GLES20.glDeleteTextures(1,textureObjectId,0) 删除创建的纹理id
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureObjectId[0]); 绑定纹理id,第一个参数表示作为一个二维纹理对待,第二个参数表示,绑定到那个纹理对象ID

介绍一下上面代码

首先我们调用glGenTextures创建一个纹理对象,如果返回值为0则创建失败,直接返回,如果返回值不为0则创建成功继续向下走,然后加载图片数据,利用options.inScaled=false表示告诉Android我们需要图片的原始数据,而不是图像的缩小版本,然后调用BitmapFactory.decodeResource加载图像资源,如果加载图像资源失败,则调用GLES20.glDeleteTextures删除纹理id,如果图片资源加载成功,则调用GLES20.glBindTexture绑定纹理对象

理解纹理过滤

当纹理被扩大或者缩小时候,我们还需要使用纹理过滤,下面介绍一下纹理过滤,当我们渲染表面绘制一个纹理时,那个纹理的元素可能无法精确的映射到Opengl的片段上,有俩种情况,放大和缩小,当我们尽力把几个纹理元素挤进一个片段时,缩小就发生了,当我们把一个纹理元素扩展到多个片段时,放大就发生了,针对每一种情况,我们都可以配置opengl使用有个纹理过滤器

首先介绍俩个基本的过滤模式,最近邻过滤双线性过滤

最近邻过滤

这个方式每个片段选择最近的纹理元素,当我们放大纹理时,他的锯齿效果看起来很明显,每个纹理单元都清楚的显示一个小方块,当我们缩小纹理时,因为没有足够的片段绘制所有的纹理单元,许多细节会被丢失,如下图:

双线性过滤

双线性过滤使用双线性插值平滑像素间的过渡,而不是为每个片段使用最近的纹理元素,Opengl会使用4个邻接的纹理元素,并在他们之间使用一个线性插值算法,之所以叫他双线性,是因为他沿着俩个维度插值的,下面是双线性过滤放大后的图像

这个纹理看起来比上面的平滑多了

MIP贴图

尽管双线性过滤很适合处理过滤,但是当缩小到超过一定大小时候,他就不好用了,一个纹理在渲染表面占用的大小减少的越多,就会有越多的纹理拥挤到一个片段上,因为双线性过滤给每个片段使用四个纹理元素,我们将失去很多细节,因为每一帧都会选择不同的纹理元素,还会引起移动中物体的闪烁

为了克服这些缺陷,我们可以使用MIP贴图技术,他们可以用来生成一组优化过的不同大小纹理,当生成这组纹理的时候,Opengl会使用所有的纹理元素,生成每个级别的纹理,当过滤纹理时,还要确保所有的纹理元素都能被使用,在渲染时Opengl会根据每个片段的纹理数量为每个片段选择适合的级别,图下图

使用MIP贴图会占用更多的内存,但是渲染也会变快,是因为这种小级别的纹理在GPU的纹理缓存占用较少的空间,如上图所示,MIP贴图会选择最合适的纹理级别,然后用优化过的纹理做双线性过滤每个级别的纹理都是用来自所有的纹理纹理信息构建的,因此得到的这些图看起来更好些,保留着更多的细节

三线性过滤

如果opengl在不同的MIP贴图级别之间来回切换,当我们使用双线性过滤来使用MIP贴图时,在其渲染的场景中,在不同的MIP贴图切换时,我们有时候看到明显的跳跃和线条,我们可以切换到三线性过滤,告诉opengl在俩个最邻近的MIP贴图级别之间也要插值,这样有助于消除每个MIP贴图之间的过渡,并且得到一个更平滑的图像

设置默认的纹理过滤参数

先上代码

      GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

我们用一个glTexParameteri来调用设置每一个过滤器,GL_TEXTURE_MIN_FILTER指缩小情况,GL_TEXTURE_MAG_FILTER指放大情况,对于缩小的情况,我们选择GL_LINEAR_MIPMAP_LINEAR他告诉opengl使用三线性过滤,对于放大的情况我们使用GL_LINEAR他告诉opengl使用双线性过滤

加载纹理到OpenGl并返回id

   GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,bitmap,0);

这个调用告诉opengl读入bitmap图片数据,并把它复制到当前绑定的纹理id上

既然这些数据已经被加载到OpenGl中,我们就不需要再继续持有bitmap的位图数据了,正常情况下,释放这个位图会花费Dalvik几个垃圾回收周期,我们可以调用bitmap.recycle,让他立即回收

生成MIP贴图

生成MIP贴图是一个比较容易的事情,直接调用下面方法

   GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);

解除纹理绑定

既然我们完成了纹理的加载,那我们需要解除与这个纹理的绑定,这样我们就不会发生调用其他纹理方法,意外改变这个纹理情况

       GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0);

传入0表示与当前纹理解除绑定,最后把纹理id返回就行了

看下完成代码

   //返回纹理id
    public static int loadTexture(Context context, int resouurceId) {
    
    
        int[] textureId = new int[1];
        //获取纹理id
        GLES20.glGenTextures(1, textureId, 0);
        if (textureId[0] == 0) {
    
    
            Log.d("mmm", "没有成功创建一个新的Texture");
            return 0;
        }

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inScaled = false;

        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resouurceId, options);

        if (bitmap == null) {
    
    
            Log.d("mmm", "创建bitmap失败");
            GLES20.glDeleteTextures(1, textureId, 0);
            return 0;
        }

        /**
         * 第一个参数:告诉opengl作为一个二维纹理对待
         * 第二个参数:告诉opengl,要绑定那个纹理id
         */
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0]);

        //设置过滤器
        /**
         * GL_TEXTURE_MIN_FILTER:在缩小的情况下用  GL_LINEAR_MIPMAP_LINEAR:三线性过滤
         * GL_TEXTURE_MAG_FILTER:在放大的情况下用  GL_TEXTURE_MAG_FILTER:双线性过滤
         */
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

        //把图片加载到opengl里面
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        //释放bitmap
        bitmap.recycle();
        //生成mip贴图
        GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);

        //解除纹理绑定  第二个参数传0 就是解除绑定
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);

        return textureId[0];

    }

创建新的着色器集合

在把纹理绘制到屏幕之前,我们需要创建一套新的着色器,他们可已接收纹理并把它绘制到片段上,

创建新的顶点找色器

 attribute vec4 a_Position;
 //具有2分量的s t 纹理坐标
 attribute vec2 a_TextureCoordinates;

 varying vec2 v_TextureCoordinates;

 uniform mat4 u_Matrix;

  void main() {
      gl_Position =  u_Matrix * a_Position;
      v_TextureCoordinates = a_TextureCoordinates;
   }

这个代码,首先设置一个uniform的矩阵,然后还是熟悉的位置属性a_Position,我们新加了一个新属性a_TextureCoordinates,他有来个分量分别为S坐标和T坐标,所以被定义为一个vec2,我们把这些坐标传给顶点着色器被插值的varying,称为v_TextureCoordinates

创建新的片段着色器

 precision mediump float;
//纹理的具体数据
 uniform sampler2D u_TextureUnit;

 //纹理坐标st
 varying vec2 v_TextureCoordinates;
   void main() {
        //texture2D:根据纹理坐标st,取出具体的颜色值
        gl_FragColor = texture2D(u_TextureUnit,v_TextureCoordinates);
    }

为了把纹理绘制到物体上,opengl会为每个片段都调用片段着色器,每个调用者都接收v_TextureCoordinates的纹理坐标,片段找色器通过u_TextureUnit接收实际的纹理数据,u_TextureUnit定义为sampler2D,这个变量类型指的是一个二维纹理数组

被插值的纹理坐标和纹理数据,传递给着色器函数texture2D他会读取纹理中那个特定坐标的颜色值,接着把结果赋值给gl_FragColor设置片段的颜色

按照面向对象的形式分类

首先我们先把顶点数据分类,每个类代表一个物理对象的类型,我们将桌子创建一个新类,并为木槌创建一个新类,我们创建Mallet类管理木槌数据,Table管理桌子数据

加入桌子数据

定义一个存储桌子数据的类,这个类会储存桌子的位置数据,还要加入纹理坐标,并把纹理用于这个桌子

public class Table {
    
    

    private final int BYTES_PER_FLOAT = 4;
    private final FloatBuffer floatBuffer;
    private int POSITION_COMPONENT_COUNT = 2;
    private final int TEXTURE_COMPONENT_COUNT = 2;
    private final int STRIDE = (POSITION_COMPONENT_COUNT + TEXTURE_COMPONENT_COUNT) * BYTES_PER_FLOAT;

    float[] tableVertices = {
    
    
            //顶点坐标xy
            0f, 0f,
            //ST纹理坐标
            0.5f, 0.5f,

            -0.5f, -0.8f,
            0f, 0.9f,

            0.5f, -0.8f,
            1f, 0.9f,

            0.5f, 0.8f,
            1f, 0.1f,

            -0.5f, 0.8f,
            0f, 0.1f,

            -0.5f, -0.8f,
            0f, 0.9f,
    };

    public Table(){
    
    
        floatBuffer = ByteBuffer
                .allocateDirect(tableVertices.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(tableVertices);
    }

    public void bindData(TextureSharderProgram textureSharderProgram) {
    
    
        setAttributeLocation(0, textureSharderProgram.getA_position(), POSITION_COMPONENT_COUNT, STRIDE);

        setAttributeLocation(POSITION_COMPONENT_COUNT, textureSharderProgram.getA_TextureCoordinates(), TEXTURE_COMPONENT_COUNT, STRIDE);

    }

    public void draw() {
    
    
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, 6);
    }

    public void setAttributeLocation(int dataOffset, int attributeLocation, int componentCount, int strite) {
    
    
        floatBuffer.position(dataOffset);
        GLES20.glVertexAttribPointer(attributeLocation, componentCount, GLES20.GL_FLOAT,
                false, strite, floatBuffer);
        GLES20.glEnableVertexAttribArray(attributeLocation);
        floatBuffer.position(0);
    }
}

理解下上方代码

首先定义顶点坐标和威力坐标

首先顶点坐标范围是[-1,1],而纹理坐标是范围是[0,1],我把它们画出来,如下图

我们看到纹理坐标和顶点坐标的第2和6个顶点,其实是按照y方向的相反方向定义的,之所以这样,前面说过计算机坐标和纹理坐标是方向不同的

然后把早构造方法顶点数据加载到本地内存

然后bindData把顶点数据绑定到着色器属性上

最后draw方法画出这个桌子

加入木槌数据

public class Mallet {
    
    
    private final int BYTES_PER_FLOAT = 4;
    private final FloatBuffer floatBuffer;
    private int POSITION_COMPONENT_COUNT = 2;
    private final int COLOR_COMPONENT_COUNT = 3;
    private final int STRIDE = (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT) * BYTES_PER_FLOAT;

    float[] MalletVertices = {
    
    
            //顶点
            0f, -0.4f,
            //rgb
            1f, 0f, 0f,

            0f, 0.4f,
            0f, 0f, 1f
    };

    public Mallet() {
    
    
        floatBuffer = ByteBuffer
                .allocateDirect(MalletVertices.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(MalletVertices);
    }


    public void bindData(ColorShaderProgram colorShaderProgram) {
    
    
        setAttributeLocation(0, colorShaderProgram.getA_position(), POSITION_COMPONENT_COUNT, STRIDE);
        setAttributeLocation(POSITION_COMPONENT_COUNT, colorShaderProgram.getA_color(), COLOR_COMPONENT_COUNT, STRIDE);

    }


    public void draw() {
    
    
        GLES20.glDrawArrays(GLES20.GL_POINTS, 0, 2);
    }

    public void setAttributeLocation(int dataOffset, int attributeLocation, int componentCount, int strite) {
    
    
        floatBuffer.position(dataOffset);
        GLES20.glVertexAttribPointer(attributeLocation, componentCount, GLES20.GL_FLOAT,
                false, strite, floatBuffer);
        GLES20.glEnableVertexAttribArray(attributeLocation);
        floatBuffer.position(0);
    }
}

这写方法和上方大致一样,就不继续说了

为着色器添加类

我们需要创建俩个着色器,一个纹理找色器,然后创建一个颜色着色器

纹理着色器

public class TextureSharderProgram {
    
    
    private final int u_matrix;
    private final int u_TextureUnit;
    private final int a_position;
    private final int a_TextureCoordinates;
    private final int program;

    public TextureSharderProgram(Context context) {
    
    
        //读取着色器源码
        String fragment_shader_source = ReadResouceText.readResoucetText(context, R.raw.texture_fragment_shader);
        String vertex_shader_source = ReadResouceText.readResoucetText(context, R.raw.texture_vertex_sharder);
        program = ShaderHelper.buildProgram(vertex_shader_source, fragment_shader_source);


        a_position = GLES20.glGetAttribLocation(program, "a_Position");
        a_TextureCoordinates = GLES20.glGetAttribLocation(program, "a_TextureCoordinates");


        u_matrix = GLES20.glGetUniformLocation(program, "u_Matrix");
        u_TextureUnit = GLES20.glGetUniformLocation(program, "u_TextureUnit");
    }


    public void setUniforms(float[] matrix, int textureId) {
    
    
        GLES20.glUniformMatrix4fv(u_matrix, 1, false, matrix, 0);

        //把活动的纹理单元设置为纹理单元0
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

        //把纹理绑定到这个单元
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);

        //把选定的着色器,传递到到片段着色器的u_TextureUnit属性
        GLES20.glUniform1i(u_TextureUnit, 0);
    }

    public int getA_position() {
    
    
        return a_position;
    }

    public int getA_TextureCoordinates() {
    
    
        return a_TextureCoordinates;
    }

    public void useProgram(){
    
    
        //使用程序
        GLES20.glUseProgram(program);
    }
}

这里新增三个新的API

方法 描述
GLES20.glActiveTexture(GLES20.GL_TEXTURE0) 把活动的纹理单元设置为纹理单元0
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId) 把纹理绑定到这个单元
GLES20.glUniform1i(u_TextureUnit, 0); 把选定的着色器,传递到到片段着色器的u_TextureUnit属性

颜色着色器

public class ColorShaderProgram {
    
    
    private final int a_color;
    private final int a_position;
    private final int u_matrix;
    private final int program;

    public ColorShaderProgram(Context context) {
    
    

        //读取着色器源码
        String fragment_shader_source = ReadResouceText.readResoucetText(context, R.raw.fragment_shader1);
        String vertex_shader_source = ReadResouceText.readResoucetText(context, R.raw.vertex_shader2);
        program = ShaderHelper.buildProgram(vertex_shader_source, fragment_shader_source);

        a_color = GLES20.glGetAttribLocation(program, "a_Color");
        a_position = GLES20.glGetAttribLocation(program, "a_Position");
        u_matrix = GLES20.glGetUniformLocation(program, "u_Matrix");
    }


    public void setUniforms(float[] matrix) {
    
    
        GLES20.glUniformMatrix4fv(u_matrix, 1, false, matrix, 0);
    }

    public int getA_color() {
    
    
        return a_color;
    }

    public int getA_position() {
    
    
        return a_position;
    }


    public void useProgram(){
    
    
        //使用程序
        GLES20.glUseProgram(program);
    }
}

绘制纹理

直接上代码了

public class AirHockKeyRender4 implements GLSurfaceView.Renderer {
    
    


    //纹理
    private final Context mContext;


    //投影矩阵
    private float[] mProjectionMatrix = new float[16];
    //模型矩阵
    private float[] mModelMatrix = new float[16];
    private Table table;
    private Mallet mallet;
    private TextureSharderProgram textureSharderProgram;
    private ColorShaderProgram colorShaderProgram;
    private int textureid;


    public AirHockKeyRender4(Context context) {
    
    
        this.mContext = context;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    
    
        //清空屏幕,并显示蓝色
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

        table = new Table();
        mallet = new Mallet();

        textureSharderProgram = new TextureSharderProgram(mContext);
        colorShaderProgram = new ColorShaderProgram(mContext);

        textureid = TextureHelper.loadTexture(mContext, R.mipmap.text);

    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
    
    
        //在Surface创建以后,每次surface尺寸大小发生变化,这个方法会被调用到,比如横竖屏切换
        //设置屏幕的大小
        GLES20.glViewport(0, 0, width, height);
        //45度视野角创建一个透视投影,这个视椎体从z轴-1开始,-10结束
        MatrixHelper.perspetiveM(mProjectionMatrix, 45, (float) width / (float) height, 1f, 10f);


        //设置为单位矩阵
        Matrix.setIdentityM(mModelMatrix, 0);
        //向z轴平移-2f
        Matrix.translateM(mModelMatrix, 0, 0f, 0f, -3f);

        //绕着x轴旋转-60度
        Matrix.rotateM(mModelMatrix, 0, -60, 1.0f, 0f, 0f);


        float[] temp = new float[16];
        //矩阵相乘
        Matrix.multiplyMM(temp, 0, mProjectionMatrix, 0, mModelMatrix, 0);
        //把矩阵重复赋值到投影矩阵
        System.arraycopy(temp, 0, mProjectionMatrix, 0, temp.length);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
    
    
        //清除屏幕所有颜色,然后重设glClearColor的颜色
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
		
        //画桌子
        textureSharderProgram.useProgram();
        textureSharderProgram.setUniforms(mProjectionMatrix, textureid);
        table.bindData(textureSharderProgram);
        table.draw();
		
        //画木槌
        colorShaderProgram.useProgram();
        colorShaderProgram.setUniforms(mProjectionMatrix);
        mallet.bindData(colorShaderProgram);
        mallet.draw();
    }
}

运行查看效果

混合俩张图片

更改找色器

 precision mediump float;
//纹理的具体数据
 uniform sampler2D u_TextureUnit;

  uniform sampler2D u_TextureUnit1;
 //纹理坐标st
 varying vec2 v_TextureCoordinates;
   void main() {
        //texture2D:根据纹理坐标st,取出具体的颜色值
        gl_FragColor = texture2D(u_TextureUnit1,v_TextureCoordinates)*texture2D(u_TextureUnit,v_TextureCoordinates);
    }

u_TextureUnit1用来加载第二个纹理数据,texture2D(u_TextureUnit1,v_TextureCoordinates)*texture2D(u_TextureUnit,v_TextureCoordinates);获取最后混合的的数据

加载第二个图片

        textureid1 = TextureHelper.loadTexture(mContext, R.mipmap.air_hockey_surface);

这个很简单还是用刚才封装的方法,加载第二章图片

绘制纹理

   public void setUniforms1(int textureId) {
    
    

        //把活动的纹理单元设置为纹理单元1
        GLES20.glActiveTexture(GLES20.GL_TEXTURE1);

        //把纹理绑定到这个单元
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);

        //把选定的着色器,传递到到片段着色器的u_TextureUnit属性
        GLES20.glUniform1i(u_TextureUnit1, 1);
    }

第二张图片用纹理1 接受,赋值到u_TextureUnit1

最后调用

   @Override
    public void onDrawFrame(GL10 gl) {
    
    
        //清除屏幕所有颜色,然后重设glClearColor的颜色
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        textureSharderProgram.useProgram();
        textureSharderProgram.setUniforms(mProjectionMatrix, textureid);
        textureSharderProgram.setUniforms1(textureid1);
        table.bindData(textureSharderProgram);
        table.draw();

        colorShaderProgram.useProgram();
        colorShaderProgram.setUniforms(mProjectionMatrix);
        mallet.bindData(colorShaderProgram);
        mallet.draw();
    }

看下效果

小结

纹理不会直接被绘制,他需要被绑定到纹理单元,然后把这些纹理单元传递到着色器,通过纹理单元机进行纹理的切换,我们还可以在场景中绘制不同的纹理,但是过分的切换会使性能下降,我们也能同时使用多个纹理单元绘制几个纹理

猜你喜欢

转载自blog.csdn.net/qq_34760508/article/details/113174203