1. 程式人生 > >opengl es3.0遊戲開發學習筆記1-繪製旋轉的三角形

opengl es3.0遊戲開發學習筆記1-繪製旋轉的三角形

前段時間一直在看opengl es2.0遊戲開發的知識 ,這幾天買了本opengl es3.0遊戲開發的書  



打算一邊學習一邊整理學習筆記,我的開發環境是Android studio 2.1.3,不過有個問題是Android studio自帶的模擬器只能支援es2.0  

無法使用es3.0  所以3.0只能在真機上除錯,手機必須是4.3或者是以上系統,我試過4.3的三星s3和  6.0的華為mates。下面是效果圖


首先是各個類的功能


LoggerConfig這個類是控制錯誤日誌輸出的,這個其實很簡單裡面就一句話,在錯誤日誌輸出時判斷它裡面是不是true

MyActivity這個類是主活動的類,建立了檢視,在這裡把顯示的檢視設定為opengl的視視窗。

MyTDView這個類是繼承自GLSurfaceView 實現opengl的視窗和 一個渲染器,我是這麼理解的

shaderUtil是著色器的幫助類  實現讀取著色器和編譯連線

Tringle是繪製三角形

我個人感覺整體過過程是這樣的


下邊是LoggerConfig的程式碼

package com.opengl.a3_1_triangle;

/**
 * Created by admin on 2016/11/12.
 */
public class LoggerConfig {
    public static final boolean ON = true;   //在後面輸出錯誤日誌時判斷是不是true
}

下邊是從shaderUtil幫助類的程式碼

首先得實現的是把著色器程式碼從文字中讀取出來並且進行編譯,書中作者使用的是把著色器程式碼寫在sh指令碼中然後讀取,Android studio中有glsl著色器的外掛,安裝後可以使用glsl格式文字,glsl格式額能提示關鍵字有個好處是防止著色器程式寫錯。



程式碼

package com.opengl.a3_1_triangle;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.opengl.GLES20;
import android.opengl.GLES30;
import android.util.Log;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * Created by admin on 2016/11/12.
 */
//載入頂點shader與片元shader的工具類
public class ShaderUtil {
    //載入定製shader的方法
    public  static int loadShader(
            int shaderType,//shader的型別 GLES30.GL_VERTEX_SHADER(頂點) GLES30.GL_FRAGMENT_SHADER(片元)
            String source //shader的指令碼字串

    ){
        //建立一個新的Shader
        int shader = GLES30.glCreateShader(shaderType);
        //建立成功則載入shader
        if(shader!=0)
        {
            //載入shader的原始碼
            GLES30.glShaderSource(shader,source);
            //編譯shader
            GLES30.glCompileShader(shader);
            //存放編譯成功shader數量的陣列
            int [] compile=new int[1];
            //獲取Shader的編譯情況
            GLES30.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS,compile,0);
            if(compile[0]==0){
                //若編譯失敗則顯示錯誤的日誌並刪除shader
                Log.e("ES20_ERROR","不能編譯著色器"+shaderType+":");
                Log.e("ES20_ERROR",GLES30.glGetShaderInfoLog(shader));
                GLES30.glDeleteShader(shader);
                shader=0;
            }
        }
        return shader;
    }
    //建立著色器程式的方法
    public  static  int createProgram(String vertexSource,String fragmentSource) {
        //載入頂點著色器
        int vertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertexSource);
        if (vertexShader == 0) {
            return 0;
        }

        //載入片元著色器
        int pixelShader=loadShader(GLES30.GL_FRAGMENT_SHADER,fragmentSource);
        if(pixelShader==0)
        {
            return 0;
        }
        //建立程式
        int program=GLES30.glCreateProgram();
        //若程式建立成功則向程式中加入頂點著色器與片元著色器
        if(program!=0){
            //向程式中加入頂點著色器
            GLES30.glAttachShader(program,vertexShader);
            //向程式中加入片元著色器
            GLES30.glAttachShader(program,pixelShader);
            checkGlError("glAttachShader");
            //連線程式
            GLES30.glLinkProgram(program);
            //存放連線成功program數量的陣列
            int [] linkStatus=new int[1];
            //獲取連線的情況
            GLES30.glGetProgramiv(program,GLES30.GL_LINK_STATUS,linkStatus,0);
            //若連結失敗則報錯並刪除程式
            if (linkStatus[0] != GLES30.GL_TRUE)
            {
                Log.e("ES20_ERROR", "Could not link program: ");
                Log.e("ES20_ERROR", GLES30.glGetProgramInfoLog(program));
                GLES30.glDeleteProgram(program);
                program = 0;
            }
        }
        return program;
        }
    //檢查每一步操作是否有錯誤的方法
    @SuppressLint("NewApi")
    public static void checkGlError(String op)
    {
        int error;
        while ((error = GLES30.glGetError()) != GLES30.GL_NO_ERROR)
        {
            Log.e("ES20_ERROR", op + ": glError " + error);
            throw new RuntimeException(op + ": glError " + error);
        }
    }
    //從sh指令碼中載入shader內容的方法
    public static String loadFromAssetsFile(String fname,Resources r)
    {
        String result=null;
        try
        {
            InputStream in=r.getAssets().open(fname);
            int ch=0;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while((ch=in.read())!=-1)
            {
                baos.write(ch);
            }
            byte[] buff=baos.toByteArray();
            baos.close();
            in.close();
            result=new String(buff,"UTF-8");
            result=result.replaceAll("\\r\\n","\n");
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return result;
    }
//從raw資料夾中讀取glsl檔案
    public  static   String loadFromRawFile(Context context ,int resourceId){

            StringBuilder body = new StringBuilder();

            try {
                InputStream inputStream =
                        context.getResources().openRawResource(resourceId);
                InputStreamReader inputStreamReader =
                        new InputStreamReader(inputStream);
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

                String nextLine;

                while ((nextLine = bufferedReader.readLine()) != null) {
                    body.append(nextLine);
                    body.append('\n');
                }
            } catch (IOException e) {
                throw new RuntimeException(
                        "不能載入資源: " + resourceId, e);
            } catch (Resources.NotFoundException nfe) {
                throw new RuntimeException("資源沒有發現: " + resourceId, nfe);
            }

            return body.toString();
        }
    }
