OpenGL绘制光照和材质效果

您所在的位置:网站首页 m=0的轨道是实函数可以描述几何图形 OpenGL绘制光照和材质效果

OpenGL绘制光照和材质效果

2024-07-09 23:58| 来源: 网络整理| 查看: 265

OpenGL绘制光照和材质效果

在这里插入图片描述

本次任务主要实践三维空间的平移旋转、透视投影、光照及材质。有关阴影效果,课程的本节内容仅讲述了手动操作模型矩阵来绘制的阴影平面,而主流的阴影绘制方法是利用纹理和贴图来渲染阴影。

1. 将三个物体同轴排列

前面几个小节已经绘制了平面五角星、三维彩色立方体以及递归细分四面体法的三维球体,本节的要求首先是将三个物体同轴排列,使之在视景体中可见并设置透视投影来观察三者。为了更好地观察空间中的物体,本代码中额外绘制了三维坐标架,并绘制出了XOY平面,相关代码如下:

void frame() { // 绘制三维坐标架 glColor3f(0.3f, 0.3f, 0.3f); glLineWidth(0.75); glBegin(GL_LINES); glVertex3f(-100.0f, 0.0f, 0.0f); glVertex3f(100.0f, 00.0f, 0.0f); glVertex3f(0.0f, -100.0f, 0.0f); glVertex3f(0.0f, 100.0f, 0.0f); glVertex3f(0.0f, 0.0f, -100.0f); glVertex3f(0.0f, 0.0f, 100.0f); glEnd(); // 绘制XOY平面 glColor4f(0.8f, 0.8f, 0.8f, 0.5f); glNormal3f(0.0f, 0.0f, 1.0f); glBegin(GL_QUADS); glVertex3f(8.0f, 8.0f, 0.0f); glVertex3f(-8.0f, 8.0f, 0.0f); glVertex3f(-8.0f, -8.0f, 0.0f); glVertex3f(8.0f, -8.0f, 0.0f); glEnd(); }

在这里插入图片描述

绘制好坐标架后,三个物品依次排列时就可以清晰地看出其空间关系。三个物体绘制的具体代码不再赘述,以下是display函数中将三个物体依次排列的相关代码:

void display() { // 设置背景为白色 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 加载单位阵 glLoadIdentity(); // 设置相机的位置和视角 gluLookAt(7, 4, 4, 0.0, 0.0, 0.0, 0, 0, 1); // 绘制坐标架以及XOY平面 frame(); // 平移坐标系,让三个待绘制物品的底部排列在X轴上 glTranslatef(0.0f, 0.0f, 1.0f); // 在中心绘制三维彩色立方体,并绘制简单阴影 glTranslatef(-0.75f, -0.75f, -0.75f); color_cube(1.5f); cube_shadow(0.0f, 0.0f, 6.0f, 1.5f); glTranslatef(0.75f, 0.75f, 0.75f); // 在左侧(Y轴负半轴)绘制红色球面 glTranslatef(0.0f, -4.0f, 0.0f); sphere(); glTranslatef(0.0f, 4.0f, 0.0f); // 在右侧(Y轴正半轴)绘制平面的五角星 glTranslatef(0.0f, 4.0f, 0.0f); pentagram(); glTranslatef(0.0f, -4.0f, 0.0f); // 刷新帧缓存 glutSwapBuffers(); }

在不添加光照和阴影效果时,三个物品依次排列绘制出的效果如下:

在这里插入图片描述

可以看出,我们将三个物体的中心排列在同一高度,但由于使用了透视投影,靠右侧的五角星在视觉上要比靠左侧的球体更大,但实际上五角星的外接圆半径与球体半径是相同的,均为1。但由于没有使用光照效果,球体看上去只是一个圆(透视投影下被拉成椭圆)。

2. 光照、材质与简单阴影效果

光照和材质效果可以在init()函数中设置,此外在使用光照时,需要在绘制图像过程中增加每一点法向量的设置。指定法线向量的方式与指定颜色的方式有雷同之处。在指定颜色时,只需要指定每一个顶点的颜色,OpenGL就可以自行计算顶点之间的其它点的颜色。并且,颜色一旦被指定,除非再指定新的颜色,否则以后指定的所有顶点都将以这一向量作为自己的颜色。在指定法线向量时,只需要指定每一个顶点的法线向量,OpenGL会自行计算顶点之间的其它点的法线向量。并且,法线向量一旦被指定,除非再指定新的法线向量,否则以后指定的所有顶点都将以这一向量作为自己的法线向量。使用glColor*()函数可以指定颜色,而使用glNormal*()函数则可以指定法线向量。

在OpenGL中,仅仅支持有限数量的光源。使用GL_LIGHT0表示第0号光源,GL_LIGHT1表示第1号光源,依次类推。OpenGL至少会支持8个光源,即GL_LIGHT0到GL_LIGHT7。每一个光源都可以设置其属性,这一动作是通过glLight*()函数完成的。glLight*()函数具有三个参数,第一个参数指明是设置哪一个光源的属性,第二个参数指明是设置该光源的哪一个属性,第三个参数则是指明把该属性值设置成多少。GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR这三个属性表示了光源所发出的光的反射特性(以及颜色),每个属性由四个值表示,分别代表了颜色的R, G, B, A值。GL_AMBIENT表示该光源所发出的光,经过非常多次的反射后,最终遗留在整个光照环境中的强度(颜色)。GL_DIFFUSE表示该光源所发出的光,照射到粗糙表面时经过漫反射,所得到的光的强度(颜色)GL_SPECULAR表示该光源所发出的光,照射到光滑表面时经过镜面反射,所得到的光的强度(颜色)。GL_POSITION属性表示光源所在的位置。由四个值(X, Y, Z, W)表示。如果第四个值W为零,则表示该光源位于无限远处,前三个值表示了它所在的方向。这种光源称为方向性光源,通常,太阳可以近似的被认为是方向性光源。如果第四个值W不为零,则X/W, Y/W, Z/W表示了光源的位置。这种光源称为位置性光源。对于位置性光源,设置其位置与设置多边形顶点的方式相似,各种矩阵变换函数例如:glTranslate*()、glRotate*()等在这里也同样有效。方向性光源在计算时比位置性光源快了不少,因此,在视觉效果允许的情况下,应该尽可能的使用方向性光源。

材质与光源相似,也需要设置众多的属性。不同的是,光源是通过glLight*()函数来设置的,而材质则是通过glMaterial*()函数来设置的。glMaterial*()函数有三个参数。第一个参数表示指定哪一面的属性。可以是GL_FRONT、GL_BACK或者GL_FRONT_AND_BACK。分别表示设置“正面”“背面”的材质,或者两面同时设置。第二、第三个参数与glLight*函数的第二、三个参数作用类似。GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR这三个属性与光源的三个对应属性类似,每一属性都由四个值组成。GL_SHININESS属性只有一个值,称为“镜面指数”,取值范围是0到128。该值越小,表示材质越粗糙,点光源发射的光线照射到上面,也可以产生较大的亮点。该值越大,表示材质越类似于镜面,光源照射到上面后,产生较小的亮点。GL_EMISSION属性由四个值组成,表示一种颜色。OpenGL认为该材质本身就微微的向外发射光线,以至于眼睛感觉到它有这样的颜色,但这光线又比较微弱,以至于不会影响到其它物体的颜色。

void init() { // 计算五角星有关数据 get_pentagram(); // 设置逆时针排列的点围成的平面为正面 glFrontFace(GL_CCW); // 设置不绘制背面,节省算力同时不会出现背面覆盖正面的情况 glCullFace(GL_BACK); glEnable(GL_CULL_FACE); // 启用抗锯齿(使线平滑) glEnable(GL_BLEND); glEnable(GL_LINE_SMOOTH); glHint(GL_LINE_SMOOTH_HINT, GL_FASTEST); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // 设置材质和光照的信息 // 有关光照与材质:https://blog.csdn.net/timidsmile/article/details/7017197 GLfloat mat_ambient[4] = { 1.0f, 1.0f, 1.0f, 0.0f }; GLfloat mat_diffuse[4] = { 1.0f, 1.0f, 1.0f, 0.0f }; GLfloat mat_specular[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; GLfloat mat_shininess[4] = { 100.0f }; GLfloat light_ambient[4] = { 0.2f, 0.2f, 0.2f, 0.0f }; GLfloat light_diffuse[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; GLfloat light_specular[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; GLfloat light_position[4] = { 0.0f, 6.0f, 0.0f, 1.0f }; // 设置正向面的材质和光源的光照 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); // 设置颜色材料,使光照模式下仍然可以显示原本的颜色 glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); // 启用平滑着色功能 glShadeModel(GL_SMOOTH); // 启用光照功能 glEnable(GL_LIGHTING); // 启用0号光源 glEnable(GL_LIGHT0); // 启用检测深度 glEnable(GL_DEPTH_TEST); // 环境颜色设置为白色 glClearColor(1.0, 1.0, 1.0, 1.0); }

在这里插入图片描述

可以看出,在(0, 0, 6)处设置一点光源,从上方照射三个物体,物体的侧面由于受到光照较少,会产生出阴影的质感。下面是变换光源位置以及光源三种属性后的不同效果。为了更好地展现光照效果,此处将环境颜色设置成深色。

从Z轴负向,即底部向上照射三个物体:

在这里插入图片描述

从顶部向下照射三个物体,但去除了环境光,只保留漫反射和镜面反射:

在这里插入图片描述

光照之后的任务要求即为绘制阴影。阴影是一种高级光照渲染,会为物体进一步增加立体感。阴影一般由深度贴图渲染实现,本节的课件中介绍了一种简单的绘制阴影的方法,即手动操作变换矩阵,将原物品直接映射到一个平面上。这种方法需要绘制原物品两次,并且需要手动计算阴影变换矩阵,不太灵活,代码耦合性较高且手动计算复杂。下面仅实现了立方体的阴影:

在这里插入图片描述

void cube_shadow(GLfloat x, GLfloat y, GLfloat z, GLfloat size) { GLfloat m[16] = { 0.0f }; m[0] = m[5] = m[10] = 1.0f; m[11] = -1.0f / z; glMatrixMode(GL_MODELVIEW); glPushMatrix(); glTranslatef(x, y, z); glMultMatrixf(m); glTranslatef(-x, -y, -z); color_cube(size); glPopMatrix(); }

添加阴影后效果如下,但可以看出阴影也是绘制出的多面体,同样也要受到全局光照的影响,其原本的颜色在光照下变亮,显然不符合阴影的效果。如果要修改,需要改动多面体本身的绘制代码,单独添加与光照有关的设置,非常麻烦,本代码中没有涉及,仅仅实践了简单的阴影绘制方法而已:

在这里插入图片描述

附录:完整代码 #include #include #include #define DEPTH 4 #define PI 3.1415926 using namespace std; void frame() { // 绘制三维坐标架 glColor3f(0.3f, 0.3f, 0.3f); glLineWidth(0.75); glBegin(GL_LINES); glVertex3f(-100.0f, 0.0f, 0.0f); glVertex3f(100.0f, 00.0f, 0.0f); glVertex3f(0.0f, -100.0f, 0.0f); glVertex3f(0.0f, 100.0f, 0.0f); glVertex3f(0.0f, 0.0f, -100.0f); glVertex3f(0.0f, 0.0f, 100.0f); glEnd(); // 绘制XOY平面 glColor4f(0.8f, 0.8f, 0.8f, 0.5f); glNormal3f(0.0f, 0.0f, 1.0f); glBegin(GL_QUADS); glVertex3f(8.0f, 8.0f, 0.0f); glVertex3f(-8.0f, 8.0f, 0.0f); glVertex3f(-8.0f, -8.0f, 0.0f); glVertex3f(8.0f, -8.0f, 0.0f); glEnd(); } void normalize(GLfloat* v) { GLfloat d = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); v[0] /= d; v[1] /= d; v[2] /= d; } void divide_triangle(GLfloat* a, GLfloat* b, GLfloat* c, int depth) { if (depth > 0) { GLfloat ab[3], ac[3], bc[3]; for (unsigned int i = 0; i < 3; i++) ab[i] = a[i] + b[i]; normalize(ab); for (unsigned int i = 0; i < 3; i++) ac[i] = a[i] + c[i]; normalize(ac); for (unsigned int i = 0; i < 3; i++) bc[i] = b[i] + c[i]; normalize(bc); divide_triangle(a, ab, ac, depth - 1); divide_triangle(b, bc, ab, depth - 1); divide_triangle(c, ac, bc, depth - 1); divide_triangle(ab, bc, ac, depth - 1); } else { glBegin(GL_TRIANGLES); glNormal3fv(a); glVertex3fv(a); glNormal3fv(b); glVertex3fv(b); glNormal3fv(c); glVertex3fv(c); glEnd(); } } void sphere() { GLfloat tetrahedron_vertex[][3] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.942809f, -0.333333f, -0.816497f, -0.471405f, -0.333333f, 0.816497f, -0.471405f, -0.333333f }; glColor3f(1.00f, 0.00f, 0.00f); divide_triangle(tetrahedron_vertex[0], tetrahedron_vertex[2], tetrahedron_vertex[1], DEPTH); divide_triangle(tetrahedron_vertex[0], tetrahedron_vertex[3], tetrahedron_vertex[2], DEPTH); divide_triangle(tetrahedron_vertex[0], tetrahedron_vertex[1], tetrahedron_vertex[3], DEPTH); divide_triangle(tetrahedron_vertex[1], tetrahedron_vertex[2], tetrahedron_vertex[3], DEPTH); } GLfloat pentagram_vertex[5][3]; GLfloat pentagon_vertex[5][3]; void get_pentagram() { // 五角星看作在圆上内接的五边形的顶点连成的,此处设置外接圆半径[0, 1] GLfloat r = 1.0f; // 五角星可以在外接圆上旋转,此处设置旋转角度[0, 360) GLfloat rotate = 18.0; // 依次通过外接圆计算正五边形的五个顶点横纵坐标 GLfloat tmp[5][2]; for (int i = 0; i < 5; i++) { tmp[i][0] = (GLfloat)(r * cos(((72.0 * (GLfloat)i + rotate) / 360.0) * (2 * PI))); tmp[i][1] = (GLfloat)(r * sin(((72.0 * (GLfloat)i + rotate) / 360.0) * (2 * PI))); } // 将正五边形顺序的五个顶点对应到五角星顺序的五个顶点(0, 1, 2, 3, 4) -> (0, 2, 4, 1, 3) for (int i = 0, j = 0; i < 5; i++, j = j + 2) { int k = j % 5; pentagram_vertex[i][1] = tmp[k][0]; pentagram_vertex[i][2] = tmp[k][1]; } // 五角星的边相交得到中间的小五边形顶点,计算以便绘图时的着色 GLfloat x[4], y[4]; for (int i = 0; i < 5; i++) { int v[4]; for (int j = 0; j < 4; j++) v[j] = (i + j) % 5; for (int j = 0; j < 4; j++) x[j] = pentagram_vertex[v[j]][1], y[j] = pentagram_vertex[v[j]][2]; tmp[i][0] = ((x[2] - x[3]) * (x[1] * y[0] - x[0] * y[1]) - (x[0] - x[1]) * (x[3] * y[2] - x[2] * y[3])) / ((x[2] - x[3]) * (y[0] - y[1]) - (x[0] - x[1]) * (y[2] - y[3])); tmp[i][1] = ((y[2] - y[3]) * (y[1] * x[0] - y[0] * x[1]) - (y[0] - y[1]) * (y[3] * x[2] - y[2] * x[3])) / ((y[2] - y[3]) * (x[0] - x[1]) - (y[0] - y[1]) * (x[2] - x[3])); } for (int i = 0, j = 0; i < 5; i++, j = j + 2) { int k = j % 5; pentagon_vertex[i][1] = tmp[k][0]; pentagon_vertex[i][2] = tmp[k][1]; } for (unsigned int i = 0; i < 5; i++) { pentagram_vertex[i][0] = 0.0f; pentagon_vertex[i][0] = 0.0f; } } void pentagram() { // 下面的段落绘制五角星中心所围成小正五边形的着色 glColor3f(1.0f, 1.0f, 0.0f); glNormal3f(1.0f, 0.0f, 0.0f); glBegin(GL_POLYGON); for (unsigned int i = 0; i < 5; i++) glVertex3fv(pentagon_vertex[4-i]); glEnd(); // 下面的段落绘制五角星的线 glColor3f(0.0, 0.0, 0.0); // 设置线的宽度(0, 10] glLineWidth(2.5); glBegin(GL_LINE_LOOP); for (int i = 0; i < 5; i++) glVertex3fv(pentagram_vertex[i]); glEnd(); // 下面的段落绘制五角星的五个顶点(实心圆) glColor3f(0.0, 0.0, 0.0); // 设置实心圆的半径 GLfloat radius = 0.05f; // 设置用来拟合圆形的多边形边个数 int sections = 50; for (int i = 0; i < 5; i++) { glBegin(GL_TRIANGLE_FAN); glVertex3fv(pentagram_vertex[i]); for (int j = 0; j


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3