Android OpenGL ES 系列連載:(01)繪製一個三角形

位元組流動發表於2020-03-22

該原創文章首發於微信公眾號:位元組流動

什麼是 OpenGLES

OpenGLES 全稱 OpenGL for Embedded Systems ,是三維圖形應用程式介面 OpenGL 的子集,本質上是一個跨程式語言、跨平臺的程式設計介面規範,主要應用於嵌入式裝置,如手機、平板等。由科納斯(Khronos)組織定義和推廣,科納斯是一個圖形軟硬體行業協會,該協會主要關注圖形和多媒體方面的開放標準。

OpenGLES 3.0 的特點

OpenGLES 3.0 實際上是 OpenGLES 2.0 的擴充套件版本,向下相容 OpenGLES 2.0 ,但不相容 OpenGLES 1.0 。

OpenGLES 3.0 圖形管線.png

OpenGLES 3.0 主要新特性

紋理

  • sRGB 紋理和幀緩衝區——允許應用程式執行伽馬校正渲染。
  • 2D 紋理陣列——儲存一組 2D 紋理的紋理目標。
  • 3D 紋理。一些 OpenGL ES 2.0 實現通過擴充套件支援3D紋理,而 OpenGL ES3.0 將此作為強制的功能。
  • 深度紋理和陰影比較——啟用儲存在紋理中的深度緩衝區。
  • 無縫立方圖。在 OpenGL ES 3.0 中,立方圖可以進行取樣如過濾來使用相鄰面的資料並刪除接縫處的偽像。
  • 浮點紋理。

著色器

  • 二進位制程式檔案。在 OpenGL ES 3.0 中,完全連結過的二進位制程式檔案可以儲存為離線二進位制格式,執行時不需要連結步驟。這有助於減少應用程式的載入時間。
  • 非方矩陣。支援方陣之外的新矩陣型別,並在 API中 增加了相關的統一呼叫,以支援這些矩陣的載入。
  • 全整數支援。支援整數(以及無符號整數)標量和向量型別以及全整數操作。
  • 平面/平滑插值程式。 OpenGL ES 3.0 中插值程式可以顯式宣告為平面或者平滑著色。
  • 統一變數塊。統一變數值可以組合為統一變數塊。統一變數塊可以更高效地載入,也可在多個著色器程式間共享。
  • 佈局限定符。頂點著色器輸入可以用佈局限定符宣告,以顯式繫結著色器原始碼中的位置,而不需要呼叫 API 。

幾何形狀

  • 變換反饋。可以在緩衝區物件中捕捉頂點著色器的輸出。
  • 布林遮擋查詢。應用程式可以查詢一個(或者一組)繪製呼叫的任何畫素是否通過深度測試。
  • 例項渲染。有效地渲染包含類似幾何形狀但是屬性(例如變化矩陣、顏色或者大小)不同的物件。

緩衝區物件

  • 統一變數緩衝區物件。為儲存/繫結大的統一變數塊提供高效的方法。統
  • VAO 頂點陣列物件。提供繫結和在頂點陣列狀態之間切換的高效方法。
  • 取樣器物件。將取樣器狀態(紋理迴圈模式和過濾)與紋理物件分離。
  • 同步物件。為應用程式提供檢查一組操作是否在GPU上完成執行的機制。
  • 畫素緩衝物件。使應用程式能夠執行對畫素操作和紋理傳輸操作的非同步資料傳輸。
  • 緩衝區物件間拷貝。提供了高效地從一個緩衝區物件向另一個緩衝區物件傳輸資料的機制,不需要CPU干預。

幀緩衝區

  • 多重渲染目標(MRT)。允許應用程式同時渲染到多個顏色緩衝區。
  • 多重取樣渲染緩衝區。使應用程式能夠渲染到具備多重取樣抗鋸齒功能的螢幕外幀緩衝區。
  • 幀緩衝區失效提示。

OpenGLES 3.0 著色器語言規範變化

OpenGLES 3.0 著色器指令碼

#version 300 es                          
layout(location = 0) in vec4 vPosition;  
void main()                              
{                                        
   gl_Position = vPosition;              
}                                        
複製程式碼

