1 纹理解读
关于OpenGL基础内容以及更多了解可关注 系列文章:专题分纲目录 OpenGL教程。
@1 纹理概念:纹理是一种图形数据,用于包装不同的物体,就像衣服一样,衣服的各种展示样式就是我们所说的纹理。
@2 纹理与渐变色:
- 渐变色:光栅化过程中,计算出颜色值,然后在fragment shader中赋值。
- 纹理:光栅化过程中,计算出当前片段在纹理上的坐标,然后在fragment shader中根据纹理坐标获取相应的颜色值。
@3 纹理坐标(也叫ST纹理坐标 或 UV坐标)与顶点坐标的对比:
@4 顶点坐标 & 立方体面数组 & 面纹理坐标
他们的关系可以参照文章:Android OpenGL ES顶点坐标、纹理贴图坐标设置
2 纹理实战 带纹理的旋转立方体
实现功能:效果如下所示(绕(1,1)向量轴旋转):
关于该程序,自定义MyRender的代码实现如下所示:
class MyRender implements GLSurfaceView.Renderer {
private static final float OFFSET = 0.5f;
// 立方体的顶点坐标(一共是24个顶点,组成12个三角形)
private float[] cubeVertices = {
-OFFSET, -OFFSET, OFFSET, OFFSET, -OFFSET, OFFSET, OFFSET, OFFSET, OFFSET, -OFFSET, OFFSET, OFFSET, //2103,前
-OFFSET, -OFFSET, -OFFSET, -OFFSET, OFFSET, -OFFSET, OFFSET, OFFSET, -OFFSET, OFFSET, -OFFSET, -OFFSET,//6745,后
-OFFSET, OFFSET, OFFSET, OFFSET, OFFSET, OFFSET, OFFSET, OFFSET, -OFFSET, -OFFSET, OFFSET, -OFFSET, //3047,上
-OFFSET, -OFFSET, OFFSET, -OFFSET, -OFFSET, -OFFSET, OFFSET, -OFFSET, -OFFSET,OFFSET, -OFFSET,OFFSET, //2651,下
-OFFSET, -OFFSET, -OFFSET, -OFFSET, -OFFSET, OFFSET, -OFFSET, OFFSET, OFFSET, -OFFSET, OFFSET, -OFFSET,//6237,左
OFFSET, -OFFSET, -OFFSET, OFFSET, OFFSET, -OFFSET, OFFSET, OFFSET, OFFSET, OFFSET, -OFFSET, OFFSET, //5401,右
};
// 立方体6个面 位置
private byte[] cubeFacets = {
0, 1, 3, 2, //前
4, 5, 7, 6, //后
8, 9, 11, 10, //上
15, 12,14,13, //下
16, 17, 19, 18, //左
20, 21, 23, 22, //右
};
// 纹理贴图坐标 6组,每组4个坐标(uv)
private float[] cubeTextures = {
0.0000f, 1.0000f, 1.0000f, 1.0000f, 1.0000f, 0.0000f, 0.0000f, 0.0000f,//前
0.0000f, 1.0000f, 0.0000f, 0.0000f, 1.0000f, 0.0000f, 1.0000f, 1.0000f,//后
0.0000f, 1.0000f, 1.0000f, 1.0000f, 1.0000f, 0.0000f, 0.0000f, 0.0000f,//上
0.0000f, 0.0000f, 1.0000f, 0.0000f, 1.0000f, 1.0000f, 0.0000f, 1.0000f,//下
0.0000f, 1.0000f, 1.0000f, 1.0000f, 1.0000f, 0.0000f, 0.0000f, 0.0000f,//左
1.0000f, 1.0000f, 1.0000f, 0.0000f, 0.0000f, 0.0000f, 0.0000f, 1.0000f,//右
};
// 定义Open GL ES绘制所需要的Buffer对象
FloatBuffer cubeVerticesBuffer;
ByteBuffer cubeFacetsBuffer;
private FloatBuffer cubeTexturesBuffer;
private float rotate = 0.0f;
private Context mContext;
public MyRender(Context context){
cubeVerticesBuffer = BufferUtil.floatBufferUtil(cubeVertices);
cubeFacetsBuffer = ByteBuffer.wrap(cubeFacets);
cubeTexturesBuffer = BufferUtil.floatBufferUtil(cubeTextures);
mContext = context;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glDisable(GL10.GL_DITHER);//关闭防抖
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,GL10.GL_FASTEST);//设置透视修正
gl.glClearColor(0, 0, 0, 0);
gl.glShadeModel(GL10.GL_SMOOTH);//设置为阴影平滑模式
gl.glEnable(GL10.GL_DEPTH_TEST);//启用深度测试
gl.glDepthFunc(GL10.GL_LEQUAL); //设置深度测试的类型
gl.glEnable(GL10.GL_TEXTURE_2D);//启用2D纹理贴图
BufferUtil.loadTexture(mContext,gl);//装载纹理
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0, 0, width, height); //设置3D视窗的大小及位置
gl.glMatrixMode(GL10.GL_PROJECTION); //设置矩阵模式设为投影矩阵
gl.glLoadIdentity(); //初始化单位矩阵
float ratio = (float) width / height; //计算视窗宽高比
gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);//设置视窗空间大小
}
@Override
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);//启用vertex shader 数据
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); //启用贴图坐标数组数据
gl.glMatrixMode(GL10.GL_MODELVIEW);//设置当前矩阵堆栈为模型堆栈
gl.glLoadIdentity();
gl.glTranslatef(0.0f, 0.0f, -4f);
gl.glRotatef(rotate, 1.0f, 1.0f, 0.0f); //沿着(1,1)向量为轴的方向旋转
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, cubeVerticesBuffer);//设置顶点的位置数据
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, cubeTexturesBuffer);//设置贴图的坐标数据
// 按cubeFacetsBuffer指定的面绘制三角形gl.glDrawElements(GL10.GL_TRIANGLE_STRIP,cubeFacetsBuffer.remaining(),GL10.GL_UNSIGNED_BYTE, cubeFacetsBuffer);
gl.glFinish();//绘制结束
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
rotate+=1;
}
}
代码中涉及到的工具类 BufferUtil 代码实现如下:
public class BufferUtil {
//将int[]数组转换为OpenGLES所需的IntBuffer
public static IntBuffer intBufferUtil(int[] arr)
{
IntBuffer buffer;
// 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4字节
ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 4);
bb.order(ByteOrder.nativeOrder()); //数组排列用nativeOrder
buffer = bb.asIntBuffer();
buffer.put(arr);
buffer.position(0);
return buffer;
}
//将float[]数组转换为OpenGLES所需的FloatBuffer
public static FloatBuffer floatBufferUtil(float[] arr)
{
FloatBuffer buffer;
//初始化ByteBuffer,长度为arr数组的长度*4,因为一个float占4字节
ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 4);
bb.order(ByteOrder.nativeOrder());
buffer = bb.asFloatBuffer();
buffer.put(arr);
buffer.position(0);
return buffer;
}
public static int loadTexture(Context context, GL10 gl)
{
Bitmap bitmap = null;
int texture = 0;
try
{
// 加载位图
InputStream isImage = null;
try {
isImage = context.getAssets().open("sand.png");
} catch (IOException e) {
e.printStackTrace();
}
bitmap = BitmapFactory.decodeStream(isImage);
int[] textures = new int[1];
// 指定生成N个纹理(第一个参数指定生成一个纹理),这里textures数组将负责存储所有纹理的代号
gl.glGenTextures(1, textures, 0);
// 获取textures纹理数组中的第一个纹理
texture = textures[0];
// 绑定纹理到GL10.GL_TEXTURE_2D目标中
gl.glBindTexture(GL10.GL_TEXTURE_2D, texture);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
// 设置横向/纵向为平铺纹理
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
GL10.GL_REPEAT);
// 生成纹理
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
}finally {
if (bitmap != null) //生成纹理之后,回收位图
bitmap.recycle();
}
return texture;
}
}
注意:这里的资源是放在asset文件夹下的,可以根据自己的需要更改bitmap和对应的纹理图片。在MainActivity中实现代码为:
public class MainActivity extends AppCompatActivity {
private static String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GLSurfaceView glSurfaceView = new GLSurfaceView(this);
MyRender myRender = new MyRender();
glSurfaceView.setRenderer(myRender);
setContentView(glSurfaceView);
}
}