Android多媒体之GLES2战记第五集--宇宙之光

1,917 阅读16分钟
你以为我的封面图只是吸引眼球?

上集说到:用矩阵的变换来操作顶点,使图形产生相应的变化(移动,选择,缩放)
这一集将点亮世界之光,让你对OpenGL的世界有更深的了解


普通副本五:黑龙之珠

本副本参照《Android 3D游戏开发技术宝典 OpenGL ES 2.0》
但是分析的要详细一些,书中绘制的方法只是一笔带过,感觉球面还是需要挖挖的
而且书中源码绘制部分写的也挺乱的,该抽的我抽了一下,看着好看些

球面的拼接.gif


1.第一关卡:球面的顶点计算

也就是经纬取分割点,再将这些点拼成三角形形成曲面效果
下面应该很形象的说明了渐变的过程

增加切割点数.png

经纬度

/**
 * 初始化顶点坐标数据的方法
 *
 * @param r          半径
 * @param splitCount 赤道分割点数
 */
public void initVertex(float r, int splitCount) {
    // 顶点坐标数据的初始化================begin============================
    ArrayList<Float> vertixs = new ArrayList<>();// 存放顶点坐标的ArrayList
    final float dθ = 360.f / splitCount;// 将球进行单位切分的角度
    //垂直方向angleSpan度一份
    for (float α = -90; α < 90; α = α + dθ) {
        // 水平方向angleSpan度一份
        for (float β = 0; β <= 360; β = β + dθ) {
            // 纵向横向各到一个角度后计算对应的此点在球面上的坐标
            float x0 = r * cos(α) * cos(β);
            float y0 = r * cos(α) * sin(β);
            float z0 = r * sin(α);
            float x1 = r * cos(α) * cos(β + dθ);
            float y1 = r * cos(α) * sin(β + dθ);
            float z1 = r * sin(α);
            float x2 = r * cos(α + dθ) * cos(β + dθ);
            float y2 = r * cos(α + dθ) * sin(β + dθ);
            float z2 = r * sin(α + dθ);
            float x3 = r * cos(α + dθ) * cos(β);
            float y3 = r * cos(α + dθ) * sin(β);
            float z3 = r * sin(α + dθ);
            // 将计算出来的XYZ坐标加入存放顶点坐标的ArrayList
            vertixs.add(x1);vertixs.add(y1);vertixs.add(z1);//p1
            vertixs.add(x3);vertixs.add(y3);vertixs.add(z3);//p3
            vertixs.add(x0);vertixs.add(y0);vertixs.add(z0);//p0
            vertixs.add(x1);vertixs.add(y1);vertixs.add(z1);//p1
            vertixs.add(x2);vertixs.add(y2);vertixs.add(z2);//p2
            vertixs.add(x3);vertixs.add(y3);vertixs.add(z3);//p3
        }
    }
    verticeCount = vertixs.size() / 3;// 顶点的数量为坐标值数量的1/3,因为一个顶点有3个坐标
    // 将vertices中的坐标值转存到一个float数组中
    float vertices[] = new float[verticeCount * 3];
    for (int i = 0; i < vertixs.size(); i++) {
        vertices[i] = vertixs.get(i);
    }
    vertexBuffer = GLUtil.getFloatBuffer(vertices);
}

/**
 * 求sin值
 *
 * @param θ 角度值
 * @return sinθ
 */
private float sin(float θ) {
    return (float) Math.sin(Math.toRadians(θ));
}

/**
 * 求cos值
 *
 * @param θ 角度值
 * @return cosθ
 */
private float cos(float θ) {
    return (float) Math.cos(Math.toRadians(θ));
}

2.第二关卡:着色器的代码及使用
2.1:片元着色代码:ball.frag

添加uR句柄,vPosition获取顶点坐标,根据坐标来进行着色

precision mediump float;
 uniform float uR;
 varying vec3 vPosition;//接收从顶点着色器过来的顶点位置
 void main(){
    vec3 color;
    float n = 8.0;//一个坐标分量分的总份数
    float span = 2.0*uR/n;//每一份的长度
    //每一维在立方体内的行列数
    int i = int((vPosition.x + uR)/span);
    int j = int((vPosition.y + uR)/span);
    int k = int((vPosition.z + uR)/span);
    //计算当点应位于白色块还是黑色块中
    int whichColor = int(mod(float(i+j+k),2.0));
    if(whichColor == 1) {//奇数时
    		color = vec3(0.16078432f,0.99215686f,0.02745098f);//绿
    } else {//偶数时为白色
    		color = vec3(1.0,1.0,1.0);//白色
    }
    //将计算出的颜色给此片元
    gl_FragColor=vec4(color,0);
 }

