欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OpenGL与3D开发-旋转光照透明

程序员文章站 2022-07-14 20:57:57
...

旋转、光照、透明

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);

OpenGL与3D开发-旋转光照透明

取负:

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 平移和旋转

由于平移和旋转操作都是基于网格自己的坐标系统,所以记住平移和旋转操作的顺序是很重要的事情。

如果先对网格进行了平移操作,然后又进行旋转,那么平移是基于当前的网格坐标状态,而旋转则是基于新的坐标。

OpenGL与3D开发-旋转光照透明

如果先进行了旋转操作,然后进行平移,那么平移操作将会在旋转之后的坐标基础上进行。

OpenGL与3D开发-旋转光照透明

2、缩放

public abstract void glScalef (float x, float y, float z)

缩放,可沿任意坐标轴方向上独立进行。缩放和所有的顶点都乘以一个缩放因子的效果是一样的。在下图中,我们用gl.glScalef(2f, 2f, 2f)进行缩放,这也就相当于所有的顶点坐标都乘以2.

OpenGL与3D开发-旋转光照透明

2.1 平移和缩放

缩放和平移的相互顺序也很重要。如果在缩放之前进行了平移,那么平移的结果不受影响。如下面的例子,我们先平移了两个单位,然后缩放0.5个单位。

gl.glTranslatef(2, 0, 0);

gl.glScalef(0.5f, 0.5f, 0.5f);

OpenGL与3D开发-旋转光照透明

但是如果先进行了缩放,然后平移,会得到一个完全不同的结果。因为先缩放了网格的坐标系统,然后才平移,所以平移操作不会和之前那样移动同样的尺度。所以如果先缩放了0.5个单位,然后平移两个单位,结果将会表现为像平移了一个单位一样。

gl.glScalef(0.5f, 0.5f, 0.5f);

gl.glTranslatef(2, 0, 0);

OpenGL与3D开发-旋转光照透明

3、 光照

3.1、真实世界中的光照

我们知道,在黑暗中,当我们将手电筒对准某个物体时,我们所看到的该物体的“亮度”有3种:

  • 物体表面发生镜面反射部分(Specular),一般是白色。

  • 物体表面发生漫反射部分(Diffuse),一般是物体表面的颜色。

  • 物体表面没有照射到光的部分,即通过环境光(Ambient)照射,在黑暗中环境光是黑色。

OpenGL与3D开发-旋转光照透明

为了使用程序效果更加美观、逼真,还可以让其模拟光照效果。

在OpenGL光照模型中,除非一个物体自己会发光,否则它将受到3种不同类型的光的照射:环境光(ambient)、散射光(diffuse)和镜面光(specular)。

3.2、 光照类型:

???? 环境光:环境光并不来自任何特定的方向。这是基本的照明水平,似乎遍布整个场景。它似乎不是来自任何光源的光,因为它在到达你之前已经反弹了很多次。光线没有方向可言。由环境光所照射的物体在所有方向的表面都是均匀照亮的。一种普通的光线,光线会照亮整个场景,即使对象背对着光线也可以。

???? 散射光:散射光具有方向性,来自于一个特定的方向,它根据入射光线的角度在表面上均匀地反射开来。这是直接从一个物体上跳弹后到达您眼睛中的光,物体的亮度随着它与照明的角度而变化,面向灯光的方向比其他角度更加明亮

????镜面光:镜面光具有很强的方向性,但它的反射角度很锐利,只沿一个特定的方向反射。与散射照明不同,当我们相对于物体移动时,镜面光照也会发生改变。

在OpenGL中,添加光照效果,通常分为以下两个步骤进行

  1. 设置光源

  2. 设置材料

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设置光照

  1. 平行光将 w 设为0.0,(x,y,z)为平行光的方向

OpenGL与3D开发-旋转光照透明

  1. 对于点光源,将 w 设成非0值,通常设为1.0. (x,y,z)为点光源的坐标位置。

OpenGL与3D开发-旋转光照透明

  1. 将点光源设置成聚光灯,需要同时设置 GL_SPOT_DIRECTION,GL_SPOT_CUTOFF 等参数,GL_POSITION的设置和点光源类似:将 w 设成非0值,通常设为1.0. (x,y,z)为点光源的坐标位置。

OpenGL与3D开发-旋转光照透明

对于GL_SPOT_DIRECTION参数,设置聚光的方向(x,y,z)

OpenGL与3D开发-旋转光照透明

GL_SPOT_CUTOFF 参数设置聚光等发散角度(0到90度)

OpenGL与3D开发-旋转光照透明

GL_SPOT_EXPONENT 给出了聚光灯光源汇聚光的程度,值越大,则聚光区域越小(聚光能力更强)。

OpenGL与3D开发-旋转光照透明

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值来作为因子。