OpenGL之基礎(chǔ)
OpenGL之繪制簡單形狀
OpenGL之顏色
OpenGL之調(diào)整屏幕寬高比
OpenGL之三維
紋理
紋理可以用來表示圖像、照片、甚至由一個數(shù)學(xué)算法生成的分形數(shù)據(jù)。每個二維的紋理都由許多小的紋理元素(texel)組成,它們是小塊的數(shù)據(jù),使用紋理最常用的方式是直接從一個圖像文件加載數(shù)據(jù)
每個二維的紋理都有其自己的坐標(biāo)空間,其范圍是從一個拐角的 (0, 0) 到另一個拐角的 (11) ,按照慣例,一個維度叫做S,而另一個稱為T,這些紋理坐標(biāo)有時也會被稱為UV紋理坐標(biāo)

當(dāng)我們想要把一個紋理應(yīng)用于一個三角形或一組三角形的時候,需要為每個頂點指定 ST 紋理坐標(biāo),以便OpenGL知道要用紋理的哪個部分畫到三角形上
加載紋理
把一個圖像文件的數(shù)據(jù)加載到一個OpenGL的紋理中
public class TextureHelper {
private static final String TAG = "TextureHelper";
public static int loadTexture(Context context, int resId) {
// OpenGL不能直接讀取PNG或者JPEG文件的數(shù)據(jù),因為這些文件被編碼為特定的壓縮格式
// OpenGL需要非壓縮形式的原始數(shù)據(jù),因此,需要用位圖解碼器BitmapFactory把圖像文件解壓縮為Bitmap
BitmapFactory.Options options = new BitmapFactory.Options();
// 告訴BitmapFactory想要原始的圖像數(shù)據(jù),而不是這個圖像的縮放版本
options.inScaled = false;
// 獲取圖像原始bitmap數(shù)據(jù)
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId, options);
if (bitmap == null) {
Log.d(TAG, "loadTexture: decodeResource failed!");
return 0;
}
// 生成一個新的紋理ID
int textureIds[] = new int[1];
glGenTextures(textureIds.length, textureIds, 0);
// 檢查生成紋理是否成功,結(jié)果等于0就是失敗
if (textureIds[0] == 0) {
Log.d(TAG, "loadTexture: glGenTextures failed!");
return 0;
}
// 綁定,告訴OpenGL后面的調(diào)用應(yīng)用于這個二維紋理
// 第一個參數(shù)GL_TEXTURE_2D告訴OpenGL這應(yīng)該被作為一個二維紋理對待,第二個參數(shù)告訴 OpenGL要綁定到哪個紋理對象的ID
glBindTexture(GL_TEXTURE_2D, textureIds[0]);
//設(shè)置紋理過濾參數(shù),縮小和放大
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 讀入bitmap的位圖數(shù)據(jù)到OpenGL,并復(fù)制到當(dāng)前紋理對象
GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
// 釋放bitmap的位圖數(shù)據(jù)
bitmap.recycle();
// 生成mipmap貼圖
glGenerateMipmap(GL_TEXTURE_2D);
// 解綁
glBindTexture(GL_TEXTURE_2D, 0);
return textureIds[0];
}
}
紋理過濾
當(dāng)紋理大小被擴大或者縮小時,我們還需要使用紋理過濾 (texture filtering) 明確說明會縮小和放大的規(guī)則
最近鄰過濾
這個方式為每個片段選擇最近的紋理元素
| 放大(鋸齒明顯) | 縮?。毠?jié)丟失) |
|---|---|
![]() image-20211221172912954.png
|
![]() image-20211221173218799.png
|
雙線性過濾
雙線性過濾使用雙線性插值平滑像素之間的過渡,而不是為每個片段使用最近的紋理元素,OpenGL會使用四個鄰接的紋理元素,并在它們之間用一個線性插值算法做插值,之所以叫它雙線性,是因為它是沿著兩個維度插值的
| 放大(效果較好) | 縮小(細節(jié)丟失) |
|---|---|
![]() image-20211221173452985.png
|
![]() image-20211221173653459.png
|
MIP貼圖
生成一組優(yōu)化過的不同大小的紋理。在渲染時,OpenGL 會根據(jù)每個片段的紋理元素數(shù)量為每個片段選擇最適合的級別,使用MIP貼圖,會占用更多的內(nèi)存,但是渲染也會更快,因為較小級別的紋理在GPU的紋理緩存中占用較少的空間

