前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OpenGL(九)-- 综合案例(公、自转)OpenGL(九)-- 综合案例(公、自转)

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

作者头像
用户8893176
发布2021-08-09 14:34:18
1.1K0
发布2021-08-09 14:34:18
举报
文章被收录于专栏:小黑娃Henry

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

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

总和案例.gif

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

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

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

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

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

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

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

//纹理标记数组
//纹理对象
GLuint texture[3];
  • 创建球、地板模型
代码语言:javascript
复制
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通过该方法来映射地板的纹理坐标
  • 绑定纹理对象
代码语言:javascript
复制
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)
代码语言:javascript
复制
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没有对象的概念,是面向过程的编程方式,根据代码执行的顺序完成赋值操作。
  • 绘制基本设置
代码语言:javascript
复制
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中矩阵的变换会有详细的解释。
绘制自转大球
代码语言:javascript
复制
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,保证小球的设置正确。
绘制公转小球
代码语言:javascript
复制
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轴的移动会按照向量方向进行。 如果调整这两句代码的顺序就会得到这样的结果:
代码语言:javascript
复制
    modelViewMatrix.Translate(0.8f, 0, 0);
    modelViewMatrix.Rotate(yRot * -2.0f, 0, 1, 0);
  • 先移动并不会改变物体的方向向量,也就无法完成公转的效果。
绘制镜面部分
代码语言:javascript
复制
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();
}
绘制上方物体部分
代码语言:javascript
复制
void RenderScene(void) {
    drawSomething(yRot);
}
地板
代码语言:javascript
复制
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);
}
  • 镜面效果其实就是对上方物体在对称位置在绘制一份,地板为半透明才可以看到下方镜面物体。
最后
代码语言:javascript
复制
void RenderScene(void) {
    //将之前设置的观察者矩阵出栈
    modelViewMatrix.PopMatrix();
    //交换缓冲区
    glutSwapBuffers();
    //强制重绘
    glutPostRedisplay();
}
  • 出栈同样是为了保证下次设置,不会受这次设置的影响。
  • 强制重绘来实现所见的动画效果,而不是定时器实现的。
完整的代码见github- 综合案例(公、自转)
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/7/20 上,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • OpenGL(九)-- 综合案例(公、自转)
    • 绘制自转大球
      • 绘制公转小球
        • 绘制镜面部分
          • 绘制上方物体部分
            • 地板
              • 最后
                • 完整的代码见github- 综合案例(公、自转)
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档