未完待续:
3D图形数学初识
向量
向量即可表示方向也可表示数量。
单位向量:长度为1。
标准化:把向量的长度变为1。
math3d库中有两个数据类型能表示向量:M3DVector3f可以表示一个三维向量(X, Y, Z),而M3DVector4f可以表示一个4维向量(X, Y, Z, W)。X、Y、Z的值通过除以W来进行缩放。
要讲他们定义成数组,需:
typedef float M3DVector3f[3];
typedef float M3DVector4f[4];
//声明及初始化
M3DVector3f vVertor;
当与4x4矩阵相乘时,就不可忽略第四个分量W。
- 点乘
两个单位向量点乘得到一个两个向量之间的余弦值,在漫射光计算中,表面法向量和指向光源的向量之间大量进行着这种运算。
m3dDotProduct3函数来获取两个向量点乘的结果:
float m3dDotProduct(const M3DVector3f u, const M3DVector3f v);
m3dGetAngleBetweenVectors3(const M3DVector3f u, const M3DVector3f v);
- 叉乘
得到一个向量。
函数m3dCrossProduct3对两个向量叉乘并还回结果向量。
void m3dCrossProduct3(M3DVector3f result, const M3DVector3f u, const M3DVector3f v);
矩阵
最普遍的例子就是坐标变换。就是一个二维数组。我们可以将矩阵看做是一组列向量。
进行3D设计时,几乎全部都是3x3和4x4矩阵,
typedef float M3DMatrix33f[9];
typedef float M3DMatrix44f[16];
OpenGL使用一维数组,因为OpenGl使用一种Column-Major(以列为主)矩阵排序的矩阵约束。
理解变换
将3D数据被“压扁”成2D数据的处理过程叫做投影(projection)。
投影只是OpenGL中发生的变换中的一种,变换还允许我们旋转对象、移动对象、伸展、收缩和扭曲。
在我们指定顶点和这些顶点出现在屏幕上之间的这段时间里,可能会发生3种类型的几何变换:视图变换、模型变换和投影变换。
OpenGL变换术语概览
变换 | 应用 |
---|---|
视图 | 指定观察者或照相机的位置 |
模型 | 在场景中移动物体 |
模型视图 | 描述视图和模型变换的二元性 |
投影 | 改变视景体的大小或重新设置它的形状 |
视口 | 这是一种伪变换,只是对窗口上的最终输出进行缩放后 |
视觉坐标
视觉坐标是相对于观察者的视角而言的。视觉坐标表示一个虚拟的固定坐标系,通常做参考坐标系使用。
从两个视角观察视觉坐标:
利用OpenGl进行3D绘制时,就会使用笛卡尔坐标系。如果不进行任何变化,那么使用的坐标系将与刚描述的视角坐标系相同。
视图变换
视图变换是应用在场景中的第一种变换,允许我们把观察点放在所希望的任何位置,并允许在任何方向上观察场景。
确定视图变换就像在场景中放置照相机并让他指向某个方向。
在透视投影中: 观察点默认在(0,0,0),并沿着z轴负方向进行观察,绘制在z坐标为正的位置的对象则位于观察者背后。
在正投影中,观察者被认为是z轴正无穷的位置,能够看到视景体中的任何东西。
从大局上考虑,在应用任何其他模型变换之前,必须先应用视图变换。这是因为,对视觉坐标而言,视图变换移动了当前的工作坐标系。所以后续变换随后都会基于新调整的坐标系进行。
模型变换
模型变换用于操纵模型和其中的特定对象。这些变换将对象移动到需要的位置,然后再对它们进行旋转和缩放。
场景或对象的最终外观可能很大程度上取决于应用的模型变换顺序。
模型变换:
模型视图的二元性
视图和模型变换按照它们内部效果和对场景的最终外观来说是一样的。即将对象向后移动和将参考坐标系向前移动在视觉上没有区别。
“模型视图”是指这两种变换在变换管线中进行组合,成为一个单独的矩阵,即模型视图矩阵。
模型变换和视图变换的相对:
投影变换
投影变换将在模型视图变换之后应用到顶点上,这种投影实际上定义了视景体并创建裁剪平面。
即投影变换指定一个完成的场景是如何投影到屏幕上的最终图像。
对于透视投影,我们需要做的是,指定适当模型视图变换的场景, 然后应用透视投影矩阵。
视口变换
把二维投影映射到屏幕上某处的窗口(物理坐标)上,称为视口变换。
模型视图矩阵
模型视图矩阵是一个4 x 4的矩阵,表示一个变换后的坐标系,可以用来放置对象和确定对象的方向。
我们为图元提供的顶点将作为一个单列矩阵(向量)的形式来使用,并乘以一个模型属兔矩阵来获得相对于视觉坐标系的经过变换的新坐标。
最后得到一个向量。
矩阵构造
存储方式为单个数组:
GLfloat matrix[16];
列优先排序:
注意: 这16个值表示空间中的一个位置,以及相对于视觉坐标系的3个轴上的方向。
前3列的前3个元素只是方向向量,他们表示空间中X、Y和Z轴上的方向,一般为方向向量。第4列向量包含变换后的坐标原点的X、Y和Z值。
如果用一个对象的所有向量乘以这个矩阵,那么我们就将整个对象变换到了空间中的给定位置和方向。
- 单位矩阵
将一个向量乘以一个单位矩阵,这个向量不变。单位矩阵对角线为1,其余为0。
生成单位矩阵的方法:
1、
GLfloat m[] = {1.0f, 0.0f, 0.0f, 0.0f, // X列
0.0f, 1.0f, 0.0f, 0.0f, // Y列
0.0f, 0.0f, 1.0f, 0.0f, // Z列
0.0f, 0.0f, 0.0f, 1.0f }; // 变换
2、使用math3d的M3DMatrix44f类型:
M3DMatrix44f m = {1.0f, 0.0f, 0.0f, 0.0f, // X列
0.0f, 1.0f, 0.0f, 0.0f, // Y列
0.0f, 0.0f, 1.0f, 0.0f, // Z列
0.0f, 0.0f, 0.0f, 1.0f }; // 变换
3、使用math3d库中的快捷函数m3dLoadidentity44初始化一个空的单位矩阵:
void m3dLoadIdentity44(M3DMatrix44f m);
- 平移
调用math3d库中的m3dTranslationMatrix44函数来使用变换矩阵:
void m3dTranslationMatrix44(M3DMatrix44f m, float x, float y, float z);
- 旋转
m3dRotationMatrix44(M3DMatrix44f m, float angle, float x, float y, float z);
angle是旋转的角度,旋转的角度沿逆时针弧度计算,
下面代码创建一个旋转矩阵,可使顶点沿任意有(1,1,1)指定的旋转轴旋转45度:
M3DMatrix44f m;
m3dRotationMatrix(m, m3dDegToRad(45.0), 1, 1, 1);
宏m3dDegToRad将角度值转换为弧度值。
- 缩放
缩放矩阵可以沿着3个坐标轴方向按照指定因子放大或缩小所有顶点,以改变对象大小。
M3DMatrix44f m;
void m3dScaleMatrix44(M3DMatrix44f m, float xScale, float yScale, float zScale);
- 综合变换
将两种矩阵相乘就可以完成这两种转换。用m3dMatrixMultiply44实现:
void m3dMatrixMultiply44( M3DMatrix44f product,
const M3DMatrix44f a, const M3DMatrix44f b );
运用模型视图矩阵
实现物体的运动,可以一次性创建批次,然后在渲染这个批次时对顶点应用一个矩阵(即模型视图矩阵),在绘制对象之前把这个变换矩阵发送给着色器。
例如:
M3DMatrix44f mFinalTransform, mTranslationMatrix, mRotationMatrix;
m3dTranslationMatrix44(mTranslationMatrix, xPos, yPos, 0.0f);
m3dRotationMatrix44(mRotationMatrix, m3dDegToRad(yRot), 0.0f, 0.0f 1.0f);
m3dMatrixMultiply44(mFinalTransform, mTranslationMatrix, mRotationMatrix);
shaderManager.UseStockShader(GLT_SHADER_FLAT, mFinalTransform, vRed);
squreBatch.Draw();
更多对象
GLTriangleBatch类——-专门作为三角形的容器。
使用三角形批次类
1、为对象创建一个事件:
GLTriangleBatch myCoolObject;
2、 通知容器最多打算使用的定点数,以创建网格:
myCoolObject.BeginMesh(200);
3、添加三角形:
void GLTriangleBatch::AddTriaangle(M3DVector3f verts[3], M3DVector3f vNorms[3], M3DVector32f vTexCoords[3]);
该函数接受一个包含3个顶点的数组,一个包含3个法线的数组,以及一个包含3个纹理坐标的数组。
GLTriangleBatch类会搜索重复值并对我们的批次进行优化。当批次非常大时,这样会明显降低速度。
4、 添加完三角形后,调用End:
myCoolObject.End();
5、 选择着色器并调用Draw函数绘制:
myCoolObject.Draw();
创建一个球体
void gltMakeSphere(GLTriangleBatch &sphereBatch,
GLfloat fRadius,
GLint iSlices,
GLint iStacks);
该函数引用一个三角形批次、球的半径和组成球体的片段及其堆叠数量,iSlices是围绕着球体排列的三角形对数,iStacks是这些从球底部堆叠到顶部的三角形带的数量,较好的对称是片段数量是堆叠数量的2倍。
创建一个花托
void gltMakeTorus(GLTriangleBatch &torusBatch,
GLfloat majorRadius,
GLfloat minorRadius,
GLint numMajor,
GLint numMinor);
numMajor是沿着主半径的细分单元的数量,而numMinor则是沿着内部半径。
创建一个圆柱或圆锥
gltMakeCylinder函数创建一个空心圆柱体。
void gltMakeCylinder(GLTriangleBatch &cylinderBatch,
GLfloat baseRadius,
GLfloat topRadius,
GLfloat fLength,
GLint numSlices,
GLint numStacks);
半径相等:
一端半径为0:
创建一个圆盘
void gltMakeDisk(GLTriangleBatch &diskBatch, GLfloat innerRadius, GLfloat outerRadius, GLint nSlices, GLint nStacks);
投影矩阵
模型视图矩阵实际上是在视觉坐标系中移动几何图形。之前默认的坐标系都是-1.0到1.0。硬件唯一能接受的坐标就是-1.0到1.0范围的,要使用不同坐标系就是,将我们想要的坐标变换到这个单位立方体中。这就是投影矩阵。
两种投影:
1、正投影:
GLFrustum::SetOrthographic(GLfloat xMin, GLfloat xMAx,
GLfloat yMin, GLfloat yMax,
GLfloat zMin, GLfloat zMax);
2、透视投影:
GLFrustum::SetPerspecctive(float fFov, float fAspect,
float fNear, floar fFar);
模型视图投影矩阵
把坐标系缩减到单位正方体范围,通过投影矩阵乘以模型视图矩阵。
模型视图投影矩阵 = 投影矩阵 X 模型视图矩阵
模型视图矩阵 是变换后的矩阵
交换管线
顶点 X 模型视图矩阵,生成变换的视觉坐标。
视觉坐标 X 投影矩阵, 生成裁剪坐标,裁剪坐标值位于单位坐标系内。
裁剪坐标 \ w坐标,生成规范化的设备坐标。
顶点变换管线:
使用矩阵堆栈
GLMatrixStack的构造函数指定堆栈的最大深度,默认深度为64。这个矩阵在初始化时已经在堆栈中包含了单位矩阵。
GLMatrixStack::GLMatrixStack(int iStackDeph = 64);
载入一个单位矩阵:
void GLMatrixStack::LoadIdentity(void);
载入任何矩阵:
void GLMatrixStack::LoadMatrix(const M3DMatrix44f m);
用一个矩阵乘以矩阵堆栈的顶部矩阵,结果存在堆栈的顶部:
void GLMatrixStack::MultMatrix(const M3DMatrix44f);
获取堆栈顶部的值:
const M3DMatrix44f &GLMatrixStack::GetMatrix(void);
void GLMatrixStack::GetMatrix(M3DMatrix44f mMatrix);
压栈出栈
void GLMatrixStack::PushMatrix(void);
void PushMatrix(const M3DMatrix44f mMatrix);
void PushMatrix(GLFrame &frame);
void GLMatrixStack::PopMatrix(void);
仿射变换
void MatrixStack::Rotate(GLfloat angle, GLfloat x,
GLfloat y, GLfloat z);
void MatrixStack::Translate(GLfloat x, GLfloat y,
GLfloat z);
void MatrixStack::Scale(GLfloat x, GLfloat y,
GLfloat z);
这三个函数创建适当的矩阵,然后乘以矩阵堆栈顶部元素。
管理管线
为模型视图矩阵和投影矩阵建立一个矩阵堆栈有很多优势。另一种有用的矩阵就是正规矩阵(矩阵 X 矩阵的转置 = 矩阵的转置 X 矩阵),它用来进行光照计算,并且可以从模型视图矩阵推导出来。
实用类GLGeometryTransform为我们跟踪记录这两种矩阵堆栈,并快速检索模型视图投影矩阵的顶部或正规矩阵的顶部。
//创建投影矩阵,并把它载入到投影矩阵堆栈中
ViewFrustum.Setperspective(35.0,
float(nWidth)/float(nHeight), 1.0f, 100f);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectMatrix());
初始化GLGeometryTransform的实例:
transformPipeline.SetMatrixStacks(modeViewMatrix, projectionMatrix);
加载到着色器中:
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModeViewProjectionMatrix(),
vTorusColor);
使用照相机和角色进行移动
在场景中移动的物体通常称为角色,角色有它们自己的变换,而且它的变换不仅与全局坐标系有关,也与其他角色有关。
每个有自己的变换角色都被称为有自己的参考帧。
角色帧
用一个数据结构表示一个参考帧,这个类中包含空间中的一个位置、一个向前方向的向量和一个向上方向的向量。这些量可以在空间中唯一确定一个给定的位置和方向,下面是一个GLFrame类,
class GLFrame
{
protected:
M3DVector3f vLocation;
M3DVector3f vUp;
M3Dvector3f vForward;
public:
······
};
可以使用这些数据直接创建一个4X4的矩阵,向上的向量vUp作为矩阵的y列,向前的向量vForward作为矩阵的z列,而位置vlocation作为平移列向量。而x 3个轴都是单位长度,并且是正交的,可以通过叉乘获取x列向量。
从一个帧中导出4x4矩阵的函数:
void GLFrame::GetMatrix(M3DMatrix44f mMatrix,
bool bRotationOnlt = false);
欧拉角
欧拉角:所需空间更少,只存储一个物体的位置和3个角度(表示沿x轴、y轴和z轴的旋转,有时称为yaw,pitch和roll)。
struct EULER{
M3DVector3f vPosition;
GLfloat fRoll;
GLfloat fPitch;
GLfloat fYaw;
};
照相机管理
Opnegl中并不存在像照相机变换这样的东西,而是作为一种比喻,帮助我们在某些类型的3D环境中管理观察点,既有位置又有方向。
为了应用照相机变换,我们使用照相机的角色变换并对它进行反转。
3D环境中典型的渲染循环流程:
类包含一个GLFrame函数,用来检索条件合适的照相机矩阵:
void GetCameraMatrix(M3DMatrix44f m,
bool bRotationOnly = false);
例子:
GLFrame cameraFrame; //全局照相机实例
//移动照相机参考帧来对方向键做出响应
void SpecialKeys(int key, int x, int y)
{
float linear = 0.1f;
float angular = float(m3dDegToRad(5.0f));
if (key == GLUT_KEY_UP)
cameraFrame.MoveForward(linear);
if (key == GLUT_KEY_DOWN)
cameraFrame.MoveForward(-linear);
if (key == GLUT_KEY_LEFT)
cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);
if (key == GLUT_KEY_RIGHT)
cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
}
//进行调用以绘制场景
void RenderScene(void)
{
// Color values
static GLfloat vFloorColor[] = { 0.0f, 1.0f, 0.0f, 1.0f };
static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f };
static GLfloat vSphereColor[] = { 0.0f, 0.0f, 1.0f, 1.0f };
// Time Based animation
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
// Clear the color and depth buffers
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Save the current modelview matrix (the identity matrix)
modelViewMatrix.PushMatrix();
M3DMatrix44f mCamera;
//应用照相机变换
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);
// 绘制不移动的物体地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
// 绘制移动的物体
modelViewMatrix.Translate(0.0f, 0.0f, -2.5f);
// Save the Translation
modelViewMatrix.PushMatrix();
// Apply a rotation and draw the torus
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(),
vTorusColor);
torusBatch.Draw();
modelViewMatrix.PopMatrix(); // "Erase" the Rotation from before
// Apply another rotation, followed by a translation, then draw the sphere
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(),
vSphereColor);
sphereBatch.Draw();
// Restore the previous modleview matrix (the identity matrix)
modelViewMatrix.PopMatrix();
modelViewMatrix.PopMatrix();
// Do the buffer Swap
glutSwapBuffers();
// Tell GLUT to do it again
glutPostRedisplay();
}
关于光线
光源位置也需要装换到视觉坐标系,但是传递到着色器的矩阵变换是几何图形,而不是光线。
将一个固定的光源位置变换到设觉坐标,而在每个场景中只需进行一次:
//将光源位置变换到设觉坐标系
M3DVector4f vLightPos = { 0.0f, 10.0f, 5.0f, 1.0f };
M3Dvector4f vLightEyePos;
//对光源位置进行变换
m3dTransformVector4(vLightEyePos, vLightPos, mCamera);
光源位置的全局坐标存储在vLightPos变量中,
要渲染一个球体,如下:
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(),
vLightEyePos, vSphereColor);
很多光照着色器需要一个正规矩阵,正规矩阵可以从模型视图矩阵推导出来,着色器帮我们完成这项工作。