問:學OpenGL能幹嗎? 答: 隨心所欲。
編程
提及
OpenGLES
,你們可能都敬而遠之,其實它並無想象中的那麼可怕
,固然也並無那麼容易
都0202年了,本系列使用OpenGLES3.0
,這是一次有預謀的計劃:數組
Android中OpenGL經過GLSurfaceView進行展示,實現Renderer接口
實現接口方法:onSurfaceCreated
、onSurfaceChanged
、onDrawFrame
緩存
public class GLWorld extends GLSurfaceView implements GLSurfaceView.Renderer {
private static final String TAG = "GLWorld";
public GLWorld(Context context) {
this(context,null);
}
public GLWorld(Context context, AttributeSet attrs) {
super(context, attrs);
setEGLContextClientVersion(3);//設置OpenGL ES 3.0 context
setRenderer(this);//設置渲染器
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.e(TAG, "onSurfaceCreated: " );
GLES30.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);//rgba
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
Log.e(TAG, "onSurfaceChanged: " );
GLES30.glViewport(0, 0, width, height);//設置GL視口
}
@Override
public void onDrawFrame(GL10 gl) {
Log.e(TAG, "onDrawFrame: " );
}
}
複製代碼
日誌以下: 默認onDrawFrame約隔16ms會不斷刷新bash
2020-01-10 13:34:35.368 3934-4035/com.toly1994.tolygl E/GLWorld: onSurfaceCreated:
2020-01-10 13:34:35.368 3934-4035/com.toly1994.tolygl E/GLWorld: onSurfaceChanged:
2020-01-10 13:34:35.368 3934-4035/com.toly1994.tolygl E/GLWorld: onDrawFrame:
2020-01-10 13:34:35.440 3934-4035/com.toly1994.tolygl E/GLWorld: onDrawFrame:
2020-01-10 13:34:35.458 3934-4035/com.toly1994.tolygl E/GLWorld: onDrawFrame:
2020-01-10 13:34:35.461 3934-4035/com.toly1994.tolygl E/GLWorld: onDrawFrame:
複製代碼
它就至關於一個View,如今直接放到setContentView中,即可以顯示微信
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(GLWorld(this))
}
}
複製代碼
如今你對OpenGLES的認識就像眼前的黑屏同樣,一無所知
咱們須要去點亮它,展示出一個新世界,大門即將打開,請扶好站穩。
這道光,你扎住了,就已經贏了一大半,抓不住,就......算了吧app
爲了避免混亂和方便使用,建立一個GLPoint類負責點的繪製測試ide
[1] 準備頂點着色代碼和片斷着色代碼
[2] 準備頂點和顏色數據
[3] 加載着色器代碼並初始化程序
[4] 繪製邏輯 (添加程序->啓用頂點->繪製)
複製代碼
public class GLPoint {
//頂點着色代碼
final String vsh = "#version 300 es\n" +
"layout (location = 0) in vec3 aPosition; \n" +
"layout (location = 1) in vec4 aColor;\n" +
"\n" +
"out vec4 color2frag;\n" +
"\n" +
"void main(){\n" +
" gl_Position = vec4(aPosition.x,aPosition.y, aPosition.z, 1.0);\n" +
" color2frag = aColor;\n" +
"gl_PointSize=10.0;"+
"}";
//片斷着色代碼
final String fsh = "#version 300 es\n" +
"precision mediump float;\n" +
"out vec4 outColor;\n" +
"in vec4 color2frag;\n" +
"\n" +
"void main(){\n" +
" outColor = color2frag;\n" +
"}";
//頂點數組
private final float vertexes[] = { //以逆時針順序
0.0f, 0.0f, 0.0f,//原點
};
// 顏色數組
private final float colors[] = new float[]{
1.0f, 1.0f, 1.0f, 1.0f,//白色
};
private int program;
private static final int VERTEX_DIMENSION = 3;
private static final int COLOR_DIMENSION = 4;
private FloatBuffer vertBuffer;
private FloatBuffer colorBuffer;
private int aPosition =0;//位置的句柄
private int aColor =1;//顏色的句柄
public GLPoint() {
program = initProgram();
vertBuffer= GLBuffer.getFloatBuffer(vertexes);
colorBuffer= GLBuffer.getFloatBuffer(colors);
}
private int initProgram() {
int program;
////頂點shader代碼加載
int vertexShader = GLLoader.loadShader(GLES30.GL_VERTEX_SHADER, vsh);
//片斷shader代碼加載
int fragmentShader = GLLoader.loadShader(GLES30.GL_FRAGMENT_SHADER, fsh);
program = GLES30.glCreateProgram();//建立空的OpenGL ES 程序
GLES30.glAttachShader(program, vertexShader);//加入頂點着色器
GLES30.glAttachShader(program, fragmentShader);//加入片元着色器
GLES30.glLinkProgram(program);//建立可執行的OpenGL ES項目
return program;
}
public void draw(){
//清除顏色緩存和深度緩存
GLES30.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
// 將程序添加到OpenGL ES環境中
GLES30.glUseProgram(program);
//啓用頂點句柄
GLES30.glEnableVertexAttribArray(aPosition);
//啓用顏色句柄
GLES30.glEnableVertexAttribArray(aColor);
//準備座標數據
GLES30.glVertexAttribPointer(
aPosition, VERTEX_DIMENSION,
GLES30.GL_FLOAT, false,
VERTEX_DIMENSION * 4, vertBuffer);
//準備顏色數據
GLES30.glVertexAttribPointer(
aColor, COLOR_DIMENSION,
GLES30.GL_FLOAT, false,
COLOR_DIMENSION * 4, colorBuffer);
//繪製點
GLES30.glDrawArrays(GLES30.GL_POINTS, 0, vertexes.length / VERTEX_DIMENSION);
//禁用頂點數組
GLES30.glDisableVertexAttribArray(aPosition);
GLES30.glDisableVertexAttribArray(aColor);
}
}
複製代碼
float數組須要經過FloatBuffer進行緩衝,以提升效率post
/**
* float數組緩衝數據
*
* @param vertexs 頂點
* @return 獲取浮點形緩衝數據
*/
public static FloatBuffer getFloatBuffer(float[] vertexs) {
FloatBuffer buffer;
///每一個浮點數:座標個數* 4字節
ByteBuffer qbb = ByteBuffer.allocateDirect(vertexs.length * 4);
//使用本機硬件設備的字節順序
qbb.order(ByteOrder.nativeOrder());
// 從字節緩衝區建立浮點緩衝區
buffer = qbb.asFloatBuffer();
// 將座標添加到FloatBuffer
buffer.put(vertexs);
//設置緩衝區以讀取第一個座標
buffer.position(0);
return buffer;
}
複製代碼
在咱們的GLWorld中建立GLPoint對象,在onDrawFrame中繪製便可測試
public class GLWorld extends GLSurfaceView implements GLSurfaceView.Renderer {
private static final String TAG = "GLWorld";
private GLPoint glPoint;
public GLWorld(Context context) {
this(context,null);
}
public GLWorld(Context context, AttributeSet attrs) {
super(context, attrs);
setEGLContextClientVersion(3);//設置OpenGL ES 3.0 context
setRenderer(this);//設置渲染器
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
glPoint = new GLPoint();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES30.glViewport(0, 0, width, height);//設置GL視口
}
@Override
public void onDrawFrame(GL10 gl) {
glPoint.draw();
}
}
複製代碼
如今繪製點,經過更改頂點和顏色便可優化
//頂點數組
private final float vertexes[] = { //以逆時針順序
0.0f, 0.0f,0.0f,//原點
0.5f,0.0f,0.0f,
0.5f,0.5f,0.0f,
0.0f,0.5f,0.0f,
};
// 顏色數組
private final float colors[] = new float[]{
1.0f, 1.0f, 1.0f, 1.0f,//白色
1.0f, 0.0f, 0.0f, 1.0f,//紅色
0.0f, 1.0f, 0.0f, 1.0f,//綠色
0.0f, 0.0f, 1.0f, 1.0f,//藍色
};
複製代碼
前面處理好了,劃線比較簡單
//繪製
GLES30.glLineWidth(10);
//GLES30.glDrawArrays(GLES30.GL_POINTS, 0, vertexes.length / VERTEX_DIMENSION);
//GLES30.glDrawArrays(GLES30.GL_LINES, 0, vertexes.length / VERTEX_DIMENSION);
//GLES30.glDrawArrays(GLES30.GL_LINE_LOOP, 0, vertexes.length / VERTEX_DIMENSION);
GLES30.glDrawArrays(GLES30.GL_LINE_STRIP, 0, vertexes.length / VERTEX_DIMENSION);
複製代碼
注:着色器的代碼會有單獨一篇進行講解,此處暫不作解釋。
可能你已經發現了,本應是個正方形,可現實成了矩形
緣由在於視口的比例不對,如今是這樣個座標系:
在頂點着色器代碼中添加用於變換的矩陣uMVPMatrix
//頂點着色代碼
final String vsh = "#version 300 es\n" +
"layout (location = 0) in vec3 aPosition; \n" +
"layout (location = 1) in vec4 aColor;\n" +
"uniform mat4 uMVPMatrix;\n" +
"out vec4 color2frag;\n" +
"\n" +
"void main(){\n" +
" gl_Position = uMVPMatrix*vec4(aPosition.x,aPosition.y, aPosition.z, 1.0);\n" +
" color2frag = aColor;\n" +
"gl_PointSize=10.0;"+
"}";
---->[變換矩陣相關代碼]----
private int uMVPMatrix ;//頂點變換矩陣句柄
//構造方法中獲取句柄
uMVPMatrix = GLES30.glGetUniformLocation(program, "uMVPMatrix");
public void draw(float[] mvpMatrix){
//英雄所見
GLES30.glUseProgram(program);
GLES30.glUniformMatrix4fv(uMVPMatrix, 1, false,mvpMatrix, 0);
複製代碼
具體的變換細節,將在
第六集
和第七集
講述,此處不作解釋 如今視角就已經校訂了
public class GLWorld extends GLSurfaceView implements GLSurfaceView.Renderer {
private static final String TAG = "GLWorld";
private GLLine line;
//Model View Projection Matrix--模型視圖投影矩陣
private final float[] mMVPMatrix = new float[16];
//投影矩陣 mProjectionMatrix
private final float[] mProjectionMatrix = new float[16];
//視圖矩陣 mViewMatrix
private final float[] mViewMatrix = new float[16];
public GLWorld(Context context) {
this(context, null);
}
public GLWorld(Context context, AttributeSet attrs) {
super(context, attrs);
setEGLContextClientVersion(3);//設置OpenGL ES 3.0 context
setRenderer(this);//設置渲染器
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
line = new GLLine();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES30.glViewport(0, 0, width, height);//設置GL視口
float ratio = (float) width / height;
//透視投影矩陣--截錐
Matrix.frustumM(mProjectionMatrix, 0,
-ratio, ratio, -1, 1,
3, 7);
// 設置相機位置(視圖矩陣)
Matrix.setLookAtM(mViewMatrix, 0,
0, 0, 4,
0f, 0f, 0f,
0f, 1.0f, 0.0f);
}
@Override
public void onDrawFrame(GL10 gl) {
Matrix.multiplyMM(
mMVPMatrix, 0,
mProjectionMatrix, 0,
mViewMatrix, 0);
line.draw(mMVPMatrix);
}
}
複製代碼
着色器shader是OpenGL靈魂般的存在,因此直接寫在代碼裏確定不太好
通常放在assets文件夾裏,另外值得一提的是AS的着色器代碼高亮顯示插件
我的習慣片斷用.fsh
的後綴名,頂點用.vsh
的後綴名
這些通用的不變的操做能夠提取出來進行復用
public static int initProgramByAssets(Context ctx, String vertName, String fragName){
int program=-1;
////頂點着色
int vertexShader = loadShaderAssets(ctx, GLES30.GL_VERTEX_SHADER, vertName);
//片元着色
int fragmentShader = loadShaderAssets(ctx, GLES30.GL_FRAGMENT_SHADER, fragName);
program = GLES30.glCreateProgram();//建立空的OpenGL ES 程序
GLES30.glAttachShader(program, vertexShader);//加入頂點着色器
GLES30.glAttachShader(program, fragmentShader);//加入片元着色器
GLES30.glLinkProgram(program);//建立可執行的OpenGL ES項目
return program;
}
//從sh腳本中加載shader內容的方法
private static int loadShaderAssets(Context ctx, int type, String name) {
byte[] buf = new byte[1024];
StringBuilder sb = new StringBuilder();
int len;
try (InputStream is = ctx.getAssets().open(name)) {
while ((len = is.read(buf)) != -1) {
sb.append(new String(buf, 0, len));
}
} catch (Exception e) {
e.printStackTrace();
}
return loadShader(type, sb.toString());
}
複製代碼
使用時直接加載便可,這樣GLLine的內容就比較精簡了。
program = GLLoader.initProgramByAssets(context, "base.vsh", "base.fsh");
複製代碼
這樣也能正常表現,之後對着色器代碼的修改就是屢見不鮮。
本篇爲你介紹了OpenGLES的基礎使用,旨在爲你打開一扇OpenGLES的大門,其中不少細節一言蔽之,後面會一一道來。因此這一篇能夠不求甚解,跑出來就算你成功了。下一篇將會爲你介紹面的繪製和貼圖,這是OpenGLES和圖片關聯起來的重要部分。
@張風捷特烈 2020.01.10 未允禁轉
個人公衆號:編程之王
聯繫我--郵箱:1981462002@qq.com --微信:zdl1994328
~ END ~