Android OpenGL纹理

首先申明下,本文为笔者学习《OpenGL ES应用开发实践指南》的笔记,并加入笔者自己的理解和归纳总结。

1、纹理


纹理就是一个图像或照片,它们可以被加载进OpenGL中。

每个二维的纹理都有其自己的坐标空间,按照惯例,一个维度叫做S,而另一个叫做T。

大多数计算机图像都有一个默认的方法,通常是y轴向下,y随着向图像的底部移动而增加。


opengles2纹理_Data


2、纹理过滤


当纹理大小被扩大或缩小时,我们还需要使用纹理过滤。


(1) 最近邻过滤
这个方式为每个片段选择最近的纹理元素。

(2) 双线性过滤
使用双线性插值平滑像素之间的过渡,使用四个邻接的纹理元素,并在它们之间用一个线性插值算法做插值。
双线性过滤很适合处理放大。

(3) MIP贴图
对于缩小处理,双线性过滤值给每个片段使用四个纹理元素,将会失去很多细节。
MIP贴图,可以生成一组优化过的不同大小的纹理。当生成这组纹理的时候,OpenGL会使用所有的纹理元素生成每个级别的纹理,当过滤纹理时,还要确保所有的纹理元素都能被使用。在渲染时,OpenGL会根据每个片段的纹理元素为每个片段选择最适合的解绑。

(4) 三线性过滤
三线性过滤,在最邻近的MIP贴图级别之间也要插值,这样,每个片段总共使用8个纹理元素插值,有助于消除每个MIP贴图级别之间的过渡,并且得到一个更平滑地图像。

3、加载纹理


(1) 生成新的纹理对象


int[] textureObjectIds = new int[1];
GLES20.glGenTextures(1, textureObjectIds, 0);


(2) 设置过滤参数


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纹理过滤模式


GL_NEAREST

最近邻过滤

GL_NEAREST_MIPMAP_NEAREST

使用MIP贴图的最近邻过滤

GL_NEAREST_MIPMAP_LINEAR

使用MIP贴图级别之间插值的最近邻过滤

GL_LINEAR

双线性过滤

GL_LINEAR_MIPMAP_NEAREST

使用MIP贴图的双线性过滤

GL_LINEAR_MIPMAP_LINEAR

三线性过滤(使用MIP贴图级别之间插值的双线性过滤)

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


     缩小     

GL_NEAREST                                   



GL_NEAREST_MIPMAP_NEAREST



GL_NEAREST_MIPMAP_LINEAR    



GL_LINEAR                                       



GL_LINEAR_MIPMAP_NEAREST    



GL_LINEAR_MIPMAP_LINEAR        

放大

GL_NEAREST                                  



GL_LINEAR                                      

(3) 绑定图片


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


(4) 加载纹理


protected int loadTexture(int resId) {
	// 生成一个新的OpenGL纹理的ID
	int[] textureObjectIds = new int[1];
	GLES20.glGenTextures(1, textureObjectIds, 0);

	if (textureObjectIds[0] == 0) {
		LogUtil.log("OpenGL", "Could not generate a new OpenGL texture object.");
		return 0;
	}

	BitmapFactory.Options options = new BitmapFactory.Options();
	// 需要原始的图像数据,而不是缩放版
	options.inScaled = false;

	Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId, options);
	if (bitmap == null) {
		LogUtil.log("OpenGL", "Resource ID " + resId + " could not be decoded.");
		// 如果失败,删除纹理对象
		GLES20.glDeleteTextures(1, textureObjectIds, 0);
		return 0;
	}

	// 我们需要告诉后面的纹理调用,都应该应用于这个纹理对象
	GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureObjectIds[0]);

	// 设置过滤参数
	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,并生成MIP贴图
	GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
	GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);

	// 回收图片
	bitmap.recycle();
	// 与当前纹理解除绑定,防止被修改
	GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);

	return textureObjectIds[0];
}

4、着色器文件


(1) 顶点着色器,texture_vertex_shader.glsl文件

a_TextureCoordinates是纹理坐标,有两个分量:S坐标和T坐标


uniform mat4 u_Matrix;

attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;

