Android OpenGL添加光照和材料屬性

轉載請註明出處:【huachao1001的專欄:http://blog.csdn.net/huachao1001】

在上一篇文章【 Android OpenGL顯示任意3D模型文件 】中,我們學習瞭如何讀取並顯示STL格式的3D文件,但是,最後,看到的並沒有添加光照效果,導致雖然模型在旋轉,但是我們看到的畫面卻像一個平面。今天我們開始學習如何給模型添加燈照效果,以及如何爲模型添加材料屬性,使得最終看到的旋轉模型真正爲3D效果。首先,看看最終效果,如下圖所示:

光照效果

材質效果

1 光照效果

因爲我們所做的立體效果是根據真實世界原理來計算的,所以很有必要去了解在現實世界中,我們所看到的一個物體有哪些光。

1.1 真實世界中的光照

我們知道,在黑暗中,當我們將手電筒對準某個物體時,我們所看到的該物體的“亮度”有3種:

  • 物體表面發生鏡面反射部分(Specular),一般是白色。
  • 物體表面發生漫反射部分(Diffuse),一般是物體表面的顏色。
  • 物體表面沒有照射到光的部分,即通過環境光(Ambient)照射,在黑暗中環境光是黑色。

如下圖所示(圖片出自www.guidebee.info):

光照圖示

從上圖中也可以看出,光源的位置也會影響到我們所看到的最終畫面。顯然,我們只需控制好光源位置、鏡面反射顏色、漫反射顏色、環境光顏色這四個參數,就可以做到了。

1.2 Android OpenGL相關API

1.2.1 光源 GL10.GL_LIGHT0

0號光源,該光源的默認顏色爲白色,即RGBA(1.0,1.0,1.0,1.0),漫反射和鏡面反射也爲白色。類似的,還有其他光源如GL10.GL_LIGHT1,系統提供了0~78種光源,其他的光源默認爲黑色,即RGBA(0.0,0.0,0.0,1.0).

開啓光源也非常簡單:

//啓用光照功能
gl.glEnable(GL10.GL_LIGHTING);
//開啓0號燈
gl.glEnable(GL10.GL_LIGHT0);

1.2.2 設置各種反射光顏色

一旦開啓了光照功能,就可以通過glLightfv函數來指定各種反射光的顏色了,glLightfv函數如下:

public void glLightfv(int light,int pname, FloatBuffer params)
public void glLightfv(int light,int pname,float[] params,int offset)
public void glLightf(int light,int pname,float param)

其中,

  • light: 指光源的序號,OpenGL ES可以設置從07共八個光源。
  • pname: 光源參數名稱,可以有如下:
    • GL_SPOT_EXPONENT
    • GL_SPOT_CUTOFF
    • GL_CONSTANT_ATTENUATION
    • GL_LINEAR_ATTENUATION
    • GL_QUADRATIC_ATTENUATION
    • GL_AMBIENT(用於設置環境光顏色)
    • GL_DIFFUSE(用於設置漫反射光顏色)
    • GL_SPECULAR(用於設置鏡面反射光顏色)
    • GL_SPOT_DIRECTION
    • GL_POSITION(用於設置光源位置)
  • params: 參數的值(數組或是Buffer類型),數組裏面含有4個值分別表示R,G,B,A。

指定光源的位置的參數爲GL_POSITION,位置的值爲(x,y,z,w),如果是平行光則將w 設爲0,此時,(x,y,z)爲平行光的方向:

1.3 代碼實現

在上一篇的基礎上,直接修改GLRenderer.java文件,添加一個openLight函數:


public void openLight(GL10 gl) {

    gl.glEnable(GL10.GL_LIGHTING);
    gl.glEnable(GL10.GL_LIGHT0);
    gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, Util.floatToBuffer(ambient));
    gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, Util.floatToBuffer(diffuse));
    gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, Util.floatToBuffer(specular));
    gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, Util.floatToBuffer(lightPosition));


}

另外,分別添加我們設定的各種反射光的顏色:

float[] ambient = {0.9f, 0.9f, 0.9f, 1.0f,};
float[] diffuse = {0.5f, 0.5f, 0.5f, 1.0f,};
float[] specular = {1.0f, 1.0f, 1.0f, 1.0f,};
float[] lightPosition = {0.5f, 0.5f, 0.5f, 0.0f,};

最後,在onSurfaceCreated函數裏面調用一下openLight(gl);函數即可。最終效果如下:

光照效果

2 材料屬性

前面我們提到了可以爲模型設置不同的材料屬性,本節中,我們一起學習如何爲模型設定不同的材料屬性。我們知道,同樣是一束光,照在不同顏色材料的物體上面,我們所看到的是不同的,反射出來的不僅僅顏色不同,光澤也是不同的。換句話說,不同的材質對最終的渲染效果影響很大!

材料的屬性設置和光源的設置有些類似,用到的函數

public void glMaterialf(int face,int pname,float param)
public void glMaterialfv(int face,int pname,float[] params,int offset)
public void glMaterialfv(int face,int pname,FloatBuffer params)

其中,

  • face : 在OpenGL ES中只能使用GL_FRONT_AND_BACK,表示修改物體的前面和後面的材質光線屬性。
  • pname: 參數類型,這些參數用在光照方程。可以取如下值:
    • GL_AMBIENT
    • GL_DIFFUSE
    • GL_SPECULAR
    • GL_EMISSION
    • GL_SHININESS
  • param:指定反射的顏色。

跟設置光照類似,設置材料屬性首先需要定義各種反射光的顏色:

