轉(zhuǎn)載請(qǐng)注明出處:【huachao1001的簡(jiǎn)書:http://www.reibang.com/users/0a7e42698e4b/latest_articles】
在上一篇文章【 Android OpenGL顯示任意3D模型文件 】中滥玷,我們學(xué)習(xí)了如何讀取并顯示STL格式的3D文件燃辖,但是,最后讯蒲,看到的并沒有添加光照效果截粗,導(dǎo)致雖然模型在旋轉(zhuǎn),但是我們看到的畫面卻像一個(gè)平面。今天我們開始學(xué)習(xí)如何給模型添加燈照效果,以及如何為模型添加材料屬性筹燕,使得最終看到的旋轉(zhuǎn)模型真正為3D效果。首先衅鹿,看看最終效果撒踪,如下圖所示:
1 光照效果
因?yàn)槲覀兯龅牧Ⅲw效果是根據(jù)真實(shí)世界原理來計(jì)算的,所以很有必要去了解在現(xiàn)實(shí)世界中塘安,我們所看到的一個(gè)物體有哪些光糠涛。
1.1 真實(shí)世界中的光照
我們知道援奢,在黑暗中兼犯,當(dāng)我們將手電筒對(duì)準(zhǔn)某個(gè)物體時(shí),我們所看到的該物體的“亮度”有3
種:
- 物體表面發(fā)生鏡面反射部分(
Specular
)集漾,一般是白色切黔。- 物體表面發(fā)生漫反射部分(
Diffuse
),一般是物體表面的顏色具篇。- 物體表面沒有照射到光的部分纬霞,即通過環(huán)境光(
Ambient
)照射,在黑暗中環(huán)境光是黑色驱显。
如下圖所示(圖片出自www.guidebee.info
):
從上圖中也可以看出诗芜,光源的位置也會(huì)影響到我們所看到的最終畫面。顯然埃疫,我們只需控制好光源位置伏恐、鏡面反射顏色、漫反射顏色栓霜、環(huán)境光顏色這四個(gè)參數(shù)翠桦,就可以做到了。
1.2 Android OpenGL相關(guān)API
1.2.1 光源 GL10.GL_LIGHT0
0
號(hào)光源胳蛮,該光源的默認(rèn)顏色為白色销凑,即RGBA
為(1.0,1.0,1.0,1.0)
丛晌,漫反射和鏡面反射也為白色。類似的斗幼,還有其他光源如GL10.GL_LIGHT1
澎蛛,系統(tǒng)提供了0~7
共8
種光源,其他的光源默認(rèn)為黑色孟岛,即RGBA
為(0.0,0.0,0.0,1.0)
.
開啟光源也非常簡(jiǎn)單:
//啟用光照功能
gl.glEnable(GL10.GL_LIGHTING);
//開啟0號(hào)燈
gl.glEnable(GL10.GL_LIGHT0);
1.2.2 設(shè)置各種反射光顏色
一旦開啟了光照功能瓶竭,就可以通過glLightfv
函數(shù)來指定各種反射光的顏色了,glLightfv
函數(shù)如下:
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
: 指光源的序號(hào)斤贰,OpenGL ES
可以設(shè)置從0
到7
共八個(gè)光源。pname
: 光源參數(shù)名稱次询,可以有如下:GL_SPOT_EXPONENT
GL_SPOT_CUTOFF
GL_CONSTANT_ATTENUATION
GL_LINEAR_ATTENUATION
GL_QUADRATIC_ATTENUATION
GL_AMBIENT
(用于設(shè)置環(huán)境光顏色)GL_DIFFUSE
(用于設(shè)置漫反射光顏色)GL_SPECULAR
(用于設(shè)置鏡面反射光顏色)GL_SPOT_DIRECTION
GL_POSITION
(用于設(shè)置光源位置)params
: 參數(shù)的值(數(shù)組或是Buffer
類型)荧恍,數(shù)組里面含有4個(gè)值分別表示R,G,B,A。
指定光源的位置的參數(shù)為GL_POSITION
,位置的值為(x,y,z,w)
屯吊,如果是平行光則將w
設(shè)為0
送巡,此時(shí),(x,y,z)
為平行光的方向:
1.3 代碼實(shí)現(xiàn)
在上一篇的基礎(chǔ)上盒卸,直接修改GLRenderer.java
文件骗爆,添加一個(gè)openLight
函數(shù):
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));
}
另外,分別添加我們?cè)O(shè)定的各種反射光的顏色:
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
函數(shù)里面調(diào)用一下openLight(gl);
函數(shù)即可摘投。最終效果如下:
2 材料屬性
前面我們提到了可以為模型設(shè)置不同的材料屬性,本節(jié)中虹蓄,我們一起學(xué)習(xí)如何為模型設(shè)定不同的材料屬性犀呼。我們知道,同樣是一束光薇组,照在不同顏色材料的物體上面外臂,我們所看到的是不同的,反射出來的不僅僅顏色不同律胀,光澤也是不同的宋光。換句話說,不同的材質(zhì)對(duì)最終的渲染效果影響很大炭菌!
材料的屬性設(shè)置和光源的設(shè)置有些類似罪佳,用到的函數(shù)
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
娃兽,表示修改物體的前面和后面的材質(zhì)光線屬性菇民。pname
: 參數(shù)類型,這些參數(shù)用在光照方程〉诹罚可以取如下值:GL_AMBIENT
GL_DIFFUSE
GL_SPECULAR
GL_EMISSION
GL_SHININESS
阔馋。param
:指定反射的顏色。
跟設(shè)置光照類似娇掏,設(shè)置材料屬性首先需要定義各種反射光的顏色:
float[] materialAmb = {0.4f, 0.4f, 1.0f, 1.0f};
float[] materialDiff = {0.0f, 0.0f, 1.0f, 1.0f};//漫反射設(shè)置藍(lán)色
float[] materialSpec = {1.0f, 0.5f, 0.0f, 1.0f};
然后就是將這些顏色通過glMaterialfv
函數(shù)設(shè)置進(jìn)去:
public void enableMaterial(GL10 gl) {
//材料對(duì)環(huán)境光的反射情況
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));
}
當(dāng)然了呕寝,最后也別忘記了在onSurfaceCreated
函數(shù)中調(diào)用 enableMaterial(gl);
,最后看看效果:
3 完整的GLRenderer類
最后項(xiàng)目代碼就不上傳了婴梧,直接參考上一篇的文章中的源碼即可下梢,本位值修改了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();// 重置當(dāng)前的模型觀察矩陣
//眼睛對(duì)著原點(diǎn)看
GLU.gluLookAt(gl, eye.x, eye.y, eye.z, center.x,
center.y, center.z, up.x, up.y, up.z);
//為了能有立體感覺塞蹭,通過改變mDegree值孽江,讓模型不斷旋轉(zhuǎn)
gl.glRotatef(mDegree, 0, 1, 0);
//將模型放縮到View剛好裝下
gl.glScalef(mScalef, mScalef, mScalef);
//把模型移動(dòng)到原點(diǎn)
gl.glTranslatef(-mCenterPoint.x, -mCenterPoint.y,
-mCenterPoint.z);
//===================begin==============================//
//允許給每個(gè)頂點(diǎn)設(shè)置法向量
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
// 允許設(shè)置頂點(diǎn)
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// 允許設(shè)置顏色
//設(shè)置法向量數(shù)據(jù)源
gl.glNormalPointer(GL10.GL_FLOAT, 0, model.getVnormBuffer());
// 設(shè)置三角形頂點(diǎn)數(shù)據(jù)源
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, model.getVertBuffer());
// 繪制三角形
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, model.getFacetCount() * 3);
// 取消頂點(diǎn)設(shè)置
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
//取消法向量設(shè)置
gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
//=====================end============================//
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 設(shè)置OpenGL場(chǎng)景的大小,(0,0)表示窗口內(nèi)部視口的左下角,(width, height)指定了視口的大小
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL10.GL_PROJECTION); // 設(shè)置投影矩陣
gl.glLoadIdentity(); // 設(shè)置矩陣為單位矩陣番电,相當(dāng)于重置矩陣
GLU.gluPerspective(gl, 45.0f, ((float) width) / height, 1f, 100f);// 設(shè)置透視范圍
//以下兩句聲明岗屏,以后所有的變換都是針對(duì)模型(即我們繪制的圖形)
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);// 設(shè)置深度緩存值
gl.glDepthFunc(GL10.GL_LEQUAL); // 設(shè)置深度緩存比較函數(shù)
gl.glShadeModel(GL10.GL_SMOOTH);// 設(shè)置陰影模式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) {
//材料對(duì)環(huán)境光的反射情況
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));
}
}
最后感謝大家的關(guān)注这刷,歡迎關(guān)注huachao1001的博客,http://blog.csdn.net/huachao10019