2.2:顶点着色代码:ball.vert
uniform mat4 uMVPMatrix; 
attribute vec3 aPosition;
varying vec3 vPosition;
void main(){
   gl_Position = uMVPMatrix * vec4(aPosition,1);
   vPosition = aPosition;
}

2.3:着色器的使用
//声明句柄
private int mPositionHandle;//位置句柄
private int muMVPMatrixHandle;//顶点变换矩阵句柄
private int muRHandle;//半径的句柄

//获取句柄
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
muRHandle = GLES20.glGetUniformLocation(mProgram, "uR");

//使用句柄
GLES20.glEnableVertexAttribArray(mPositionHandle);//启用顶点的句柄
//顶点矩阵变换
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mvpMatrix, 0);
//准备顶点坐标数据
GLES20.glVertexAttribPointer(
        mPositionHandle,//int indx, 索引
        COORDS_PER_VERTEX,//int size,大小
        GLES20.GL_FLOAT,//int type,类型
        false,//boolean normalized,//是否标准化
        vertexStride,// int stride,//跨度
        vertexBuffer);// java.nio.Buffer ptr//缓冲
// 将半径尺寸传入shader程序
GLES20.glUniform1f(muRHandle, mR * Cons.UNIT_SIZE);

3.第三关卡:关于UNIT_SIZE

就是一个尺寸的伸缩量而已,定义成常量,方便放大与缩小,没别的

UNIT_SIZE.png


普通副本六宇宙之光

OpenGL ES 中只有三种光:

环境光:光照的作用全体
散射光:单点光源
镜面光:镜面反射

1.第一关卡:环境光

就像太阳光,我们身处的环境,环境中,光照的作用结果一致
修改起来也比较方便,环境光说白了就是对片元颜色的运算而已

0.350.751.00

1.1.片元着色器ball.frag
precision mediump float;
 uniform float uR;
 varying vec3 vPosition;//接收从顶点着色器过来的顶点位置
 varying vec4 vAmbient;//接收从顶点着色器过来的环境光分量
 void main(){
    vec3 color;
    float n = 8.0;//一个坐标分量分的总份数
    float span = 2.0*uR/n;//每一份的长度
    //每一维在立方体内的行列数
    int i = int((vPosition.x + uR)/span);
    int j = int((vPosition.y + uR)/span);
    int k = int((vPosition.z + uR)/span);
    //计算当点应位于白色块还是黑色块中
    int whichColor = int(mod(float(i+j+k),2.0));
    if(whichColor == 1) {//奇数时
    		color = vec3(0.16078432f,0.99215686f,0.02745098f);//绿
    } else {//偶数时为白色
    		color = vec3(1.0,1.0,1.0);//白色
    }
    //最终颜色
    vec4 finalColor=vec4(color,0);
    //给此片元颜色值
    gl_FragColor=finalColor*vAmbient;
 }

1.2.顶点着色器ball.vert
uniform mat4 uMVPMatrix; //总变换矩阵
attribute vec3 aPosition;  //顶点位置
varying vec3 vPosition;//用于传递给片元着色器的顶点位置
uniform vec4 uAmbient;
varying vec4 vAmbient;//用于传递给片元着色器的环境光分量

void main(){
   //根据总变换矩阵计算此次绘制此顶点位置
   gl_Position = uMVPMatrix * vec4(aPosition,1);
   //将顶点的位置传给片元着色器
   vPosition = aPosition;//将原始顶点位置传递给片元着色器
   //将的环境光分量传给片元着色器
   vAmbient = vec4(uAmbient);
}

1.3.使用:句柄拿到传值而已,也没什么难的
private int muAmbientHandle;//环境光句柄
//获取环境光句柄
muAmbientHandle = GLES20.glGetUniformLocation(mProgram, "uAmbient");
//使用环境光
GLES20.glUniform4f(muAmbientHandle, 0.5f,0.5f,0.5f,1f);