varying vec2 v_TextureCoordinates;

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


(2) 片段着色器,texture_fragment_shader文件

u_textureUnit被定义为一个sampler2D,这个变量类型指的是一个二维纹理数据的数组。


precision mediump float;

uniform sampler2D u_textureUnit;
varying vec2 v_TextureCoordinates;

void main()
{
     gl_FragColor = texture2D(u_textureUnit, v_TextureCoordinates);
}

4、绘制着色器


(1) 顶点数据

纹理数据采用不同的坐标方式


float[] tableVertices = {
		   0f,    0f, 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,
};


(2) 绘制纹理


// 把活动的纹理单元设置为纹理单元0
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
// 把纹理绑定到这个单元
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);
// 把被选择的纹理单元传递给片段着色器中的
GLES20.glUniform1i(uTextureUnitLocation, 0);


(3) 绘制不同着色器

Resource类,包含一个顶点数据类VertexArray,bindData方法用来绑定属性,draw方法用来绘制界面。


public class Resource {

    protected VertexArray mVertextArray;

    public Resource(float[] vertexData) {
        mVertextArray = new VertexArray(vertexData);
    }

    public void bindData(int attributeLocation, int dataOffset,
        int componentCount, int stride) {
        mVertextArray.setVertexAttribPointer(attributeLocation,
                dataOffset, componentCount, stride);
    }

    public void draw() {
    }

}


VertexArray类,保存顶点数据

public class VertexArray {
    public static final int BYTES_PER_FLOAT = 4;
    private final FloatBuffer floatBuffer;

    public VertexArray(float[] vertexData) {
        floatBuffer = ByteBuffer
                .allocateDirect(vertexData.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        floatBuffer.put(vertexData);
    }

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

}


Table类,绘制桌子


public class Table extends Resource {
    private final static int POSITION_COMPONENT_COUNT = 2;
    private final static int TEXTURE_COMPONENT_COUNT = 2;

    private final static int STRIDE = (POSITION_COMPONENT_COUNT + TEXTURE_COMPONENT_COUNT)
            * VertexArray.BYTES_PER_FLOAT;

    private final static float[] vertexData = new float[]{
               0f,    0f, 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() {
        super(vertexData);
    }

    public void bindData(TextureProgram program) {
        bindData(program.getPositionLocation(), 0, POSITION_COMPONENT_COUNT, STRIDE);
        bindData(program.getTextureCoordLocation(),
                POSITION_COMPONENT_COUNT, TEXTURE_COMPONENT_COUNT, STRIDE);
    }

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

}


Mallet类,暂用点代替


public class Mallet extends Resource {
    private final static int POSITION_COMPONENT_COUNT = 2;
    private final static int COLOR_COMPONENT_COUNT = 3;
    private final static int STRIDE = (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT)
            * VertexArray.BYTES_PER_FLOAT;

    private final static float[] vertexData = new float[]{
            0f, -0.25f, 0f, 0f, 1f,
            0f,  0.25f, 1f, 0f, 0f
    };

    public Mallet() {
        super(vertexData);
    }

    public void bindData(ColorProgram program) {
        bindData(program.getPositionLocation(), 0,
                POSITION_COMPONENT_COUNT, STRIDE);
        bindData(program.getColorLocation(), POSITION_COMPONENT_COUNT,
                COLOR_COMPONENT_COUNT, STRIDE);
    }

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

}


Program类,保留顶点数据,加载着色器程序


public class Program {

    protected int mProgramId;

    public Program(Context context, int vertexShaderResId,
                   int fragmentShaderResId) {
        mProgramId = useProgram(context, vertexShaderResId, fragmentShaderResId);
    }

    public void setUniform(float[] projectionMatrix) {
    }

    protected String readShaderFromRaw(Context context, int resId) {
        BufferedReader br = null;
        StringBuffer stringBuffer = new StringBuffer();
        try {
            br = new BufferedReader(new InputStreamReader(
                    context.getResources().openRawResource(resId)));
            String line = null;
            while ((line = br.readLine()) != null) {
                stringBuffer.append(line + "\n");
            }
        } catch (IOException e) {

        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                }
            }
        }
        return stringBuffer.toString();
    }

