转:http://blog.csdn.net/junzia/article/details/52842816

前面几篇博客,我们将了Android中利用OpenGL ES 2.0绘制各种形体,并在上一篇博客中专门讲了GLSL语言。但是我们看到的基于OpenGL开发的应用和游戏,可不仅仅是那些规则形体和一些简单的色彩构成,而是各种不规则的形体构成了现实世界或者卡通世界的人和事物,他们都是外面穿着漂亮“衣服”的。本篇博客就是来讲解这些“衣服”的基础的。这些衣服就是纹理贴图。

什么是纹理贴图

一般说来,纹理是表示物体表面的一幅或几幅二维图形,也称纹理贴图(texture)。当把纹理按照特定的方式映射到物体表面上的时候,能使物体看上去更加真实。当前流行的图形系统中,纹理绘制已经成为一种必不可少的渲染方法。在理解纹理映射时,可以将纹理看做应用在物体表面的像素颜色。在真实世界中,纹理表示一个对象的颜色、图案以及触觉特征。纹理只表示对象表面的彩色图案,它不能改变对象的几何形式。更进一步的说,它只是一种高强度的计算行为。——百度百科

比如我们在利用OpenGL做游戏的时候,加载了一个人物模型进来了,这个人物模型上是没有色彩的。我们需要给它绘上需要的色彩才行。但是这些色彩从哪里来呢?我们不可能像之前处理球体那样,根据顶点取生成需要的色彩,那样对于我们给这个人物模型绘色的工作量实在太大了。这个时候我们就需要用到纹理贴图的技术了——把一个纹理(对于2D贴图,可以简单的理解为图片),按照所期望的方式显示在诸多三角形组成的物体的表面。

纹理映射原理

启用纹理映射后,如果想把一幅纹理映射到相应的几何图元,就必须告诉GPU如何进行纹理映射,也就是为图元的顶点指定恰当的纹理坐标。纹理坐标用浮点数来表示,范围一般从0.0到1.0,左上角坐标为(0.0,0.0),右上角坐标为(1.0,0.0),左下角坐标为(0.0,1.0),右下角坐标为(1.0,1.0),如下图所示: 
  
左图为纹理图和纹理坐标,右图为顶点图和顶点坐标。 
将纹理映射到右边的两个三角形上(也就是一个矩形),需要将纹理坐标指定到正确的顶点上,才能使纹理正确的显示,否则显示出来的纹理会无法显示,或者出现旋转、翻转、错位等情况。 
将右图顶点按照V2V1V4V3传入,以三角形条带方式绘制,则纹理坐标应按照V2V1V4V3传入。如果按照V3V4V1V2传入,会得到一个旋转了180度的纹理。如果按照V4V3V2V1传入,则会得到一个左右翻转的纹理。

显示图片

根据纹理映射原理,结合之前绘制正方形的经验,我们可以根据以下步骤利用OpenGL ES显示一张图片:

第一步,修改着色器 
首先,我们需要修改我们的着色器,将顶点着色器修改为:

attribute vec4 vPosition;
attribute vec2 vCoordinate;
uniform mat4 vMatrix; varying vec2 aCoordinate; void main(){
gl_Position=vMatrix*vPosition;
aCoordinate=vCoordinate;
}

可以看到,顶点着色器中增加了一个vec2变量,并将这个变量传递给了片元着色器,这个变量就是纹理坐标。接着我们修改片元着色器为:

precision mediump float;

uniform sampler2D vTexture;
varying vec2 aCoordinate; void main(){
gl_FragColor=texture2D(vTexture,aCoordinate);
}

片元着色器中,增加了一个sampler2D的变量,sampler2D我们在前一篇博客GLSL语言基础中提到过,是GLSL的变量类型之一的取样器。texture2D也有提到,它是GLSL的内置函数,用于2D纹理取样,根据纹理取样器和纹理坐标,可以得到当前纹理取样得到的像素颜色。

第二步,设置顶点坐标和纹理坐标

根据纹理映射原理中的介绍,我们将顶点坐标设置为:

private final float[] sPos={
-1.0f,1.0f, //左上角
-1.0f,-1.0f, //左下角
1.0f,1.0f, //右上角
1.0f,-1.0f //右下角
};

相应的,对照顶点坐标,我们可以设置纹理坐标为:

