专栏首页小黑娃HenryOpenGL(九)-- 综合案例(公、自转)OpenGL(九)-- 综合案例(公、自转)

OpenGL(九)-- 综合案例(公、自转)OpenGL(九)-- 综合案例(公、自转)

OpenGL(九)-- 综合案例(公、自转)

相信学习过OpenGL的同学应该过玩过这个经典案例:

总和案例.gif

通过观察这个案例中有三部分:

  1. 地板
  2. 自转大球
  3. 公转小球

这篇文章中会省略一部分基本的初始化代码,而且代码都是按模块进行了分割,如果想要了解可以去另一篇文章中了解一下OpenGL (三)--一个"HelloWorld"的执行全过程,也可以直接下载源码来看github

下面就按照这个顺序贴上代码+个人理解:

  • 全局属性
//投影视图矩阵堆栈
GLMatrixStack projectMatrix;
//模型视图矩阵堆栈
GLMatrixStack modelViewMatrix;
//mvp变换管道
GLGeometryTransform transform;
//固定着色器管理类
GLShaderManager shaderManager;

//参考帧
//观察者帧
GLFrame cameraFrame;

//批次类
//地板
GLBatch floorBatch;
//大球
GLTriangleBatch sphereBatch;
//小球
GLTriangleBatch sphereSmallBatch;

//纹理标记数组
//纹理对象
GLuint texture[3];
  • 创建球、地板模型
void setupRC() {
    //地板
    //使用三角形图元装配
    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();
    
    //大球
    //iSlices 将图形分为多少片
    //iStacks 将每一层分为多少的图元三角形
    //这两个参数越大图像越细腻;一般规律iStacks是iSlices的两倍
    gltMakeSphere(sphereBatch, 0.4f, 40, 80);
    
    //小球
    gltMakeSphere(sphereSmallBatch, 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);
        
        spheresLocation[i].SetOrigin(x, 0.0f, z);
    }
}
  • 球通过OpenGL提供的固定模型来创建,纹理坐标是系统来创建
  • 地板通过三角形图元装配来来创建
  • MultiTexCoord2f通过该方法来映射地板的纹理坐标
  • 绑定纹理对象
void setupRC() {
    //绑定纹理对象
    glGenTextures(3, texture);
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    //9.将TGA文件加载为2D纹理。
    //参数1:纹理文件名称
    //参数2&参数3:需要缩小&放大的过滤器
    //参数4:纹理坐标环绕模式
    LoadTGATexture("marble.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT);
}
  • 项目涉及到3个纹理,所以创建3个纹理对象保存到texture数组中。
  • 绑定纹理(LoadTGATexture)
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
    GLbyte *pBits;
    int iWidth,iHeight,iComponents;
    GLenum eFormat;
    
    //1.读取纹理数据
    pBits = gltReadTGABits(szFileName, &iWidth, &iHeight, &iComponents, &eFormat);
    if (pBits == NULL) {return false;}
    
    //参数1:纹理维度
    //参数2:线性过滤
    //参数3:wrapMode,环绕模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    
    //2、设置纹理参数
    //参数1:纹理维度
    //参数2:为S/T坐标设置模式
    //参数3:wrapMode,环绕模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
    
    //3.载入纹理
    //参数1:纹理维度
    //参数2:mip贴图层次
    //参数3:纹理单元存储的颜色成分(从读取像素图是获得)-将内部参数nComponents改为了通用压缩纹理格式GL_COMPRESSED_RGB
    //参数4:加载纹理宽
    //参数5:加载纹理高
    //参数6:加载纹理的深度
    //参数7:像素数据的数据类型(GL_UNSIGNED_BYTE,每个颜色分量都是一个8位无符号整数)
    //参数8:指向纹理图像数据的指针
    glTexImage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBits);
    free(pBits);
    
    //只有minFilter 等于以下四种模式,才可以生成Mip贴图
    //GL_NEAREST_MIPMAP_NEAREST具有非常好的性能,并且闪烁现象非常弱
    //GL_LINEAR_MIPMAP_NEAREST常常用于对游戏进行加速,它使用了高质量的线性过滤器
    //GL_LINEAR_MIPMAP_LINEAR 和GL_NEAREST_MIPMAP_LINEAR 过滤器在Mip层之间执行了一些额外的插值,以消除他们之间的过滤痕迹。
    //GL_LINEAR_MIPMAP_LINEAR 三线性Mip贴图。纹理过滤的黄金准则,具有最高的精度。
    if(minFilter == GL_LINEAR_MIPMAP_LINEAR ||
       minFilter == GL_LINEAR_MIPMAP_NEAREST ||
       minFilter == GL_NEAREST_MIPMAP_LINEAR ||
       minFilter == GL_NEAREST_MIPMAP_NEAREST)
    //4.加载Mip,纹理生成所有的Mip层
    //参数:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
    glGenerateMipmap(GL_TEXTURE_2D);
    
    return true;
}
  • 当前载入的纹理保存到当前绑定的纹理对象中。时刻记着:OpenGL是一个巨大的状态机。OpenGL没有对象的概念,是面向过程的编程方式,根据代码执行的顺序完成赋值操作。
  • 绘制基本设置
