OpenGL之纹理

OpenGL之基础
OpenGL之绘制简单形状
OpenGL之颜色
OpenGL之调整屏幕宽高比
OpenGL之三维

纹理

纹理可以用来表示图像、照片、甚至由一个数学算法生成的分形数据。每个二维的纹理都由许多小的纹理元素(texel)组成,它们是小块的数据,使用纹理最常用的方式是直接从一个图像文件加载数据

每个二维的纹理都有其自己的坐标空间,其范围是从一个拐角的 (0, 0) 到另一个拐角的 (11) ,按照惯例,一个维度叫做S,而另一个称为T,这些纹理坐标有时也会被称为UV纹理坐标


image-20211221171121512.png

当我们想要把一个纹理应用于一个三角形或一组三角形的时候,需要为每个顶点指定 ST 纹理坐标,以便OpenGL知道要用纹理的哪个部分画到三角形上

加载纹理

把一个图像文件的数据加载到一个OpenGL的纹理中

public class TextureHelper {
    private static final String TAG = "TextureHelper";

    public static int loadTexture(Context context, int resId) {
        // OpenGL不能直接读取PNG或者JPEG文件的数据,因为这些文件被编码为特定的压缩格式
        // OpenGL需要非压缩形式的原始数据,因此,需要用位图解码器BitmapFactory把图像文件解压缩为Bitmap
        BitmapFactory.Options options = new BitmapFactory.Options();
        // 告诉BitmapFactory想要原始的图像数据,而不是这个图像的缩放版本
        options.inScaled = false;
        // 获取图像原始bitmap数据
        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId, options);
        if (bitmap == null) {
            Log.d(TAG, "loadTexture: decodeResource failed!");

            return 0;
        }

        // 生成一个新的纹理ID
        int textureIds[] = new int[1];
        glGenTextures(textureIds.length, textureIds, 0);
        // 检查生成纹理是否成功,结果等于0就是失败
        if (textureIds[0] == 0) {
            Log.d(TAG, "loadTexture: glGenTextures failed!");
            return 0;
        }

        // 绑定,告诉OpenGL后面的调用应用于这个二维纹理
        // 第一个参数GL_TEXTURE_2D告诉OpenGL这应该被作为一个二维纹理对待,第二个参数告诉 OpenGL要绑定到哪个纹理对象的ID
        glBindTexture(GL_TEXTURE_2D, textureIds[0]);
        //设置纹理过滤参数,缩小和放大
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        // 读入bitmap的位图数据到OpenGL,并复制到当前纹理对象
        GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
        // 释放bitmap的位图数据
        bitmap.recycle();
        // 生成mipmap贴图
        glGenerateMipmap(GL_TEXTURE_2D);

        // 解绑
        glBindTexture(GL_TEXTURE_2D, 0);
        return textureIds[0];
    }
}

纹理过滤

当纹理大小被扩大或者缩小时,我们还需要使用纹理过滤 (texture filtering) 明确说明会缩小和放大的规则

最近邻过滤

这个方式为每个片段选择最近的纹理元素

放大(锯齿明显) 缩小(细节丢失)
image-20211221172912954.png
image-20211221173218799.png

双线性过滤

双线性过滤使用双线性插值平滑像素之间的过渡,而不是为每个片段使用最近的纹理元素,OpenGL会使用四个邻接的纹理元素,并在它们之间用一个线性插值算法做插值,之所以叫它双线性,是因为它是沿着两个维度插值的

放大(效果较好) 缩小(细节丢失)
image-20211221173452985.png
image-20211221173653459.png

MIP贴图

生成一组优化过的不同大小的纹理。在渲染时,OpenGL 会根据每个片段的纹理元素数量为每个片段选择最适合的级别,使用MIP贴图,会占用更多的内存,但是渲染也会更快,因为较小级别的纹理在GPU的纹理缓存中占用较少的空间


image-20211221174105702.png

三线性过滤

OpenGL 在不同的 MIP 贴图级别之间来回切换,当我们用双线性过滤来使用 MIP 贴图时,在其渲染的场景中,在不同级别的 MIP 贴图切换时,有时候能看到明显的跳跃或者线条,切换到三线性过滤( trilinear filtering),告诉 OpenGL 在两个最邻近的 MIP 贴图级别之间也要插值,这样,每个片段总共要使用 8 个纹理元素插值。这有助于消除每个 MIP 贴图级别之间的过渡,并且得到一个更平滑的图像

