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中,添加光照效果,通常分为以下两个步骤进行

  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)为平行光的方向

在这里插入图片描述

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

在这里插入图片描述

  1. 将点光源设置成聚光灯,需要同时设置 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值来作为因子。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的OpenGL程序,可以实现基本图元控制、动作及其控制、纹理、光照、半透明效果: ``` #include <GL/glut.h> #include <iostream> GLfloat angle = 0.0f; GLfloat x = 0.0f; GLfloat y = 0.0f; GLfloat z = 0.0f; GLfloat light_position[] = { 0.0f, 0.0f, 10.0f, 1.0f }; GLfloat light_ambient[] = { 0.0f, 0.0f, 0.0f, 1.0f }; GLfloat light_diffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f }; GLfloat light_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f }; GLfloat mat_ambient[] = { 0.7f, 0.7f, 0.7f, 1.0f }; GLfloat mat_diffuse[] = { 0.8f, 0.8f, 0.8f, 1.0f }; GLfloat mat_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f }; GLfloat mat_shininess[] = { 100.0f }; GLubyte img[] = { 255, 255, 255, 255, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255 }; void init(void) { glClearColor(0.0, 0.0, 0.0, 0.0); glShadeModel(GL_SMOOTH); glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); } void display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glTranslatef(x, y, z); glRotatef(angle, 0.0f, 0.0f, 1.0f); glEnable(GL_TEXTURE_2D); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, img); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient); glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess); glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); glLightfv(GL_LIGHT0, GL_POSITION, light_position); glBegin(GL_TRIANGLES); glColor4f(1.0f, 0.0f, 0.0f, 0.5f); glVertex3f(-1.0f, -1.0f, 0.0f); glColor4f(0.0f, 1.0f, 0.0f, 0.5f); glVertex3f(1.0f, -1.0f, 0.0f); glColor4f(0.0f, 0.0f, 1.0f, 0.5f); glVertex3f(0.0f, 1.0f, 0.0f); glEnd(); glPopMatrix(); glutSwapBuffers(); } void reshape(int w, int h) { glViewport(0, 0, (GLsizei)w, (GLsizei)h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60.0, (GLfloat)w / (GLfloat)h, 1.0, 20.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0.0, 0.0, -5.0); } void keyboard(unsigned char key, int x, int y) { switch (key) { case 'q': exit(0); break; case 'a': angle += 5.0f; if (angle > 360.0f) { angle -= 360.0f; } break; case 'd': angle -= 5.0f; if (angle < 0.0f) { angle += 360.0f; } break; case 'w': z += 0.5f; break; case 's': z -= 0.5f; break; } glutPostRedisplay(); } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(500, 500); glutCreateWindow("OpenGL Demo"); init(); glutDisplayFunc(display); glutReshapeFunc(reshape); glutKeyboardFunc(keyboard); glutMainLoop(); return 0; } ``` 这个程序中,我们使用了OpenGL的基本图元控制函数glBegin()和glEnd()来绘制一个三角形。我们还使用了glTranslatef()和glRotatef()函数控制三角形的位置和旋转。另外,我们还引入了纹理和光照OpenGL的高级功能。 希望这个例子能够帮助你更好地理解OpenGL的使用。如果你有任何问题,请随时问我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值