OpenGL Mac環境搭建

什麼是OpenGL

一般它被認爲是一個API(Application Programming Interface, 應用程序編程接口),包含了一系列可以操作圖形、圖像的函數。然而,OpenGL本身並不是一個API,它僅僅是一個由Khronos組織制定並維護的規範

OpenGL英文全稱 Open Graphics Library ,是一個開放圖形庫,定義了一個跨編程語言、跨平臺的API規範,它用於生成二維、三維圖像。這個接口由近三百五十個不同的函數調用組成,用來從簡單的圖形比特繪製複雜的三維景象。而另一種程序界面系統是僅用於Microsoft Windows上的Direct3D。OpenGL常用於CAD、虛擬實境、科學可視化程序和電子遊戲開發。

實際的OpenGL庫的開發者通常是顯卡的生產商。你購買的顯卡所支持的OpenGL版本都爲這個系列的顯卡專門開發的。當你使用Apple系統的時候,OpenGL庫是由Apple自身維護的。在Linux下,有顯卡生產商提供的OpenGL庫,也有一些愛好者改編的版本。這也意味着任何時候OpenGL庫表現的行爲與規範規定的不一致時,基本都是庫的開發者留下的bug。
(以上摘自learnopengl)

在我們畫出出色的效果之前,首先要做的就是創建一個OpenGL上下文(Context)和一個用於顯示的窗口。然而,這些操作在每個系統上都是不一樣的,OpenGL有目的地從這些操作抽象(Abstract)出去。這意味着我們不得不自己處理創建窗口,定義OpenGL上下文以及處理用戶輸入。幸運的是,有一些庫已經提供了我們所需的功能,其中一部分是特別針對OpenGL的。這些庫節省了我們書寫操作系統相關代碼的時間,提供給我們一個窗口和上下文用來渲染。最流行的幾個庫有GLUT,SDL,SFML和GLFW。

我們搭建的使用兩個庫GLFW和GLEW,我的理解就是他們可以幫我們方便跨平臺的開發使用代碼。

開源庫簡介

  • GLFW:
    GLFW是一個跨平臺的OpenGL應用框架,支持窗口創建,接受輸入和事件等功能。

  • GLEW:
    GLEW是一個基於OpenGL圖形接口的跨平臺的C++擴展庫。GLEW能自動識別當前平臺所支持的全部OpenGL高級擴展涵數。只要包含glew.h頭文件,就能使用gl,glu,glext,wgl,glx的全部函數。GLEW支持目前流行的各種操作系統。

Mac Xcode PC應用環境配置

開發環境:MacOs Sierra 10.12.6
開發工具:Xcode 8.3.3

看了網上的一些教程都是在電腦上面運行的,在移動端Android和IOS上面運行的沒有那麼多,這裏配置Xcode的環境可以在看到教程的代碼可以直接運行在PC上面看到效果。

配置GLFW和GLEW庫

看了幾種安裝的方式,第一種在GitHub上面下載庫的源代碼然後使用Cmake編譯來安裝,這種我感覺有點麻煩,我使用的是使用Homebrew來安裝。兩種方式最後都是在Mac的usr下面生成C++頭文件和庫文件。Homebrew的安裝過程省略了因爲比較簡單。

安裝命令

brew install glfw
brew install glew

  • 兩個庫安裝完成以後如果提示沒有link那麼就執行link指令link一下

    brew link glfw
    brew link glew

  • 得到的如果link失敗我看了提示是沒有權限讀取usr/local/include目錄的權限

    使用sudo chmod u+w /usr/local/include 提升一下這個文件爲可寫

安裝完成以後brew把安裝包放到了/usr/local/Cellar下面,拷貝安裝包link到了/usr/local/include下面,拷貝lib放到了/usr/local/lib下面

Xcode配置

  • 打開Perference菜單找到Locations選項,打開CustomPaths選項卡配置如下信息。
Name Display Name Path
glew_header glew_header /usr/local/Cellar/glew/2.1.0/include
glew_lib glew_lib /usr/local/Cellar/glew/2.1.0/lib
glfw_header glfw_header /usr/local/Cellar/glfw/3.2.1/include
glfw_lib glfw_lib /usr/local/Cellar/glfw/3.2.1/lib
  • 創建Xcode command line tool項目,現在C++ 語言在Build Settings 的Header Search Paths裏面配置
$(glew_header) $(glfw_header)

在Library Search Paths配置

$(glew_lib) $(glfw_lib)

