OpenGL之基础
OpenGL之绘制简单形状
OpenGL之颜色
OpenGL之调整屏幕宽高比
OpenGL之三维
纹理
纹理可以用来表示图像、照片、甚至由一个数学算法生成的分形数据。每个二维的纹理都由许多小的纹理元素(texel)组成,它们是小块的数据,使用纹理最常用的方式是直接从一个图像文件加载数据
每个二维的纹理都有其自己的坐标空间,其范围是从一个拐角的 (0, 0) 到另一个拐角的 (11) ,按照惯例,一个维度叫做S,而另一个称为T,这些纹理坐标有时也会被称为UV纹理坐标
当我们想要把一个纹理应用于一个三角形或一组三角形的时候,需要为每个顶点指定 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) 明确说明会缩小和放大的规则
最近邻过滤
这个方式为每个片段选择最近的纹理元素
放大(锯齿明显) | 缩小(细节丢失) |
---|---|
双线性过滤
双线性过滤使用双线性插值平滑像素之间的过渡,而不是为每个片段使用最近的纹理元素,OpenGL会使用四个邻接的纹理元素,并在它们之间用一个线性插值算法做插值,之所以叫它双线性,是因为它是沿着两个维度插值的
放大(效果较好) | 缩小(细节丢失) |
---|---|
MIP贴图
生成一组优化过的不同大小的纹理。在渲染时,OpenGL 会根据每个片段的纹理元素数量为每个片段选择最适合的级别,使用MIP贴图,会占用更多的内存,但是渲染也会更快,因为较小级别的纹理在GPU的纹理缓存中占用较少的空间
三线性过滤
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();
}
}