iOS图像:OpenGL(下)

原创:知识探索型文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

目录

  • 一、OpenGL变化
    • 1、3D数学
    • 2、仿射变换
  • 二、绘制图形
    • 1、绘制球体
    • 2、绘制其他图形
    • 3、绘制自动旋转的球体
    • 4、绘制围绕太阳旋转的地球
  • 三、OpenGL纹理
    • 1、绘制金字塔
    • 2、绘制隧道
    • 3、绘制自动旋转的球体世界
  • Demo
  • 参考文献

续文见上篇:iOS多媒体:OpenGL(上)


一、OpenGL变化

1、3D数学

a、3D数学的介绍

GITools库中有一个组件叫Math3d,其中包含了大量好用的OpenGL的3D数学类型。虽然我们不必亲自进行所有的矩阵和向量的操作,但我们需要知道它们是什么以及如何运用它们。AR Kit框架以及Unity3D、游戏开发都必须学习3D数学知识。

在开发过程中,我们涉及到的图形变换,就会涉及到矩阵/向量的计算。例如大家在使用CAnimation实现仿射变换时就使用了OpenGL渲染技术。图形的各个顶点*统一的变换矩阵就可以进行平移、旋转、缩放。

// 旋转
void MatrixStack::Rotate(GLfloat angle,GLfloat x,GLfloat y,GLfloat z);// angle参数传递的度数,⽽不是弧度
// 平移
void MatrixStack::Translate(GLfloat x,GLfloat y,GLfloat z);
// 缩放
void MatrixStack::Scale(GLfloat x,GLfloat y,GLfloat z);

b、OpenGL math3d库

math3d库有2个数据类型,能够表示⼀个三维或者四维向量。M3DVector3f可以表示⼀个三维向量(x,y,z),而M3DVector4f则可以表示一个四维向量(x,y,z,w)。在典型情况下,w 坐标设为1.0。x,y,z值通过除以w来进⾏行行缩放。除以1.0本质上不改变x,y,z值。

// 声明⼀个三维向量顶点数组,例如生成一个三角形
M3DVector3f vVerts[] = {
-0.5f,0.0f,0.0f,
0.5f,0.0f,0.0f,
0.0f,0.5f,0.0f
};

c、实现向量计算的方法
点乘

返回的是-1,1之间的值,代表这2个向量的余弦值

float m3dDotProduct3(const M3DVector3f u,const M3DVector3f v);

返回2个向量之间的弧度值

float m3dGetAngleBetweenVector3(const M3DVector3f u,const M3DVector3f v);
叉乘

叉乘运算结果返回⼀个新的向量,这个新的向量与原来的2个向量垂直

void m3dCrossProduct3(M3DVector3f result,const M3DVector3f u ,const M3DVector3f v);

d、图形的变化过程
  • 视图:指定观察者位置
  • 模型:在场景中移动物体
  • 模型视图:描述视图/模型变换的⼆元性
  • 投影:改变视景体⼤小和设置它的投影⽅式
  • 视口:伪变化,对窗口上最终输出进行缩放
顶点变换管线

2、仿射变换

a、使用到的属性

在x、y轴上移动的距离

GLfloat xPos = 0.0f;
GLfloat yPos = 0.0f;
b、实现移动的方法
void SpecialKeys(int key, int x, int y)
{
}

移动的步长

GLfloat stepSize = 0.025f;

上下左右移动

if(key == GLUT_KEY_UP) yPos += stepSize;
if(key == GLUT_KEY_DOWN) yPos -= stepSize;
if(key == GLUT_KEY_LEFT) xPos -= stepSize;
if(key == GLUT_KEY_RIGHT) xPos += stepSize;

检测是否碰撞边界

if(xPos < (-1.0f + blockSize)) xPos = -1.0f + blockSize;
if(xPos > (1.0f - blockSize)) xPos = 1.0f - blockSize;
if(yPos < (-1.0f + blockSize)) yPos = -1.0f + blockSize;
if(yPos > (1.0f - blockSize)) yPos = 1.0f - blockSize;

重新渲染

glutPostRedisplay();

c、实现渲染场景的方法
void RenderScene(void)
{
}

平移、旋转、最终矩阵

M3DMatrix44f mFinalTransform, mTranslationMatrix, mRotationMatrix;

根据xPos,yPos进行平移,每一个顶点都乘以平移矩阵

m3dTranslationMatrix44(mTranslationMatrix, xPos, yPos, 0.0f);

每次重绘时,旋转5度