三線性過濾
OpenGL 在不同的 MIP 貼圖級別之間來回切換,當(dāng)我們用雙線性過濾來使用 MIP 貼圖時,在其渲染的場景中,在不同級別的 MIP 貼圖切換時,有時候能看到明顯的跳躍或者線條,切換到三線性過濾( trilinear filtering),告訴 OpenGL 在兩個最鄰近的 MIP 貼圖級別之間也要插值,這樣,每個片段總共要使用 8 個紋理元素插值。這有助于消除每個 MIP 貼圖級別之間的過渡,并且得到一個更平滑的圖像
紋理過濾總結(jié)
| 過濾模式 | 對應(yīng)代碼 |
|---|---|
| 最近鄰過濾 | GL_NEAREST |
| 使用MIP貼圖的最近鄰過濾 | GL_NEAREST_MIPMAP_NEAREST |
| 使用MIP貼圖級別之間插值的最近鄰過濾 | GL_NEAREST_MIPMAP_LINEAR |
| 雙線性過濾 | GL_LINEAR |
| 使用MIP貼圖的雙線性過濾 | GL_LINEAR_MIPMAP_NEAREST |
| 三線性過濾(使用MIP貼圖級別之間插值的雙線性過濾) | GL_LINEAR_MIPMAP_LINEAR |
每種情況下允許的紋理過濾模式
| 情況 | 允許的模式 |
|---|---|
| 縮小 | GL_NEAREST、GL_NEAREST_MIPMAP_NEAREST、GL_NEAREST_MIPMAP_LINEAR、GL_LINEAR、GL_LINEAR_MIPMAP_NEAREST、GL_LINEAR_MIPMAP_LINEAR |
| 放大 | GL_NEAREST、GL_LINEAR |
修改著色器
修改著色器,讓其可以接收紋理,并把它應(yīng)用在要繪制的片段上
修改頂點著色器
attribute vec4 a_Position;
uniform mat4 u_Matrix;
attribute vec2 a_TextureCoordinates;
varying vec2 v_Texture_Coordinates;
void main(){
v_Texture_Coordinates=a_TextureCoordinates;
gl_Position=u_Matrix*a_Position;
gl_PointSize=10.0;
}
給紋理坐標(biāo)加了一個新的屬性 "a_TextureCoordinates" ,因為它有兩個分量:S坐標(biāo)和T坐標(biāo),所以被定義為一個vec2,因為要把紋理坐標(biāo)插值后傳遞給片段著色器,因此使用 varying 創(chuàng)建了 vec2 的 "v_Texture_Coordinates"
修改片段著色器
precision mediump float;
uniform sampler2D u_Texture_Unit;
varying vec2 v_Texture_Coordinates;
void main(){
gl_FragColor=texture2D(u_Texture_Unit, v_Texture_Coordinates);
}
為了把紋理繪制到一個物體上,OpenGL會為每個片段都調(diào)用片段著色器,并且每個調(diào)用都接收v_Texture_Coordinates 的紋理坐標(biāo)
片段著色器也通過 uniform 的 u_Texture_Unit 接收實際的紋理數(shù)據(jù),u_TextureUnit 被定義為一個 sampler2D ,這個變量類型指的是一個二維紋理數(shù)據(jù)的數(shù)組
被插值的紋理坐標(biāo)和紋理數(shù)據(jù)被傳遞給著色器函數(shù) texture2D() ,它會讀人紋理中那個特定坐標(biāo)處的顏色值,然后把結(jié)果賦值給 gl_FragColor 設(shè)置片段的最終顏色
創(chuàng)建物體
創(chuàng)建Mallet類管理木槌的數(shù)據(jù),以及Table類管理桌子的數(shù)據(jù),并且每個類都會有一個 VertexArray類的實例,它用來封裝存儲頂點的 FloatBuffer
封裝存儲頂點類
public class VertexArray {
private final FloatBuffer mVertexBuffer;
/**
* 創(chuàng)建頂點數(shù)組
*
* @param data
*/
public VertexArray(float[] data) {
mVertexBuffer = ByteBuffer.allocateDirect(data.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(data);
mVertexBuffer.position(0);
}
/**
* 綁頂點數(shù)據(jù)和OpenGL中的屬性
*
* @param dataOffset 頂點數(shù)據(jù)偏移量,定位到數(shù)組中的那個位置
* @param attributeLocation 屬性位置
* @param componentCount 此屬性所占位數(shù),例如位置(x,y)占兩位,位置(x,y,z)占3位
* @param stride 所有屬性占位總和字節(jié)數(shù),例如(x,y,z,r,g,b)為6*4
*/
public void setVertexAttribPointer(int dataOffset, int attributeLocation, int componentCount, int stride) {
mVertexBuffer.position(dataOffset);
glVertexAttribPointer(
attributeLocation,
componentCount,
GL_FLOAT,
false,
stride,
mVertexBuffer
);
glEnableVertexAttribArray(attributeLocation);
mVertexBuffer.position(0);
}
}
桌子類
public class Table {
private static final int POSITION_COMPONENT = 2;
private static final int TEXTURE_COORDINATES_COMPONENT = 2;
private static final int STRIDE = (POSITION_COMPONENT + TEXTURE_COORDINATES_COMPONENT) * Constants.BYTES_PER_FLOAT;
// 桌子的頂點數(shù)據(jù)
private VertexArray mVertexArray;
// 要注意,x、y范圍是 [-1,1](如果不想橫屏被裁減的話),s、t范圍是[0,1]
// 這里并沒有使用圖像的所有數(shù)據(jù),在t方向上只使用了0.1~0.9的范圍
// 因為桌子的寬高比是1:1.6,為了避免壓縮,裁減圖像的邊緣
public static final float[] VERTEX_DATA = new float[]{
// x,y,s,t
0f, 0f, 0.5f, 0.5f,
-0.5f, -0.8f, 0f, 0.1f,
0.5f, -0.8f, 1f, 0.1f,
0.5f, 0.8f, 1f, 0.9f,
-0.5f, 0.8f, 0f, 0.9f,
-0.5f, -0.8f, 0f, 0.1f
};
public Table() {
mVertexArray = new VertexArray(VERTEX_DATA);
}
/**
* 綁定頂點數(shù)據(jù)到OpenGL程序
*/
public void bindData(TextureProgram textureProgram) {
mVertexArray.setVertexAttribPointer(
0,
textureProgram.getAPosition(),
POSITION_COMPONENT,
STRIDE
);
mVertexArray.setVertexAttribPointer(
POSITION_COMPONENT,
textureProgram.getATextureCoordinates(),
TEXTURE_COORDINATES_COMPONENT,
STRIDE
);
}
/**
* 畫出桌子
*/
public void draw() {
glDrawArrays(GL_TRIANGLE_FAN, 0, 6);
}
}
上面代碼中的 TextureProgram 類,用于生成程序,獲取著色器中 attribute 和 uniform 的位置,并提供設(shè)置 uniform 的方法和 attribute 的方法方法,如下
public class TextureProgram extends BaseProgram {
private final int mATextureCoordinates;
private final int mUTextureUnit;
private final int mAPosition;
private final int mUMatrix;
public TextureProgram(Context context) {
super(context, R.raw.buildobjects6_vertex, R.raw.buildobjects6_fragment);
mATextureCoordinates = glGetAttribLocation(mProgram, A_TEXTURE_COORDINATES);
mUTextureUnit = glGetUniformLocation(mProgram, U_TEXTURE_UNIT);
mAPosition = glGetAttribLocation(mProgram, A_POSITION);
mUMatrix = glGetUniformLocation(mProgram, U_MATRIX);
}
public void setUniforms(float[] matrix, int textureId) {
glUniformMatrix4fv(mUMatrix, 1, false, matrix, 0);
//激活紋理單元0
glActiveTexture(GL_TEXTURE0);
//將紋理textureId綁定到這個單元
glBindTexture(GL_TEXTURE_2D, textureId);
//把被選定的紋理單元0傳遞給片段著色器中的mUTextureUnit
glUniform1i(mUTextureUnit, 0);
}
public int getATextureCoordinates() {
return mATextureCoordinates;
}
public int getAPosition() {
return mAPosition;
}
}
BaseProgram
public class BaseProgram {
public static final String A_TEXTURE_COORDINATES = "a_TextureCoordinates";
public static final String A_POSITION = "a_Position";
public static final String A_COLOR = "a_Color";
public static final String U_TEXTURE_UNIT = "u_Texture_Unit";
public static final String U_MATRIX = "u_Matrix";
public final int mProgram;
public BaseProgram(Context context, int vertexResId, int fragmentResId) {
int vertexId = ShaderHelper.compileVertexShader(context, vertexResId);
int fragmentId = ShaderHelper.compileFragmentShader(context, fragmentResId);
mProgram = ProgramHelper.getProgram(vertexId, fragmentId);
}
/**
* 調(diào)用此方法,告訴OpenGL接下來的渲染使用此程序
*/
public void useProgram() {
glUseProgram(mProgram);
}
}
木槌類
用和桌子類同樣的方法創(chuàng)建木槌類,以及它對應(yīng)的程序類
public class Mallet {
private static final int POSITION_COMPONENT = 2;
private static final int COLOR_COMPONENT = 3;
private static final int STRIDE = (POSITION_COMPONENT + COLOR_COMPONENT) * Constants.BYTES_PER_FLOAT;
//頂點數(shù)據(jù)
private VertexArray mVertexArray;
private static final float[] VERTEX_DATA = new float[]{
// x,y,r,g,b
0f, 0.4f, 1f, 0f, 0f,
0f, -0.4f, 0f, 1f, 0f
};
public Mallet() {
mVertexArray = new VertexArray(VERTEX_DATA);
}
/**
* 頂點數(shù)據(jù)綁定到OpenGL程序
*
* @param colorProgram
*/
public void bindData(ColorProgram colorProgram) {
mVertexArray.setVertexAttribPointer(
0,
colorProgram.getAPosition(),
POSITION_COMPONENT,
STRIDE
);
mVertexArray.setVertexAttribPointer(
POSITION_COMPONENT,
colorProgram.getAColor(),
COLOR_COMPONENT,
STRIDE
);
}
/**
* 畫點
*/
public void draw() {
glDrawArrays(GL_POINTS, 0, 2);
}
}
public class ColorProgram extends BaseProgram {
private final int mAColor;
private final int mAPosition;
private final int mUMatrix;
public ColorProgram(Context context) {
super(context, R.raw.texture5_color_vertex, R.raw.texture5_color_fragment);
mAColor = glGetAttribLocation(mProgram, A_COLOR);
mAPosition = glGetAttribLocation(mProgram, A_POSITION);
mUMatrix = glGetUniformLocation(mProgram, U_MATRIX);
}
public void setUniforms(float[] matrix) {
glUniformMatrix4fv(mUMatrix, 1, false, matrix, 0);
}
public int getAColor() {
return mAColor;
}
public int getAPosition() {
return mAPosition;
}
}
修改渲染器
修改渲染器,在其中使用定義好的物體類(桌子和木槌)以及他們對應(yīng)的程序類
public class MyRenderer implements GLSurfaceView.Renderer {
private Context mContext;
private float[] projectionMatrix = new float[16];
private float[] modelMatrix = new float[16];
private TextureProgram mTextureProgram;
private ColorProgram mColorProgram;
private Table mTable;
private Mallet mMallet;
private int mTextureId;
public MyRenderer(Context context) {
mContext = context;
}
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
glClearColor(1f, 1f, 1f, 1f);
mTextureProgram = new TextureProgram(mContext);
mColorProgram = new ColorProgram(mContext);
mTable = new Table();
mMallet = new Mallet();
mTextureId = 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(projectionMatrix, 45, (float) width / height, 1, 10);
Matrix.setIdentityM(modelMatrix, 0);
Matrix.translateM(modelMatrix, 0, 0f, 0f, -3f);
Matrix.rotateM(modelMatrix, 0, -45f, 1f, 0f, 0f);
float[] temp = new float[16];
Matrix.multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);
System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);
}
@Override
public void onDrawFrame(GL10 gl10) {
// 清空渲染表面
glClear(GL_COLOR_BUFFER_BIT);
// 畫桌子
// 告訴OpenGL使用這個程序
mTextureProgram.useProgram();
// 把uniform傳遞進去
mTextureProgram.setUniforms(projectionMatrix, mTextureId);
// 把頂點數(shù)組數(shù)據(jù)和著色器程序綁定起來
mTable.bindData(mTextureProgram);
// 繪制桌子
mTable.draw();
// 畫木槌
mColorProgram.useProgram();
mColorProgram.setUniforms(projectionMatrix);
mMallet.bindData(mColorProgram);
mMallet.draw();
}
}
效果




