之前介绍了许多OpenGL平面图形的知识,再写一篇关于OpenGL立体图形的渲染。
三维坐标系统
由于要渲染立体图形,所以就不得不引入z轴形成三维坐标系统。在OpenGL中z轴就是穿过屏幕指向你的坐标轴。
在三维坐标中定义一个点与二维坐标一样,调用void glVertex3f(GLfloat x,GLfloat y,GLfloat z,)
函数可以定义一个三维坐标。若要传入一个三维坐标数组就调用void glVertex3fv(const GLfloat *v)
函数即可。
矩阵堆栈
由于OpenGL对图形的变换是通过坐标点进行的,而三维图形又有大量的坐标点需要渲染。为了管理这些坐标点在变换中的状态,我们可以使用矩阵堆栈。
矩阵堆栈和一般堆栈一样,有压入和弹出两个操作。需要压入堆栈就可以调用void glPushMatrix(void)
函数,需要弹出就调用void glPopMatrix(void)
函数。这些堆栈可以存放视图矩阵、投影矩阵和纹理矩阵,具体的内容暂且不提。
绘制图形
有了这些知识我们就可以简单的画一个六棱柱:
void display()
{
glEnable(GL_SMOOTH);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
static GLfloat vtx[12][3] = //棱柱顶点坐标
{
{-0.5f,0.0f,0.0f},//0
{-0.25f,0.0f,static_cast<GLfloat>(sqrt(3.0f)/4)},//1
{-0.25f,0.0f,-static_cast<GLfloat>(sqrt(3.0f)/4)},//2
{0.25f,0.0f,static_cast<GLfloat>(sqrt(3.0f)/4)},//3
{0.25f,0.0f,-static_cast<GLfloat>(sqrt(3.0f)/4)},//4
{0.5f,0.0f,0.0f},//5
{-0.5f,0.5f,0.0f},//6
{-0.25f,0.5f,static_cast<GLfloat>(sqrt(3.0f)/4)},//7
{-0.25f,0.5f,-static_cast<GLfloat>(sqrt(3.0f)/4)},//8
{0.25f,0.5f,static_cast<GLfloat>(sqrt(3.0f)/4)},//9
{0.25f,0.5f,-static_cast<GLfloat>(sqrt(3.0f)/4)},//10
{0.5f,0.5f,0.0f},//11
};
GLfloat color[6][3] = //棱柱顶点颜色
{
{0.0f,0.0f,1.0f},
{1.0f,0.0f,0.0f},
{1.0f,1.0f,0.0f},
{1.0f,0.0f,1.0f},
{1.0f,0.0f,0.0f},
{0.0f,1.0f,0.0f}
};
glRotatef(60.0f,0.0f,0.0f,0.0f);
glFrontFace(GL_CCW);
glPolygonMode(GL_FRONT, GL_FILL);
glPolygonMode(GL_BACK, GL_FILL);
glBegin(GL_POLYGON);
glColor3fv(color[0]);
glVertex3fv(vtx[2]);
glColor3fv(color[1]);
glVertex3fv(vtx[4]);
glColor3fv(color[2]);
glVertex3fv(vtx[5]);
glColor3fv(color[3]);
glVertex3fv(vtx[3]);
glColor3fv(color[4]);
glVertex3fv(vtx[1]);
glColor3fv(color[5]);
glVertex3fv(vtx[0]);
glEnd();
glBegin(GL_QUADS);
glColor3fv(color[0]);
glVertex3fv(vtx[8]);
glColor3fv(color[1]);
glVertex3fv(vtx[10]);
glColor3fv(color[2]);
glVertex3fv(vtx[4]);
glColor3fv(color[0]);
glVertex3fv(vtx[2]);
glColor3fv(color[0]);
glVertex3fv(vtx[6]);
glColor3fv(color[1]);
glVertex3fv(vtx[8]);
glColor3fv(color[2]);
glVertex3fv(vtx[2]);
glColor3fv(color[0]);
glVertex3fv(vtx[0]);
glColor3fv(color[0]);
glVertex3fv(vtx[10]);
glColor3fv(color[1]);
glVertex3fv(vtx[11]);
glColor3fv(color[2]);
glVertex3fv(vtx[5]);
glColor3fv(color[0]);
glVertex3fv(vtx[4]);
glColor3fv(color[0]);
glVertex3fv(vtx[1]);
glColor3fv(color[1]);
glVertex3fv(vtx[3]);
glColor3fv(color[2]);
glVertex3fv(vtx[9]);
glColor3fv(color[0]);
glVertex3fv(vtx[7]);
glColor3fv(color[3]);
glVertex3fv(vtx[0]);
glColor3fv(color[4]);
glVertex3fv(vtx[1]);
glColor3fv(color[5]);
glVertex3fv(vtx[7]);
glColor3fv(color[0]);
glVertex3fv(vtx[6]);
glColor3fv(color[0]);
glVertex3fv(vtx[3]);
glColor3fv(color[1]);
glVertex3fv(vtx[5]);
glColor3fv(color[2]);
glVertex3fv(vtx[11]);
glColor3fv(color[0]);
glVertex3fv(vtx[9]);
glEnd();
glBegin(GL_POLYGON);
glColor3fv(color[3]);
glVertex3fv(vtx[6]);
glColor3fv(color[4]);
glVertex3fv(vtx[7]);
glColor3fv(color[5]);
glVertex3fv(vtx[9]);
glColor3fv(color[0]);
glVertex3fv(vtx[11]);
glColor3fv(color[1]);
glVertex3fv(vtx[10]);
glColor3fv(color[2]);
glVertex3fv(vtx[8]);
glEnd();
glPopMatrix();
glFlush();
}
这段代码简单的运用了三维坐标和矩阵堆栈,虽然矩阵堆栈在这里用处不大。颜色随便定义的。此外我还用到旋转函数glRotatef(60.0f,0.0f,0.0f,0.0f);
将图形沿x轴稍微向外旋转了一下。看一下效果:
有一点需要注意,我画面的时候是先画背面再画正面,否则正面会被背面挡住。另外,由于我定义了面的方向,所以我画点也是有顺序的(具体可看上一篇)。
但是这是静态的,我们画了那么多只能看见一部分,因此我通过调用时间库让它动起来。
引入时间库函数头文件和数学库头文件
#include <cmath>
#include <time.h>
利用刚刚讲的void glRotatef(GLfloat x,GLfloat y,GLfloat z,GLfloat w)
函数实现旋转,随便绕哪个轴。
clock_t now = clock();
glRotatef(sin(0.0001 * now) * 360,cos(0.0001 * now) * 360 ,sin(0.0001 * now) * 360,0.0f);
在刷新缓冲后设置重绘状态,这个函数应该在前面讲过。(虽然这么做有可能存在堆栈的问题)
glutPostRedisplay();
看一下效果。
法线向量和光照
画好了立体图形并且会动了,但还是感觉距离真是物体差很远。我决定给它加上阴影让它看起来更真实点。
有阴影就必须有光照,而OpenGL的光照效果除了要设置光源外还要给不同的面设置法线向量。我通过调用void glNormal3fv(const GLfloat *v)
函数直接传一个法线向量。
GLfloat norm[8][3] = //三棱柱各面法向
{
{0.0f,1.0f,0.0f}, //底面
{0.0f,0.0f,1.0f}, //前一面
{static_cast<GLfloat>(sqrt(3.0f)/4),0.0f,-0.25f}, //前二面
{static_cast<GLfloat>(sqrt(3.0f)/4),0.0f,0.25f}, //前三面
{0.0f,0.0f,1.0f}, //后一面
{static_cast<GLfloat>(sqrt(3.0f)/4),0.0f,0.25f}, //后二面
{static_cast<GLfloat>(sqrt(3.0f)/4),0.0f,-0.25f},//后三面
{0.0f,1.0f,0.0f} //顶面
};
然后在每次画面的同时插入相应的法线向量绘制函数,例如底面的glNormal3fv(norm[0]);
。
最后再加入光源。这个光源也是有讲究的,不过我不打算深入的介绍。放一下我的代码。
GLfloat light_pos[] = {-1.0f,-1.0f,-1.0f,1.0f};
GLfloat light_ambient[] = {0.0f,0.0f,0.0f,1.0f};
GLfloat light_difuse[] = {1.0f,1.0f,1.0f,1.0f};
GLfloat light_specular[] = {1.0f,1.0f,1.0f,1.0f};
glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_difuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
照理说这样设置就可以了,但是光照到物体上的话,物体也会根据材质反射不同的质感。比如说紫砂壶的光泽和陶瓷瓶的光泽肯定是不一样的。
GLfloat mat_ambient[] = {0.5f, 0.5f, 0.5f, 1.0f};
GLfloat mat_diffuse[] = {0.5f, 0.5f, 0.5f, 1.0f};
GLfloat mat_specular[] = {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat mat_emission[] = {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat mat_shininess = 30.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_EMISSION, mat_emission);
glMaterialf (GL_FRONT, GL_SHININESS, mat_shininess);
最后看一下效果(为了看效果我把颜色都去掉了)。
这次博客其实是按照我做作业的顺序讲的,接下来可能会讲到图形变换吧。但早期OpenGL我感觉会马上结束了,之后我会尝试现代OpenGL,不过那也是暑假的事了吧。