static float yRot = 0.0f;
yRot += 5.0f;
m3dRotationMatrix44(mRotationMatrix, m3dDegToRad(yRot), 0.0f, 0.0f, 1.0f);

将旋转和移动的结果合并到mFinalTransform中(矩阵叉乘)

m3dMatrixMultiply44(mFinalTransform, mTranslationMatrix, mRotationMatrix);

将矩阵结果提交到固定着色器(平面着色器)中

shaderManager.UseStockShader(GLT_SHADER_FLAT, mFinalTransform, vRed);
squareBatch.Draw();

执行缓冲区交换

glutSwapBuffers();

二、绘制图形

1、绘制球体

a、使用到的属性
// 观察者位置
GLFrame                cameraFrame;
// 世界坐标位置
GLFrame             objectFrame;

// 视景体,用来构造投影矩阵
GLFrustum            viewFrustum;

// 三角形批次类
GLTriangleBatch     CC_Triangle;
// 球
GLTriangleBatch     sphereBatch;
// 环
GLTriangleBatch     torusBatch;
// 圆柱
GLTriangleBatch     cylinderBatch;
// 锥
GLTriangleBatch     coneBatch;
// 磁盘
GLTriangleBatch     diskBatch;
b、上下左右,移动图形

移动世界坐标系,而不是去移动物体。比如将世界坐标系在X方向移动-5.0。

void SpecialKeys(int key, int x, int y)
{
    if(key == GLUT_KEY_UP) objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
    if(key == GLUT_KEY_DOWN) objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
    if(key == GLUT_KEY_LEFT) objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
    if(key == GLUT_KEY_RIGHT) objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
    
    glutPostRedisplay();
}
c、添加数据源
void SetupRC()
{
}

开启深度测试(立体)

glEnable(GL_DEPTH_TEST);

表示观察者离屏幕之间的距离,若是负数,是往屏幕后面移动;若是正数,往屏幕前面移动。将观察者坐标位置Z往屏幕里移动15个单位位置。

cameraFrame.MoveForward(-15.0f);

或者将物体向屏幕外移动15.0

objectFrame.MoveForward(15.0f);
利用三角形批次类构造图形对象——球
  • sphereBatch:三角形批次类对象
  • fRadius:球体半径
  • iSlices:从球体底部堆叠到顶部的三角形带的数量,其实球体是一圈一圈三角形带组成
  • iStacks:围绕球体一圈排列的三角形对数
  • 一个对称性较好的球体的片段数量是堆叠数量的2倍,就是iStacks = 2 * iSlices
  • 绘制球体都是围绕Z轴,这样+z就是球体的顶点,-z就是球体的底部
gltMakeSphere(sphereBatch, 3.0, 10, 20);

d、渲染场景

用当前清除颜色清除窗口背景

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

模型视图矩阵栈堆,压栈。或者对objectFrame进行压栈,删掉3.和4. 直接进入switch(nStep)的判断步骤

modelViewMatrix.PushMatrix();
modelViewMatrix.PushMatrix(objectFrame);

获取摄像头矩阵,从camereaFrame中获取矩阵到mCamera

M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);

模型视图堆栈的矩阵与mCamera矩阵相乘之后,存储到modelViewMatrix矩阵堆栈中

modelViewMatrix.MultMatrix(mCamera);

创建矩阵mObjectFrame,从ObjectFrame获取矩阵到mOjectFrame

M3DMatrix44f mObjectFrame;
objectFrame.GetMatrix(mObjectFrame);

modelViewMatrix的堆栈中的矩阵与mOjbectFrame矩阵相乘,存储到modelViewMatrix矩阵堆栈中

modelViewMatrix.MultMatrix(mObjectFrame);

判断目前是绘制第几个图形

switch(nStep)
{
    case 0:
        DrawWireFramedBatch(&sphereBatch);
        break;
    case 1:
        DrawWireFramedBatch(&torusBatch);
        break;
    case 2:
        DrawWireFramedBatch(&cylinderBatch);
        break;
    case 3:
        DrawWireFramedBatch(&coneBatch);
        break;
    case 4:
        DrawWireFramedBatch(&diskBatch);
        break;
}

出栈

modelViewMatrix.PopMatrix();

交换缓存

glutSwapBuffers();

e、绘制图形和边框
❶ 绘制图形

平面着色器,绘制三角形

shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vGreen);

传过来的参数对应不同的图形Batch