private final float[] sCoord={
0.0f,0.0f,
0.0f,1.0f,
1.0f,0.0f,
1.0f,1.0f,
};

第三步,计算变换矩阵

按照上步设置顶点坐标和纹理坐标,大多数情况下我们得到的一定是一张拉升或者压缩的图片。为了让图片完整的显示,且不被拉伸和压缩,我们需要向绘制等腰直角三角形一样,计算一个合适的变换矩阵,传入顶点着色器,代码如下:

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0,0,width,height); int w=mBitmap.getWidth();
int h=mBitmap.getHeight();
float sWH=w/(float)h;
float sWidthHeight=width/(float)height;
if(width>height){
if(sWH>sWidthHeight){
Matrix.orthoM(mProjectMatrix, 0, -sWidthHeight*sWH,sWidthHeight*sWH, -1,1, 3, 7);
}else{
Matrix.orthoM(mProjectMatrix, 0, -sWidthHeight/sWH,sWidthHeight/sWH, -1,1, 3, 7);
}
}else{
if(sWH>sWidthHeight){
Matrix.orthoM(mProjectMatrix, 0, -1, 1, -1/sWidthHeight*sWH, 1/sWidthHeight*sWH,3, 7);
}else{
Matrix.orthoM(mProjectMatrix, 0, -1, 1, -sWH/sWidthHeight, sWH/sWidthHeight,3, 7);
}
}
//设置相机位置
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7.0f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
//计算变换矩阵
Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
}

mMVPMatrix即为我们所需要的变换矩阵。

第四步,显示图片

然后我们需要做的,就和之前绘制正方形一样容易了。和之前不同的是,在绘制之前,我们还需要将纹理和纹理坐标传入着色器:

@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT|GLES20.GL_DEPTH_BUFFER_BIT);
GLES20.glUseProgram(mProgram);
onDrawSet();
GLES20.glUniformMatrix4fv(glHMatrix,1,false,mMVPMatrix,0);
GLES20.glEnableVertexAttribArray(glHPosition);
GLES20.glEnableVertexAttribArray(glHCoordinate);
GLES20.glUniform1i(glHTexture, 0);
textureId=createTexture();
//传入顶点坐标
GLES20.glVertexAttribPointer(glHPosition,2,GLES20.GL_FLOAT,false,0,bPos);
//传入纹理坐标
GLES20.glVertexAttribPointer(glHCoordinate,2,GLES20.GL_FLOAT,false,0,bCoord);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);
} public abstract void onDrawSet();
public abstract void onDrawCreatedSet(int mProgram); private int createTexture(){
int[] texture=new int[1];
if(mBitmap!=null&&!mBitmap.isRecycled()){
//生成纹理
GLES20.glGenTextures(1,texture,0);
//生成纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,texture[0]);
//设置缩小过滤为使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
//设置放大过滤为使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
//设置环绕方向S,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE);
//设置环绕方向T,截取纹理坐标到[1/2n,1-1/2n]。将导致永远不会与border融合
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE);
//根据以上指定的参数,生成一个2D纹理
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmap, 0);
return texture[0];
}
return 0;
}

这样我们就可以显示出我们需要显示的图片,并且保证它完整的居中显示而且不会变形了,如下图: 

源码

所有的代码全部在一个项目中,托管在Github上——Android OpenGLES 2.0系列博客的Demo