其中,#version 300 es 為 OpenGLES 3.0 版本宣告,3.0 中使用 inout 關鍵字取代 attributevaryinglayout 關鍵字直接為指令碼中的屬性指定位置,為屬性賦值變成了:

GLfloat vVertices[] = {
			0.0f,  0.5f, 0.0f,
			-0.5f, -0.5f, 0.0f,
			0.5f, -0.5f, 0.0f,
			0.0f,  -1.0f, 0.0f,
			0.5f, -0.5f, 0.0f,
			-0.5f, -0.5f, 0.0f
	};

//第一個引數為對應屬性的 location 值
glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
複製程式碼

而原來 2.0 的賦值方式為:

positionLoc = glGetAttribLocation(program, "vPosition");
glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
複製程式碼

繪製三角形

繪製一個三角形的步驟:

  1. 建立 OpenGLES 環境(可以藉助於 GLSurfaceView 建立的上下文物件);
  2. 編譯並連結著色器程式;
  3. 指定著色器程式,為著色器程式中的變數賦值;
  4. 繪製。

基於 GLSurfaceView 搭建 OpenGLES 環境

簡單自定義 GLSurfaceView。

package com.byteflow.app;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.util.Log;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MyGLSurfaceView extends GLSurfaceView {
    private static final String TAG = "MyGLSurfaceView";

    public static final int IMAGE_FORMAT_RGBA = 0x01;
    public static final int IMAGE_FORMAT_NV21 = 0x02;
    public static final int IMAGE_FORMAT_NV12 = 0x03;
    public static final int IMAGE_FORMAT_I420 = 0x04;

    private MyGLRender mGLRender;
    private MyNativeRender mNativeRender;

    public MyGLSurfaceView(Context context) {
        this(context, null);
    }

    public MyGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.setEGLContextClientVersion(3);
        mNativeRender = new MyNativeRender();
        mGLRender = new MyGLRender(mNativeRender);
        setRenderer(mGLRender);
        setRenderMode(RENDERMODE_CONTINUOUSLY);
    }

    public MyNativeRender getNativeRender() {
        return mNativeRender;
    }

    public static class MyGLRender implements GLSurfaceView.Renderer {
        private MyNativeRender mNativeRender;

        MyGLRender(MyNativeRender myNativeRender) {
            mNativeRender = myNativeRender;
        }

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
            Log.d(TAG, "onSurfaceCreated() called with: gl = [" + gl + "], config = [" + config + "]");
            mNativeRender.native_OnSurfaceCreated();

        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            Log.d(TAG, "onSurfaceChanged() called with: gl = [" + gl + "], width = [" + width + "], height = [" + height + "]");
            mNativeRender.native_OnSurfaceChanged(width, height);

        }

        @Override
        public void onDrawFrame(GL10 gl) {
            Log.d(TAG, "onDrawFrame() called with: gl = [" + gl + "]");
            mNativeRender.native_OnDrawFrame();

        }
    }
}

複製程式碼

JNI 類。

package com.byteflow.app;

public class MyNativeRender {
    static {
        System.loadLibrary("native-render");
    }

    public native void native_OnInit();

    public native void native_OnUnInit();

    public native void native_SetImageData(int format, int width, int height, byte[] bytes);

    public native void native_OnSurfaceCreated();

    public native void native_OnSurfaceChanged(int width, int height);

    public native void native_OnDrawFrame();
}
複製程式碼

Native 層簡單實現 JNI 。

//
// Created by ByteFlow on 2019/7/9.
//
#include "util/LogUtil.h"
#include <MyGLRenderContext.h>
#include "jni.h"

#define NATIVE_RENDER_CLASS_NAME "com/byteflow/app/MyNativeRender"

#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_byteflow_app_MyNativeRender
 * Method:    native_OnInit
 * Signature: ()V
 */
JNIEXPORT void JNICALL native_OnInit(JNIEnv *env, jobject instance)
{
	MyGLRenderContext::GetInstance();

}

/*
 * Class:     com_byteflow_app_MyNativeRender
 * Method:    native_OnUnInit
 * Signature: ()V
 */
JNIEXPORT void JNICALL native_OnUnInit(JNIEnv *env, jobject instance)
{
	MyGLRenderContext::DestroyInstance();
}