pBatch->Draw();
❷ 开启多边形偏移
glEnable(GL_POLYGON_OFFSET_LINE);

将多边形背面设为线框模式

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

开启多边形偏移(设置偏移数量)

glPolygonOffset(-1.0f, -1.0f);

线条宽度

glLineWidth(2.5f);
❸ 开启混合功能(颜色混合&抗锯齿功能)
glEnable(GL_BLEND);

开启处理线段抗锯齿功能使线条更加柔和

glEnable(GL_LINE_SMOOTH);

设置颜色混合因子

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
❹ 平面着色器绘制线条
shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
pBatch->Draw();
❺ 恢复多边形模式和深度测试
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDisable(GL_POLYGON_OFFSET_LINE);
glLineWidth(1.0f);
glDisable(GL_BLEND);
glDisable(GL_LINE_SMOOTH);

2、绘制其他图形

环面
  • torusBatch:三角形批次类对象
  • majorRadius:甜甜圈中心到外边缘的半径
  • minorRadius:甜甜圈中心到内边缘的半径
  • numMajor:沿着主半径的三角形数量
  • numMinor:沿着内部较小半径的三角形数量
gltMakeTorus(torusBatch, 3.0f, 0.75f, 15, 15);

圆柱
  • cylinderBatch:三角形批次类对象
  • baseRadius:底部半径
  • topRadius:头部半径
  • fLength:圆形长度
  • numSlices:围绕Z轴的三角形对的数量
  • numStacks:圆柱底部堆叠到顶部圆环的三角形数量
gltMakeCylinder(cylinderBatch, 2.0f, 2.0f, 3.0f, 15, 2);

圆锥体
  • cylinderBatch:三角形批次类对象
  • baseRadius:底部半径
  • topRadius:头部半径
  • fLength:圆形长度
  • numSlices:围绕Z轴的三角形对的数量
  • numStacks:圆柱底部堆叠到顶部圆环的三角形数量
  • 圆柱体,从0开始向Z轴正方向延伸。圆锥体,是一端的半径为0,另一端半径可指定
gltMakeCylinder(coneBatch, 2.0f, 0.0f, 3.0f, 13, 2);

磁盘
  • diskBatch:三角形批次类对象
  • innerRadius:内圆半径
  • outerRadius:外圆半径
  • nSlices:圆盘围绕Z轴的三角形对的数量
  • nStacks:圆盘外网到内围的三角形数量
gltMakeDisk(diskBatch, 1.5f, 3.0f, 13, 3);

3、绘制自动旋转的球体

void RenderScene(void)
{
}
❶ 建立基于时间变化的动画
static CStopWatch rotTimer;

当前时间 * 60s

float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
❷ 矩阵变量
  • mTranslate:平移
  • mRotate:旋转
  • mModelview:模型视图
  • mModelViewProjection:模型视图投影MVP
M3DMatrix44f mTranslate, mRotate, mModelview, mModelViewProjection;
❸ 仿射变换

创建一个4*4矩阵变量,将球体沿着Z轴负方向移动2.5个单位长度

m3dTranslationMatrix44(mTranslate, 0.0f, 0.0f, -2.5f);

创建一个4*4矩阵变量,将球体在Y轴上旋转yRot度,yRot根据经过时间设置动画帧率

m3dRotationMatrix44(mRotate, m3dDegToRad(yRot), 0.0f, 1.0f, 0.0f);

mModerView通过矩阵旋转矩阵、移动矩阵相乘,将结果添加到mModerView

m3dMatrixMultiply44(mModelview, mTranslate, mRotate);

将投影矩阵乘以模型视图矩阵,将变化结果通过矩阵乘法应用到mModelViewProjection矩阵上

m3dMatrixMultiply44(mModelViewProjection, viewFrustum.GetProjectionMatrix(),mModelview);
❹ 着色器绘图

绘图颜色

GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f };

通过平面着色器提交矩阵和颜色

shaderManager.UseStockShader(GLT_SHADER_FLAT, mModelViewProjection, vBlack);

开始绘图

torusBatch.Draw();

交换缓冲区,并立即刷新

glutSwapBuffers();
glutPostRedisplay();

4、绘制围绕太阳旋转的地球