Triangle類程式碼


package com.opengl.a3_1_triangle;

import android.annotation.SuppressLint;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLES30;
import android.opengl.Matrix;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

/**
 * Created by admin on 2016/11/13.
 */
public class Triangle {
    Context context;
    public  static  float [] mProjMatrix=new float[16];//4*4投影矩陣
    public  static  float [] mVMatrix=new float[16];//攝像機位置朝向的引數矩陣
    public  static  float[] mMVPMatrix;//最後起作用的總的變換矩陣

    int mProgram;//自定義的渲染管執行緒序id
    int muMVPMatrixHandle;//總的變換矩陣引用
    int maPositionHandle;//頂點位置屬性的應用
    int maColorHandle;//頂點顏色屬性的引用

    String mVertexShader;//頂點著色器的程式碼指令碼
    String mFragmentShader;//片元著色器程式碼指令碼
    static  float[] mMMatrix=new float[16];//具體物體的移動旋轉矩陣,包括旋轉,平移,縮放
    FloatBuffer mVertexBuffer;//頂點座標資料緩衝
    FloatBuffer mColorBuffer;//頂點著色資料緩衝

    int vCount=0;
    float xAngle=0;//繞x軸旋轉的角度
    public  Triangle(MyTDView mv, Context context){
        this.context=context;
        //呼叫初始化頂點資料的initVertexData的方法
        initVertexData();
        //呼叫初始化著色器的initShader的方法
       // initShader(mv);
        initShaderGLSL(mv);
    }
    public  void initVertexData(){
        //頂點座標資料的初始化
        vCount=3;
        final  float UNIT_SIZE=0.2f;


        float vertices[]=new  float[]{
                -4*UNIT_SIZE,0,0,
                0,-4*UNIT_SIZE,0,
                4*UNIT_SIZE,0,0,

        };
        ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
        vbb.order(ByteOrder.nativeOrder());//設定位元組順序為本地作業系統順序
        mVertexBuffer = vbb.asFloatBuffer();//轉換為浮點(Float)型緩衝
        mVertexBuffer.put(vertices);//在緩衝區內寫入資料
        mVertexBuffer.position(0);//設定緩衝區起始位置

        float colors[]=new float[]//頂點顏色陣列
                {
                        1,1,1,0,//白色
                        0,0,1,0,//藍
                        0,1,0,0//綠
                };

        ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length*4);
        cbb.order(ByteOrder.nativeOrder());//設定位元組順序為本地作業系統順序
        mColorBuffer = cbb.asFloatBuffer();//轉換為浮點(Float)型緩衝
        mColorBuffer.put(colors);//在緩衝區內寫入資料
        mColorBuffer.position(0);//設定緩衝區起始位置
    }
    /*
//如果使用從sh指令碼中讀取著色器程式碼用這個程式碼
    //初始化著色器的方法
    @SuppressLint("NewApi")
    public void initShader(MyTDView mv)
    {
        //載入頂點著色器的指令碼內容
        mVertexShader=ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources());
        //載入片元著色器的指令碼內容
        mFragmentShader=ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources());
        //基於頂點著色器與片元著色器建立程式
        mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
        //獲取程式中頂點位置屬性引用
        maPositionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition");
        //獲取程式中頂點顏色屬性引用
        maColorHandle= GLES30.glGetAttribLocation(mProgram, "aColor");
        //獲取程式中總變換矩陣引用
        muMVPMatrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix");
    }
    */