1.第二关卡:散射光

忙活了好一会,总算搞定了,一个api用错了,一直崩溃...
相当于打个灯,灯的位置是固定不动的

-1,1,-11,1,-1

看下图的点光源在(1,1,-1) 你应该知道灯在哪了吧,注意看轴色

散射光.png


2.1:顶点着色器:ball.vert

代码有点复杂,做好心理准备

uniform mat4 uMVPMatrix; //总变换矩阵
attribute vec3 aPosition;  //顶点位置
varying vec3 vPosition;//用于传递给片元着色器的顶点位置

varying vec4 uAmbient;//环境光分量
varying vec4 vAmbient;//用于传递给片元着色器的环境光分量

uniform mat4 uMMatrix;//变换矩阵(包括平移、旋转、缩放)
uniform vec3 uLightLocation;//光源位置
attribute vec3 aNormal;//顶点法向量
varying vec4 vDiffuse;	//用于传递给片元着色器的散射光分量

//散射光光照计算的方法(法向量,散射光计算结果,光源位置,散射光强度)
void pointLight (in vec3 normal,inout vec4 diffuse,in vec3 lightLocation,in vec4 lightDiffuse){
  vec3 normalTarget=aPosition+normal;//计算变换后的法向量
  vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
  newNormal=normalize(newNormal);//对法向量规格化
   //计算从表面点到光源位置的向量vp
  vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);
  vp=normalize(vp);//单位化vp
  float nDotViewPosition=max(0.0,dot(newNormal,vp));//求法向量与vp向量的点积与0的最大值
  diffuse=lightDiffuse*nDotViewPosition;//计算散射光的最终强度
}

void main(){
   //根据总变换矩阵计算此次绘制此顶点位置
   gl_Position = uMVPMatrix * vec4(aPosition,1);
   //将顶点的位置传给片元着色器
   vPosition = aPosition;//将原始顶点位置传递给片元着色器

    vec4 diffuseTemp=vec4(0.0,0.0,0.0,0.0);
    pointLight(normalize(aNormal), diffuseTemp, uLightLocation, vec4(0.8,0.8,0.8,1.0));
    vDiffuse=diffuseTemp;//将散射光最终强度传给片元着色器

   //将的环境光分量传给片元着色器
   vAmbient = vec4(uAmbient);
}

2.2:片元着色器:ball.frag
precision mediump float;
   uniform float uR;
   varying vec3 vPosition;//接收从顶点着色器过来的顶点位置
   varying vec4 vAmbient;//接收从顶点着色器过来的环境光分量
   varying vec4 vDiffuse;//接收从顶点着色器过来的散射光分量
   void main(){
      vec3 color;
      float n = 8.0;//一个坐标分量分的总份数
      float span = 2.0*uR/n;//每一份的长度
      //每一维在立方体内的行列数
      int i = int((vPosition.x + uR)/span);
      int j = int((vPosition.y + uR)/span);
      int k = int((vPosition.z + uR)/span);
      //计算当点应位于白色块还是黑色块中
      int whichColor = int(mod(float(i+j+k),2.0));
      if(whichColor == 1) {//奇数时
      		color = vec3(0.16078432f,0.99215686f,0.02745098f);//绿
      } else {//偶数时为白色
      		color = vec3(1.0,1.0,1.0);//白色
      }
      //最终颜色
      vec4 finalColor=vec4(color,0);
      //给此片元颜色值
//      gl_FragColor=finalColor*vAmbient;//环境光
      gl_FragColor=finalColor*vDiffuse;
   }

2.3:使用

新添了三个句柄,用法也是写烂了...
这里用一个GLState类管理全局的状态