a、使用到的属性
GLShaderManager        shaderManager;            // 着色器管理器
GLMatrixStack        modelViewMatrix;        // 模型视图矩阵
GLMatrixStack        projectionMatrix;        // 投影矩阵
GLFrustum            viewFrustum;            // 视景体
GLGeometryTransform    transformPipeline;        // 几何图形变换管道
GLTriangleBatch        torusBatch;             // 大球
GLTriangleBatch     sphereBatch;            // 小球(随机球,包括静止和自转两种类型)
GLBatch                floorBatch;          // 地板
// 角色帧 照相机角色帧
GLFrame             cameraFrame;
// 添加附加随机球
#define NUM_SPHERES 50
GLFrame spheres[NUM_SPHERES];

b、添加数据源
void SetupRC()
{
}

清空颜色缓冲区中的残留颜色值,再进行初始化

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
shaderManager.InitializeStockShaders();

开启深度测试

glEnable(GL_DEPTH_TEST);

设置地板顶点数据

floorBatch.Begin(GL_LINES, 324);// 共324个
for(GLfloat x = -20.0; x <= 20.0f; x+= 0.5)
{
    // 地板是平面,只会在X、Y上发生变化
    floorBatch.Vertex3f(x, -0.55f, 20.0f);
    floorBatch.Vertex3f(x, -0.55f, -20.0f);
    
    floorBatch.Vertex3f(20.0f, -0.55f, x);
    floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();

设置大球模型

gltMakeSphere(torusBatch, 0.4f, 40, 80);

设置小球球模型

gltMakeSphere(sphereBatch, 0.1f, 26, 13);

随机位置放置小球

for (int i = 0; i < NUM_SPHERES; I++)
{
    // 小球在同一个平面,说明Y轴不变,X,Z使用随机值
    GLfloat x = ((GLfloat)((rand() % 400) - 200 ) * 0.1f);
    GLfloat z = ((GLfloat)((rand() % 400) - 200 ) * 0.1f);
    
    // 在y轴方向,将球体设置为0.0的位置,这使得它们看起来是飘浮在眼睛的高度
    // 对spheres数组中的每一个顶点,设置顶点数据
    spheres[i].SetOrigin(x, 0.0f, z);
}

c、渲染场景
void RenderScene(void)
{
}

颜色值(地板、大球、小球颜色)

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};

清除颜色缓存区和深度缓冲区

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

时间动画

static CStopWatch    rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;

为了让蓝色的小球的公转在任何角度都能看到需要加入观察者。观察者放在地板之前,让地板也支持摄影机的移动

M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);

使用平面着色器绘制地板

shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vFloorColor);
floorBatch.Draw();

获取光源位置(固定管线里面都是4维矩阵的计算)

M3DVector4f vLightPos = {0.0f,10.0f,5.0f,1.0f};
画中央的红色大球
  • 移动观察者camera进行平移
  • 使用objectFrame移动物体自身,最终还是调用了modelViewMatrix
  • 使用modelViewMatrix移动物体自身
// 使得大球位置平移(3.0)向屏幕里面
modelViewMatrix.Translate(0.0f, 0.0f, -3.0f);
// 压栈(复制栈顶),只有当图形发生了仿射变换的时候才需要使用到堆栈
modelViewMatrix.PushMatrix();
// 大球自转(围绕Y轴旋转)
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
// 指定合适的着色器(点光源着色器)
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(), vLightPos, vTorusColor);
torusBatch.Draw();
// 绘制完毕则Pop
modelViewMatrix.PopMatrix();

画静态的小球

for (int i = 0; i < NUM_SPHERES; I++)
{
    modelViewMatrix.PushMatrix();
    modelViewMatrix.MultMatrix(spheres[I]);
    shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(), vLightPos, vSphereColor);
    sphereBatch.Draw();
    modelViewMatrix.PopMatrix();
}

让一个小篮球围绕大球公众自转

// 围绕Y轴绕负方向2倍速度旋转
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
// 因为篮球和红球位置都在中央,为区分,将篮球沿着X轴正方向移动0.8
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vSphereColor);
sphereBatch.Draw();
modelViewMatrix.PopMatrix();

执行缓存区交换

glutSwapBuffers();

为了让球旋转起来,需要进行不断渲染

glutPostRedisplay();

d、屏幕更改大小或已初始化
void ChangeSize(int nWidth, int nHeight)
{
}

设置视口

glViewport(0, 0, nWidth, nHeight);

创建投影矩阵

viewFrustum.SetPerspective(35.0f, float(nWidth)/float(nHeight), 1.0f, 100.0f);

获取viewFrustum投影矩阵,并将其加载到投影矩阵堆栈上

projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());

设置变换管道以使用两个矩阵堆栈(变换矩阵modelViewMatrix ,投影矩阵projectionMatrix)。

transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);

e、键盘操作上下平移和左右旋转

void SpeacialKeys(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);
}

三、OpenGL纹理

1、绘制金字塔

// 纹理变量,一般使用无符号整型
GLuint              textureID;
a、添加数据源
void SetupRC()
{
    glClearColor(0.7f, 0.7f, 0.7f, 1.0f );
    shaderManager.InitializeStockShaders();
    glEnable(GL_DEPTH_TEST);
    .....
}
分配纹理对象
  • 参数1:纹理对象个数
  • 参数2:纹理对象指针
glGenTextures(1, &textureID);
绑定纹理状态
  • 参数1:纹理状态2D
  • 参数2:纹理对象
glBindTexture(GL_TEXTURE_2D, textureID);
将TGA文件加载为2D纹理
  • 参数1:纹理文件名称
  • 参数2&参数3:需要缩小&放大的过滤器
  • 参数4:纹理坐标环绕模式
LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);
创造金字塔pyramidBatch

设置金字塔顶点坐标数据/纹理坐标数据

MakePyramid(pyramidBatch);
修改观察者,将相机平移
cameraFrame.MoveForward(-10);

b、将TGA文件加载为2D纹理
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
    // 指向图像数据的指针
    GLbyte *pBits;
    // 图片的宽、高、颜色
    int nWidth, nHeight, nComponents;
    // 颜色存储方式
    GLenum eFormat;
    .....
}
读取纹理位置,读取像素
  • 参数1:纹理文件名称
  • 参数2:文件宽度地址
  • 参数3:文件高度地址
  • 参数4:文件组件地址
  • 参数5:文件格式地址
  • 返回值:指向图像数据的指针
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if(pBits == NULL) return false;// 未能成功读取到数据
设置纹理参数
  • 参数1:纹理维度
  • 参数2:为S/T坐标设置模式(线性过滤)
  • 参数3:环绕模式(过滤方式)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
载入纹理
  • 参数1:纹理维度
  • 参数2:贴图层次
  • 参数3:纹理单元存储的颜色成分(从读取像素图是获得)
  • 参数4:加载纹理宽
  • 参数5:加载纹理高
  • 参数6:加载纹理的深度
  • 参数7:像素数据的数据类型(GL_UNSIGNED_BYTE,每个颜色分量都是一个8位无符号整数)
  • 参数8:指向纹理图像数据的指针
glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBits);
// 使用完毕释放pBits
free(pBits);
纹理生成所有的Mip层
glGenerateMipmap(GL_TEXTURE_2D);

c、绘制金字塔
void MakePyramid(GLBatch& pyramidBatch)
{
}
❶ 通过pyramidBatch组建三角形批次
  • 参数1:图元枚举值
  • 参数2:顶点数
  • 参数3:这个批次中将会应用1个纹理,如果不写这个参数,默认为0
pyramidBatch.Begin(GL_TRIANGLES, 18, 1);
❷ 创建顶点数据
M3DVector3f vApex = { 0.0f, 1.0f, 0.0f };// 塔顶
M3DVector3f vFrontLeft = { -1.0f, -1.0f, 1.0f };// 前左
M3DVector3f vFrontRight = { 1.0f, -1.0f, 1.0f };// 前右
M3DVector3f vBackLeft = { -1.0f,  -1.0f, -1.0f };// 后左
M3DVector3f vBackRight = { 1.0f,  -1.0f, -1.0f };// 后右
M3DVector3f n;// 法线变量
❸ 绘制金字塔底部四边形中的三角形X(vBackLeft,vBackRight,vFrontRight)
  • 找到三角形X的法线
// 参数1:结果
// 参数2-4:3个顶点数据
m3dFindNormal(n, vBackLeft, vBackRight, vFrontRight);
  • vBackLeft
// 添加一个表面法线。表面法线是有方向的向量,法线坐标与Vertex顶点坐标中的Y轴一致。
pyramidBatch.Normal3fv(n);// 设置法线

// texture:纹理层次,使用存储着色器来进行渲染,设置为0
// s:对应顶点坐标中的x坐标
// t:对应顶点坐标中的y
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);// 设置纹理坐标

pyramidBatch.Vertex3fv(vBackLeft);// 向三角形批次类添加顶点数据
  • vBackRight
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackRight);
  • vFrontRight
pyramidBatch.Normal3fv(n);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
pyramidBatch.Vertex3fv(vFrontRight);
❹ 按照绘制三角形X的步骤绘制金字塔各面的三角形
❺ 结束批次设置
pyramidBatch.End();