在項目General中找到Linked Frameworks and libraries 添加OpenGL.framework,libGLEW.2.1.0.dylib,libglfw3.3.2.dylib文件
後面那兩個庫在Xcode中找不到,點擊Finder右鍵前往文件夾到/usr/local/Cellar/下面的兩個安裝包的lib下面找到我們要的文件。

配置完成以後在main.cpp中試運行這些代碼

#include <iostream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>

int main(int argc, const char * argv[]) {
    GLFWwindow* window;
    if (!glfwInit())
        return -1;
    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    while (!glfwWindowShouldClose(window))
    {
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    glfwTerminate();
    return 0;
}

運行成功顯示一個窗口則一切OK了。

Mac AndroidStudio環境配置

因爲本人做Android開發比較多,一些教程的PC實例會轉變成爲Android手機的實例來看效果。我們沒有使用OpenGL的Java提供的API來進行開發學習,直接使用的C++。

開發環境:MacOs Sierra 10.12.6
開發工具:AndroidStudio 2.3.3

AndroidStudio環境配置

首先NDK的東西和Cmake的使用要都弄好,可以看以前的一篇文章NDK配置。我們以後使用實例都是通過cmake來編譯C++的代碼的。

完成經典的三角形實例

創建一個Android項目,並且勾選C++的支持,我們就可以直接使用模版裏面提供的Cmake的使用了,沒有勾選支持C++的Android項目見上面的Ndk配置的文章使用Cmake拷貝那些東西過去就可以了。完整的項目代碼結構如下:

這裏寫圖片描述

Android應用層代碼構建

  • 聲明jni函數以及引入c++庫文件類TriangleLib
public class TriangleLib {
    static {
        System.loadLibrary("triangle-lib");
    }

    //初始化GLES
    public static native boolean init();

    //設置GLES寬高
    public static native void resize(int wight, int height);

    //繪製三角形
    public static native void drawTriangle();
}
  • 創建TriangleView繼承自GLSurfaceView。這個View我現在的理解就是鏈接OpenGL代碼和Android顯示的一箇中介。
public class TriangleView extends GLSurfaceView {
    public TriangleView(Context context) {
        this(context, null);
    }

    public TriangleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        //設置顏色模式爲RGB_888,透明度,色度深度爲16
        setEGLConfigChooser(8, 8, 8, 0, 16, 0);
        //設置EGL版本爲3
        setEGLContextClientVersion(3);
        //創建GLSurfaceView的渲染器,現在不知道具體有啥作用
        setRenderer(new TriangleRender());
    }

    class TriangleRender implements Renderer {

        @Override
        public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
            TriangleLib.init();
        }

        @Override
        public void onSurfaceChanged(GL10 gl10, int wight, int height) {
            TriangleLib.resize(wight, height);
        }

        @Override
        public void onDrawFrame(GL10 gl10) {
            TriangleLib.drawTriangle();
        }
    }

C++底層代碼構建

在cpp目錄下面建立triangle-lib.cpp文件來寫我們的OpenGL代碼,這個是我們要重點理解的。

#include <GLES3/gl3.h>
#include <android/log.h>

#define LOG_TAG "TRIANGLE-LIB"
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

#include <jni.h>
#include <stdlib.h>

//頂點着色器
static const char VERTEX_SHADER[] =
        "#version 300 es\n"
                "layout(location = 0) in vec4 vPosition;\n"
                "void main(){\n"
                "gl_Position = vPosition;\n"
                "}\n";
//片段着色器
static const char FRAGMENT_SHADER[] =
        "#version 300 es\n"
                "precision mediump float;\n"
                "out vec4 fragColor;\n"
                "void main(){\n"
                "fragColor = vec4(1.0,0.0,0.0,1.0);\n"
                "}\n";
//頂點着色器在標準化設備座標中的位置
static const GLfloat VERTEX[] = {
        0.0f, 0.5f, 0.0f,
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f
};

bool checkGlError(const char *funcName) {
    GLint err = glGetError();
    if (err != GL_NO_ERROR) {
        ALOGE("GL error after %s(): 0x%08x\n", funcName, err);
        return true;
    }
    return false;
}

