并沒有添加光照效果,導致雖然模型在旋轉,但是我們看到的畫面卻像一個平面。今天我們開始學習如何給模型添加燈照效果,以及如何為模型添加材料屬性,使得最終看到的旋轉模型真正為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類,把該類的完整源碼貼上:
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));
}
}