---->[Ball#initProgram]-----------
//获取顶点法向量的句柄
maNormalHandle = GLES20.glGetAttribLocation(mProgram, "aNormal");
//获取程序中光源位置引用
maLightLocationHandle = GLES20.glGetUniformLocation(mProgram, "uLightLocation");
//获取位置、旋转变换矩阵引用
muMMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMMatrix");

---->[Ball#draw]-----------
//将位置、旋转变换矩阵传入着色器程序
GLES20.glUniformMatrix4fv(muMMatrixHandle, 1, false, MatrixStack.getOpMatrix(), 0);
//将光源位置传入着色器程序
GLES20.glUniform3fv(maLightLocationHandle, 1, GLState.lightPositionFB);
//将顶点法向量数据传入渲染管线
GLES20.glVertexAttribPointer(maNormalHandle, 3, GLES20.GL_FLOAT, false,
                3 * 4, vertexBuffer);
                
---->[GLState.java]-----------
////////----------设置光源
private static float[] lightLocation = new float[]{0, 0, 0};//定位光光源位置
public static FloatBuffer lightPositionFB;
//设置灯光位置的方法
public static void setLightLocation(float x, float y, float z) {
    lightLocation[0] = x;
    lightLocation[1] = y;
    lightLocation[2] = z;
    lightPositionFB = getFloatBuffer(lightLocation);
}

---->[WorldRenderer#onDrawFrame]-----------
GLState.setLightLocation(-1, 1, -1);

3.第三关卡:镜面光

真的有些hold不住了...
同一束光,照在粗糙度不同的物体上,越光滑,我们可以看到的部分就越多
单独写了一个Ball_M.java的类,以及两个ball_m.vert,ball_m.frag着色器
平面光就够喝一壶的了,升级到三维...还是先用着吧,原理等百无聊赖的时候再分析吧

镜面反射.png


3.1:顶点着色器:ball_m.vert
uniform mat4 uMVPMatrix; 	//总变换矩阵
uniform mat4 uMMatrix; 		//变换矩阵
uniform vec3 uLightLocation;	//光源位置
uniform vec3 uCamera;		//摄像机位置
attribute vec3 aPosition;  	//顶点位置
attribute vec3 aNormal;   	//法向量
varying vec3 vPosition;		//用于传递给片元着色器的顶点位置
varying vec4 vSpecular;		//用于传递给片元着色器的镜面光最终强度
void pointLight(				//定位光光照计算的方法
  in vec3 normal,			//法向量
  inout vec4 specular,		//镜面反射光分量
  in vec3 lightLocation,		//光源位置
  in vec4 lightSpecular		//镜面光强度
){
  vec3 normalTarget=aPosition+normal; 	//计算变换后的法向量
  vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
  newNormal=normalize(newNormal);  	//对法向量规格化
  //计算从表面点到摄像机的向量
  vec3 eye= normalize(uCamera-(uMMatrix*vec4(aPosition,1)).xyz);
  //计算从表面点到光源位置的向量vp
  vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);
  vp=normalize(vp);//格式化vp
  vec3 halfVector=normalize(vp+eye);	//求视线与光线的半向量
  float shininess=5.0;				//粗糙度,越小越光滑
  float nDotViewHalfVector=dot(newNormal,halfVector);			//法线与半向量的点积
  float powerFactor=max(0.0,pow(nDotViewHalfVector,shininess)); 	//镜面反射光强度因子
  specular=lightSpecular*powerFactor;    //最终的镜面光强度
}
void main()  {
   gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点的位置
   vec4 specularTemp=vec4(0.0,0.0,0.0,0.0);
   pointLight(normalize(aNormal), specularTemp, uLightLocation, vec4(0.7,0.7,0.7,1.0));//计算镜面光
   vSpecular=specularTemp;	//将最终镜面光强度传给片元着色器
   vPosition = aPosition; 		//将顶点的位置传给片元着色器
} 

3.2:片元着色器:ball_m.vert
precision mediump float;
uniform float uR;
varying vec3 vPosition;//接收从顶点着色器过来的顶点位置
varying vec4 vSpecular;//接收从顶点着色器过来的镜面反射光分量
void main(){
   vec3 color;
   float n = 8.0;//一个坐标分量分的总份数
   float span = 2.0*uR/n;//每一份的长度
   //每一维在立方体内的行列数
   int i = int((vPosition.x + uR)/span);
   int j = int((vPosition.y + uR)/span);
   int k = int((vPosition.z + uR)/span);
   //计算当点应位于白色块还是黑色块中
   int whichColor = int(mod(float(i+j+k),2.0));
   if(whichColor == 1) {//奇数时为红色
   		color = vec3(0.678,0.231,0.129);//红色
   }
   else {//偶数时为白色
   		color = vec3(1.0,1.0,1.0);//白色
   }
   //最终颜色
   vec4 finalColor=vec4(color,0);
   //给此片元颜色值
   gl_FragColor=finalColor*vSpecular;
}

3.使用

增加了一个uCamera句柄,增加相机位置的状态,在MatrixStack#lookAt里初始化

maCameraHandle = GLES20.glGetUniformLocation(mProgram, "uCamera");
---->[Ball_M#draw]-------
GLES20.glUniform3fv(maCameraHandle, 1, GLState.cameraFB);

---->[GLState]-------
////////----------设置相机位置
static float[] cameraLocation = new float[3];//摄像机位置
public static FloatBuffer cameraFB;
//设置灯光位置的方法
public static void setCameraLocation(float x, float y, float z) {
    cameraLocation[0] = x;
    cameraLocation[1] = y;
    cameraLocation[2] = z;
    cameraFB = GLUtil.getFloatBuffer(cameraLocation);
}

---->[MatrixStack#lookAt]-------
GLState.setCameraLocation(cx, cy, cz);//设置相机位置

4.第四关卡:三光同时作用

就是综合一下而已...,跟书中小不同,这里我把粗糙度和环境光提出来了
基本上代码里没有什么变化,终点在着色器里

三光.gif

三光.png

---->[ball.vert]---------------------
uniform mat4 uMVPMatrix; 		//总变换矩阵
uniform mat4 uMMatrix; 			//变换矩阵
uniform vec3 uLightLocation;		//光源位置
uniform vec3 uCamera;			//摄像机位置
uniform float uShininess;			//摄像机位置
uniform vec4 uAmbient;//环境光
attribute vec3 aPosition;  		//顶点位置
attribute vec3 aNormal;    		//法向量
varying vec3 vPosition;			//用于传递给片元着色器的顶点位置
varying vec4 vAmbient;			//用于传递给片元着色器的环境光最终强度
varying vec4 vDiffuse;			//用于传递给片元着色器的散射光最终强度
varying vec4 vSpecular;			//用于传递给片元着色器的镜面光最终强度

void pointLight(					//定位光光照计算的方法
  in vec3 normal,				//法向量
  inout vec4 ambient,			//环境光最终强度
  inout vec4 diffuse,				//散射光最终强度
  inout vec4 specular,			//镜面光最终强度
  in vec3 lightLocation,			//光源位置
  in vec4 lightAmbient,			//环境光强度
  in vec4 lightDiffuse,			//散射光强度
  in vec4 lightSpecular			//镜面光强度
){
  ambient=lightAmbient;			//直接得出环境光的最终强度
  vec3 normalTarget=aPosition+normal;	//计算变换后的法向量
  vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
  newNormal=normalize(newNormal); 	//对法向量规格化
  //计算从表面点到摄像机的向量
  vec3 eye= normalize(uCamera-(uMMatrix*vec4(aPosition,1)).xyz);
  //计算从表面点到光源位置的向量vp
  vec3 vp= normalize(lightLocation-(uMMatrix*vec4(aPosition,1)).xyz);
  vp=normalize(vp);//格式化vp
  vec3 halfVector=normalize(vp+eye);	//求视线与光线的半向量
  float shininess=uShininess;				//粗糙度,越小越光滑
  float nDotViewPosition=max(0.0,dot(newNormal,vp)); 	//求法向量与vp的点积与0的最大值
  diffuse=lightDiffuse*nDotViewPosition;				//计算散射光的最终强度
  float nDotViewHalfVector=dot(newNormal,halfVector);	//法线与半向量的点积
  float powerFactor=max(0.0,pow(nDotViewHalfVector,shininess)); 	//镜面反射光强度因子
  specular=lightSpecular*powerFactor;    			//计算镜面光的最终强度
}
void main(){
   gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置
   vec4 ambientTemp,diffuseTemp,specularTemp;	  //用来接收三个通道最终强度的变量
   pointLight(normalize(aNormal),ambientTemp,diffuseTemp,specularTemp,uLightLocation,
   uAmbient,vec4(0.8,0.8,0.8,1.0),vec4(0.7,0.7,0.7,1.0));
   vAmbient=ambientTemp; 		//将环境光最终强度传给片元着色器
   vDiffuse=diffuseTemp; 		//将散射光最终强度传给片元着色器
   vSpecular=specularTemp; 		//将镜面光最终强度传给片元着色器
   vPosition = aPosition;  //将顶点的位置传给片元着色器
}

---->[ball.frag]---------------------
precision mediump float;
uniform float uR;
varying vec3 vPosition;//接收从顶点着色器过来的顶点位置
varying vec4 vAmbient;//接收从顶点着色器过来的环境光分量
varying vec4 vDiffuse;//接收从顶点着色器过来的散射光分量
varying vec4 vSpecular;//接收从顶点着色器过来的镜面反射光分量
void main()
{
   vec3 color;
   float n = 8.0;//一个坐标分量分的总份数
   float span = 2.0*uR/n;//每一份的长度
   //每一维在立方体内的行列数
   int i = int((vPosition.x + uR)/span);
   int j = int((vPosition.y + uR)/span);
   int k = int((vPosition.z + uR)/span);
   //计算当点应位于白色块还是黑色块中
   int whichColor = int(mod(float(i+j+k),2.0));
   if(whichColor == 1) {//奇数时
   		color = vec3(0.16078432f,0.99215686f,0.02745098f);//绿
   }
   else {//偶数时为白色
   		color = vec3(1.0,1.0,1.0);//白色
   }
   //最终颜色
   vec4 finalColor=vec4(color,0);
   //给此片元颜色值
   gl_FragColor=finalColor*vAmbient + finalColor*vDiffuse + finalColor*vSpecular;
}

---->[GLState.java]---------------------
////////----------环境光
static float[] eviLight = new float[4];//摄像机位置
public static FloatBuffer eviLightFB;
//设置灯光位置的方法
public static void setEviLight(float r, float g, float b,float a) {
    eviLight[0] = r;
    eviLight[1] = g;
    eviLight[2] = b;
    eviLight[3] = a;
    eviLightFB = GLUtil.getFloatBuffer(eviLight);
}

---->[Ball.java]---------------------
//粗糙度
muShininessHandle = GLES20.glGetUniformLocation(mProgram, "uShininess");

GLES20.glUniform1f(muShininessHandle, 30);

普通副本七:龙之盛装LEVEL2

新手副本龙之盛装LEVEL1中已经简单知道了纹理的贴图 这个副本将来深入了解一下贴图

贴图展示.gif


1.第一关卡:纹理坐标系

纹理坐标系(右侧)是一个二维坐标,方向和Android中的屏幕坐标系一致
书上说贴图的宽高像素数必须是2的n次方,但是我试了不是也可以。为免争议,这里用的是2的n次方

点位坐标与纹理坐标.png

贴图1.png

static float vertexs[] = {   //以逆时针顺序
        -1, 1, 0,
        -1, -1, 0,
        1, -1, 0,
};

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

2.第二关卡:矩形

先用三点矩形来画,比较形象一些,就是两个三角形拼合

矩形.png

static float vertexs[] = {   //以逆时针顺序
        -1, 1, 0,
        -1, -1, 0,
        1, -1, 0,
        
        1, -1, 0,
        1, 1, 0,
        -1, 1, 0
};

private final float[] textureCoo = {
        0, 0,
        0, 1,
        1, 1,
        
        1, 1,
        1, 0,
        0, 0
};

3.第三关卡:纹理的裁剪与拉伸

剪裁.png

3.1:添加两个系数控制纹理坐标的大小
---->[TextureRectangle]---------
float s = 1;//s纹理坐标系数
float t = 1f;//t纹理坐标系数

private final float[] textureCoo = {
        0, 0,
        0, t,
        s, t,
        
        s, t,
        s, 0,
        0, 0
};

3.2:加载纹理的工具

其中s和t的包裹方式:GL_CLAMP_TO_EDGE

//---------------纹理加载工具--GLUtil.java-----

/**
 * 资源id 加载纹理
 *
 * @param ctx   上下文
 * @param resId 资源id
 * @return 纹理id
 */
public static int loadTexture(Context ctx, int resId) {
    Bitmap bitmap = BitmapFactory.decodeResource(ctx.getResources(), resId);
    return loadTexture(ctx, bitmap);
}

/**
 * bitmap 加载纹理
 *
 * @param ctx    上下文
 * @param bitmap bitmap
 * @return 纹理id
 */
public static int loadTexture(Context ctx, Bitmap bitmap) {
    //生成纹理ID
    int[] textures = new int[1];
    //(产生的纹理id的数量,纹理id的数组,偏移量)
    GLES20.glGenTextures(1, textures, 0);
    int textureId = textures[0];
    //绑定纹理id
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
    //采样方式MIN
    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轴包裹方式---截取
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
    //设置t轴包裹方式---截取
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
    //实际加载纹理(纹理类型,纹理的层次,纹理图像,纹理边框尺寸)
    GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
    bitmap.recycle();          //纹理加载成功后释放图片
    return textureId;
}

4.第四关卡:纹理的重复

这和css的重复方式挺像的,看一眼就应该明白,我就不废话了
要改的就两局代码:GLUtil#loadTexture

重复

//设置s轴拉伸方式---重复
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,  GLES20.GL_REPEAT);
//设置t轴拉伸方式---重复
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,  GLES20.GL_REPEAT);

5.第五关卡:重复模式的封装
5.1:重复模式的枚举
/**
 * 作者:张风捷特烈<br/>
 * 时间:2019/1/16/016:9:31<br/>
 * 邮箱:1981462002@qq.com<br/>
 * 说明:重复方式枚举
 */
public enum RepeatType {
    NONE,//不重复
    REPEAT_X,//仅x轴重复
    REPEAT_Y,//仅y轴重复
    REPEAT//x,y重复
}

5.1:加载纹理方法的封装
//---------------纹理加载工具--GLUtil.java-----
/**
 * 资源id 加载纹理,默认重复方式:RepeatType.REPEAT
 *
 * @param ctx   上下文
 * @param resId 资源id
 * @return 纹理id
 */
public static int loadTexture(Context ctx, int resId) {
    return loadTexture(ctx, resId, RepeatType.REPEAT);
}

/**
 * 资源id 加载纹理
 *
 * @param ctx        上下文
 * @param resId      资源id
 * @param repeatType 重复方式 {@link com.toly1994.picture.world.bean.RepeatType}
 * @return 纹理id
 */
public static int loadTexture(Context ctx, int resId, RepeatType repeatType) {
    Bitmap bitmap = BitmapFactory.decodeResource(ctx.getResources(), resId);
    return loadTexture(bitmap, repeatType);
}

/**
 * bitmap 加载纹理
 *
 * @param bitmap     bitmap
 * @param repeatType 重复方式 {@link com.toly1994.picture.world.bean.RepeatType}
 * @return 纹理id
 */
public static int loadTexture(Bitmap bitmap, RepeatType repeatType) {
    //生成纹理ID

    int[] textures = new int[1];
    //(产生的纹理id的数量,纹理id的数组,偏移量)
    GLES20.glGenTextures(1, textures, 0);
    int textureId = textures[0];
    //绑定纹理id
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
    //采样方式MIN
    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);

    int wrapS = 0;
    int wrapT = 0;
    switch (repeatType) {
        case NONE:
            wrapS = GLES20.GL_CLAMP_TO_EDGE;
            wrapT = GLES20.GL_CLAMP_TO_EDGE;
            break;
        case REPEAT_X:
            wrapS = GLES20.GL_REPEAT;
            wrapT = GLES20.GL_CLAMP_TO_EDGE;
            break;
        case REPEAT_Y:
            wrapS = GLES20.GL_CLAMP_TO_EDGE;
            wrapT = GLES20.GL_REPEAT;
            break;
        case REPEAT:
            wrapS = GLES20.GL_REPEAT;
            wrapT = GLES20.GL_REPEAT;
            break;
    }

    //设置s轴拉伸方式---重复
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, wrapS);
    //设置t轴拉伸方式---重复
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, wrapT);

    //实际加载纹理(纹理类型,纹理的层次,纹理图像,纹理边框尺寸)
    GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
    bitmap.recycle();          //纹理加载成功后释放图片
    return textureId;
}