//從指定的glsl格式讀取程式碼
    public  void initShaderGLSL(MyTDView mv)
    {
        // 載入頂點著色器的指令碼內容
        mVertexShader = ShaderUtil.loadFromRawFile(context,
                R.raw.vertex);
        // 載入片元著色器的指令碼內容
        mFragmentShader = ShaderUtil.loadFromRawFile(context,
                R.raw.frag);
        //基於頂點著色器與片元著色器建立程式
        mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
        //獲取程式中頂點位置屬性引用
        maPositionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition");
        //獲取程式中頂點顏色屬性引用
        maColorHandle= GLES30.glGetAttribLocation(mProgram, "aColor");
        //獲取程式中總變換矩陣引用
        muMVPMatrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix");
    }


    @SuppressLint("NewApi")
    public  void  drawSelf(){
        //指定使用某套shader程式
        GLES30.glUseProgram(mProgram);
        //指定使用某套shader程式
        GLES30.glUseProgram(mProgram);
        //初始化變換矩陣
        Matrix.setRotateM(mMMatrix,0,0,0,1,0);
        //設定沿Z軸正向位移1
        Matrix.translateM(mMMatrix,0,0,0,1);
        //設定繞x軸旋轉
        Matrix.rotateM(mMMatrix,0,xAngle,1,0,0);
        //將變換矩陣傳入渲染管線
        GLES30.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, Triangle.getFianlMatrix(mMMatrix), 0);
        //將頂點位置資料傳送進渲染管線
        GLES30.glVertexAttribPointer(
                maPositionHandle,
                3,
                GLES30.GL_FLOAT,
                false,
                3*4,
                mVertexBuffer
        );
        //將頂點顏色資料傳送進渲染管線
        GLES30.glVertexAttribPointer
                (
                        maColorHandle,
                        4,
                        GLES30.GL_FLOAT,
                        false,
                        4*4,
                        mColorBuffer
                );
        GLES30.glEnableVertexAttribArray(maPositionHandle);//啟用頂點位置資料
        GLES30.glEnableVertexAttribArray(maColorHandle);//啟用頂點著色資料
        //繪製三角形
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, vCount);
    }
    public static float[] getFianlMatrix(float[] spec)
    {
        mMVPMatrix=new float[16];
        Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, spec, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
        return mMVPMatrix;
    }
}

MyTDView程式碼
package com.opengl.a3_1_triangle;

import android.content.Context;
import android.opengl.GLES30;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
 * Created by admin on 2016/11/13.
 */
public class MyTDView extends GLSurfaceView{
    final float ANGLE_SPAN=0.375f;