/*
 * Class:     com_byteflow_app_MyNativeRender
 * Method:    native_SetImageData
 * Signature: (III[B)V
 */
JNIEXPORT void JNICALL native_SetImageData
(JNIEnv *env, jobject instance, jint format, jint width, jint height, jbyteArray imageData)
{
	int len = env->GetArrayLength (imageData);
	uint8_t* buf = new uint8_t[len];
	env->GetByteArrayRegion(imageData, 0, len, reinterpret_cast<jbyte*>(buf));
	MyGLRenderContext::GetInstance()->SetImageData(format, width, height, buf);
	delete[] buf;
	env->DeleteLocalRef(imageData);
}

/*
 * Class:     com_byteflow_app_MyNativeRender
 * Method:    native_OnSurfaceCreated
 * Signature: ()V
 */
JNIEXPORT void JNICALL native_OnSurfaceCreated(JNIEnv *env, jobject instance)
{
	MyGLRenderContext::GetInstance()->OnSurfaceCreated();
}

/*
 * Class:     com_byteflow_app_MyNativeRender
 * Method:    native_OnSurfaceChanged
 * Signature: (II)V
 */
JNIEXPORT void JNICALL native_OnSurfaceChanged
(JNIEnv *env, jobject instance, jint width, jint height)
{
	MyGLRenderContext::GetInstance()->OnSurfaceChanged(width, height);

}

/*
 * Class:     com_byteflow_app_MyNativeRender
 * Method:    native_OnDrawFrame
 * Signature: ()V
 */
JNIEXPORT void JNICALL native_OnDrawFrame(JNIEnv *env, jobject instance)
{
	MyGLRenderContext::GetInstance()->OnDrawFrame();

}

#ifdef __cplusplus
}
#endif

static JNINativeMethod g_RenderMethods[] = {
		{"native_OnInit",             "()V",       (void *)(native_OnInit)},
		{"native_OnUnInit",           "()V",       (void *)(native_OnUnInit)},
		{"native_SetImageData",       "(III[B)V",  (void *)(native_SetImageData)},
		{"native_OnSurfaceCreated",   "()V",       (void *)(native_OnSurfaceCreated)},
		{"native_OnSurfaceChanged",   "(II)V",     (void *)(native_OnSurfaceChanged)},
		{"native_OnDrawFrame",        "()V",       (void *)(native_OnDrawFrame)},
};

static int RegisterNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int methodNum)
{
	LOGCATE("RegisterNativeMethods");
	jclass clazz = env->FindClass(className);
	if (clazz == NULL)
	{
		LOGCATE("RegisterNativeMethods fail. clazz == NULL");
		return JNI_FALSE;
	}
	if (env->RegisterNatives(clazz, methods, methodNum) < 0)
	{
		LOGCATE("RegisterNativeMethods fail");
		return JNI_FALSE;
	}
	return JNI_TRUE;
}

static void UnregisterNativeMethods(JNIEnv *env, const char *className)
{
	LOGCATE("UnregisterNativeMethods");
	jclass clazz = env->FindClass(className);
	if (clazz == NULL)
	{
		LOGCATE("UnregisterNativeMethods fail. clazz == NULL");
		return;
	}
	if (env != NULL)
	{
		env->UnregisterNatives(clazz);
	}
}

// call this func when loading lib
extern "C" jint JNI_OnLoad(JavaVM *jvm, void *p)
{
	LOGCATE("===== JNI_OnLoad =====");
	jint jniRet = JNI_ERR;
	JNIEnv *env = NULL;
	if (jvm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK)
	{
		return jniRet;
	}

	jint regRet = RegisterNativeMethods(env, NATIVE_RENDER_CLASS_NAME, g_RenderMethods,
										sizeof(g_RenderMethods) /
										sizeof(g_RenderMethods[0]));
	if (regRet != JNI_TRUE)
	{
		return JNI_ERR;
	}

	return JNI_VERSION_1_6;
}

extern "C" void JNI_OnUnload(JavaVM *jvm, void *p)
{
	JNIEnv *env = NULL;
	if (jvm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK)
	{
		return;
	}

	UnregisterNativeMethods(env, NATIVE_RENDER_CLASS_NAME);
}

複製程式碼

Native 層的 Render 封裝類,其他的 Sample 都在此類中實現。