float[] materialAmb = {0.4f, 0.4f, 1.0f, 1.0f};
float[] materialDiff = {0.0f, 0.0f, 1.0f, 1.0f};//漫反射設置藍色
float[] materialSpec = {1.0f, 0.5f, 0.0f, 1.0f};

然後就是將這些顏色通過glMaterialfv函數設置進去:

public void enableMaterial(GL10 gl) {

    //材料對環境光的反射情況
    gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, Util.floatToBuffer(materialAmb));
    //散射光的反射情況
    gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, Util.floatToBuffer(materialDiff));
    //鏡面光的反射情況
    gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, Util.floatToBuffer(materialSpec));

}

當然了,最後也別忘記了在onSurfaceCreated函數中調用 enableMaterial(gl);,最後看看效果:
材質效果

3 完整的GLRenderer類

最後項目代碼就不上傳了,直接參考上一篇的文章中的源碼即可,本位值修改了GLRenderer類,把該類的完整源碼貼上:

package com.hc.opengl;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;

import java.io.IOException;

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

/**
 * Package com.hc.opengl
 * Created by HuaChao on 2016/8/9.
 */
public class GLRenderer implements GLSurfaceView.Renderer {

    private Model model;
    private Point mCenterPoint;
    private Point eye = new Point(0, 0, -3);
    private Point up = new Point(0, 1, 0);
    private Point center = new Point(0, 0, 0);
    private float mScalef = 1;
    private float mDegree = 0;

    public GLRenderer(Context context) {
        try {

            model = new STLReader().parserBinStlInAssets(context, "huba.stl");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void rotate(float degree) {
        mDegree = degree;
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        // 清除屏幕和深度緩存
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);


        gl.glLoadIdentity();// 重置當前的模型觀察矩陣


        //眼睛對着原點看
        GLU.gluLookAt(gl, eye.x, eye.y, eye.z, center.x,
                center.y, center.z, up.x, up.y, up.z);

        //爲了能有立體感覺,通過改變mDegree值,讓模型不斷旋轉
        gl.glRotatef(mDegree, 0, 1, 0);

        //將模型放縮到View剛好裝下
        gl.glScalef(mScalef, mScalef, mScalef);
        //把模型移動到原點
        gl.glTranslatef(-mCenterPoint.x, -mCenterPoint.y,
                -mCenterPoint.z);


        //===================begin==============================//

        //允許給每個頂點設置法向量
        gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
        // 允許設置頂點
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        // 允許設置顏色

        //設置法向量數據源
        gl.glNormalPointer(GL10.GL_FLOAT, 0, model.getVnormBuffer());
        // 設置三角形頂點數據源
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, model.getVertBuffer());

        // 繪製三角形
        gl.glDrawArrays(GL10.GL_TRIANGLES, 0, model.getFacetCount() * 3);

        // 取消頂點設置
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
        //取消法向量設置
        gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);

        //=====================end============================//

    }


    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {

        // 設置OpenGL場景的大小,(0,0)表示窗口內部視口的左下角,(width, height)指定了視口的大小
        gl.glViewport(0, 0, width, height);

        gl.glMatrixMode(GL10.GL_PROJECTION); // 設置投影矩陣
        gl.glLoadIdentity(); // 設置矩陣爲單位矩陣,相當於重置矩陣
        GLU.gluPerspective(gl, 45.0f, ((float) width) / height, 1f, 100f);// 設置透視範圍

        //以下兩句聲明,以後所有的變換都是針對模型(即我們繪製的圖形)
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();


    }


    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        gl.glEnable(GL10.GL_DEPTH_TEST); // 啓用深度緩存
        gl.glClearColor(0f, 0f, 0f, 0f);// 設置深度緩存值
        gl.glDepthFunc(GL10.GL_LEQUAL); // 設置深度緩存比較函數
        gl.glShadeModel(GL10.GL_SMOOTH);// 設置陰影模式GL_SMOOTH

        //開啓光
        openLight(gl);
        enableMaterial(gl);
        float r = model.getR();
        //r是半徑,不是直徑,因此用0.5/r可以算出放縮比例
        mScalef = 0.5f / r;
        mCenterPoint = model.getCentrePoint();
    }


    float[] ambient = {0.9f, 0.9f, 0.9f, 1.0f};
    float[] diffuse = {0.5f, 0.5f, 0.5f, 1.0f};
    float[] specular = {1.0f, 1.0f, 1.0f, 1.0f};
    float[] lightPosition = {0.5f, 0.5f, 0.5f, 0.0f};

    public void openLight(GL10 gl) {

        gl.glEnable(GL10.GL_LIGHTING);
        gl.glEnable(GL10.GL_LIGHT0);
        gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, Util.floatToBuffer(ambient));
        gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, Util.floatToBuffer(diffuse));
        gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, Util.floatToBuffer(specular));
        gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, Util.floatToBuffer(lightPosition));


    }

    float[] materialAmb = {0.4f, 0.4f, 1.0f, 1.0f,};
    float[] materialDiff = {0.0f, 0.0f, 1.0f, 1.0f,};
    float[] materialSpec = {1.0f, 0.5f, 0.0f, 1.0f,};

    public void enableMaterial(GL10 gl) {

        //材料對環境光的反射情況
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, Util.floatToBuffer(materialAmb));
        //散射光的反射情況
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, Util.floatToBuffer(materialDiff));
        //鏡面光的反射情況
        gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, Util.floatToBuffer(materialSpec));

    }
}

最後感謝大家的關注,歡迎關注huachao1001的博客,http://blog.csdn.net/huachao10019

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