    RotateThread rthread;
    SceneRenderer mRenderer;//自定義渲染器的引用
    public MyTDView(Context context){
        super(context);
        this.setEGLContextClientVersion(3); //設定opengl es的版本為3
        mRenderer=new SceneRenderer(context);
        this.setRenderer(mRenderer);
        this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//持續渲染模式    還有一種模式是當有請求時才渲染
    }
    private  class SceneRenderer implements  GLSurfaceView.Renderer
    {
        Context  context;
        Triangle tle;
        public  SceneRenderer(Context context) {
            this.context=context;
        }
        public void onDrawFrame(GL10 gl)  //繪製
        {
            //清除深度緩衝與顏色緩衝
            GLES30.glClear( GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
            //繪製三角形物件
            tle.drawSelf();
        }
        public void onSurfaceChanged(GL10 gl, int width, int height)    //當視窗更改的時候呼叫
        {
            //設定視窗大小及位置
            GLES30.glViewport(0, 0, width, height);
            //計算GLSurfaceView的寬高比
            float ratio = (float) width / height;
            //呼叫此方法計算產生透視投影矩陣
            Matrix.frustumM(Triangle.mProjMatrix, 0, -ratio, ratio, -1, 1, 1, 10);
            //呼叫此方法產生攝像機9引數位置矩陣
            Matrix.setLookAtM(Triangle.mVMatrix, 0, 0,0,3,0f,0f,0f,0f,1.0f,0.0f);
        }
        public void onSurfaceCreated(GL10 gl, EGLConfig config)   //建立視窗
        {
            //設定螢幕背景色RGBA
            GLES30.glClearColor(0,0,0,1.0f);
            //建立三角形對物件
            tle=new Triangle(MyTDView.this,context);
            //開啟深度檢測
            GLES30.glEnable(GLES30.GL_DEPTH_TEST);
            rthread=new RotateThread();
            rthread.start();
        }
    }
    public  class RotateThread extends  Thread //自定義的內部類執行緒  重新發發起一個執行緒讓繞x軸旋轉
    {
        public   boolean flag=true;
        @Override
        public  void run()//重寫run方法
        {
            while(flag){
                mRenderer.tle.xAngle=mRenderer.tle.xAngle+ANGLE_SPAN;
                try {
                    Thread.sleep(20);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}

頂點著色器的程式碼

#version 300 es
uniform mat4 uMVPMatrix;//總的變換矩陣
layout (location=0) in vec3 aPosition;//頂點的位置
layout (location=1) in vec4 aColor;//頂點顏色
out vec4 vColor;//用於傳遞給片元著色器的變數
void main()
{
gl_Position=uMVPMatrix*vec4(aPosition,1);

vColor=aColor;
}

片元著色器程式碼
#version 300 es
precision mediump float;
in vec4 vColor;//接受從頂點著色器過來的引數
out vec4 fragColor;//輸出到片元這顏色
void main(){
fragColor=vColor;//此片元的顏色值
}
MyActivity類程式碼
package com.opengl.a3_1_triangle;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MyActivity extends Activity {   //建立繼承Activity的主控制類

  MyTDView mview;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);//呼叫父類
        //設定為豎屏模式
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
          mview=new MyTDView(this);//建立MyTDView類而物件
         mview.requestFocus();
        mview.setFocusableInTouchMode(true);
        setContentView(mview);
    }
    @Override
    public void onResume()//繼承Activity後重寫的onResume方法
    {
        super.onResume();
        mview.onResume();//通過MyTDView類的物件呼叫onResume方法
    }
    @Override
    public void onPause()//繼承Activity後重寫的onPause方法
    {
        super.onPause();
        mview.onPause();//通過MyTDView類的物件呼叫onPause方法
    }
}


相關推薦

opengl es3.0遊戲開發學習筆記1-繪製旋轉三角形

前段時間一直在看opengl es2.0遊戲開發的知識 ,這幾天買了本opengl es3.0遊戲開發的書   打算一邊學習一邊整理學習筆記,我的開發環境是Android studio 2.1.3,不過有個問題是Android studio自帶的模擬器只能支援es2

cocos2d-x 3.x遊戲開發學習筆記(1)--mac下配置cocos2d-x 3.x開發環境

原文:http://blog.csdn.net/likendsl/article/details/34617725 開啟使用者目錄下.bash_profile檔案,配置環境  [python] view plaincopyprint? vim ~/.b

遊戲開發學習筆記

nor scrip 筆記 nsrunloop posit ppr 遊戲開發 tor http sdk%E6%9B%B4%E6%96%B0%E4%B8%8D%E6%88%90%E5%8A%9F%E6%B1%82%E5%A4%A7%E7%A5%9E%E5%B8%AE%E5%BF

UWP開發學習筆記1

導航到頁面: this.Frame.Navigate(typeof(SecondPage));   導航進入當前頁面時會呼叫OnNavigatedTo方法;導航從當前頁面離開時會呼叫OnNavigatingFrom方法導航時傳遞引數採用: this.Frame.Navigat

遊戲開發學習筆記(七)開發揹包系統

思路: Bag:管理揹包裡的格子 BagItemGrid:管理格子儲存物品的資訊(id及num) BagItem:管理物品拖拽功能及物品物品的更新顯示 Bag:管理揹包裡的格子 建立UI,Bag_item 和Bag_item_grid的Prefab, Bag新增Bag指令碼

【Unity 3D遊戲開發學習筆記】實現太陽系

目標: 寫一個程式,實現一個完整的太陽系, 其他星球圍繞太陽的轉速必須不一樣,且不在一個法平面上。 基本思路是在裡面建立物件,架構成一個太陽系,sun作為父物件,其他行星作為子物件,並且相對sun的初始位置均不一樣,那麼角速度相同的情況下轉速就不一樣了,另外

APP開發學習筆記(1)

什麼是APP 手機應用程式,通常是移動裝置(手機和平板的作業系統)應用程式的統稱。 APP的分類 IOS---------------------------------Objective-CAndroid---------------------------JavaW

JAVA串列埠開發學習筆記1

1、下載java Communications api開發包。2、將win32com.dll拷貝入C:\j2sdk1.4.2_04\bin3、將comm.jar拷貝入C:\j2sdk1.4.2_04\jre\lib\ext4、將javax.comm.properties拷

Unity 3D遊戲開發學習筆記(2) 牧師與魔鬼

遊戲事物: 3牧師,3惡魔,2河岸,河,船。 遊戲故事:3牧師和3惡魔需要用一艘船全部到達彼岸,但是船上和岸上都不能出現惡魔比牧師多的情形,否則惡魔會把牧師K.O,玩家輸掉比賽;直到所有牧師惡魔都到達對岸,玩家取得勝利。 MVC架構: IUser

Photon + Unity3D 線上遊戲開發 學習筆記(一)