纹理过滤总结

过滤模式 对应代码
最近邻过滤 GL_NEAREST
使用MIP贴图的最近邻过滤 GL_NEAREST_MIPMAP_NEAREST
使用MIP贴图级别之间插值的最近邻过滤 GL_NEAREST_MIPMAP_LINEAR
双线性过滤 GL_LINEAR
使用MIP贴图的双线性过滤 GL_LINEAR_MIPMAP_NEAREST
三线性过滤(使用MIP贴图级别之间插值的双线性过滤) GL_LINEAR_MIPMAP_LINEAR

每种情况下允许的纹理过滤模式

情况 允许的模式
缩小 GL_NEAREST、GL_NEAREST_MIPMAP_NEAREST、GL_NEAREST_MIPMAP_LINEAR、GL_LINEAR、GL_LINEAR_MIPMAP_NEAREST、GL_LINEAR_MIPMAP_LINEAR
放大 GL_NEAREST、GL_LINEAR

修改着色器

修改着色器,让其可以接收纹理,并把它应用在要绘制的片段上

修改顶点着色器

attribute vec4 a_Position;
uniform mat4 u_Matrix;

attribute vec2 a_TextureCoordinates;
varying vec2 v_Texture_Coordinates;

void main(){
    v_Texture_Coordinates=a_TextureCoordinates;
    gl_Position=u_Matrix*a_Position;
    gl_PointSize=10.0;
}

给纹理坐标加了一个新的属性 "a_TextureCoordinates" ,因为它有两个分量:S坐标和T坐标,所以被定义为一个vec2,因为要把纹理坐标插值后传递给片段着色器,因此使用 varying 创建了 vec2 的 "v_Texture_Coordinates"

修改片段着色器

precision mediump float;
uniform sampler2D u_Texture_Unit;
varying vec2 v_Texture_Coordinates;
void main(){
    gl_FragColor=texture2D(u_Texture_Unit, v_Texture_Coordinates);
}

为了把纹理绘制到一个物体上,OpenGL会为每个片段都调用片段着色器,并且每个调用都接收v_Texture_Coordinates 的纹理坐标

片段着色器也通过 uniform 的 u_Texture_Unit 接收实际的纹理数据,u_TextureUnit 被定义为一个 sampler2D ,这个变量类型指的是一个二维纹理数据的数组

被插值的纹理坐标和纹理数据被传递给着色器函数 texture2D() ,它会读人纹理中那个特定坐标处的颜色值,然后把结果赋值给 gl_FragColor 设置片段的最终颜色

创建物体

创建Mallet类管理木槌的数据,以及Table类管理桌子的数据,并且每个类都会有一个 VertexArray类的实例,它用来封装存储顶点的 FloatBuffer

封装存储顶点类

public class VertexArray {
    private final FloatBuffer mVertexBuffer;

    /**
     * 创建顶点数组
     *
     * @param data
     */
    public VertexArray(float[] data) {
        mVertexBuffer = ByteBuffer.allocateDirect(data.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(data);
        mVertexBuffer.position(0);
    }


    /**
     * 绑顶点数据和OpenGL中的属性
     *
     * @param dataOffset        顶点数据偏移量,定位到数组中的那个位置
     * @param attributeLocation 属性位置
     * @param componentCount    此属性所占位数,例如位置(x,y)占两位,位置(x,y,z)占3位
     * @param stride            所有属性占位总和字节数,例如(x,y,z,r,g,b)为6*4
     */
    public void setVertexAttribPointer(int dataOffset, int attributeLocation, int componentCount, int stride) {
        mVertexBuffer.position(dataOffset);
        glVertexAttribPointer(
                attributeLocation,
                componentCount,
                GL_FLOAT,
                false,
                stride,
                mVertexBuffer
        );
        glEnableVertexAttribArray(attributeLocation);
        mVertexBuffer.position(0);
    }
}

桌子类

public class Table {
    private static final int POSITION_COMPONENT = 2;
    private static final int TEXTURE_COORDINATES_COMPONENT = 2;
    private static final int STRIDE = (POSITION_COMPONENT + TEXTURE_COORDINATES_COMPONENT) * Constants.BYTES_PER_FLOAT;
    // 桌子的顶点数据
    private VertexArray mVertexArray;
    // 要注意,x、y范围是 [-1,1](如果不想横屏被裁减的话),s、t范围是[0,1]
    // 这里并没有使用图像的所有数据,在t方向上只使用了0.1~0.9的范围
    // 因为桌子的宽高比是1:1.6,为了避免压缩,裁减图像的边缘
    public static final float[] VERTEX_DATA = new float[]{
            // x,y,s,t
            0f, 0f, 0.5f, 0.5f,
            -0.5f, -0.8f, 0f, 0.1f,
            0.5f, -0.8f, 1f, 0.1f,
            0.5f, 0.8f, 1f, 0.9f,
            -0.5f, 0.8f, 0f, 0.9f,
            -0.5f, -0.8f, 0f, 0.1f
    };