d、绘制场景

颜色值&光源位置

static GLfloat vLightPos [] = { 1.0f, 1.0f, 0.0f };
static GLfloat vWhite [] = { 1.0f, 1.0f, 1.0f, 1.0f };

清理缓存区

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

当前模型视频压栈

modelViewMatrix.PushMatrix();

// 添加照相机矩阵
M3DMatrix44f mCamera;
// 从camraFrame中获取一个4*4的矩阵
cameraFrame.GetCameraMatrix(mCamera);
// 矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部将照相机矩阵与当前模型矩阵相乘压入栈顶
modelViewMatrix.MultMatrix(mCamera);

// 创建mObjectFrame矩阵
M3DMatrix44f mObjectFrame;
// 从objectFrame中获取矩阵,objectFrame保存的是特殊键位的变换矩阵
objectFrame.GetMatrix(mObjectFrame);
// 矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部将照相机矩阵与当前模型矩阵相乘压入栈顶
modelViewMatrix.MultMatrix(mObjectFrame);

绑定纹理。因为我们的项目中只有一个纹理,可以省略这步,但如果有多个纹理,绑定纹理很重要

glBindTexture(GL_TEXTURE_2D, textureID);
点光源着色器
  • 参数1:GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF(着色器标签)
  • 参数2:模型视图矩阵
  • 参数3:投影矩阵
  • 参数4:视点坐标系中的光源位置
  • 参数5:基本漫反射颜色
  • 参数6:图形颜色(用纹理就不需要设置颜色,设置为0)
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,
                             transformPipeline.GetModelViewMatrix(),
                             transformPipeline.GetProjectionMatrix(),
                             vLightPos, vWhite, 0);

绘制金字塔

pyramidBatch.Draw();

模型视图出栈,恢复矩阵(push一次就要pop一次)

modelViewMatrix.PopMatrix();

交换缓存区

glutSwapBuffers();

e、进行清理,例如删除纹理对象
void ShutdownRC(void)
{
    glDeleteTextures(1, &textureID);
}

2、绘制隧道

a、使用到的属性

4个批次容器类

GLBatch             floorBatch;//地面
GLBatch             ceilingBatch;//天花板
GLBatch             leftWallBatch;//左墙面
GLBatch             rightWallBatch;//右墙面

深度初始值为-65

GLfloat             viewZ = -65.0f;

纹理标识符号

#define TEXTURE_BRICK   0 //墙面
#define TEXTURE_FLOOR   1 //地板
#define TEXTURE_CEILING 2 //纹理天花板
#define TEXTURE_COUNT   3 //纹理个数

纹理标记数组

GLuint  textures[TEXTURE_COUNT];

文件tag名字数组

const char *szTextureFiles[TEXTURE_COUNT] = { "brick.tga", "floor.tga", "ceiling.tga" };

b、辅助函数中的修改
在main函数中添加菜单入口,改变过滤器
glutCreateMenu(ProcessMenu);
glutAddMenuEntry("GL_NEAREST",0);
glutAddMenuEntry("GL_LINEAR",1);
glutAddMenuEntry("GL_NEAREST_MIPMAP_NEAREST",2);
glutAddMenuEntry("GL_NEAREST_MIPMAP_LINEAR", 3);
glutAddMenuEntry("GL_LINEAR_MIPMAP_NEAREST", 4);
glutAddMenuEntry("GL_LINEAR_MIPMAP_LINEAR", 5);
glutAddMenuEntry("Anisotropic Filter", 6);
glutAddMenuEntry("Anisotropic Off", 7);
glutAttachMenu(GLUT_RIGHT_BUTTON);
在ChangeSize函数中生成透视投影
GLfloat fAspect = (GLfloat)w/(GLfloat)h;
viewFrustum.SetPerspective(80.0f,fAspect,1.0,120.0);
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
前后移动视口来对方向键作出响应的函数中改变的是深度值Z
if(key == GLUT_KEY_UP) viewZ += 0.5f;
if(key == GLUT_KEY_DOWN) viewZ -= 0.5f;
关闭渲染环境函数中删除纹理
glDeleteTextures(TEXTURE_COUNT, textures);