//創建Shader
GLuint createShader(GLenum shaderType, const char *src) {
    GLuint shader = glCreateShader(shaderType);
    if (!shader) {
        checkGlError("glCreateShader");
        return 0;
    }
    glShaderSource(shader, 1, &src, NULL);
    GLint compiled = GL_FALSE;
    glCompileShader(shader);
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    if (!compiled) {
        GLint infoLogLen = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLen);
        if (infoLogLen > 0) {
            GLchar *infoLog = (GLchar *) malloc(infoLogLen);
            if (infoLog) {
                glGetShaderInfoLog(shader, infoLogLen, NULL, infoLog);
                ALOGE("Could not compile %s shader:\n%s\n",
                      shaderType == GL_VERTEX_SHADER ? "vertex" : "fragment",
                      infoLog);
                free(infoLog);
            }
        }
        glDeleteShader(shader);
        return 0;
    }
    return shader;
}

//創建着色器應用
GLuint createProgram(const char *vtxSrc, const char *fragSrc) {
    GLuint vtxShader = 0;
    GLuint fragShader = 0;
    GLuint program = 0;
    GLint linked = GL_FALSE;
    vtxShader = createShader(GL_VERTEX_SHADER, vtxSrc);
    if (!vtxShader)
        goto exit;
    fragShader = createShader(GL_FRAGMENT_SHADER, fragSrc);
    if (!fragShader)
        goto exit;
    program = glCreateProgram();
    if (!program) {
        checkGlError("glCreateProgram");
        goto exit;
    }
    glAttachShader(program, vtxShader);
    glAttachShader(program, fragShader);
    glLinkProgram(program);
    glGetProgramiv(program, GL_LINK_STATUS, &linked);
    if (!linked) {
        ALOGE("Could not link program");
        GLint infoLogLen = 0;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLen);
        if (infoLogLen) {
            GLchar *infoLog = (GLchar *) malloc(infoLogLen);
            if (infoLog) {
                glGetProgramInfoLog(program, infoLogLen, NULL, infoLog);
                ALOGE("Could not link program:\n%s\n", infoLog);
                free(infoLog);
            }
        }
        glDeleteProgram(program);
        program = 0;
    }
    exit:
    glDeleteShader(vtxShader);
    glDeleteShader(fragShader);
    return program;
}

GLuint program;


extern "C" {
JNIEXPORT jboolean JNICALL
Java_com_lyman_hellotriangle_lib_TriangleLib_init(JNIEnv *env, jobject obj);
JNIEXPORT void JNICALL
Java_com_lyman_hellotriangle_lib_TriangleLib_resize(JNIEnv *env, jobject obj, jint width,
                                                    jint height);
JNIEXPORT void JNICALL
Java_com_lyman_hellotriangle_lib_TriangleLib_drawTriangle(JNIEnv *env, jobject obj);
}

JNIEXPORT jboolean JNICALL
Java_com_lyman_hellotriangle_lib_TriangleLib_init(JNIEnv *env, jobject obj) {
    program = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
    if (!program) {
        ALOGE("程序創建失敗");
        return JNI_FALSE;
    }
    glClearColor(0, 0, 0, 0);
    return JNI_TRUE;
}

JNIEXPORT void JNICALL
Java_com_lyman_hellotriangle_lib_TriangleLib_resize(JNIEnv *env, jobject obj, jint width,
                                                    jint height) {
    glViewport(0, 0, width, height);
    glClear(GL_COLOR_BUFFER_BIT);
}

JNIEXPORT void JNICALL
Java_com_lyman_hellotriangle_lib_TriangleLib_drawTriangle(JNIEnv *env, jobject obj) {
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(program);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, VERTEX);
    glEnableVertexAttribArray(0);
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

總結

這個代碼看上去很多,也有很多概念要先理解

  • Q:什麼是着色器?
    A:這裏寫圖片描述

  • Q:什麼是 GLSL
    A:OpenGL着色器使用的是OpenGL着色器語言(OpenGL Shading Language)GLSL寫的,以後再瞭解他的具體寫法。

  • Q:上述代碼的執行步驟總結
    A:
    這裏寫圖片描述

    這裏的每一個步驟具體的我也不知道是幹啥,但是先有個大綱就好。
  • Q:上述代碼步驟本人總結:

    A:聲明頂點着色器(它就是我們在手機標準化設備座標看到的2d畫面的三個頂點)和片段着色器(計算每一個像素的最終顏色)的數據;

    創建上面兩個着色器;

    創建着色器應用並且把上面兩個着色器附加到着色器應用程序上;

    繪製圖形;

上面的Android應用層的代碼和C++的代碼會成爲我們以後學習使用OpenGL的一個固定的步驟。

項目完整代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章