void RenderScene(void) {
    // 重置颜色、深度缓存区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    // 定时器
    static CStopWatch rotTimer;
    // 旋转角度
    float yRot = rotTimer.GetElapsedSeconds()*60.0f;
    
    // 观察者矩阵压栈
    M3DMatrix44f cameraMatrix;
    cameraFrame.GetCameraMatrix(cameraMatrix);
    modelViewMatrix.PushMatrix(cameraMatrix);
}
  • 定时器纯粹是为了记录累计时间,并不会导致动画(重绘)。
  • 旋转角度会根据时间一直增加,物体的旋转其实每次都是从起始位置重新计算并渲染的,这一点和iOS中的动画还是有一些区别。
  • 对于压栈、出栈在OpenGL(五)-- OpenGL中矩阵的变换会有详细的解释。

绘制自转大球

void drawSomething(GLfloat yRot){
    //压栈
    modelViewMatrix.PushMatrix();
    //旋转
    modelViewMatrix.Rotate(yRot, 0, 1, 0);
    //绑定对应的纹理
    glBindTexture(GL_TEXTURE_2D, texture[1]);
    //使用纹理-光源着色器绘制
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, modelViewMatrix.GetMatrix(), transform.GetProjectionMatrix(), vLightPos, vWhite, 0);
    sphereBatch.Draw();
    modelViewMatrix.PopMatrix();
}
  • 在3D模型中自转是根据y轴旋转的所以是:(角度, x, y, x) -> (yRot, 0, 1, 0)
  • 因为旋转是针对大球来设置的,所以设置后需要将大球的设置从modelViewMatrix进行PopMatrix,保证小球的设置正确。

绘制公转小球

void drawSomething(GLfloat yRot){
    //压栈
    modelViewMatrix.PushMatrix();
    //先旋转在移动
    modelViewMatrix.Rotate(yRot * -2.0f, 0, 1, 0);
    modelViewMatrix.Translate(0.8f, 0, 0);
    //绑定对应的纹理
    glBindTexture(GL_TEXTURE_2D, texture[2]);
    //使用固定着色器-光源着色器绘制
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, modelViewMatrix.GetMatrix(), transform.GetProjectionMatrix(), vLightPos, vWhite, 0);
    sphereSmallBatch.Draw();
    modelViewMatrix.PopMatrix();
}
  • 小球先旋转,旋转后导致物体的方向向量的角度也发生了变化,所以x轴的移动会按照向量方向进行。 如果调整这两句代码的顺序就会得到这样的结果:
    modelViewMatrix.Translate(0.8f, 0, 0);
    modelViewMatrix.Rotate(yRot * -2.0f, 0, 1, 0);
  • 先移动并不会改变物体的方向向量,也就无法完成公转的效果。

绘制镜面部分

void RenderScene(void) {
    //绘制镜面
    modelViewMatrix.PushMatrix();
    //按照Y轴翻转
    modelViewMatrix.Scale(1.0f, -1.0f, 1.0f);
    //翻转后物体向量也会反向,所以0.8是向下移动
    modelViewMatrix.Translate(0, 0.8, 0);
    
    //指定顺时针为正面
    //由于沿y轴旋转,为了保证绘制正常,需要重新定义正反面
    glFrontFace(GL_CW);
    drawSomething(yRot);
    glFrontFace(GL_CCW);
    
    modelViewMatrix.PopMatrix();
}

绘制上方物体部分

void RenderScene(void) {
    drawSomething(yRot);
}

地板

void RenderScene(void) {
    //开启混合功能(绘制地板)
    glEnable(GL_BLEND);
    //指定glBlendFunc 颜色混合方程式
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    //绑定创建好的第一个纹理对象用于绘制
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    
    //半透明色,保证可以看到镜面效果
    static GLfloat vFloorColor[] = { 1.0f, 1.0f, 0.0f, 0.75f};
/*
    纹理调整着色器(将一个半透明基本色乘以一个取自纹理的单元texture的纹理,达到透明效果)
    参数1:GLT_SHADER_TEXTURE_MODULATE
    参数2:模型视图投影矩阵
    参数3:颜色
    参数4:纹理单元(第0层的纹理单元)
    */
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_MODULATE, transform.GetModelViewProjectionMatrix(), vFloorColor,0);
    floorBatch.Draw();
    glDisable(GL_BLEND);
}
  • 镜面效果其实就是对上方物体在对称位置在绘制一份,地板为半透明才可以看到下方镜面物体。

最后

void RenderScene(void) {
    //将之前设置的观察者矩阵出栈
    modelViewMatrix.PopMatrix();
    //交换缓冲区
    glutSwapBuffers();
    //强制重绘
    glutPostRedisplay();
}
  • 出栈同样是为了保证下次设置,不会受这次设置的影响。
  • 强制重绘来实现所见的动画效果,而不是定时器实现的。