    protected int compileShader(int type, String shaderCode) {
        // 创建一个新的着色器对象
        int shaderObjectId = GLES20.glCreateShader(type);
        if (shaderObjectId == 0) {
            // 创建失败
            return 0;
        }

        // 上传和编译着色器代码
        GLES20.glShaderSource(shaderObjectId, shaderCode);
        GLES20.glCompileShader(shaderObjectId);

        // 获取编译状态
        int[] compileStatus = new int[1];
        GLES20.glGetShaderiv(shaderObjectId, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
        // 获取着色器信息日志
        LogUtil.log("OpenGL", GLES20.glGetShaderInfoLog(shaderObjectId));

        if (compileStatus[0] == 0) {
            // 如果失败,删除着色器对象
            GLES20.glDeleteShader(shaderObjectId);
            return 0;
        }

        return shaderObjectId;
    }

    protected int linkProgram(int vertexShaderId, int fragmentShaderId) {
        // 创建一个新的程序对象
        int programId = GLES20.glCreateProgram();
        if (programId == 0) {
            return 0;
        }

        // 新建程序对象附上着色器,并链接程序
        GLES20.glAttachShader(programId, vertexShaderId);
        GLES20.glAttachShader(programId, fragmentShaderId);
        GLES20.glLinkProgram(programId);

        // 获取链接状态
        int[] linkStatus = new int[1];
        GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, 0);
        // 获取着程序链接信息日志
        LogUtil.log("OpenGL", GLES20.glGetProgramInfoLog(programId));

        if (linkStatus[0] == 0) {
            // 如果链接失败,删除程序对象
            GLES20.glDeleteProgram(programId);
            return 0;
        }

        return programId;
    }

    protected boolean validateProgram(int programId) {
        // 验证程序,只在开发阶段需要
        GLES20.glValidateProgram(programId);
        LogUtil.log("OpenGL", GLES20.glGetProgramInfoLog(programId));

        int[] validateStatus = new int[1];
        GLES20.glGetProgramiv(programId, GLES20.GL_VALIDATE_STATUS, validateStatus, 0);

        return validateStatus[0] != 0;
    }

    protected int useProgram(Context context, int vertexShaderResId, int fragmentShaderResId) {
        String vertexShaderCode = readShaderFromRaw(context, vertexShaderResId);
        String fragmentShaderCode = readShaderFromRaw(context, fragmentShaderResId);

        int vertexShaderId = compileShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
        int fragmentShaderId = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);

        int programId = linkProgram(vertexShaderId, fragmentShaderId);
        validateProgram(programId);

        GLES20.glUseProgram(programId);

        return programId;
    }

}


ColorProgram类,绘制颜色着色器


public class ColorProgram extends Program {
    private final static String A_POSITION = "a_Position";
    private final static String A_COLOR = "a_Color";
    private final static String U_MATRIX = "u_Matrix";

    private int aPositionLocation, aColorLocation, uMatrixLocation;

    public ColorProgram(Context context) {
        super(context, R.raw.ortho_vertex_shader, R.raw.ortho_fragment_shader);

        aPositionLocation = GLES20.glGetAttribLocation(mProgramId, A_POSITION);
        aColorLocation = GLES20.glGetAttribLocation(mProgramId, A_COLOR);
        uMatrixLocation = GLES20.glGetUniformLocation(mProgramId, U_MATRIX);
    }

    @Override
    public void setUniform(float[] projectionMatrix) {
        GLES20.glUseProgram(mProgramId);

        GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);
    }

    public int getPositionLocation() {
        return aPositionLocation;
    }

    public int getColorLocation() {
        return aColorLocation;
    }

}


TextureProgram类,绘制纹理着色器


public class TextureProgram extends Program {
    private final static String A_POSITION = "a_Position";
    private final static String A_TEXTURRE_COORDINATES = "a_TextureCoordinates";
    private final static String U_MATRIX = "u_Matrix";
    private final static String U_TEXTURE_UNIT = "u_TextureUnit";

    private int aPositionLocation, aTextureCoordLocation, uMatrixLocation, uTextureUnitLocation;
    private int mTextureId;