      大家好, 我也是學習Photon + unity3D 的新手 有什麼說錯的地方大家見諒哈, 我的開發環境是 unity3D 4.1.3  ,   Visual Studio 是2010 版本的  photon 用的是 V3  語言C#       photon

【Unity3D遊戲開發學習筆記】(四)一切都動起來—Animator元件的應用

一、Animation簡介 動畫原本是指由許多連續的圖片在人眼前面快速播放,肉眼因視覺殘像產生錯覺,而誤以為畫面活動的作品。 但在 Unity3D 中的“ Animation”【動畫】 系統應該這樣理解——用於為遊戲者自動播放人物動作或自動演示物體運動路徑、色

【Unity3D遊戲開發學習筆記】(六)上帝之手—GameObject的操作

在Unity中,所有實體都屬於遊戲物件(GameObject),比如外部匯入到場景中的模型,Unity自帶的立方體等等,而要將這些GameOject進行管理,互動等操作,則需要用到指令碼來實現,上一節我們已經學習瞭如何建立一個指令碼並繫結到一個物體上,現在我們將

【Unity 3D遊戲開發學習筆記】粒子光環

實現如下圖的粒子光環: 參考網站:http://i-remember.fr/en 思路: 首先宣告定義一個類用於儲存每個粒子的半徑和角度 public class particleClass { public float radius =

小程式開發 學習筆記-1 (邏輯層、檢視層)

1、小程式包含一個描述整體程式的 app 和多個描述各自頁面的 page。 2、頁面的.json只能設定 window 相關的配置項,以決定本頁面的視窗表現,所以無需寫 window 這個鍵 邏輯層(App Service) 1、小程式開發框架的邏

【Unity3D遊戲開發學習筆記】(七)上帝之眼—第三人稱攝像機的簡單實現(跟隨視角,自由視角)

陸陸續續又開始更新自己的部落格,看來自我驅動能力還是不夠啊= =,廢話不多說了,之前的內容大概說了一下Unity的一些基礎知識,接下來我們將要對一些基本功能做一些學習。大家都知道,一個遊戲,少不了攝像機的參與(這不是廢話麼!沒攝像機怎麼玩!畫面都不呈現了好伐!)

Linux學習筆記1.0

Linux 基礎 知識積累 終端(terminal)物理終端(/dev/console) 控制臺console虛擬終端(/dev/tty[1-6]) tty:teletypewriters,可有n個,ctrl+alt+F[1-6]圖形終端(/dev/tty7)串行終端(/dev/ttyS#)偽終端

spring (4.0.2)——(尚矽谷)學習筆記1

aspect 什麽 企業應用 周期 持久層 非侵入 mvc 註入 JD 1、Spring是什麽?   ①Spring 是一個開源框架;   ②Spring 為簡化企業級應用開發而生。使用Spring可以使簡單的JavaBean實現以前只有EJB才能實現的功能。   ③Spr

Docker學習筆記1-從0創建並發布一個docker鏡像

雲計算 Docker Docker安裝略運行環境centos7先創建一個本地的目錄[root@localhost /]# mkdir nginx && cd nginx下載示例的配置文件留著備用[root@localhost nginx]# wget http://raw.github

Arduino 入門學習筆記1 開發環境及雙色LED實驗

本系列學習教程來自 創樂博智慧學習視訊 Arduino 發展 2005年,Massimo Banzi和David Cuartielles、David Mellis設計 Arduino。取該名稱的原因是Massimo Banzi喜歡去一家名叫Arduino的酒吧,其名稱是1000年

《Oracle PL/SQL開發指南》學習筆記1——Oracle PL/SQL程式開發概覽

本章內容: 1. PL/SQL的歷史和背景 2. Oracle開發架構   知識點: 1. SQL和PL/SQL的關係: The SQL language is the interface to the Oracle Database 12c database