OpenGL筆記(五) 着色器渲染(以Android為例)


一、Android平台上下文環境的創建及初始化

1. 首先實例化Android上下文環境,即EGL的初始化。

bool EGLCore::init(EGLContext sharedContext) {
    EGLint numConfigs;
    EGLint width;
    EGLint height;

    const EGLint attribs[] = { EGL_BUFFER_SIZE, 32, EGL_ALPHA_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
    EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_NONE };
    //eglGetDisplay來返回OpenGL ES渲染的目標,每個廠商都會返回默認的顯示設備
    if ((display = eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY) {
        LOGE("eglGetDisplay() returned error %d", eglGetError());
        return false;
    }
    // 初始化顯示設備
    if (!eglInitialize(display, 0, 0)) {
        LOGE("eglInitialize() returned error %d", eglGetError());
        return false;
    }
    // 得到配置選項信息
    if (!eglChooseConfig(display, attribs, &config, 1, &numConfigs)) {
        LOGE("eglChooseConfig() returned error %d", eglGetError());
        release();
        return false;
    }
    // 創建OpenGL的上下文環境EGLContext
    EGLint eglContextAttributes[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
    if (!(context = eglCreateContext(display, config, NULL == sharedContext ? EGL_NO_CONTEXT : sharedContext, eglContextAttributes))) {
        LOGE("eglCreateContext() returned error %d", eglGetError());
        release();
        return false;
    }

    pfneglPresentationTimeANDROID = (PFNEGLPRESENTATIONTIMEANDROIDPROC)eglGetProcAddress("eglPresentationTimeANDROID");
    if (!pfneglPresentationTimeANDROID) {
        LOGE("eglPresentationTimeANDROID is not available!");
    }

    return true;
}

2. 將EGL和設備的屏幕連接起來。使用EGLSurface,通過EGL庫提供的eglCreateWindowSurface可以創建一個可實際顯示的Surface,通過EGL庫提供的eglCreatePbufferSurface可以創建一個OffScreen的Surface。_window就是通過Java層的Surface對象創建出的ANativeWindow類型的對象,即本地設備屏幕的表示。也就是說真實顯示的Surface還是通過Java層創建好的, 然后OpenGL只是繪制到了這個target上邊?

EGLSurface EGLCore::createWindowSurface(ANativeWindow* _window) {
    EGLSurface surface = NULL;
    EGLint format;
    if (!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format)) {
        LOGE("eglGetConfigAttrib() returned error %d", eglGetError());
        release();
        return surface;
    }
    ANativeWindow_setBuffersGeometry(_window, 0, 0, format);
    if (!(surface = eglCreateWindowSurface(display, config, _window, 0))) {
        LOGE("eglCreateWindowSurface() returned error %d", eglGetError());
    }
    return surface;
}

通過Java層的Surface對象創建ANativeWindow類型的對象方法如下:

JNIEXPORT void JNICALL Java_com_phuket_tour_opengl_renderer_PngPreviewController_setSurface
(JNIEnv * env, jobject obj, jobject surface) {
    if (surface != 0 && NULL != controller) {
        window = ANativeWindow_fromSurface(env, surface);
        LOGI("Got window %p", window);
        controller->setWindow(window);
    } else if (window != 0) {
        LOGI("Releasing window");
        ANativeWindow_release(window);
        window = 0;
    }
}

3. 開發者需要開辟一個新的線程,來執行OpenGL ES的渲染操作,而且還必須為該線程綁定顯示設備(surface)與上下文環境(Context)。EGL是雙緩沖模式,內部有兩個FrameBuffer,當EGL將一個FrameBuffer顯示到屏幕上的時候,另外一個FrameBuffer就在后台等待OpenGL ES進行渲染輸出了。直到調用函數eglSwapBuffers這條指令的時候,才會把前台的FrameBuffer和后台的FrameBuffer進行交換。

bool EGLCore::makeCurrent(EGLSurface eglSurface) {
    return eglMakeCurrent(display, eglSurface, eglSurface, context);
}

二、Texture/Shader/Program

 1. 創建Texture(紋理)

int PicPreviewTexture::initTexture() {
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    return 1;
}

2. 創建/初始化/編譯Shader

GLuint PicPreviewRender::compileShader(GLenum type, const char *sources) {
    GLint status;
    GLuint shader = glCreateShader(type);
    if (shader == 0 || shader == GL_INVALID_ENUM) {
        LOGI("Failed to create shader %d", type);
        return 0;
    }
    glShaderSource(shader, 1, &sources, NULL);
    glCompileShader(shader);
    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status == GL_FALSE) {
        glDeleteShader(shader);
        LOGI("Failed to compile shader : %s\n", sources);
        return 0;
    }
    return shader;
}

3. 創建並使用Program(顯卡可執行程序)

int PicPreviewRender::useProgram() {
    program = glCreateProgram();
    glAttachShader(program, vertShader);
    glAttachShader(program, fragShader);
    glBindAttribLocation(program, ATTRIBUTE_VERTEX, "position");
    glBindAttribLocation(program, ATTRIBUTE_TEXCOORD, "texcoord");
    glLinkProgram(program);
    GLint status;
    glGetProgramiv(program, GL_LINK_STATUS, &status);
    if (status == GL_FALSE) {
        LOGI("Failed to link program %d", program);
        return -1;
    }
    glUseProgram(program);

    uniformSampler = glGetUniformLocation(program, "yuvTexSampler");

    return 1;
}

 三、渲染操作

void PicPreviewRender::render(){
    glViewport(_backingLeft, _backingTop, _backingWidth, _backingHeight);
    glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glUseProgram(program);
    static const GLfloat _vertices[] = { -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f };
    glVertexAttribPointer(ATTRIBUTE_VERTEX, 2, GL_FLOAT, 0, 0, _vertices);
    glEnableVertexAttribArray(ATTRIBUTE_VERTEX);
    static const GLfloat texCoords[] = { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f };
    glVertexAttribPointer(ATTRIBUTE_TEXCOORD, 2, GL_FLOAT, 0, 0, texCoords);
    glEnableVertexAttribArray(ATTRIBUTE_TEXCOORD);
    picPreviewTexture->bindTexture(uniformSampler);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

    首先,_vertices有4個頂點,每個頂點都會調用一次頂點着色器程序。按照規則,一共有四個點,每個點為vec2,但是頂點着色器的in輸入變量為vec3,在GLSL語法中,vec2傳遞給vec3,其中擴充的維度的值默認為0,這也可以接受因為所在的是二維平面,不然也不會2個float代表一個點,所以,第三維自動為0。gl_Position 的第四維是和裁剪以及變換有關的,沒有相關的變換時候是1.0

    頂點坐標的歸一化區間是[-1, 1],紋理坐標的歸一化區間是[0, 1]。所以代碼是將紋理整體完整鋪在顯示區域,如果想把紋理的一部分鋪上去,那就在[0, 1]區間內部分寫進去。

執行完渲染操作后,調用eglSwapBuffers即可顯示。至此,一次渲染操作完成。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM