OpenGL初探

第二十一节—纹理填充卫星图

2020-09-07  本文已影响0人  L_Ares

本文为L_Ares个人写作,包括图片皆为个人亲自操作,以任何形式转载请表明原文出处。

本节是对第十五节的卫星图代码进行了纹理的填充,加上了镜面效果,算是对之前所学习的重点知识的综合复习。


//
//  main.cpp
//  11球体
//
//  Created by EasonLi on 2020/9/6.
//  Copyright © 2020 EasonLi. All rights reserved.
//

#include <stdio.h>

#pragma mark - 引用类

#include "GLTools.h"
#include "GLShaderManager.h"
#include "GLFrame.h"
#include "GLFrustum.h"
#include "GLBatch.h"
#include "GLMatrixStack.h"
#include "GLGeometryTransform.h"
#include "StopWatch.h"
#include "math3d.h"

#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif

#pragma mark - 常用变量
//着色器管理器
GLShaderManager shaderManager;
//视景体
GLFrustum viewFrustum;
//观察者(视点/摄像机)参考帧
GLFrame cameraFrame;
//模型视图矩阵堆栈
GLMatrixStack modelViewMatrixStack;
//投影矩阵堆栈
GLMatrixStack projectionMatrixStack;
//变换管道
GLGeometryTransform transformPipeLine;
//地板批次类
GLBatch floorBatch;
//小球批次类
GLTriangleBatch sBallBatch;
//大球批次类
GLTriangleBatch lBallBatch;
//小球个数
#define SMALL_BALL_COUNT 50
//纹理数量
#define TEXTURE_COUNT 3
//小球坐标数组
GLFrame smallBallFrame[SMALL_BALL_COUNT];
//纹理标记数组
GLuint nTextures[TEXTURE_COUNT];

//点光源位置
static GLfloat nLightPos[] = {0.f,3.f,0.f,1.f};
//漫反射颜色
static GLfloat nWhite[] = {1.f,1.f,1.f,1.f};
//地板颜色数组
static GLfloat nfloorColor[] = {1.f,1.f,0.f,0.75f};


#pragma mark - 函数
//初建或重塑窗口
void ChangeSize(int w , int h)
{
    
    //避免除数为0
    if (h == 0) {
        h = 1;
    }
    
    //设置视口大小
    glViewport(0, 0, w, h);
    
    //设置投影方式和参数
    viewFrustum.SetPerspective(36.f, float(w)/float(h), 1.f, 120.f);
    
    //获取投影矩阵并放置到投影矩阵堆栈栈顶
    projectionMatrixStack.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    //设置变换管道
    transformPipeLine.SetMatrixStacks(modelViewMatrixStack, projectionMatrixStack);
    
}

//从TGA文件读取纹理、加载纹理、设置纹理
bool LoadTextureFromTGA (const char *fileName , GLenum minFilter , GLenum magFilter , GLenum wrapMode)
{
    
    //定义字节类型变量,用来存储从tga文件读取出来的纹理数据
    GLbyte *nByte;
    //定义变量,用来存储从tga文件中读取出的纹理的各项属性
    int nWidth,nHeight,nComponent;
    GLenum nFormat;
    
    //从tga文件中读取纹理
    nByte = gltReadTGABits(fileName, &nWidth, &nHeight, &nComponent, &nFormat);
    
    if (nByte == NULL) {
        printf("没有纹理数据");
        return false;
    }
    
    //设置纹理属性
    //设置纹理过滤器
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    
    //设置纹理环绕模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
    
    //载入纹理
    glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, nWidth, nHeight, 0, nFormat, GL_UNSIGNED_BYTE, nByte);
    
    //使用完了纹理数据就可以释放了
    free(nByte);
    
    //生成Mipmap贴图
    if(minFilter == GL_LINEAR_MIPMAP_LINEAR ||
    minFilter == GL_LINEAR_MIPMAP_NEAREST ||
    minFilter == GL_NEAREST_MIPMAP_LINEAR ||
    minFilter == GL_NEAREST_MIPMAP_NEAREST)
        
    glGenerateMipmap(GL_TEXTURE_2D);
    
    return true;
    
}

//设置地板
void SetFloor ()
{
    
    //参数
    //(1).图元绘制方式
    //(2).顶点数量
    //(3).纹理数组数量
    floorBatch.Begin(GL_TRIANGLE_FAN, 4,1);
    
    //设置纹理坐标和顶点坐标
    //这里将纹理坐标扩大了,属于特殊的情况,因为tga纹理有点小,但是地板有点大,所以放
    //大一点
    floorBatch.MultiTexCoord2f(0, 0.f, 0.f);
    floorBatch.Vertex3f(-20.f, -0.5f, 20.f);
    
    floorBatch.MultiTexCoord2f(0, 10.f, 0.f);
    floorBatch.Vertex3f(20.f, -0.5f, 20.f);
    
    floorBatch.MultiTexCoord2f(0, 10.f, 10.f);
    floorBatch.Vertex3f(20.f, -0.5f, -20.f);
    
    floorBatch.MultiTexCoord2f(0, 0.f, 10.f);
    floorBatch.Vertex3f(-20.f, -0.5f, -20.f);
    floorBatch.End();
    
}


//设置渲染环境
void SetUpRC ()
{
    
    //设置清屏颜色
    glClearColor(0.f, 0.f, 0.f, 1.f);
    //初始化着色器管理器
    shaderManager.InitializeStockShaders();
    
    //开启正背面剔除和深度测试
    glEnable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);
    
/********************************设置地板*********************************/
    //设置地板顶点和纹理顶点数据
    SetFloor();
    //生成纹理标记,分配纹理对象
    glGenTextures(3, nTextures);
    //绑定纹理对象(地板)
    glBindTexture(GL_TEXTURE_2D, nTextures[0]);
    //读取纹理、设置纹理、载入纹理
    LoadTextureFromTGA("Marble.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT);
/********************************设置大球*********************************/
    //设置大球属性
    gltMakeSphere(lBallBatch, 0.5f, 60, 120);
    //绑定纹理对象(大球)
    glBindTexture(GL_TEXTURE_2D, nTextures[1]);
    //读取纹理、设置纹理、载入纹理
    LoadTextureFromTGA("Marslike.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
/********************************设置公转小球*********************************/
    //设置公转小球属性
    gltMakeSphere(sBallBatch, 0.2f, 36, 72);
    //绑定纹理对象(公转小球)
    glBindTexture(GL_TEXTURE_2D, nTextures[2]);
    //读取纹理、设置纹理、载入纹理
    LoadTextureFromTGA("MoonLike.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
/********************************设置随机位置小球*********************************/
    for (int i = 0; i < SMALL_BALL_COUNT; i++) {
        
        GLfloat x = ((rand() % 400) - 200) * 0.1f;
        GLfloat z = ((rand() % 400) - 200) * 0.1f;
        
        smallBallFrame[i].SetOrigin(x, 0.f, z);
        
    }
    
}

//绘制除地板以外的其他图形
void DrawOthers (GLfloat yRot)
{

/********************************绘制随机位置小球*********************************/
    glBindTexture(GL_TEXTURE_2D, nTextures[2]);
    for (int i = 0; i < SMALL_BALL_COUNT; i++) {
        modelViewMatrixStack.PushMatrix();
        modelViewMatrixStack.MultMatrix(smallBallFrame[i]);
        shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,transformPipeLine.GetModelViewMatrix(),transformPipeLine.GetProjectionMatrix(),nLightPos,nWhite,0);
        sBallBatch.Draw();
        modelViewMatrixStack.PopMatrix();
    }
    
/********************************绘制大球*********************************/
    //先将大球从观察者位置移动一个距离,不然最开始就看不到整体的大球了
    modelViewMatrixStack.Translate(0.f, 0.2f, -2.5f);
    //压栈,之所以在移动了大球的位置后压栈,是因为公转小球要围绕着大球,公转小球的位置是要相对于大球位置,而不是观察者
    modelViewMatrixStack.PushMatrix();
    //大球围绕y轴做旋转
    modelViewMatrixStack.Rotate(yRot, 0.f, 1.f, 0.f);
    //绑定一下大球的纹理
    glBindTexture(GL_TEXTURE_2D, nTextures[1]);
    //设置着色器
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,transformPipeLine.GetModelViewMatrix(),transformPipeLine.GetProjectionMatrix(),nLightPos,nWhite,0);
    //绘制大球
    lBallBatch.Draw();
    //绘制完大球就出栈
    modelViewMatrixStack.PopMatrix();
/********************************绘制公转小球*********************************/
    //压栈
    modelViewMatrixStack.PushMatrix();
    //让小球旋转的速度变成2倍,先旋转再平移,不然是没有公转效果的,因为平移是物体坐标系在世界坐标系中平移
    modelViewMatrixStack.Rotate(-2 * yRot, 0.f, 1.f, 0.f);
    //相对大球再在x轴多平移出来一些距离,这样才能看到小球
    modelViewMatrixStack.Translate(0.8f, 0.f, 0.f);
    glBindTexture(GL_TEXTURE_2D, nTextures[2]);
    //设置着色器
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,transformPipeLine.GetModelViewMatrix(),transformPipeLine.GetProjectionMatrix(),nLightPos,nWhite,0);
    sBallBatch.Draw();
    modelViewMatrixStack.PopMatrix();
    
    
}

