轉載請註明出處:【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~7
共8
種光源,其他的光源默認爲黑色,即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
可以設置從0
到7
共八個光源。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