//
// Created by ByteFlow on 2019/7/9.
//

#include <TriangleSample.h>
#include "MyGLRenderContext.h"
#include "LogUtil.h"

MyGLRenderContext* MyGLRenderContext::m_pContext = nullptr;

MyGLRenderContext::MyGLRenderContext()
{

}

MyGLRenderContext::~MyGLRenderContext()
{

}

void MyGLRenderContext::SetImageData(int format, int width, int height, uint8_t *pData)
{
	LOGCATE("MyGLRenderContext::SetImageData format=%d, width=%d, height=%d, pData=%p", format, width, height, pData);
	NativeImage nativeImage;
	nativeImage.format = format;
	nativeImage.width = width;
	nativeImage.height = height;
	nativeImage.ppPlane[0] = pData;

	switch (format)
	{
		case IMAGE_FORMAT_NV12:
		case IMAGE_FORMAT_NV21:
			nativeImage.ppPlane[1] = nativeImage.ppPlane[0] + width * height;
			break;
		case IMAGE_FORMAT_I420:
			nativeImage.ppPlane[1] = nativeImage.ppPlane[0] + width * height;
			nativeImage.ppPlane[2] = nativeImage.ppPlane[1] + width * height / 4;
			break;
		default:
			break;
	}

	//m_TextureMapSample->LoadImage(&nativeImage);

}

void MyGLRenderContext::OnSurfaceCreated()
{
	LOGCATE("MyGLRenderContext::OnSurfaceCreated");
	glClearColor(1.0f,1.0f,0.5f, 1.0f);
	m_Sample.Init();
}

void MyGLRenderContext::OnSurfaceChanged(int width, int height)
{
	LOGCATE("MyGLRenderContext::OnSurfaceChanged [w, h] = [%d, %d]", width, height);
	glViewport(0, 0, width, height);
}

void MyGLRenderContext::OnDrawFrame()
{
	LOGCATE("MyGLRenderContext::OnDrawFrame");
	glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

    m_Sample.Draw();


}

MyGLRenderContext *MyGLRenderContext::GetInstance()
{
	LOGCATE("MyGLRenderContext::GetInstance");
	if (m_pContext == nullptr)
	{
		m_pContext = new MyGLRenderContext();
	}
	return m_pContext;
}

void MyGLRenderContext::DestroyInstance()
{
	LOGCATE("MyGLRenderContext::DestroyInstance");
	if (m_pContext)
	{
		delete m_pContext;
		m_pContext = nullptr;
	}

}

複製程式碼

編譯連結著色器程式,進行繪製

三角形的繪製實現類。

//
// Created by ByteFlow on 2019/7/9.
//

#include "TriangleSample.h"
#include "../util/GLUtils.h"
#include "../util/LogUtil.h"

TriangleSample::TriangleSample()
{

}

TriangleSample::~TriangleSample()
{
	if (m_ProgramObj)
	{
		glDeleteProgram(m_ProgramObj);
	}

}

void TriangleSample::Init()
{
	char vShaderStr[] =
			"#version 300 es                          \n"
			"layout(location = 0) in vec4 vPosition;  \n"
			"void main()                              \n"
			"{                                        \n"
			"   gl_Position = vPosition;              \n"
			"}                                        \n";

	char fShaderStr[] =
			"#version 300 es                              \n"
			"precision mediump float;                     \n"
			"out vec4 fragColor;                          \n"
			"void main()                                  \n"
			"{                                            \n"
			"   fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );  \n"
			"}                                            \n";

	m_ProgramObj = GLUtils::CreateProgram(vShaderStr, fShaderStr, m_VertexShader, m_FragmentShader);

}

void TriangleSample::Draw()
{
	LOGCATE("TriangleSample::Draw");
	GLfloat vVertices[] = {
			0.0f,  0.5f, 0.0f,
			-0.5f, -0.5f, 0.0f,
			0.5f, -0.5f, 0.0f,
	};

	if(m_ProgramObj == 0)
		return;

	// Use the program object
	glUseProgram (m_ProgramObj);

	// Load the vertex data
	glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
	glEnableVertexAttribArray (0);

	glDrawArrays (GL_TRIANGLES, 0, 3);

}