欢迎转载,转载请保留文章出处。湖广午王的博客[http://blog.csdn.net/junzia/article/details/52842816]

(转)使用OpenGL显示图像(七)Android OpenGLES2.0——纹理贴图之显示图片的更多相关文章

  1. Android OpenGLES2.0(十七)——球形天空盒VR效果实现

    在3D游戏中通常都会用到天空盒,在3D引擎中也一般会存在天空盒组件,让开发者可以直接使用.那么天空盒是什么?天空盒又是如何实现的呢?本篇博客主要介绍如何在Android中利用OpenGLES绘制一个天 ...

  2. Android开发——获得Json数据,并显示图片

    流程介绍 使用okhttp网络框架进行get请求,获得json数据 //一个封装好的工具类的静态方法 public static void sendOkHttpRequest(final String ...

  3. Android实例-操作sqlite数据库之Grid显示图片(XE8+小米2)

    结果: 1.数据库文件,记得打包到程序中(assets\internal\). 操作方法: 1.新建firemonkey mobile application①菜单->File->New- ...

  4. Android 5.0 行为变更

    Android 5.0 除了提供诸多新特性和功能外,还对系统和 API 行为做出了各种变更.本文重点介绍您应该了解并在开发应用时加以考虑的一些主要变更. 如果您之前发布过 Android 应用,请注意 ...

  5. Android 6.0(棉花糖)新特性

    1.支持4K显示 Android 6.0本身已经支持4K显示,会通过一定优化形式使4K内容更加清晰. 2. 启动验证 (更完整的应用权限管理) Android 6.0在开机时会自动运行验证代码,检测设 ...

  6. Android上使用OpenglES2.0遇到的一点问题

    按照教程开发OpenglES2.0应用,遇到Logcat报错“Called unimplemented OpenGL ES API” 在论坛和stackoverflow上找到了答案. 1.manife ...

  7. Android OpenGL ES 3.0 纹理应用

    本文主要演示OpenGL ES 3.0 纹理演示.接口大部分和2.0没什么区别,脚本稍微有了点变化而已. 扩展GLSurfaceView package com.example.gles300; im ...

  8. Android进阶:七、Retrofit2.0原理解析之最简流程【下】

    紧接上文Android进阶:七.Retrofit2.0原理解析之最简流程[上] 一.请求参数整理 我们定义的接口已经被实现,但是我们还是不知道我们注解的请求方式,参数类型等是如何发起网络请求的呢? 这 ...

  9. Android Camera2 Opengles2.0 实时滤镜(冷暖色/放大镜/模糊/美颜)

    https://blog.csdn.net/keen_zuxwang/article/details/78363464 demo: http://download.csdn.net/download/ ...

随机推荐

  1. hdu 5396 Expression

    考虑到此题麻烦了某hust大神&体现出了自己数学能力的欠缺 虽然最近一直比较忙 还是把这题的题解写下来吧 首先看完数据范围后 应该有不少人会反应到是$n^3$的DP 以$F[i][j]$表示从 ...

  2. tarjan复习笔记

    tarjan复习笔记 (关于tarjan读法,优雅一点读塔洋,接地气一点读塔尖) 0. 连通分量 有向图: 强连通分量(SCC)是个啥 就是一张图里面两个点能互相达到,那么这两个点在同一个强连通分量里 ...

  3. js 判断对象的长度

    Object.size = function(obj) { var size = 0, key; for (key in obj) { if (obj.hasOwnProperty(key)) siz ...

  4. 基于第二次数独游戏,添加GUI界面

    高级软件工程第三次作业:基于第二次数独游戏,添加GUI界面.GUI界面代码如下: package firstGui; import java.awt.*; import java.awt.event. ...

  5. Codeforces 1114D(区间DP)

    题面 传送门 分析 法1(区间DP): 首先,我们可以把连续的相等区间缩成一个数,用unique来实现,不影响结果 {1,2,2,3,3,3,5,3,4}->{1,2,3,5,3,4} 先从一个 ...

  6. 配置访问公网主机上的jupyter notebook

    文章结构: 一.安装python 二.安装并配置jupyter并配置jupyter 三.第一个python程序 一.安装python 1.1下载python安装包 # wget https://www ...

  7. Codeforces 500D New Year Santa Network(树 + 计数)

    D. New Year Santa Network time limit per test 2 seconds memory limit per test 256 megabytes input st ...

  8. struct模块的使用

    原理: 将一组简单数据进行打包,转换为bytes格式发送.或者将一组bytes格式数据,进行解析. 接口使用 Struct(fmt) 功能: 生成结构化对象 参数:fmt 定制的数据结构 st.pac ...

  9. 转载:java面试题(二)

    这部分主要是开源Java EE框架方面的内容,包括Hibernate.MyBatis.Spring.Spring MVC等,由于Struts 2已经是明日黄花,在这里就不讨论Struts 2的面试题, ...

  10. 【服务端开发-杂】REST 和 Graphql

    基本知识链接: 1.[译]对比GraphQL与REST——两种HTTP API的差异:https://www.jianshu.com/p/2ad286397f7a 2. 怎样用通俗的语言解释REST, ...