//渲染
void RenderScene ()
{
    
    //定义定时器
    static CStopWatch nTimer;
    //获取每秒的旋转角度
    GLfloat yRot = nTimer.GetElapsedSeconds() * 60.f;
    
    //清空缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    //压栈
    modelViewMatrixStack.PushMatrix();
    //定义一个矩阵变量,用以获取观察者矩阵
    M3DMatrix44f mCamera;
    //获取观察者矩阵并放入矩阵变量中
    cameraFrame.GetCameraMatrix(mCamera);
    //将观察者矩阵变化放入模型视图矩阵堆栈栈顶
    modelViewMatrixStack.MultMatrix(mCamera);
    
/********************************绘制镜面部分*********************************/
    //镜面其实就是画了两遍,只不过相对于x和z轴构成的平面是对称的,所以要将Y轴的正方向旋转180度
    //为了让镜面和非镜面的旋转方向是一样的,可以把正背面的顺时针逆时针调换,因为球体的背面旋转和正面旋转刚好是相反的
    //所以,先压栈
    modelViewMatrixStack.PushMatrix();
    //然后旋转Y轴180度,可以稍微再做下缩放,这样更逼真
    modelViewMatrixStack.Scale(1.f, -0.9f, 1.f);
    //让镜面往下面移动多一点,不然和非镜面的图形会重叠
    modelViewMatrixStack.Translate(0.f, 1.2f, 0.f);
    //设置顺时针旋转为正面
    glFrontFace(GL_CW);
    //绘制镜面
    DrawOthers(yRot);
    //绘制完成把正面换回顺时针
    glFrontFace(GL_CCW);
    //出栈
    modelViewMatrixStack.PopMatrix();
    
/********************************绘制地板部分*********************************/
    //开启混合
    glEnable(GL_BLEND);
    //指定颜色混合方程式
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    //绑定一下纹理,因为绘制的不只是一个图形,避免图形上的纹理出现错误
    glBindTexture(GL_TEXTURE_2D, nTextures[0]);
    
    //设置着色器管理器
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_MODULATE,transformPipeLine.GetModelViewProjectionMatrix(),nfloorColor,0);
    
    //绘制地板
    floorBatch.Draw();
    
    //绘制完成,取消混合
    glDisable(GL_BLEND);
    
/********************************绘制非镜面部分*********************************/
    //绘制其他部分(非镜面)
    DrawOthers(yRot);
/****************************************************************************/
    //绘制完成,出栈
    modelViewMatrixStack.PopMatrix();
    
    //交换缓冲区
    glutSwapBuffers();
    
    //因为球每秒都要旋转,所以发送渲染通知
    glutPostRedisplay();
    
}

//特殊键位
void SpecialKeys (int key , int x , int y)
{
    
    //每次前后移动的距离
    GLfloat nStep = 0.1f;
    //每次旋转的角度
    GLfloat nRot = (GLfloat)(m3dDegToRad(5.f));
    switch (key) {
        case GLUT_KEY_UP:
            cameraFrame.MoveForward(nStep);
            break;
            
        case GLUT_KEY_DOWN:
            cameraFrame.MoveForward(-nStep);
            break;
        case GLUT_KEY_LEFT:
            cameraFrame.RotateWorld(nRot, 0.f, 1.f, 0.f);
            break;
        case GLUT_KEY_RIGHT:
            cameraFrame.RotateWorld(-nRot, 0.f, 1.f, 0.f);
            break;
    }
    
}

//关闭纹理
void ShutdownTexture ()
{
    
    glDeleteTextures(TEXTURE_COUNT, nTextures);
    
}

#pragma mark - main
int main (int argc,char *argv[])
{
    
    //设置工作目录和项目目录在同一文件夹下
    gltSetWorkingDirectory(argv[0]);
    
    //初始化glut
    glutInit(&argc, argv);
    
    //初始化显示模式
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_STENCIL | GLUT_DEPTH);
    
    //初始化窗口尺寸
    glutInitWindowSize(600, 600);
    
    //创建窗口并命名
    glutCreateWindow("球体");
    
    //注册函数
    //重塑函数
    glutReshapeFunc(ChangeSize);
    //渲染函数
    glutDisplayFunc(RenderScene);
    //特殊键位
    glutSpecialFunc(SpecialKeys);
    
    //初始化Glew库
    GLenum status = glewInit();
    if (status != GLEW_OK) {
        printf("glew init error : %s \n",glewGetErrorString(status));
        return 1;
    }
    
    //设置渲染环境
    SetUpRC();
    
    //建立消息循环
    glutMainLoop();
    
    //关闭纹理
    ShutdownTexture();
    
    return 0;
    
}


效果图如图1.1所示:

1.1.png

纹理图网盘链接,里面M开头的三个文件就是这节要用到的纹理图tga,提取码uhtv

上一篇下一篇

猜你喜欢

热点阅读