Android OpenGL的简单使用(8):材质和光照

Posted by alonealice on 2020-12-27

光照

观察一个真实的3D物体,在不同的部位必然有不同的光照效果,有的地方暗一点,有的地方亮一点。而这种视觉差异是由光源和材质(物体的材料)共同决定的。光源强度由红、绿、蓝三色光强度共同决定,最终的光照效果由4部分组成:

1
2
3
4
- Emitted(光源)
- diffuse(漫反射光)
- specular(镜面反射光)
- ambient(环境光)

Emitted(光源)

物体本身所发射出的光,有的物体不发射光,那就没有这个属性。

diffuse(漫反射光)

漫反射和镜面反射大家应该都知道,初中物理有讲过。
漫反射,是投射在粗糙表面上的光向各个方向反射的现象。当一束平行的入射光线射到粗糙的表面时,表面会把光线向着四面八方反射,所以入射线虽然互相平行,由于各点的法线方向不一致,造成反射光线向不同的方向无规则地反射,这种反射称之为“漫反射”或“漫射”。

specular(镜面反射光)

镜面反射是指若反射面比较光滑,当平行入射的光线射到这个反射面时,仍会平行地向一个方向反射出来,这种反射就属于镜面反射。

ambient(环境光)

在环境中进行了多次散射的光,而最终无法分辨其方向的光。

材质

物体的材质属性通过反射不同方向的环境光,漫反射光,镜面光的RGB颜色来表示的。分为四种:

1
2
3
4
- 泛射材质
- 漫反射材质
- 镜面反射材质
- 发射材质

光照模型

添加光照效果也就是使用OpenGL的光照模型需要以下步骤

  1. 打开光源
  2. 设置光源的种类、位置和方向(对于平行光源)
  3. 设置顶点的法向量
  4. 设置材质

打开光源

首先需要打开光源的总开关

1
2
//打开光源总开关
gl.glEnable(GL10.GL_LIGHTING);12

在OpenGL ES中,仅仅支持有限数量的光源。使用GL_LIGHT0表示第0号光源,GL_LIGHT1表示第1号光源,依次类推,OpenGL至少会支持8个光源 。GL_LIGHT0到GL_LIGHT7。

打开0号光源

1
2
//打开0号光源
gl.glEnable(GL10.GL_LIGHT0);

设置光源的种类、位置和方向

设置光源的方法如下

1
2
- glLightfv(int light, int pname, FloatBuffer params)
- glLightfv(int light, int pname, float[] params, int offset)

各个参数含义如下

1
2
3
4
- light—————— 光源的序号
- pname—————— 光源属性名称
- params—————— 光源属性的值(float 数组或是Buffer类型)
- offset—————— 偏移量

params有以下几种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- GL_SPOT_EXPONENT————表示聚光的程度,为零时表示光照范围内向各方向发射的光线强度相同,为正数时表示光照向中央集中,正对发射方向的位置受到更多光照,其它位置受到较少光照。数值越大,聚光效果就越明显

- GL_SPOT_CUTOFF————表示一个角度,它是光源发射光线所覆盖角度的一半,其取值范围在0到90之间,也可以取180这个特殊值。取值为180时表示光源发射光线覆盖360度,即不使用聚光灯,向全周围发射

- GL_CONSTANT_ATTENUATION————表示光线按常量衰减

- GL_LINEAR_ATTENUATION————表示光线按距离线性衰减

- GL_QUADRATIC_ATTENUATION————表示光线按距离以二次函数衰减

- GL_AMBIENT————表示各种光线照射到该材质上,经过很多次反射后最终遗留在环境中的光线强度

- GL_DIFFUSE————表示光线照射到该材质上,经过漫反射后形成的光线强度

- GL_SPECULAR————表示光线照射到该材质上,经过镜面反射后形成的光线强度

- GL_SPOT_DIRECTION————表示一个向量,即光源发射的方向。默认方向是(0.0,0.0,-1.0)

- GL_POSITION————表示光源所在的位置,由四个值(X, Y, Z, W)表示,W为0表示平行光源,表示该光源位于无限远处,类似太阳,W不为0表示位置性光源,(X/W, Y/W, Z/W)表示了光源的位置,可以设置各种衰减因子

上面说的与聚光强度、角度、衰减等有关的属性只适用于位置光源

设置顶点的法向量

设置法向量来确定图元的明暗程度,不过我的例子并没有用到。有两个方法可以为平面设置法线。

1
2
3
- public void glNormal3f(float nx,float ny,float nz) ————为后续所有平面设置同样的方向,直到重新设置新的法线为止

- public void glNormalPointer(int type,int stride, Buffer pointer)————为某个顶点设置法线

设置材质

设置材质的方法如下

1
2
3
- 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)

各参数含义如下:

1
2
3
4
- face ———— 表示物体的前、后面,有GL_FRONT(正面),GL_BACK(反面),GL_FRONT_AND_BACK(正反两面)
- pname ———— 材质属性类型
- param ———— 材质属性类型的对应的值(float 数组或者Buffer 类型)
- offset ———— 偏移量