c、菜单栏选择
绑定纹理
void ProcessMenu(int value)
{
    GLint iLoop;
    
    for(iLoop = 0; iLoop < TEXTURE_COUNT; iLoop++)
    {
        // 绑定纹理
        glBindTexture(GL_TEXTURE_2D, textures[iLoop]);
        .......
        }
    }
    
    // 触发重画
    glutPostRedisplay();
}
配置纹理参数
// 0-缩小过滤器、最邻近过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

// 1-缩小过滤器、线性过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

// 2-缩小过滤器、选择最邻近的Mip层,并执行最邻近过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);

// 3-缩小过滤器、在Mip层之间执行线性插补,并执行最邻近过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);

// 4-缩小过滤器、选择最邻近Mip层,并执行线性过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);

// 5-缩小过滤器、在Mip层之间执行线性插补,并执行线性过滤,又称为三线性过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

// 6
// 设置各向异性过滤
GLfloat fLargest;
// 获取各向异性过滤的最大数量
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
// 设置纹理参数(各向异性采样)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);

// 7-设置各向同性过滤,数量为1.0表示(各向同性采样)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f);

d、设置数据源
void SetupRC()
{
    glClearColor(0.0f, 0.0f, 0.0f,1.0f);// 黑色的背景
    shaderManager.InitializeStockShaders();
    
    GLbyte *pBytes;
    GLint iWidth, iHeight, iComponents;
    GLenum eFormat;
    GLint iLoop;

    // 分配纹理对象
    glGenTextures(TEXTURE_COUNT, textures);
    
    // 循环设置纹理数组的纹理参数
    for(iLoop = 0; iLoop < TEXTURE_COUNT; iLoop++)
    {
        ......
    }
}

绑定纹理对象

glBindTexture(GL_TEXTURE_2D, textures[iLoop]);

加载tga文件

pBytes = gltReadTGABits(szTextureFiles[iLoop],&iWidth, &iHeight, &iComponents, &eFormat);

加载纹理、设置过滤器和包装模式

// 放大过滤器、最邻近过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// 缩小过滤器、最邻近过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// s轴环绕、环绕模式强制对范围之外的纹理坐标沿着合法的纹理单元的最后一行或一列进行采样
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
// t轴环绕、环绕模式强制对范围之外的纹理坐标沿着合法的纹理单元的最后一行或一列进行采样
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

载入纹理 glTexImage2D

glTexImage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBytes);

为纹理对象生成一组完整的mipmap

glGenerateMipmap(GL_TEXTURE_2D);

释放原始纹理数据,不在需要纹理原始数据了

free(pBytes);

设置图形上下左右各面的顶点/纹理坐标

GLfloat z;// 隧道的深度
floorBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);

// 地板墙面
for(z = 60.0f; z >= 0.0f; z -=10.0f)
{
    floorBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    floorBatch.Vertex3f(-10.0f, -10.0f, z);
    
    floorBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    floorBatch.Vertex3f(10.0f, -10.0f, z);
    
    floorBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
    floorBatch.Vertex3f(-10.0f, -10.0f, z - 10.0f);
    
    floorBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
    floorBatch.Vertex3f(10.0f, -10.0f, z - 10.0f);
}
floorBatch.End();

// 天花板墙面
......

// 左面墙面
.....

// 右面墙面
.....

e、渲染场景
void RenderScene(void)
{
    // 用当前清除色,清除窗口
    glClear(GL_COLOR_BUFFER_BIT);
    
    // 模型视图压栈
    modelViewMatrix.PushMatrix();

    ......

    // 模型视图出栈
    modelViewMatrix.PopMatrix();
    
    // 缓存区交换
    glutSwapBuffers();
}

在Z轴上平移viewZ距离

modelViewMatrix.Translate(0.0f, 0.0f, viewZ);
纹理替换矩阵着色器
  • 参数1:GLT_SHADER_TEXTURE_REPLACE(着色器标签)
  • 参数2:模型视图投影矩阵
  • 参数3:纹理层
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformPipeline.GetModelViewProjectionMatrix(), 0);

绑定纹理

glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_FLOOR]);
floorBatch.Draw();

glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_CEILING]);
ceilingBatch.Draw();

glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_BRICK]);
leftWallBatch.Draw();

rightWallBatch.Draw();

3、绘制自动旋转的球体世界

a、使用到的属性

添加附加随机球

#define NUM_SPHERES 50
GLFrame spheres[NUM_SPHERES];

批处理容器

GLTriangleBatch        torusBatch;             // 花托批处理
GLBatch                floorBatch;             // 地板批处理
GLTriangleBatch     sphereBatch;            //公转球的批处理

照相机角色帧