完整的代码见github- 综合案例(公、自转)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 音视频开发之旅(42)-光照基础(一)

    环境光(Ambient Lightiing)不来自任何特定方向的光,在经典光照模型中会用一个常量来表示 使用时只需要对其片源着色器添加一个环境光常量,作为gl_...

    音视频开发之旅
  • 从零开始学图像渲染

    图形学是一门综合学科,涉及的基础学科内容繁多,多用于跨领域的工程应用,比如传统的图像处理、游戏引擎,现在比较热门的图像分割、人脸识别、无人驾驶、AR/VR、三维...

    sumsmile
  • 推荐几个堪称教科书级别的 Android 音视频入门项目

    目前,市面上关于音视频学习的相关书籍并不多,而且即使看了书籍学了理论,最终还是要回归到代码上来。

    音视频开发进阶
  • 音视频开发之旅(37) -FFmpeg + OpenGLES 边解码边播放视频(一)

    视频是由一幅幅图像或者说一帧帧 YUV 数据组成 表示图片、视频的色彩空间有几种:YUV、RGB、HSV等,FFmpeg解码后的视频数据是YUV数据,而Open...

    音视频开发之旅
  • 为什么EDA软件对芯片设计如此重要?

    对于系统厂商而言,如果说芯片是子弹,是粮食的话,那么芯片EDA工具则是制造子弹,加工粮食的工具,其重要性可见一斑。

    数字芯片社区
  • 音视频开发之旅(43)-光照基础(二)

    上一篇我们学习实践了关照基础的基本概念和立方体的实现。有不清楚的可以先进行回看。 这篇我们来光照基础的具体实践

    音视频开发之旅
  • OpenGL ES 2.0 Using Modern Mobile Graphics Hardware

    四. 问题:CPU 和 GPU 的 Memory 是有数据交换的,这种交换不会出问题吗?CPU 和 GPU 的计算速度一样吗?

    半纸渊
  • 【专业技术】OpenGL操作技巧介绍

    存在问题: opengl中如何渲染管线? 解决方案: 绝大数OpenGL实现都有相似的操作顺序,一系列相关的处理阶段称为OpenGL渲染管线。图1-2显示了这些...

    程序员互动联盟
  • 全平台硬件解码渲染方法与优化实践

    大家好,我是来自PPTV的王斌。接下来我将围绕以下几个话题,为大家分享有关全平台硬件解码的渲染与优化的实践经验。

    LiveVideoStack
  • Android开发笔记(一百五十四)OpenGL的画笔工具GL10

    上一篇文章介绍了OpenGL绘制三维图形的流程,其实没有传说中的那么玄乎,只要放平常心把它当作一个普通控件就好了,接下来继续介...

    用户4464237
  • FreeGlut配置简介

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wpxu08/article/details/8778...

    步行者08
  • 音视频技术开发周刊 | 147

    WebRTC是一个非常新的技术,很多用户仍然在初步摸索阶段。有一些用户是不清楚WebRTC的用户场景,不知道WebRTC究竟可以使用在哪些应用场景中。

    LiveVideoStack
  • OpenGL ES 学习资源分享

    学习了一段时间的 OpenGL ES,并在公司的项目中得到了运用,也算是有了一些积累,现在分享一些当初学习的资源,大家一起来学习,共同交流进步。

    音视频开发进阶
  • 移动直播技术知多少:基础原理解析 & 腾讯云直播接入

    2020 年,由于新型冠状病毒疫情的爆发,视频直播互动更是一飞冲天,在网购、游戏、教育、金融等等方面都呈现爆发式发展。

    开发的猫
  • Android开发笔记(序)写在前面的目录

    一方面写写自己走过的弯路掉进去的坑,避免以后再犯;另一方面希望通过分享自己的经验教训,与网友互相切磋,从而去芜存菁进一步提升自己的水平。因此博主就想,入门的东西...

    用户4464237
  • iOS图形处理概论:OpenGL ES,Metal,Core Graphics,Core Image,GPUImage,Scene Kit (3D) ,Sprite Kit (2D),OpenCV

    对于刚接触iOS图形相关框架的小白,有一些图形框架在字面上和功能上非常容易混淆。这里旨在总结一下各种框架,区分它们的概念和功能,以作日后进一步细分学习的指引。因...

    陈满iOS
  • 视频直播与虚拟现实的渲染 - OpenGL ES

    这是一篇OpenGL ES的学习笔记,介绍图像绘制里面用到的概念,学习OpenGL ES的基础知识备忘录。 教程 OpenGLES入门教程1-Tutorial0...

    落影
  • ubuntu18.04 安装qt5.12.8及环境配置的详细教程

    下载地址http://download.qt.io/archive/qt/5.12/5.12.8/

    砸漏
  • 大数据周周看 | 360云盘业务叫停,究竟谁才是这个时代的网盘“刽子手”?

    <数据猿导读> 国际视通与星云纵横推出新闻大数据平台DataCloud;UIC“牵手”中国联通打造智慧校园,共建大数据产学研实验室;大数据拉抬风控门槛,易联支付...

    数据猿

扫码关注云+社区

领取腾讯云代金券