一惶凝、開始
在上篇文章中吼虎,我們已經(jīng)讓桌面的顏色有了變化,也讓桌面適配了屏幕的比例苍鲜,讓手機在旋轉(zhuǎn)的時候桌面也不會產(chǎn)生變形思灰。但現(xiàn)在看到的桌面就像是在紙上畫的一樣,一點都沒有立體感混滔。所以現(xiàn)在我們要學(xué)習(xí)的就是3D的去展示我們的桌面洒疚,這個只是第一點,為了讓我們的桌面更好看一些坯屿,我們準(zhǔn)備了一張圖片拳亿,我們也會學(xué)習(xí)如何讓OpenGL去渲染我們要展示的圖片,總的來說我們要學(xué)習(xí)兩點:
1.3D展示桌面
2.OpenGL渲染圖片
最終我們做出來的效果應(yīng)該是這個樣子的:
二愿伴、3D展示桌面
為了用3D效果展示桌面,我們先要知道一個gl_Position是如何轉(zhuǎn)換成屏幕上顯示的點的坐標(biāo)的:
大致流程就像上圖所示电湘,書中講的很詳細(xì)隔节,我這里簡單講下,大家感興趣的可以自己去看書或者查資料寂呛,我們制造出3D效果主要實在Perspective Division(透視除法)這一步怎诫,一個點的坐標(biāo)應(yīng)該是(x , y , z , w)在Clip Space(裁剪空間)這個里面,這個時候x,y,z的取值范圍是在-w到w之間的贷痪,然后會經(jīng)過Perspective Division轉(zhuǎn)換幻妓,這個時候的坐標(biāo)就應(yīng)該是(x/w , y/w , z/w)。
然后我們現(xiàn)在就用代碼去實現(xiàn)了劫拢,這個時候我們一個點的坐標(biāo)就應(yīng)該有4個參數(shù)了肉津,所以先修改如下代碼:
private static final int POSITION_COMPONENT_COUNT = 4;
然后我們需要修改我們的數(shù)據(jù)
private final float[] mData = new float[]{
// Order of coordinates: X, Y, Z, W, R, G, B
// Triangle Fan
0f, 0f, 0f, 1.5f, 1f, 1f, 1f,
-0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f,
0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f,
0.5f, 0.8f, 0f, 2f, 0.7f, 0.7f, 0.7f,
-0.5f, 0.8f, 0f, 2f, 0.7f, 0.7f, 0.7f,
-0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f,
//線
-0.5f, 0f, 0f, 1.5f, 1f, 0f, 0f,
0.5f, 0f, 0f, 1.5f, 1f, 0f, 0f,
//點
0f, -0.4f, 0f, 1.25f, 0f, 0f, 1f,
0f, 0.4f, 0f, 1.75f, 1f, 0f, 0f
};
運行下看看,是不是有點3D的感覺了舱沧!但是這樣我們要硬編碼w參數(shù)妹沙,這樣明顯是不好的,當(dāng)然我們的前人也不會這么傻熟吏,當(dāng)然有別的方法去處理距糖,我們可以做移動玄窝,縮放,旋轉(zhuǎn)等操作悍引,讓我們把之前的代碼還原恩脂,重新開始吧。
這個時候我們又要回到矩陣的操作了趣斤,這次我們要同時處理屏幕適配俩块,和視野的處理,書中講了很多原理唬渗,大致就是講透視投影是怎么一回事典阵,有興趣的可以自己去看看,我們就是要用這個東西去做出我們的3D效果镊逝。
這個是書中給出的投影矩陣的說明壮啊,這個投影矩陣可以同時適配屏幕和調(diào)整視野角度具體的參數(shù)說明都有說明,a,f,n可能需要看書中對透視投影的講解才知道撑蒜,大家可以google下歹啼,個人覺得不理解的話,寫完代碼之后也大概知道是什么了座菠,所以就不講解了狸眼,接下來我們定義一個這樣的矩陣。
書中是這樣告訴我們的
Android’s Matrix class contains two methods for this, frustumM() and perspectiveM(). Unfortunately, frustumM() has a bug that affects some types of projections,3 and perspectiveM() was only introduced in Android Ice Cream Sandwich and is not available on earlier versions of Android.
意思就是說浴滴,騷年拓萌,自己寫吧!
所以我們就自己來寫吧升略!我們在util
包下新建一個類叫MatrixHelper.java
類微王,并聲明如下方法:
public static void perspectiveM(float[] m, float yFovInDegrees, float aspect, float n, float f) {
final float angleInRadians = (float) (yFovInDegrees * Math.PI / 180.0);
final float a = (float) (1.0 / Math.tan(angleInRadians / 2.0));
m[0] = a / aspect;
m[1] = 0f;
m[2] = 0f;
m[3] = 0f;
m[4] = 0f;
m[5] = a;
m[6] = 0f;
m[7] = 0f;
m[8] = 0f;
m[9] = 0f;
m[10] = -((f + n) / (f - n));
m[11] = -1f;
m[12] = 0f;
m[13] = 0f;
m[14] = -((2f * f * n) / (f - n));
m[15] = 0f;
}
這樣我們就可以生成一個上面的矩陣了,現(xiàn)在讓我們刪掉onSurfacedChanged()
方法中除了glViewport()
之外的東西品嚣,然后調(diào)用上面的方法:
MatrixHelper.perspectiveM(mProjectionMatrix , 45 , (float)width / (float)height , 1 , 10);
運行一遍之后炕倘,你會發(fā)現(xiàn),WTF翰撑,怎么成黑的了罩旋,小伙子,不要著急眶诈,這是正常的涨醋,因為我們n,f的值分別為1逝撬,10东帅,就是說我們只能看到z軸上-1到-10之間的東西,這個時候我們可以用移動矩陣在z軸上移動一下球拦,就能看到了靠闭。
我們先定義一個Model矩陣:
private float[] mModelMatrix = new float[16];
然后我們就要算出一個Model矩陣了帐我,在onSurfacedChanged()
中添加如下代碼:
setIdentityM(mModelMatrix, 0);
translateM(mModelMatrix, 0, 0f, 0f, -2f);
第一行代碼是設(shè)置成單位矩陣,第二行代碼是生成我們要的移動矩陣愧膀。這個時候我們就遇到一個問題拦键,難道我們又要給gl_Point設(shè)置一個轉(zhuǎn)換矩陣了么?答案是不需要檩淋,我們可以只使用一個矩陣就可以解決了芬为,只不過這個時候需要把兩個矩陣乘一下,但是順序要特別注意蟀悦,不能顛倒:
vertexclip = ProjectionMatrix * ModelMatrix * vertexmodel
在onSurfacedChanged()
方法中再添加如下方法:
final float[] temp = new float[16];
multiplyMM(temp, 0, mProjectionMatrix, 0, mModelMatrix, 0);
System.arraycopy(temp, 0, mProjectionMatrix, 0, temp.length);
我們用一個臨時的變量去儲存兩個矩陣相乘的結(jié)果媚朦,然后再把臨時變量里面的內(nèi)容copy到mProjectionMatrix變量傳遞給OpenGL,這樣我們就可以同時看到兩個矩陣作用的效果了日戈,運行下询张,看看吧,是不是能看到了浙炼。
然后我們可以再加上一個旋轉(zhuǎn)的效果就能看到之前做出來的3D效果了份氧,旋轉(zhuǎn)需要選擇繞著X,Y,Z那個軸旋轉(zhuǎn),根據(jù)不同的軸旋轉(zhuǎn)矩陣也不一樣弯屈,書中有說明蜗帜,這里就不講解了,我們在代碼中只需要調(diào)用一個方法就可以了资厉,在translateM(mModelMatrix, 0, 0f, 0f, -2f);
添加如下代碼
rotateM(mModelMatrix, 0 , -60 , 1 , 0 , 0);
同時把移動的z軸的值改成-2.5厅缺,這樣可以讓桌面離我們遠(yuǎn)一點。
我們調(diào)用方法宴偿,讓桌面繞著x軸旋轉(zhuǎn)了-60度湘捎,這里可能涉及到坐標(biāo)系的問題,書中有講解酪我,可以自行了解。再運行一遍且叁,看看效果是不是就出來了都哭。
三、用Texture渲染圖片
現(xiàn)在我們就可以開始想辦法讓我們的桌面更好看一些了逞带。首先我們要知道Texture是個什么東西
Textures in OpenGL can be used to represent images, pictures, and even fractal data that are generated by a mathematical algorithm.
這是原文的解釋欺矫,就是用來展示圖片的,甚至可以展示數(shù)學(xué)算法制作出來的數(shù)據(jù)展氓。所以我們就是用這個東西去渲染我們要渲染的圖片的穆趴。
首先我們要做的事情就是把圖片加載到Texture上,我們在util
包下建一個類TextureHelper.java
遇汞,并實現(xiàn)如下代碼:
public static int loadTexture(Context context , int resourceId){
final int[] textureObjectId = new int[1];
glGenTextures(1 , textureObjectId , 0);
if (textureObjectId[0] == 0){
Logger.debug(TAG , "create texture fail.");
return 0;
}
return textureObjectId[0];
}
我們聲明了一個loadTexture()
方法來加載texture未妹,我們先創(chuàng)建了一個Texture的對象簿废,接下來我們要把圖片加載到texture上,我們繼續(xù)添加如下代碼:
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
final Bitmap bitmap = BitmapFactory.decodeResource(
context.getResources(), resourceId, options);
if (bitmap == null) {
Logger.debug(TAG, "Resource ID " + resourceId + " could not be decoded.");
glDeleteTextures(1, textureObjectId, 0);
return 0;
}
glBindTexture(GL_TEXTURE_2D , textureObjectId[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D , 0);
首先我們把圖片資源轉(zhuǎn)換成了bitmap络它,然后我們讓OpenGL綁定了我們之前創(chuàng)建的Texture對象族檬,并設(shè)置了兩個參數(shù)GL_TEXTURE_MIN_FILTER
、GL_TEXTURE_MAG_FILTER
化戳,這兩個參數(shù)在書中有詳細(xì)解釋单料,大家可以自己去了解,大概就是設(shè)置圖片的清晰度点楼,同時如果考慮到效率的話有多種選擇扫尖。然后我們就可以把圖片加載到我們創(chuàng)建的Texture對象上了,最后就是bitmap的回收掠廓,解除Texture的綁定了换怖。
加載圖片資源到Texture的問題解決了,接下來我們需要知道OpenGL如何去繪制Texture了却盘,這個跟之前只畫顏色類似狰域,我們需要新建一組.glsl
文件,我們先創(chuàng)建一個texture_vertex_shader.glsl
文件黄橘,實現(xiàn)如下代碼:
uniform mat4 u_Matrix;
attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;
varying vec2 v_TextureCoordinates;
void main() {
gl_Position = u_Matrix * a_Position;
v_TextureCoordinates = a_TextureCoordinates;
}
大家注意到了兆览,我們聲明了一個有兩個參數(shù)的向量的變量a_TextureCoordinates,這個Texture是有一個范圍為(0,0)到(1,1)的坐標(biāo)空間的塞关,它的坐標(biāo)系有兩種ST抬探、UV,具體解釋書中是有講解的帆赢,在這里不做過多講解了小压。
接下來我們創(chuàng)建texture_fragment_shader.glsl
文件,實現(xiàn)如下代碼:
precision mediump float;
uniform sampler2D u_TextureUnit;
varying vec2 v_TextureCoordinates;
void main() {
gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
}
大家可以這樣理解椰于,u_TextureUnit就相當(dāng)于我們要繪制的圖片的每個點的顏色怠益,然后v_TextureCoordinates就是告訴我們這些顏色要畫在什么位置的坐標(biāo),最后通過texture2D()
方法獲得我們在當(dāng)前點要繪制的顏色瘾婿。
書中講到這里是重構(gòu)了一遍代碼蜻牢,其實如果看過前面兩篇文章的大概應(yīng)該是知道如何去渲染了圖片了,為了后面一篇文章做準(zhǔn)備偏陪,我還是把書中的重構(gòu)過程講一遍吧抢呆。
首先我們把繪制的對象的架構(gòu)改成下圖的樣子:
我們創(chuàng)建一個
data
包,然后創(chuàng)建VertexArray.java
類實現(xiàn)如下代碼:
public class VertexArray {
private final FloatBuffer mFloatBuffer;
public VertexArray(float[] data){
mFloatBuffer = ByteBuffer
.allocateDirect(Constants.BYTE_PRE_FLOAT * data.length)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
mFloatBuffer.put(data);
}
public void setVertexAttribPointer(int dataOffset, int attributeLocation, int componentCount, int stride){
mFloatBuffer.position(dataOffset);
glVertexAttribPointer(attributeLocation, componentCount, GL_FLOAT,
false, stride, mFloatBuffer);
glEnableVertexAttribArray(attributeLocation);
mFloatBuffer.position(0);
}
}
在我們創(chuàng)建Mallet和Table之前笛谦,我們要先創(chuàng)建Shader類抱虐,因為我們想在Mallet和Table中實現(xiàn)繪制功能,所以我們先把Shader實現(xiàn)這部分代碼抽離出來饥脑,整體架構(gòu)如下:
我們先在
ShaderHelper.java
中添加如下代碼:
public static int buildProgram(String vertexShaderCode , String fragmentShaderCode){
int vertexShaderObjectId = compileVertexShader(vertexShaderCode);
int fragmentShaderObjectId = compileFragmentShader(fragmentShaderCode);
int program = linkProgram(vertexShaderObjectId , fragmentShaderObjectId);
vaildProgram(program);
return program;
}
然后新建一個包programs
并創(chuàng)建類ShaderProgram.java
實現(xiàn)如下代碼:
public class ShaderProgram {
// Uniform constants
protected static final String U_MATRIX = "u_Matrix";
protected static final String U_TEXTURE_UNIT = "u_TextureUnit";
// Attribute constants
protected static final String A_POSITION = "a_Position";
protected static final String A_COLOR = "a_Color";
protected static final String A_TEXTURE_COORDINATES = "a_TextureCoordinates";
// Shader program
protected final int mProgram;
protected ShaderProgram(Context context, int vertexShaderResourceId,
int fragmentShaderResourceId) {
// Compile the shaders and link the program.
mProgram = ShaderHelper.buildProgram(
TextResouceReader.readTextFileFromResource(context, vertexShaderResourceId),
TextResouceReader.readTextFileFromResource(context, fragmentShaderResourceId));
}
public void useProgram() {
// Set the current OpenGL shader program to this program.
glUseProgram(mProgram);
}
}
在該包下繼續(xù)創(chuàng)建TextureShaderProgram.java
類實現(xiàn)如下代碼:
public class TextureShaderProgram extends ShaderProgram{
// Uniform locations
private final int mUMatrixLocation;
private final int mUTextureUnitLocation;
// Attribute locations
private final int mAPositionLocation;
private final int mATextureCoordinatesLocation;
public TextureShaderProgram(Context context) {
super(context, R.raw.texture_vertex_shader, R.raw.texture_fragment_shader);
mUMatrixLocation = glGetUniformLocation(mProgram , U_MATRIX);
mUTextureUnitLocation = glGetUniformLocation(mProgram , U_TEXTURE_UNIT);
mAPositionLocation = glGetAttribLocation(mProgram , A_POSITION);
mATextureCoordinatesLocation = glGetAttribLocation(mProgram , A_TEXTURE_COORDINATES);
}
public void setUniforms(float[] matrix, int textureId) {
//把矩陣傳遞給Shader程序
glUniformMatrix4fv(mUMatrixLocation, 1, false, matrix, 0);
// Set the active texture unit to texture unit 0.
glActiveTexture(GL_TEXTURE0);
// Bind the texture to this unit.
glBindTexture(GL_TEXTURE_2D, textureId);
//告訴Texture Uniform Sampler用這個位置的Texture去渲染恳邀,從0開始
glUniform1i(mUTextureUnitLocation, 0);
}
public int getPositionAttributeLocation() {
return mAPositionLocation;
}
public int getTextureCoordinatesAttributeLocation() {
return mATextureCoordinatesLocation;
}
}
主要注意setUniforms()
這塊的代碼懦冰,這塊代碼就是用Texture去渲染的方法了。我們再在該包下面創(chuàng)建ColorShaderProgram.java
實現(xiàn)如下代碼:
public class ColorShaderProgram extends ShaderProgram{
// Uniform locations
private final int mUMatrixLocation;
// Attribute locations
private final int mAPositionLocation;
private final int mAColorLocation;
public ColorShaderProgram(Context context) {
super(context, R.raw.simple_vertex_shader, R.raw.simple_fragment_shader);
mUMatrixLocation = glGetUniformLocation(mProgram , U_MATRIX);
mAColorLocation = glGetAttribLocation(mProgram , A_COLOR);
mAPositionLocation = glGetAttribLocation(mProgram , A_POSITION);
}
public void setUniforms(float[] matrix) {
// 把矩陣傳遞給渲染程序
glUniformMatrix4fv(mUMatrixLocation, 1, false, matrix, 0);
}
public int getPositionAttributeLocation() {
return mAPositionLocation;
}
public int getColorAttributeLocation() {
return mAColorLocation;
}
}
這部分代碼就不用多做解釋了轩娶,現(xiàn)在我們就可以去創(chuàng)建我們的Table儿奶、Mallet了,
先創(chuàng)建Table.java
鳄抒,實現(xiàn)如下代碼:
public class Table {
private final VertexArray mVertexData;
private static final int POSITION_COMPONENT_COUNT = 2;
private static final int TEXTURE_COORDINATES_COMPONENT_COUNT = 2;
private static final int STRIDE = (POSITION_COMPONENT_COUNT
+ TEXTURE_COORDINATES_COMPONENT_COUNT) * Constants.BYTE_PRE_FLOAT;
private static final float[] VERTEX_DATA = new float[]{
// Order of coordinates: X, Y, S, T
// Triangle Fan
0f, 0f, 0.5f, 0.5f,
-0.5f, -0.8f, 0f, 0.9f,
0.5f, -0.8f, 1f, 0.9f,
0.5f, 0.8f, 1f, 0.1f,
-0.5f, 0.8f, 0f, 0.1f,
-0.5f, -0.8f, 0f, 0.9f
};
public Table(){
mVertexData = new VertexArray(VERTEX_DATA);
}
public void bindData(TextureShaderProgram program){
mVertexData.setVertexAttribPointer(0 ,
program.getPositionAttributeLocation() ,
POSITION_COMPONENT_COUNT , STRIDE);
mVertexData.setVertexAttribPointer(POSITION_COMPONENT_COUNT ,
program.getTextureCoordinatesAttributeLocation() ,
TEXTURE_COORDINATES_COMPONENT_COUNT , STRIDE);
}
public void draw(){
glDrawArrays(GL_TRIANGLE_FAN, 0, 6);
}
}
再創(chuàng)建Mallet.java
闯捎,實現(xiàn)如下代碼:
public class Mallet {
private static final int POSITION_COMPONENT_COUNT = 2;
private static final int COLOR_COMPONENT_COUNT = 3;
private static final int STRIDE = (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT)
* Constants.BYTE_PRE_FLOAT;
private static final float[] VERTEXT_DATA = new float[]{
// Order of coordinates: X, Y, R, G, B
0f, -0.4f, 0f, 0f, 1f,
0f, 0.4f, 1f, 0f, 0f
};
private VertexArray mVertexData;
public Mallet(){
mVertexData = new VertexArray(VERTEXT_DATA);
}
public void bindData(ColorShaderProgram program){
mVertexData.setVertexAttribPointer(0 ,
program.getPositionAttributeLocation() ,
POSITION_COMPONENT_COUNT , STRIDE);
mVertexData.setVertexAttribPointer(
POSITION_COMPONENT_COUNT ,
program.getColorAttributeLocation(),
COLOR_COMPONENT_COUNT , STRIDE);
}
public void draw(){
glDrawArrays(GL_POINTS, 0, 2);
}
}
準(zhǔn)備工作終于做好了,我們可以開始繪制了许溅,因為在Renderer中基本要刪掉所有代碼瓤鼻,所以我們直接新建一個好了叫做TextrueRenderer.java
,并實現(xiàn)如下代碼:
public class TextureRenderer implements GLSurfaceView.Renderer{
private final Context mContext;
private float[] mProjectionMatrix = new float[16];
private float[] mModelMatrix = new float[16];
private Table mTable;
private Mallet mMallet;
private TextureShaderProgram mTextureShaderProgram;
private ColorShaderProgram mColorShaderProgram;
private int mTexture;
public TextureRenderer(Context context){
mContext = context;
}
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
mTable = new Table();
mMallet = new Mallet();
mTextureShaderProgram = new TextureShaderProgram(mContext);
mColorShaderProgram = new ColorShaderProgram(mContext);
mTexture = 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(mProjectionMatrix , 45 , (float)width / (float)height , 1f , 10f);
setIdentityM(mModelMatrix, 0);
translateM(mModelMatrix, 0, 0f, 0f, -2.5f);
rotateM(mModelMatrix, 0 , -60 , 1 , 0 , 0);
final float[] temp = new float[16];
multiplyMM(temp, 0, mProjectionMatrix, 0, mModelMatrix, 0);
System.arraycopy(temp, 0, mProjectionMatrix, 0, temp.length);
}
@Override
public void onDrawFrame(GL10 gl10) {
glClear(GL_COLOR_BUFFER_BIT);
mTextureShaderProgram.useProgram();
mTextureShaderProgram.setUniforms(mProjectionMatrix , mTexture);
mTable.bindData(mTextureShaderProgram);
mTable.draw();
mColorShaderProgram.useProgram();
mColorShaderProgram.setUniforms(mProjectionMatrix);
mMallet.bindData(mColorShaderProgram);
mMallet.draw();
}
}
然后現(xiàn)在運行一遍贤重,你就能看到我們效果圖的效果了茬祷。
項目代碼在這里:https://github.com/KevinKmoo/AirHockey3DWithTexture
能力有限,自己讀書的學(xué)習(xí)所得并蝗,有錯誤請指導(dǎo)祭犯,輕虐!
轉(zhuǎn)載請注明出處滚停。----by kmoo