    public TextureProgram(Context context, int resId) {
        super(context, R.raw.texture_vertex_shader, R.raw.texture_fragment_shader);

        aPositionLocation = GLES20.glGetAttribLocation(mProgramId, A_POSITION);
        aTextureCoordLocation = GLES20.glGetAttribLocation(mProgramId, A_TEXTURRE_COORDINATES);
        uMatrixLocation = GLES20.glGetUniformLocation(mProgramId, U_MATRIX);
        uTextureUnitLocation = GLES20.glGetUniformLocation(mProgramId, U_TEXTURE_UNIT);

        mTextureId = loadTexture(context, resId);
    }

    protected int loadTexture(Context context, int resId) {
        // 生成一个新的OpenGL纹理的ID
        int[] textureObjectIds = new int[1];
        GLES20.glGenTextures(1, textureObjectIds, 0);

        if (textureObjectIds[0] == 0) {
            LogUtil.log("OpenGL", "Could not generate a new OpenGL texture object.");
            return 0;
        }

        BitmapFactory.Options options = new BitmapFactory.Options();
        // 需要原始的图像数据,而不是缩放版
        options.inScaled = false;

        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId, options);
        if (bitmap == null) {
            LogUtil.log("OpenGL", "Resource ID " + resId + " could not be decoded.");
            // 如果失败,删除纹理对象
            GLES20.glDeleteTextures(1, textureObjectIds, 0);
            return 0;
        }

        // 我们需要告诉后面的纹理调用,都应该应用于这个纹理对象
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureObjectIds[0]);

        // 设置过滤参数
        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,并生成MIP贴图
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);

        // 回收图片
        bitmap.recycle();
        // 与当前纹理解除绑定,防止被修改
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);

        return textureObjectIds[0];
    }

    @Override
    public void setUniform(float[] projectionMatrix) {
        GLES20.glUseProgram(mProgramId);

        GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);

        // 把活动的纹理单元设置为纹理单元0
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        // 把纹理绑定到这个单元
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);
        // 把被选择的纹理单元传递给片段着色器中的
        GLES20.glUniform1i(uTextureUnitLocation, 0);
    }

    public int getPositionLocation() {
        return aPositionLocation;
    }

    public int getTextureCoordLocation() {
        return aTextureCoordLocation;
    }
}


(4) OpenGLTextureShaderRender类


class OpenGLTextureShaderRender implements GLSurfaceView.Renderer {
	private float[] projectionMatrix = new float[16];
	private float[] modelMatrix = new float[16];
	private float[] modelProjectionMatrix = new float[16];

	private TextureProgram mTextureProgram;
	private ColorProgram mColorProgram;

	private Table mTable;
	private Mallet mMallet;

	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

		mTable = new Table();
		mMallet = new Mallet();

		mTextureProgram = new TextureProgram(OpenGLTextureShaderActivity.this,
				R.drawable.air_hockey_surface);
		mColorProgram = new ColorProgram(OpenGLTextureShaderActivity.this);
	}

	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height) {
		GLES20.glViewport(0, 0, width, height);

		// 创建透视投影
		Matrix.perspectiveM(projectionMatrix, 0, 45, (float)width / (float)height, 1, 10);

		// 定义模型矩阵
		Matrix.setIdentityM(modelMatrix, 0);
		// z轴平移-2.8
		Matrix.translateM(modelMatrix, 0, 0, 0, -2.8f);
		Matrix.rotateM(modelMatrix, 0, -60, 1f, 0f, 0f);

		// 把投影矩阵和模型矩阵相乘
		Matrix.multiplyMM(modelProjectionMatrix, 0, projectionMatrix, 0, modelMatrix, 0);
	}

	@Override
	public void onDrawFrame(GL10 gl) {
		GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

		mTextureProgram.setUniform(modelProjectionMatrix);
		mTable.bindData(mTextureProgram);
		mTable.draw();

		mColorProgram.setUniform(modelProjectionMatrix);
		mMallet.bindData(mColorProgram);
		mMallet.draw();
	}
}


显示如下


opengles2纹理_着色器_02