Android多媒體之GLES2戰記第五集--宇宙之光

張風捷特烈發表於2019-01-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.35 0.75 1.00
Android多媒體之GLES2戰記第五集--宇宙之光
Android多媒體之GLES2戰記第五集--宇宙之光
Android多媒體之GLES2戰記第五集--宇宙之光

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,-1 1,1,-1
Android多媒體之GLES2戰記第五集--宇宙之光
Android多媒體之GLES2戰記第五集--宇宙之光

看下圖的點光源在(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-github 2018-1-16 Android多媒體之GLES2戰記第五集--宇宙之光
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
我的github 我的簡書 我的掘金 個人網站
3.宣告

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援


icon_wx_200.png

相關文章