OpenGL与3D开发-旋转光照透明
旋转、光照、透明
文章目录
1、旋转
1.1、旋转的单位是角度值
1.2 旋转的是整个坐标系
glRotatef()方法的语法格式如下:glRotatef (float angle, float x, float y, float z)
其中,参数angle通常是一个变量,表示对象转过的角度;x表示X轴的旋转方向(值为1表示顺时针、-1表示逆时针方向、0表示不旋转);y表示Y轴的旋转方向(值为1表示顺时针、-1表示逆时针方向、0表示不旋转);z表示Z轴的旋转方向(值为1表示顺时针、-1表示逆时针方向、0表示不旋转)。
例如,要将对象经过X轴旋转n角度,可以使用下面的代码:gl.glRotatef(n, 1, 0, 0);
注意:旋转遵循右手规则,如果向量(x,y,z)点正对着用户的话,旋转是逆时针的。
1.3 如果进行一系列旋转操作,顺序很重要
如果你要还原一个旋转操作,只需要把角度值或者三个坐标值取负就可以,例如glRotatef(angle, x, y, z)
可以被glRotatef(angle, -x, -y, -z)
或者glRotatef(-angle, x, y, z)
复位。
但是如果你像下面这样做了一系列的旋转操作:
gl.glRotatef(90f, 1.0f, 0.0f, 0.0f);
gl.glRotatef(90f, 0.0f, 1.0f, 0.0f);
gl.glRotatef(90f, 0.0f, 0.0f, 1.0f);
取负:
gl.glRotatef(90f, -1.0f, 0.0f, 0.0f);
gl.glRotatef(90f, 0.0f, -1.0f, 0.0f);
gl.glRotatef(90f, 0.0f, 0.0f, -1.0f);
还需要逆序执行,如下:
gl.glRotatef(90f, 0.0f, 0.0f, -1.0f);
gl.glRotatef(90f, 0.0f, -1.0f, 0.0f);
gl.glRotatef(90f, -1.0f, 0.0f, 0.0f);
1.4 平移和旋转
由于平移和旋转操作都是基于网格自己的坐标系统,所以记住平移和旋转操作的顺序是很重要的事情。
如果先对网格进行了平移操作,然后又进行旋转,那么平移是基于当前的网格坐标状态,而旋转则是基于新的坐标。
如果先进行了旋转操作,然后进行平移,那么平移操作将会在旋转之后的坐标基础上进行。
2、缩放
public abstract void glScalef (float x, float y, float z)
缩放,可沿任意坐标轴方向上独立进行。缩放和所有的顶点都乘以一个缩放因子的效果是一样的。在下图中,我们用gl.glScalef(2f, 2f, 2f)进行缩放,这也就相当于所有的顶点坐标都乘以2.
2.1 平移和缩放
缩放和平移的相互顺序也很重要。如果在缩放之前进行了平移,那么平移的结果不受影响。如下面的例子,我们先平移了两个单位,然后缩放0.5个单位。
gl.glTranslatef(2, 0, 0);
gl.glScalef(0.5f, 0.5f, 0.5f);
但是如果先进行了缩放,然后平移,会得到一个完全不同的结果。因为先缩放了网格的坐标系统,然后才平移,所以平移操作不会和之前那样移动同样的尺度。所以如果先缩放了0.5个单位,然后平移两个单位,结果将会表现为像平移了一个单位一样。
gl.glScalef(0.5f, 0.5f, 0.5f);
gl.glTranslatef(2, 0, 0);
3、 光照
3.1、真实世界中的光照
我们知道,在黑暗中,当我们将手电筒对准某个物体时,我们所看到的该物体的“亮度”有3种:
-
物体表面发生镜面反射部分(Specular),一般是白色。
-
物体表面发生漫反射部分(Diffuse),一般是物体表面的颜色。
-
物体表面没有照射到光的部分,即通过环境光(Ambient)照射,在黑暗中环境光是黑色。
为了使用程序效果更加美观、逼真,还可以让其模拟光照效果。
在OpenGL光照模型中,除非一个物体自己会发光,否则它将受到3种不同类型的光的照射:环境光(ambient)、散射光(diffuse)和镜面光(specular)。
3.2、 光照类型:
???? 环境光:环境光并不来自任何特定的方向。这是基本的照明水平,似乎遍布整个场景。它似乎不是来自任何光源的光,因为它在到达你之前已经反弹了很多次。光线没有方向可言。由环境光所照射的物体在所有方向的表面都是均匀照亮的。一种普通的光线,光线会照亮整个场景,即使对象背对着光线也可以。
???? 散射光:散射光具有方向性,来自于一个特定的方向,它根据入射光线的角度在表面上均匀地反射开来。这是直接从一个物体上跳弹后到达您眼睛中的光,物体的亮度随着它与照明的角度而变化,面向灯光的方向比其他角度更加明亮
????镜面光:镜面光具有很强的方向性,但它的反射角度很锐利,只沿一个特定的方向反射。与散射照明不同,当我们相对于物体移动时,镜面光照也会发生改变。
在OpenGL中,添加光照效果,通常分为以下两个步骤进行
-
设置光源
-
设置材料
3.3 光源
三种主要类型的光源:平行光,点光源,聚光灯
OpenGL可以同时为我们提供8个有效的光源。也就是说最多可以同时启用8个光源。它们分别是GL_LIGHT0~7;其中,GL_LIGHT0是最特殊的一个光源,该光源的默认颜色为白色,即RGBA为(1.0,1.0,1.0,1.0),漫反射和镜面反射也为白色;其他的光源默认为黑色,即RGBA为(0.0,0.0,0.0,1.0)。
开启光源
//启用光照功能
gl.glEnable(GL10.GL_LIGHTING);
//开启0号灯
gl.glEnable(GL10.GL_LIGHT0);
设置各种反射光参数
通过GL10提供的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_SPOT_DIRECTION:设置聚光方向
//设置光线衰减系数
GL_CONSTANT_ATTENUATION:表示光线按常熟衰减(与距离无关)
GL_LINEAR_ATTENUATION:表示光线按距离线性衰减
GL_QUADRATIC_ATTENUATION :表示光线按距离以二次函数衰减
//为光源设置颜色
GL_AMBIENT:用于设置环境光颜色
GL_DIFFUSE:用于设置漫反射光颜色
GL_SPECULAR:用于设置镜面反射光颜色
//设置光源位置
GL_POSITION:用于设置光源位置
params: 参数的值(数组或是Buffer类型),数组里面含有4个值分别表示R,G,B,A。
设置光源的位置
可以选择让光源位于无限远处,也可以让它靠近场景。第一种类型称为方向性光源。由于光源位于无限远处,当光线到达物体表面时,可以认为所有的光线都是平行的。方向性光源的一个现实世界的例子就是太阳。第二种类型称为位置性光源,它决定了光线的方向。台灯就是一个位置性光源的例子。
glLightfv(GL_LIGHT0, GL_POSITION, sun_light_position);
???? GL_POSITION属性。表示光源所在的位置。由四个值(X, Y, Z, W)表示。
-
方向性光源(Directional Light) 第四个值W为零,则表示该光源位于无限远处,前三个值表示了它所在的方向。通常,太阳可以近似的被认为是方向性光源。
-
位置性光源 (Positional Light) 第四个值W不为零,则X/W, Y/W, Z/W表示了光源的位置。这种光源称为位置性光源。
????OpenGL设置光照
- 平行光将 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
给出了聚光灯光源汇聚光的程度,值越大,则聚光区域越小(聚光能力更强)。
4、材料
同样是一束光,照在不同颜色材料的物体上面,我们所看到的是不同的,反射出来的不仅仅颜色不同,光泽也是不同的。换句话说,不同的材质对最终的渲染效果影响很大!
材料的属性设置和光源的设置有些类似,用到的函数
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:指定反射的颜色。
5、光照计算
环境光= 光源的环境光颜色 * 物体的环境材质颜色
例如,我们有个红色的物体和一个暗白色的环境照明。我们假设三个颜色(红,绿,蓝)的数组存储颜色,使用RGB颜色模型:
// 最终颜色 = 红色 * 暗白色 = 暗红色
final color = {1, 0, 0} * {0.1, 0.1, 0.1} = {0.1, 0.0, 0.0}
漫反射颜色 = 光源的漫反射颜色 * 物体的漫发射材质颜色 * 朗伯因子 * 亮度
补充:
第一步:计算朗伯因子(lambert factor)
我们最重要的是需要弄清楚表面和光线之间的角度。面向光直射的表面因该全强度照射,而倾斜的表面因该得到较少的照射,比较合适的计算方式是使用Lambert的余弦定律。
如果我们有两个向量,一个是从光到表面上的一个点,第二个是表面的法线(如果表面是平面,则表面法线是指向上或垂直于该表面的矢量),然后我们可以通过对每个向量进行归一化来计算余弦,使其长度为1,然后通过计算两个向量的点积(数量积)。
这个操作可以由OpenGL ES 2轻松完成。
// 光线向量 = 光源位置 - 物体位置
light vector = light position - object position
// 余弦 = 物体法线和归一化后的光线向量的点积
cosine = dot product(object normal, normalize(light vector))
// 朗伯因子 = 取余弦和0中最大的
lambert factor = max(cosine, 0)
首先通过光源位置减去物体位置得到光线向量,然后通过物体法线和光向量的点积得到余弦。我们标准化光向量,这意味着改变它的长度,长度为1,这个物体的法线长度也是1,两个归一化向量的点积得到他们之间的余弦。因为点积的取值范围是-11,所以我们将其限制到01。
例如:处在原点的平面,其表面法线指向上
光的位置在{0, 10, -10},我们想要计算在原点的光。
// 光线向量
light vector = {0, 10, -10} - {0, 0, 0} = {0, 10, -10}
// 物体法线
object normal = {0, 1, 0}
简洁的说,如果们沿着光线矢量走,我们到达光源的位置。为了归一化矢量,我们将每个分量除以矢量长度:
// 光线向量长度 = 平方根(0*0 + 10*10 + (-10 * -10)) = 平方根(200) = 14.14
light vector length = square root(0*0 + 10*10 + (-10 * -10)) = square root(200) = 14.14
// 归一化光线向量
normalize light vector = {0, 10/14.14, -10/14.14} = {0, 0.707, -0.707}
然后我们计算点积:
// 点积
dot product({0, 1, 0}, {0, 0.707, -0.707}) = (0 * 0) + (1 * 0.707) + (0 * -0.707) = 0.707
最后我们限制范围:
// 朗伯因子
lambert factor = max(0.707, 0) = 0.707
第二步:计算哀减系数
接下来,我们需要计算哀减。来自光源的实际光哀减遵循反平方定律
也可以这样表示:
// 亮度 = 1 / 距离的平方
luminosity = 1 / (distance * distance)
结合上面的列子,因为我们有光线长度为14.14,这儿我们最终的亮度:
luminosity = 1 / (14.14 * 14.14) = 1 / 200 = 0.005
我们可以看出距离越远,亮度强度越小
第三步:计算最终颜色
现在我们知道了余弦和哀减度,我们可以计算我们最终的亮度:
// 最终颜色 = 材质颜色 * (光的颜色 * 朗伯因子 * 亮度)
final color = material color * (light color * lambert factor * luminosity)
继续我们之前的红色物体和白光源的例子,这儿计算最终颜色:
final color = {1, 0, 0} * ({1, 1, 1} * 0.707 * 0.005) = {1, 0, 0} * {0.0035, 0.0035, 0.0035} = {0.0035, 0, 0}
镜面反射颜色 = 光源的镜面光的颜色 * 物体的镜面材质颜色 * SpecularFactor
注:SpecularFactor(镜面反射因子) = power(max(0,dot(N,H)),shininess)
平行光(正常光),光照效果=环境颜色 + 漫反射颜色 + 镜面反射颜色
点光源,光照效果=环境颜色 + (漫反射颜色 + 镜面反射颜色)× 衰减因子
聚光灯,光照效果=环境颜色 + (漫反射颜色 + 镜面反射颜色)× 衰减因子 × 聚光灯效果
6、透明
在游戏中,经常需要应用透明效果,使用OpenGL ES实现简单的透明效果也比较简单,只需要应用以下代码就可以实现。
gl.glDisable(GL10.GL_DEPTH_TEST); //关闭深度测试
gl.glEnable(GL10.GL_BLEND); //打开混合
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE); //使用alpha通道值进行混色,从而达到透明效果
说明:实现透明效果时,需要关闭深度测试,并且打开混合效果,然后才能使用GL10类的glBlendFunc()方法进行混色,从而达到透明效果。
6.1、混合
????混合是什么呢?混合就是把两种颜色混在一起。具体一点,就是把某一像素位置原来的颜色和将要画上去的颜色,通过某种方式混在一起,从而实现特殊的效果。
???? 假设我们需要绘制这样一个场景:透过红色的玻璃去看绿色的物体,那么可以先绘制绿色的物体,再绘制红色玻璃。在绘制红色玻璃的时候,利用“混合”功能,把将要绘制上去的红色和原来的绿色进行混合,于是得到一种新的颜色,看上去就好像玻璃是半透明的。
????注意:只有在RGBA模式下,才可以使用混合功能,颜色索引模式下是无法使用混合功能的。
6.2、源因子和目标因子
???? 把将要画上去的颜色称为“源颜色”,把原来的颜色称为“目标颜色”。OpenGL 会把源颜色和目标颜色各自取出,并乘以一个系数(源颜色乘以的系数称为“源因子”,目标颜色乘以的系数称为“目标因子”),然后相加,这样就得到了新的颜色
???? 假设源颜色的四个分量(指红色,绿色,蓝色,alpha值)是(Rs, Gs, Bs, As),目标颜色的四个分量是(Rd, Gd, Bd, Ad),又设源因子为(Sr, Sg, Sb, Sa),目标因子为(Dr, Dg, Db, Da)。则混合产生的新颜色可以表示为:(RsSr+RdDr, GsSg+GdDg, BsSb+BdDb, AsSa+AdDa)当然了,如果颜色的某一分量超过了1.0,则它会被自动截取为1.0,不需要考虑越界的问题。
6.3 、glBlendFunc函数
glBlendFunc
有两个参数,前者表示源因子,后者表示目标因子
???? GL_ZERO:表示使用0.0作为因子,实际上相当于不使用这种颜色参与混合运算。
???? GL_ONE:表示使用1.0作为因子,实际上相当于完全的使用了这种颜色参与混合运算。
???? GL_SRC_ALPHA:表示使用源颜色的alpha值来作为因子。GL_DST_ALPHA:表示使用目标颜色的alpha值来作为因子。
???? GL_ONE_MINUS_SRC_ALPHA:表示用1.0减去源颜色的alpha值来作为因子。
???? GL_ONE_MINUS_DST_ALPHA:表示用1.0减去源颜色的alpha值来作为因子。
???? GL_ONE_MINUS_DST_ALPHA:表示用1.0减去目标颜色的alpha值来作为因子。