GLFrame             cameraFrame;

纹理标记数组

GLuint uiTextures[3];

b、设置数据源
void SetupRC()
{
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    shaderManager.InitializeStockShaders();
    .....
}

开启深度测试/背面剔除

glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);

设置大球、小球(公转自转)

gltMakeSphere(torusBatch, 0.4f, 40, 80);
gltMakeSphere(sphereBatch, 0.1f, 26, 13);

设置地板顶点数据&地板纹理

GLfloat texSize = 10.0f;
floorBatch.Begin(GL_TRIANGLE_FAN, 4,1);

floorBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
floorBatch.Vertex3f(-20.f, -0.41f, 20.0f);

floorBatch.MultiTexCoord2f(0, texSize, 0.0f);
floorBatch.Vertex3f(20.0f, -0.41f, 20.f);

floorBatch.MultiTexCoord2f(0, texSize, texSize);
floorBatch.Vertex3f(20.0f, -0.41f, -20.0f);

floorBatch.MultiTexCoord2f(0, 0.0f, texSize);
floorBatch.Vertex3f(-20.0f, -0.41f, -20.0f);

floorBatch.End();

随机小球顶点坐标数据

for (int i = 0; i < NUM_SPHERES; I++)
{
    GLfloat x = ((GLfloat)((rand() % 400) - 200 ) * 0.1f);
    GLfloat z = ((GLfloat)((rand() % 400) - 200 ) * 0.1f);
    spheres[i].SetOrigin(x, 0.0f, z);
}

命名纹理对象

glGenTextures(3, uiTextures);

将TGA文件加载为2D纹理

glBindTexture(GL_TEXTURE_2D, uiTextures[0]);
LoadTGATexture("marble.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT);

glBindTexture(GL_TEXTURE_2D, uiTextures[1]);
LoadTGATexture("marslike.tga", GL_LINEAR_MIPMAP_LINEAR,
               GL_LINEAR, GL_CLAMP_TO_EDGE);

glBindTexture(GL_TEXTURE_2D, uiTextures[2]);
LoadTGATexture("moonlike.tga", GL_LINEAR_MIPMAP_LINEAR,
               GL_LINEAR, GL_CLAMP_TO_EDGE);

c、绘制地板和球
void drawSomething(GLfloat yRot)
{
}
定义光源位置&漫反射颜色
static GLfloat vWhite[] = { 1.0f, 1.0f, 1.0f, 1.0f };
static GLfloat vLightPos[] = { 0.0f, 3.0f, 0.0f, 1.0f };
绘制悬浮小球
glBindTexture(GL_TEXTURE_2D, uiTextures[2]);
for(int i = 0; i < NUM_SPHERES; I++)
{
    modelViewMatrix.PushMatrix();
    modelViewMatrix.MultMatrix(spheres[I]);
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,
                                 modelViewMatrix.GetMatrix(),
                                 transformPipeline.GetProjectionMatrix(),
                                 vLightPos,
                                 vWhite,
                                 0);
    sphereBatch.Draw();
    modelViewMatrix.PopMatrix();
}

// 绘制大球
........

// 绘制公转自转小球
.......

d、渲染场景
void RenderScene(void)
{
    ......
}
添加反光效果
// 压栈(镜面)
modelViewMatrix.PushMatrix();

// 翻转Y轴
modelViewMatrix.Scale(1.0f, -1.0f, 1.0f);
// 镜面世界围绕Y轴平移一定间距
modelViewMatrix.Translate(0.0f, 0.8f, 0.0f);

// 指定顺时针为正面
glFrontFace(GL_CW);
// 绘制地面以外其他部分(镜面)
drawSomething(yRot);
// 恢复为逆时针为正面
glFrontFace(GL_CCW);

// 绘制镜面,恢复矩阵
modelViewMatrix.PopMatrix();
开启混合功能(绘制地板)
glEnable(GL_BLEND);
// 指定glBlendFunc 颜色混合方程式
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 绑定地面纹理
glBindTexture(GL_TEXTURE_2D, uiTextures[0]);
// 纹理调整着色器(将一个基本色乘以一个取自纹理的单元nTextureUnit的纹理)
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_MODULATE,
                             transformPipeline.GetModelViewProjectionMatrix(),
                             vFloorColor,
                             0);
// 开始绘制
floorBatch.Draw();
// 取消混合
glDisable(GL_BLEND);

Demo

Demo在我的Github上,欢迎下载。
Multi-MediaDemo

参考文献

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容