第一步:熟悉opengl 编程。
制作一个会旋转的圆锥体,并加入贴图。
第二步:读取复杂物体表面的顶点信息。已知复杂物体表面是由一个个三角面片构成的三角网格图形。
读出点、线、面 信息,然后绘制顶点。
第三步:在给定模型的基础上,在取消光照情况下,首先尝试自己计算漫反射光照的值。
设定光源位置LightPosition(三维坐标)、光源颜色值LightShiness、漫反射系数值Diffuse(RGB格式),同时可由函数求出每个顶点的属性(包括顶点坐标pVertex,顶点法线向量等),然后将LightPosition与顶点坐标pVertex的各坐标轴的值想减,求得光源向量,然后与顶点法线向量求点积,得到cos值,然后利用公式I=Diffuse*cos*LightShiness求得作用于顶点的漫反射光照值I(RGB格式)。
为了简化计算,光源使用平行光。初始位置为(0,0,1)。
首先在CSceneGraph3d类中定义了光源位置、光源颜色值(即光强)、漫反射系数三个变量,并在CSceneGraph3d类的构造函数CSceneGraph3d::CSceneGraph3d()中初始化。为了能在CMesh3d类中访问到这三个变量,需要编写一个函数实现传值操作,这个函数是添加在CMesh3d类中,命名为DrawMe(GLfloat* fDiffuse,GLfloat* fLightness,GLfloat*flightpos);
然后在CSceneGraph3d类中的glDraw()函数中添加如下代码:
GLfloat m_LightPos[] = {m_lightposition.x(),m_lightposition.y(),m_lightposition.z()};
((CMesh3d*)pObject3d)->DrawMe(m_fDiffu,m_fLightness,m_LightPos);
pObject3d->glDraw();
最后在CMesh3d类中的glBuildList()函数中实现计算漫反射光照值。
第四步:光源位置不变,物体旋转。
物体旋转时,法向改变,则有多少个顶点CPU需要重复计算多少次,这样耗时巨大,所以想到另一种方法,就是在利用glRotate()进行物体旋转时求出其旋转矩阵,然后求其逆矩阵,再和光源方向做点积,这种方法保证只计算一次光源方向,提高了CPU利用率。
在CMeshView::OnTimer()函数中求旋转物体时转换矩阵的逆矩阵inv_matrix,将上面的glRotatef顺序倒着写,并使旋转角度求反,即由正变负。然后将inv_matrix传递到CMesh3D类。
传值:CMeshDoc* pDOc = GetDocument();
CMesh3d *pMesh = (CMesh3d *)pDOc->m_SceneGraph.GetAt(0);
pMesh->SendInverseMatrix(inv_matrix);
其中SendInverseMatrix 函数为:
void SendInverseMatrix(GLfloat *inv_matrix)
{
memcpy(m_fInvMatrix, inv_matrix, 16 * sizeof(GLfloat));//内存拷贝
}
第五步:让光源位置移动,使其围绕物体旋转
在CSceneGraph3d类中添加成员函数spinDisplay()使光源绕Y轴旋转,则 x 、z 值改变。然后在CMeshView::OnTimer()中调用此函数。
为了清晰地看到光源是如何移动的,绕着什么方向移动的,我设置了一条红色的线(从原点到光源位置),当光源绕y轴旋转时若从正前方看模型,则此条线在屏幕上会显示为一条直线,所以我将平面(x,0,z)转换为(x,-z,0)平面,即是将俯视图向上旋转90度,使我们能在屏幕正前方看到俯视图,此时绘制的线移动时显示为移动一个圆周。
glColor3f(1,0,0);
glBegin(GL_LINES);
glVertex3f(0,0,0);
glVertex3f(10*m_light_parallel.x(),-10*m_light_parallel.z(),0);//保证看到俯视图
glEnd();
第六步:计算镜面反射光照,最后通过数学计算公式,在计算机上编程实现phong模型(简单光照模型)。
在使用phong模型时不考虑环境光,因为环境光对物体的影响很小,可以忽略。所以最终颜色是漫反射光照和镜面反射光照构成的phong模型。
CVector3d pv = *pVector; // 赋值,得到CVector3d类型的pv,而非CVector3d*类型!
pv.NormalizeL2(1); //法向单位化,归一化
GLfloat fCos = (GLfloat)Scalar(&pv,&m_light_parallel); //点积求得cos角
if (fCos<0.0f) fCos=0.0f;
GLfloat fXX1 = m_fDiffuse[0] * m_fLightness[0]*fCos;
GLfloat fYY1 = m_fDiffuse[1] * m_fLightness[1]*fCos;
GLfloat fZZ1 = m_fDiffuse[2] * m_fLightness[2]*fCos;
GLfloat m_specular[3]={0.7,0.7,0.7}; //镜面反射系数
CVector3d m_eye; //观察方向
m_eye.Set(0.0,0.0,1.0);//从屏幕里面指向外面,为Z轴正向
GLfloat L_V; //光源方向和观察方向
L_V=sqrt(pow(m_eye.x()+m_light_parallel.x(),2) +
pow(m_eye.y()+m_light_parallel.y(),2) +
pow(m_eye.y()+m_light_parallel.y(),2) );
CVector3d m_H_specular; // 公式 H=(L+V)/ | L+V |
m_H_specular.Set((m_light_parallel.x()+m_eye.x())/L_V,
(m_light_parallel.y()+m_eye.y())/L_V,
(m_light_parallel.z()+m_eye.z())/L_V);
m_H_specular.NormalizeL2(1);
GLfloat V_R=(GLfloat)Scalar(&m_H_specular,&pv); //求点积
if (V_R<0.0f) V_R=0.0f;
GLfloat n=5;//镜面高光系数 (0--2000) n越大,光斑越暗
GLfloat m_V_R=pow(V_R,n); // V_R的n次方
GLfloat fXX2=m_fLightness[0]*m_specular[0]*m_V_R;
GLfloat fYY2=m_fLightness[1]*m_specular[1]*m_V_R;
GLfloat fZZ2=m_fLightness[2]*m_specular[2]*m_V_R;
GLfloat fXX= fXX1+fXX2;
GLfloat fYY=fYY1+fYY2;
GLfloat fZZ=fZZ1+fZZ2;
if (fXX>255) fXX=255;
if (fYY>255) fYY=255;
if (fZZ>255) fZZ=255;
::glColor3ub(fXX,fYY,fZZ);