封装重复方式.png

当然这也仅是纹理的简单认识,跟高级的龙之盛装副本,敬请期待


普通副本八:黑龙之型 LEVEL1

你以为我的封面图只是吸引眼球?

效果.gif


1.第一关卡:3DMAX与.obj文件

3DMAX大学的时候用过,知道OpenGL ES 可以加载3DMAX的模型,激动之心无法言表
模型自己去网上下,3DMAX装软件我也不废话了,安装教程一大堆

导出obj.png

可见都是点的数据,现在要开始解析数据了,Are you ready?

内容.png


2.第二关卡:加载与解析点:

参见《Android 3D游戏开发技术宝典 OpenGL ES 2.0》

//-------------加载obj点集----------------
//从obj文件中加载仅携带顶点信息的物体
public static float[] loadPosInObj(String name, Context ctx) {
    ArrayList<Float> alv = new ArrayList<>();//原始顶点坐标列表
    ArrayList<Float> alvResult = new ArrayList<>();//结果顶点坐标列表
    try {
        InputStream in = ctx.getAssets().open(name);
        InputStreamReader isr = new InputStreamReader(in);
        BufferedReader br = new BufferedReader(isr);
        String temps = null;
        while ((temps = br.readLine()) != null) {
            String[] tempsa = temps.split("[ ]+");
            if (tempsa[0].trim().equals("v")) {//此行为顶点坐标
                alv.add(Float.parseFloat(tempsa[1]));
                alv.add(Float.parseFloat(tempsa[2]));
                alv.add(Float.parseFloat(tempsa[3]));
            } else if (tempsa[0].trim().equals("f")) {//此行为三角形面
                int index = Integer.parseInt(tempsa[1].split("/")[0]) - 1;
                alvResult.add(alv.get(3 * index));
                alvResult.add(alv.get(3 * index + 1));
                alvResult.add(alv.get(3 * index + 2));
                index = Integer.parseInt(tempsa[2].split("/")[0]) - 1;
                alvResult.add(alv.get(3 * index));
                alvResult.add(alv.get(3 * index + 1));
                alvResult.add(alv.get(3 * index + 2));
                index = Integer.parseInt(tempsa[3].split("/")[0]) - 1;
                alvResult.add(alv.get(3 * index));
                alvResult.add(alv.get(3 * index + 1));
                alvResult.add(alv.get(3 * index + 2));
            }
        }
    } catch (Exception e) {
        Log.d("load error", "load error");
        e.printStackTrace();
    }
    //生成顶点数组
    int size = alvResult.size();
    float[] vXYZ = new float[size];
    for (int i = 0; i < size; i++) {
        vXYZ[i] = alvResult.get(i);
    }
    return vXYZ;
}

