为了能看出3D效果,给场景中添加光源。如果没有光照,绘出的球看上去和一个二维平面上圆没什么差别,如下图,左边为有光照效果的球体,右边为同一个球体但没有设置光源,看上去就没有立体效果,因此OpenGL 光照效果对显示3D效果非常明显。
在OpenGL 光照模型中光源和光照效果可以细分为红,绿,蓝三个部分,光源由红,绿,蓝强度来定义,而物体表面材料由其反射红,绿,蓝的程度和方向来定义。OpenGL 光照模型使用的计算公式是对于现实世界光照的一个近似但效果非常好并适合快速计算。
OpenGL 光照模型中定义的光源可以分别控制,打开或关闭,OpenGL ES支持最多八个光源。
OpenGL 光照模型中最终的光照效果可以分为四个组成部分:Emitted(光源), ambient(环境光),diffuse(漫射光)和specular(镜面反射光),最终结果由这四种光叠加而成。
Emitted : 一般只发光物体或者光源,这种光不受其它光源的影响。
ambient: 指光线经过多次反射后已经无法得知其方向(可以看作来自所有方向),可以成为环境光,该光源如果射到某个平面,其反射方向为所有方向。Ambient 不依赖于光源的方向。
diffuse:当一束平行的入射光线射到粗糙的表面时,因面上凹凸不平,所以入射线虽然互相平行,由于各点的法线方向不一致,造成反射光线向不同的方向无规则地反射,这种反射称之为“漫反射”或“漫射”。这个反射的光则称为漫射光。漫射光射到某个平面时,其反射方向也为所有方向。diffuse 只依赖于光源的方向和法线的方向。
specular : 一般指物体被光源直射的高亮区域,也可以成为镜面反射区,如金属。specular依赖于光源的方向,法线的方向和视角的方向。
尽管光源可能只发送某一频率的光线,但ambient,diffuse和specular可能不同。比如使用白光照射一堵红墙,散射的光线可能为红色。OpenGL允许为光源分别设置红,绿,蓝三个元素的值。
最终决定所看到物体的颜色除了光源的颜色和方向外,还取决于物体本身的颜色,比如红色的光照在红色的物体和蓝色的物体,最终看到的物体一个还是红色,一个为黑色。OpenGL 中对物体材料(Material)的颜色是通过其反射红,绿,蓝的比例来定义的。 和光源一样,物体的颜色也可以有不同的ambient,diffuse和specular,表现为反射这些光的比例。ambient,diffuse反射通常为同样的颜色,而specular常常表现为白色或灰色光,如使用白光照射一个红色的球,球的大部分区域显示为红色,而高亮区域为白色
,本篇结合OpenGL ES API说明如何使用光照效果:
光源
OpenGL ES中可以最多同时使用八个光源,分别使用0到7表示。
OpenGL ES光源可以分为
- 平行光源(Parallel light source), 代表由位于无限远处均匀发光体,太阳可以近似控制平行光源。
- 点光源(Spot light source) 如灯泡就是一个点光源,发出的光可以指向360度,可以为点光源设置光衰减属性(attenuation)或者让点光源只能射向某个方向(如射灯)。
- 可以为图形的不同部分设置不同的光源。
下面方法可以打开某个光源,使用光源首先要开光源的总开关:
1 | gl.glEnable(GL10.GL_LIGHTING); |
然后可以再打开某个光源如0号光源:
1 | gl.glEnable(GL10.GL_LIGHTI0); |
设置光源方法如下:
- 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类型)。
其中为光源设置颜色的参数类型为GL_AMBIENT,GL_DIFFUSE,GL_SPECULAR,可以分别指定R,G,B,A 的值。
指定光源的位置的参数为GL_POSITION,值为(x,y,z,w):
平行光将w 设为0.0,(x,y,z)为平行光的方向:
对于点光源,将 w 设成非0值,通常设为1.0. (x,y,z)为点光源的坐标位置。
将点光源设置成聚光灯,需要同时设置GL_SPOT_DIRECTION,GL_SPOT_CUTOFF等 参数,GL_POSITION的设置和点光源类似:将 w 设成非0值,通常设为1.0. (x,y,z)为点光源的坐标位置。而对于GL_SPOT_DIRECTION 参数,设置聚光的方向(x,y,z)
GL_SPOT_CUTOFF 参数设置聚光等发散角度(0到90度)
GL_SPOT_EXPONENT 给出了聚光灯光源汇聚光的程度,值越大,则聚光区域越小(聚光能力更强)。
对应点光源(包括聚光灯),其它几个参数GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, GL_QUADRATIC_ATTENUATION 为点光源设置光线衰减参数,公式有如下形式,一般无需详细了解:
在场景中设置好光源后,下一步要为所绘制的图形设置法线(Normal),只有设置了法线,光源才能在所会物体上出现光照效果。三维平面的法线是垂直于该平面的三维向量。曲面在某点P处的法线为垂直于该点切平面的向量
和设置颜色类似,有两个方法可以为平面设置法线,一是
public void glNormal3f(float nx,float ny,float nz)
这个方法为后续所有平面设置同样的方向,直到重新设置新的法线为止。
为某个顶点设置法线:
public void glNormalPointer(int type,int stride, Buffer pointer)
- type 为Buffer 的类型,可以为GL_BYTE, GL_SHORT, GL_FIXED,或 GL_FLOAT
- stride: 每个参数之间的间隔,通常为0.
- pointer: 法线值。
打开法线数组
1 | gl.glEnableClientState(GL10.GL_NORMAL_ARRAY); |
用法和Color, Vertex 类似。参见Android OpenGL ES 开发教程(8):基本几何图形定义。
规范化法向量,比如使用坐标变换(缩放),如果三个方向缩放比例不同的话,顶点或是平面的法线可能就有变好,此时需要打开规范化法线设置:
1 | gl.glEnable(GL10.GL_NORMALIZE); |
经过规范化后法向量为单位向量(长度为1)。同时可以打开缩放法线设置
1 | gl.glEnable(GL10.GL_RESCALE_NORMAL); |
设置好法线后,需要设置物体表面材料(Material)的反光属性(颜色和材质)。
将在下篇介绍设置物体表面材料(Material)的反光属性(颜色和材质)并给出一个光照的示例。
设置物体表面材料(Material)的反光属性(颜色和材质)的方法如下:
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: 参数的值。
其中GL_AMBIENT,GL_DIFFUSE,GL_SPECULAR ,GL_EMISSION为颜色RGBA值,GL_SHININESS 值可以从0到128,值越大,光的散射越小:
此外,方法glLightModleXX给出了光照模型的参数
public void glLightModelf(int pname,float param)
public void glLightModelfv(int pname,float[] params,int offset)
public void glLightModelfv(int pname,FloatBuffer params)
- pname: 参数类型,可以为GL_LIGHT_MODEL_AMBIENT和GL_LIGHT_MODEL_TWO_SIDE
- params: 参数的值。
最终顶点的颜色由这些参数(光源,材质光学属性,光照模型)综合决定(光照方程计算出)。
下面例子在场景中设置一个白色光源:
1 | public void initScene(GL10 gl){ |
2 |
float [] amb = { 1 .0f, 1 .0f, 1 .0f, 1 .0f, }; |
3 |
float [] diff = { 1 .0f, 1 .0f, 1 .0f, 1 .0f, }; |
4 |
float [] spec = { 1 .0f, 1 .0f, 1 .0f, 1 .0f, }; |
5 |
float [] pos = { 0 .0f, 5 .0f, 5 .0f, 1 .0f, }; |
6 |
float [] spot_dir = { 0 .0f, - 1 .0f, 0 .0f, }; |
7 |
gl.glEnable(GL10.GL_DEPTH_TEST); |
8 |
gl.glEnable(GL10.GL_CULL_FACE); |
10 |
gl.glEnable(GL10.GL_LIGHTING); |
11 |
gl.glEnable(GL10.GL_LIGHT0); |
13 |
= ByteBuffer.allocateDirect(amb.length* 4 ); |
14 |
abb.order(ByteOrder.nativeOrder()); |
15 |
FloatBuffer ambBuf = abb.asFloatBuffer(); |
20 |
= ByteBuffer.allocateDirect(diff.length* 4 ); |
21 |
dbb.order(ByteOrder.nativeOrder()); |
22 |
FloatBuffer diffBuf = dbb.asFloatBuffer(); |
27 |
= ByteBuffer.allocateDirect(spec.length* 4 ); |
28 |
sbb.order(ByteOrder.nativeOrder()); |
29 |
FloatBuffer specBuf = sbb.asFloatBuffer(); |
34 |
= ByteBuffer.allocateDirect(pos.length* 4 ); |
35 |
pbb.order(ByteOrder.nativeOrder()); |
36 |
FloatBuffer posBuf = pbb.asFloatBuffer(); |
41 |
= ByteBuffer.allocateDirect(spot_dir.length* 4 ); |
42 |
spbb.order(ByteOrder.nativeOrder()); |
43 |
FloatBuffer spot_dirBuf = spbb.asFloatBuffer(); |
44 |
spot_dirBuf.put(spot_dir); |
45 |
spot_dirBuf.position( 0 ); |
48 |
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, ambBuf); |
49 |
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, diffBuf); |
50 |
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, specBuf); |
51 |
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, posBuf); |
52 |
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPOT_DIRECTION, |
54 |
gl.glLightf(GL10.GL_LIGHT0, GL10.GL_SPOT_EXPONENT, 0 .0f); |
55 |
gl.glLightf(GL10.GL_LIGHT0, GL10.GL_SPOT_CUTOFF, 45 .0f); |
58 |
GLU.gluLookAt(gl, 0 .0f, 4 .0f, 4 .0f, 0 .0f, 0 .0f, 0 .0f, |
绘制一个球,并使用蓝色材质:
1 | public void drawScene(GL10 gl) { |
4 |
float [] mat_amb = { 0 .2f * 0 .4f, 0 .2f * 0 .4f, |
6 |
float [] mat_diff = { 0 .4f, 0 .4f, 1 .0f, 1 .0f,}; |
7 |
float [] mat_spec = { 1 .0f, 1 .0f, 1 .0f, 1 .0f,}; |
11 |
= ByteBuffer.allocateDirect(mat_amb.length* 4 ); |
12 |
mabb.order(ByteOrder.nativeOrder()); |
13 |
FloatBuffer mat_ambBuf = mabb.asFloatBuffer(); |
14 |
mat_ambBuf.put(mat_amb); |
15 |
mat_ambBuf.position( 0 ); |
18 |
= ByteBuffer.allocateDirect(mat_diff.length* 4 ); |
19 |
mdbb.order(ByteOrder.nativeOrder()); |
20 |
FloatBuffer mat_diffBuf = mdbb.asFloatBuffer(); |
21 |
mat_diffBuf.put(mat_diff); |
22 |
mat_diffBuf.position( 0 ); |
25 |
= ByteBuffer.allocateDirect(mat_spec.length* 4 ); |
26 |
msbb.order(ByteOrder.nativeOrder()); |
27 |
FloatBuffer mat_specBuf = msbb.asFloatBuffer(); |
28 |
mat_specBuf.put(mat_spec); |
29 |
mat_specBuf.position( 0 ); |
31 |
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, |
32 |
GL10.GL_AMBIENT, mat_ambBuf); |
33 |
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, |
34 |
GL10.GL_DIFFUSE, mat_diffBuf); |
35 |
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, |
36 |
GL10.GL_SPECULAR, mat_specBuf); |
37 |
gl.glMaterialf(GL10.GL_FRONT_AND_BACK, |
38 |
GL10.GL_SHININESS, 64 .0f); |