複製程式碼

編譯和連結著色器程式的類

#include "GLUtils.h"
#include "LogUtil.h"
#include <stdlib.h>

GLuint GLUtils::LoadShader(GLenum shaderType, const char *pSource)
{
    GLuint shader = 0;
        shader = glCreateShader(shaderType);
        if (shader)
        {
            glShaderSource(shader, 1, &pSource, NULL);
            glCompileShader(shader);
            GLint compiled = 0;
            glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
            if (!compiled)
            {
                GLint infoLen = 0;
                glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
                if (infoLen)
                {
                    char* buf = (char*) malloc((size_t)infoLen);
                    if (buf)
                    {
                        glGetShaderInfoLog(shader, infoLen, NULL, buf);
                        LOGCATE("GLUtils::LoadShader Could not compile shader %d:\n%s\n", shaderType, buf);
                        free(buf);
                    }
                    glDeleteShader(shader);
                    shader = 0;
                }
            }
        }
	return shader;
}

GLuint GLUtils::CreateProgram(const char *pVertexShaderSource, const char *pFragShaderSource, GLuint &vertexShaderHandle, GLuint &fragShaderHandle)
{
    GLuint program = 0;
        vertexShaderHandle = LoadShader(GL_VERTEX_SHADER, pVertexShaderSource);
        if (!vertexShaderHandle) return program;

        fragShaderHandle = LoadShader(GL_FRAGMENT_SHADER, pFragShaderSource);
        if (!fragShaderHandle) return program;

        program = glCreateProgram();
        if (program)
        {
            glAttachShader(program, vertexShaderHandle);
            CheckGLError("glAttachShader");
            glAttachShader(program, fragShaderHandle);
            CheckGLError("glAttachShader");
            glLinkProgram(program);
            GLint linkStatus = GL_FALSE;
            glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);

            glDetachShader(program, vertexShaderHandle);
            glDeleteShader(vertexShaderHandle);
            vertexShaderHandle = 0;
            glDetachShader(program, fragShaderHandle);
            glDeleteShader(fragShaderHandle);
            fragShaderHandle = 0;
            if (linkStatus != GL_TRUE)
            {
                GLint bufLength = 0;
                glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
                if (bufLength)
                {
                    char* buf = (char*) malloc((size_t)bufLength);
                    if (buf)
                    {
                        glGetProgramInfoLog(program, bufLength, NULL, buf);
                        LOGCATE("GLUtils::CreateProgram Could not link program:\n%s\n", buf);
                        free(buf);
                    }
                }
                glDeleteProgram(program);
                program = 0;
            }
        }
    LOGCATE("GLUtils::CreateProgram program = %d", program);
	return program;
}

void GLUtils::DeleteProgram(GLuint &program)
{
    LOGCATE("GLUtils::DeleteProgram");
    if (program)
    {
        glUseProgram(0);
        glDeleteProgram(program);
        program = 0;
    }
}

void GLUtils::CheckGLError(const char *pGLOperation)
{
    for (GLint error = glGetError(); error; error = glGetError())
    {
        LOGCATE("GLUtils::CheckGLError GL Operation %s() glError (0x%x)\n", pGLOperation, error);
    }

}

複製程式碼

在 Init 函式中實現編譯連結著色器程式 m_ProgramObj ,其中頂點著色器指令碼:

#version 300 es                          
layout(location = 0) in vec4 vPosition;  
void main()                              
{                                        
   gl_Position = vPosition;              
}                                        
複製程式碼

片元著色器指令碼:

#version 300 es                              
precision mediump float;                     
out vec4 fragColor;                          
void main()                                  
{                                            
   fragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );  //填充三角形區域為紅色
}                                            
複製程式碼

在 Draw 函式中指定著色器程式,為著色器程式中的變數賦值,傳入頂點座標資訊,然後繪製三角形。

opengles 座標系中三角形頂點座標

繪製的結果圖

實現程式碼路徑: Android_OpenGLES_3_0

參考

OpenGLES 維基百科 https://zh.wikipedia.org/wiki/OpenGL_ES OpenGLES 3.0 程式設計指南 https://book.douban.com/subject/26414014/

聯絡與交流

我的公眾號

我的微信

相關文章