3.第三关卡:绘制:ObjShape.java
3.1:绘制无果

激动人心的时刻到了...点集在手天下我有
然后果然不出所料...没有出现,我就想不会这么简单吧

/**
 * 缓冲数据
 */
private void bufferData() {
    float[] vertexs = GLUtil.loadPosInObj("obj.obj", mContext);
    mVertexCount = vertexs.length / COORDS_PER_VERTEX;
    vertexBuffer = GLUtil.getFloatBuffer(vertexs);
}

3.2:全体缩放

碰到问题怎么办? 废话,debug 啊。然后秒发现坐标是200多,晕,怪不得
聪明的你肯定能想到,缩小呗,总算出来了,but违和感十足,坐标系都没了。怎么办?

缩小100倍.png

MatrixStack.save();
MatrixStack.rotate(currDeg, 0, 1, 0);
MatrixStack.scale(0.01f,0.01f,0.01f);
mWorldShape.draw(MatrixStack.peek());
MatrixStack.restore();

3.3:截胡

我在ObjShape里用个缩放矩阵,截胡不就行了吗?

截胡缩小.png

然后再移动一下放在中间

---->[WorldRenderer]----------
MatrixStack.save();
MatrixStack.rotate(currDeg, 0, 1, 0);
// MatrixStack.scale(0.01f,0.01f,0.01f);
mWorldShape.draw(MatrixStack.peek());
MatrixStack.restore();

---->[ObjShape]----------
private static float[] mMVPMatrix = new float[16];//最终矩阵

---->[ObjShape#draw]----------
Matrix.scaleM(mMVPMatrix, 0, mvpMatrix, 0, 0.02f, 0.02f, 0.02f);
Matrix.translateM(mMVPMatrix,0,-230,-50,30);
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);

移动.png

本集结束,下一集:九层之台 敬请期待

后记:捷文规范

1.本文成长记录及勘误表
项目源码日期备注
V0.1-github2018-1-16Android多媒体之GLES2战记第五集--宇宙之光
2.更多关于我
笔名QQ微信爱好
张风捷特烈1981462002zdl1994328语言
我的github我的简书我的掘金个人网站
3.声明

1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持


icon_wx_200.png