    public Table() {
        mVertexArray = new VertexArray(VERTEX_DATA);
    }

    /**
     * 绑定顶点数据到OpenGL程序
     */
    public void bindData(TextureProgram textureProgram) {
        mVertexArray.setVertexAttribPointer(
                0,
                textureProgram.getAPosition(),
                POSITION_COMPONENT,
                STRIDE
        );

        mVertexArray.setVertexAttribPointer(
                POSITION_COMPONENT,
                textureProgram.getATextureCoordinates(),
                TEXTURE_COORDINATES_COMPONENT,
                STRIDE
        );
    }

    /**
     * 画出桌子
     */
    public void draw() {
        glDrawArrays(GL_TRIANGLE_FAN, 0, 6);
    }
}

上面代码中的 TextureProgram 类,用于生成程序,获取着色器中 attribute 和 uniform 的位置,并提供设置 uniform 的方法和 attribute 的方法方法,如下

public class TextureProgram extends BaseProgram {

    private final int mATextureCoordinates;
    private final int mUTextureUnit;

    private final int mAPosition;
    private final int mUMatrix;

    public TextureProgram(Context context) {
        super(context, R.raw.buildobjects6_vertex, R.raw.buildobjects6_fragment);

        mATextureCoordinates = glGetAttribLocation(mProgram, A_TEXTURE_COORDINATES);
        mUTextureUnit = glGetUniformLocation(mProgram, U_TEXTURE_UNIT);

        mAPosition = glGetAttribLocation(mProgram, A_POSITION);
        mUMatrix = glGetUniformLocation(mProgram, U_MATRIX);
    }

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

        //激活纹理单元0
        glActiveTexture(GL_TEXTURE0);
        //将纹理textureId绑定到这个单元
        glBindTexture(GL_TEXTURE_2D, textureId);
        //把被选定的纹理单元0传递给片段着色器中的mUTextureUnit
        glUniform1i(mUTextureUnit, 0);
    }

    public int getATextureCoordinates() {
        return mATextureCoordinates;
    }

    public int getAPosition() {
        return mAPosition;
    }
}

BaseProgram

public class BaseProgram {
    public static final String A_TEXTURE_COORDINATES = "a_TextureCoordinates";
    public static final String A_POSITION = "a_Position";
    public static final String A_COLOR = "a_Color";

    public static final String U_TEXTURE_UNIT = "u_Texture_Unit";
    public static final String U_MATRIX = "u_Matrix";
    public final int mProgram;

    public BaseProgram(Context context, int vertexResId, int fragmentResId) {
        int vertexId = ShaderHelper.compileVertexShader(context, vertexResId);
        int fragmentId = ShaderHelper.compileFragmentShader(context, fragmentResId);
        mProgram = ProgramHelper.getProgram(vertexId, fragmentId);
    }

    /**
     * 调用此方法,告诉OpenGL接下来的渲染使用此程序
     */
    public void useProgram() {
        glUseProgram(mProgram);
    }
}

木槌类

用和桌子类同样的方法创建木槌类,以及它对应的程序类

public class Mallet {
    private static final int POSITION_COMPONENT = 2;
    private static final int COLOR_COMPONENT = 3;
    private static final int STRIDE = (POSITION_COMPONENT + COLOR_COMPONENT) * Constants.BYTES_PER_FLOAT;
    //顶点数据
    private VertexArray mVertexArray;
    private static final float[] VERTEX_DATA = new float[]{
            // x,y,r,g,b
            0f, 0.4f, 1f, 0f, 0f,
            0f, -0.4f, 0f, 1f, 0f
    };