其中pname 有以下几种

1
2
3
4
5
6
- GL_AMBIENT————表示各种光线照射到该材质上,经过很多次反射后最终遗留在环境中的光线强度
- GL_DIFFUSE ————表示光线照射到该材质上,经过漫反射后形成的光线强度
- GL_SPECULAR————表示光线照射到该材质上,经过镜面反射后形成的光线强度
- GL_SHININESS————“镜面指数”,取值范围是0到128。该值越小,表示材质越粗糙,点光源发射的光线照射到上面,也可以产生较大的亮点。该值越大,表示材质越类似于镜面,光源照射到上面后,产生较小的亮点
- GL_EMISSION————该属性由四个值组成,表示一种颜色。OpenGL认为材质本身就微微的向外发射光线,以至于眼睛感觉到它有这样的颜色,但这光线又比较微弱,以至于不会影响到其它物体的颜色
- GL_COLOR_INDEXES———— 颜色索引

完整实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
class PointRenderer implements GLSurfaceView.Renderer {


//顶点数组
private float[] mArray = {
-0.5f, -0.5f, 0f,
-0.5f, 0.5f, 0f,
0.5f, 0.5f, 0f,
1f, 0f, 0f,
1f, -1f, 0f
};

// 缓冲区
private FloatBuffer mBuffer;

private FloatBuffer amb_light_buffer;
private FloatBuffer diff_light_buffer;
private FloatBuffer spec_light_buffer;
private FloatBuffer pos_light_buffer;
private FloatBuffer spot_dir_buffer;

//环境光材质
float amb_mat[] = { 0f, 0.3f, 0f, 1f };
//漫反射光材质
float diff_mat[] ={ 0f, 0.3f, 0f, 1f };
//镜面反射光材质
float spec_mat[] = { 0f, 0.3f, 0f, 1f };
//本身颜色
float emi_mat[] = { 0.0f, 0f, 0.0f, 1.0f };

//环境光强度
float[] amb_light = {0f, 0f, 1f, 1f };
//漫反射光强度
float[] diff_light = { 1f, 1f, 1f, 1f };
//镜面反射光强度
float[] spec_light = {1f, 1f, 1f, 1f };

//光源位置
float[] pos_light = {0f, 0.0f, 1f, 0.4f};

//光源方向
float[] spot_dir = { 0.0f, 0.0f, -1f, };

//镜面指数
float shini_mat = 0f;


private FloatBuffer amb_mat_buffer;
private FloatBuffer diff_mat_buffer;
private FloatBuffer spec_mat_buffer;
private FloatBuffer emi_mat_buffer;

public PointRenderer() {
mBuffer = Util.getFloatBuffer(mArray);

//获取浮点型环境光数据
amb_light_buffer= Util.getFloatBuffer(amb_light);
//获取浮点型漫反射光数据
diff_light_buffer= Util.getFloatBuffer(diff_light);
//获取浮点型镜面反射光数据
spec_light_buffer= Util.getFloatBuffer(spec_light);
//获取浮点型光源位置数据
pos_light_buffer= Util.getFloatBuffer(pos_light);

//获取浮点型光源方向数据
spot_dir_buffer= Util.getFloatBuffer(spot_dir);

//获取浮点型环境光材质数据
amb_mat_buffer= Util.getFloatBuffer(amb_mat);
//获取浮点型漫反射光材质数据
diff_mat_buffer= Util.getFloatBuffer(diff_mat);
//获取浮点型镜面反射光材质数据
spec_mat_buffer= Util.getFloatBuffer(spec_mat);
//获取浮点型本身颜色数据
emi_mat_buffer= Util.getFloatBuffer(emi_mat);
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glClearColor(0f, 1f, 0f, 0f);
}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0,0,width,height);
}

@Override
public void onDrawFrame(GL10 gl) {
// 清除屏幕
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

//打开光源总开关
gl.glEnable(GL10.GL_LIGHTING);

//打开0号光源
gl.glEnable(GL10.GL_LIGHT0);

//设置光源位置
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, pos_light_buffer);

//设置光源方向
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPOT_DIRECTION , spot_dir_buffer);

//设置光源种类
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT , amb_light_buffer );
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE , diff_light_buffer );
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, spec_light_buffer);

//设置聚光强度
gl.glLightf(GL10.GL_LIGHT0, GL10.GL_SPOT_EXPONENT, 64f);

//设置聚光角度
gl.glLightf(GL10.GL_LIGHT0, GL10.GL_SPOT_CUTOFF, 45f);


// 允许设置顶点 // GL10.GL_VERTEX_ARRAY顶点数组
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

// 设置顶点
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBuffer);

//设置点的颜色为绿色
gl.glColor4f(1f, 0f, 0f, 0f);

//设置材质种类
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, amb_mat_buffer);

gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_DIFFUSE, diff_mat_buffer);

gl.glMaterialfv(GL10.GL_FRONT_AND_BACK,GL10.GL_SPECULAR, spec_mat_buffer);

//设置镜面指数
gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, shini_mat);

// 绘制面
gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, 3);


// 禁止顶点设置
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}
}