    public Mallet() {
        mVertexArray = new VertexArray(VERTEX_DATA);
    }

    /**
     * 顶点数据绑定到OpenGL程序
     *
     * @param colorProgram
     */
    public void bindData(ColorProgram colorProgram) {
        mVertexArray.setVertexAttribPointer(
                0,
                colorProgram.getAPosition(),
                POSITION_COMPONENT,
                STRIDE
        );
        mVertexArray.setVertexAttribPointer(
                POSITION_COMPONENT,
                colorProgram.getAColor(),
                COLOR_COMPONENT,
                STRIDE
        );
    }

    /**
     * 画点
     */
    public void draw() {
        glDrawArrays(GL_POINTS, 0, 2);
    }
}
public class ColorProgram extends BaseProgram {
    private final int mAColor;
    private final int mAPosition;
    private final int mUMatrix;

    public ColorProgram(Context context) {
        super(context, R.raw.texture5_color_vertex, R.raw.texture5_color_fragment);
        mAColor = glGetAttribLocation(mProgram, A_COLOR);
        mAPosition = glGetAttribLocation(mProgram, A_POSITION);
        mUMatrix = glGetUniformLocation(mProgram, U_MATRIX);
    }

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

    public int getAColor() {
        return mAColor;
    }

    public int getAPosition() {
        return mAPosition;
    }
}

修改渲染器

修改渲染器,在其中使用定义好的物体类(桌子和木槌)以及他们对应的程序类

public class MyRenderer implements GLSurfaceView.Renderer {
    private Context mContext;
    private float[] projectionMatrix = new float[16];
    private float[] modelMatrix = new float[16];

    private TextureProgram mTextureProgram;
    private ColorProgram mColorProgram;
    private Table mTable;
    private Mallet mMallet;
    private int mTextureId;

    public MyRenderer(Context context) {
        mContext = context;
    }

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        glClearColor(1f, 1f, 1f, 1f);
        mTextureProgram = new TextureProgram(mContext);
        mColorProgram = new ColorProgram(mContext);
        mTable = new Table();
        mMallet = new Mallet();
        mTextureId = TextureHelper.loadTexture(mContext, R.drawable.air_hockey_surface);
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        glViewport(0, 0, width, height);
        MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width / height, 1, 10);

        Matrix.setIdentityM(modelMatrix, 0);
        Matrix.translateM(modelMatrix, 0, 0f, 0f, -3f);
        Matrix.rotateM(modelMatrix, 0, -45f, 1f, 0f, 0f);

        float[] temp = new float[16];
        Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);
        System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        // 清空渲染表面
        glClear(GL_COLOR_BUFFER_BIT);
        // 画桌子
        // 告诉OpenGL使用这个程序
        mTextureProgram.useProgram();
        // 把uniform传递进去
        mTextureProgram.setUniforms(projectionMatrix, mTextureId);
        // 把顶点数组数据和着色器程序绑定起来
        mTable.bindData(mTextureProgram);
        // 绘制桌子
        mTable.draw();

        // 画木槌
        mColorProgram.useProgram();
        mColorProgram.setUniforms(projectionMatrix);
        mMallet.bindData(mColorProgram);
        mMallet.draw();
    }
}

效果

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

推荐阅读更多精彩内容

  • 一常用函数 改变像素存储⽅方式 从颜⾊色缓存区内容作为像素图直接读取 载⼊纹理 常用: glTexImage2D ...
    只写Bug程序猿阅读 124评论 0 1
  • 一、什么是纹理? 艺术家和程序员更喜欢使用纹理(Texture)。纹理是一个2D图片(甚至也有1D和3D的纹理),...
    Sheisone阅读 329评论 0 0
  • 1、了解纹理 图像的存储空间 = 图片width * 图片height * 每个像素的字节数 OpenGL纹理文件...
    紫水依阅读 3,567评论 0 0
  • 纹理可以理解为一张图片,OpenGL渲染图片会将图片的像素保存在纹理缓存中。 OpenGL常用纹理函数 载入纹理 ...
    逃避不面对阅读 338评论 0 0
  • 一.先看看整体效果 二.绘制流程 在OpenGl综合案例(地板,大小球,公转,自转,移动)文章中,我们绘制了大小球...
    枫紫_6174阅读 473评论 0 1