第十八节—纹理金字塔

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

首先介绍几个函数,下面要用到的。

1. 根据三个点寻找法线:

void m3dFindNormal(M3DVector3f result, const M3DVector3f point1, const M3DVector3f point2, const M3DVector3f point3);

参数:

(1).result:找到的法线结果。
(2).(3).(4).point:用来找法线的三个点。这三个点是要有顺序的,正面是逆时针排列,背面是顺时针排列。

2. 设置法线:

void Normal3fv(M3DVector3f vNormal);

参数:

vNormal:法线坐标数组

3. 设置纹理坐标:

void MultiTexCoord2f(GLuint texture, GLclampf s, GLclampf t);

参数:

(1).texture:纹理的mimap涂层数,也就是你要设置哪个纹理的纹理坐标,填入纹理的顺序数,我们这节只有一个纹理,所以这个纹理就是第1个,所以写0。

(2).s: 纹理的横坐标对应的是多少。

(2).t:纹理的纵坐标对应的是多少。

4. 设置顶点坐标:

void Vertex3fv(M3DVector3f vVertex);

参数:

vVertex:顶点坐标数组

代码如下:


//
//  main.cpp
//  09纹理金字塔
//
//  Created by EasonLi on 2020/9/2.
//  Copyright © 2020 EasonLi. All rights reserved.
//

#include <stdio.h>

#pragma mark - 引用类

//工具类GLTools,GLTool中大多数类似于C语言的工具类都在其中
#include "GLTools.h"
//着色器管理器
#include "GLShaderManager.h"
//参考帧
#include "GLFrame.h"
//矩阵投影工具类
#include "GLFrustum.h"
//容器类。三角形批次类
#include "GLBatch.h"
//矩阵堆栈类。
#include "GLMatrixStack.h"
//管道类。管理mvp矩阵的
#include "GLGeometryTransform.h"
//3D数学类
#include "math3d.h"

//根据系统引入GLUT
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif

#pragma mark - 公共变量

//着色器管理器
GLShaderManager shaderManager;
//三角形批次类
GLBatch pyramidBatch;
//观察者参考帧
GLFrame cameraFrame;
//物体参考帧
GLFrame objFrame;
//模型视图矩阵堆栈
GLMatrixStack mvMatrixStack;
//投影矩阵堆栈
GLMatrixStack projMatrixStack;
//矩阵投影工具对象
GLFrustum viewFrustum;
//变换管道
GLGeometryTransform transformPipeline;
//纹理变量,一般用无符号整型
GLuint textureID;


#pragma mark - 函数
//初建或重塑窗口
void ChangeSize (int w , int h)
{
    
    //设置视口大小
    if (h == 0) {
        h = 1;
    }
    glViewport(0, 0, w, h);
    
    //设置投影方式并创建投影矩阵
    viewFrustum.SetPerspective(36.f, float(w)/float(h), 1.f, 500.f);
    
    //将投影视图矩阵放入投影矩阵堆栈
    projMatrixStack.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    //设置变换管道
    transformPipeline.SetMatrixStacks(mvMatrixStack, projMatrixStack);
    
}

/**
 将TGA文件加载成2D纹理
 里面包括了读取、加载、设置纹理
 参数
 (1)fileName:要加载的TGA文件的文件名
 (2)minFilter:缩小过滤模式
 (3)magFilter:方大过滤模式
 (4)wrapMode:环绕方式
 */
bool LoadTGATexture(const char *fileName , GLenum minFilter , GLenum magFilter , GLenum wrapMode)
{
    
    //先定义一下要用到的变量,用来存储读取纹理之后拿到的数据信息
    //因为读取纹理tga文件的函数返回的是字节类型
    GLbyte *mByte;
    //定义获取到的宽、高、组成和颜色成分变量
    int nWidth,nHeight,nComponent;
    GLenum nFormat;
    
    //1.读取纹理。将TGA文件读取成像素
    //参数
    //(1) 要读取的tga的文件名,必要时可以添加路径进去
    //注:从这里开始,后面的参数都是要的指针,所以给的是地址
    //(2) tga文件的宽度
    //(3) tga文件的高度
    //(4) tga文件的颜色成分组成
    //(5) tga文件的格式
    mByte = gltReadTGABits(fileName, &nWidth, &nHeight, &nComponent, &nFormat);
    if (mByte == NULL) {
        return false;
    }
    
    //2.设置纹理
    //设置纹理的环绕方式
    //横向s轴的环绕方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    //纵向t轴的环绕方式
    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);
    
    
    //3.载入纹理
    //参数
    //(1)纹理维度
    //(2)mip贴图的层次
    //(3)每个纹理单元中存储多少的颜色成分
    //(4)加载纹理的宽度
    //(5)加载纹理的高度
    //(6)允许为纹理添加一个边框,就是给一个边界的限度,一般设置成0就行了
    //(7)载入纹理要用的像素模式
    //(8)像素数据的数据类型
    //(9)指向纹理图像数据的指针。
    glTexImage2D(GL_TEXTURE_2D, 0, nComponent, nWidth, nHeight, 0, nFormat, GL_UNSIGNED_BYTE, mByte);
    
    //使用完成之后要释放
    free(mByte);
    
    //4.加载Mip纹理所生成的所有Mip层
    //参数选择:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
    glGenerateMipmap(GL_TEXTURE_2D);
    
    return true;
    
}

//设置金字塔顶点及纹理数据等
void MakePyramid(GLBatch &pyramidBatch)
{
    //通过三角形批次类设置金字塔基本绘制信息
    //参数:
    //(1)绘制方式
    //(2)顶点数量,金字塔是6个三角形组成的,4个侧面每个都是1个三角形,底面矩形是2个三角形,所以是6个三角形18个顶点
    //(3)要用几个纹理。如果这个参数不设置的话,默认是0的,也就是不用纹理。
    pyramidBatch.Begin(GL_TRIANGLES, 18 , 1);
    
    //设置顶点坐标
    //金字塔顶点
    //Front是靠近观察者的顶点,Back是远离观察者的顶点
    M3DVector3f vApex = {0.f,1.f,0.f};
    M3DVector3f vFrontLeft = {-1.f,-1.f,1.f};
    M3DVector3f vFrontRight = {1.f,-1.f,1.f};
    M3DVector3f vBackLeft = {-1.f,-1.f,-1.f};
    M3DVector3f vBackRight = {1.f,-1.f,-1.f};
    
    //临时的法线变量
    M3DVector3f vNormal;
    
    
    
    //金字塔底部是两个三角形构成,分别命名三角形A和三角形B
/********************************************设置三角形A******************************************/
    //这里注意,法线在获取的时候,顶点的顺序是有方向性的,正面是逆时针顺序,背面是顺时针顺序,不然会出现黑色,没有纹理
    //顶点:vFrontLeft,vBackLeft,vBackRight
    //根据三个点,找到三角形A的法线,也可以自己算法线,无所谓的
    //参数:
    //(1)result:找到的法线。存储到刚才定义的临时法线变量里面
    //(2)(3)(4):用来找法线的三个点
    m3dFindNormal(vNormal, vFrontLeft, vBackLeft, vBackRight);
    
    //利用找到的三角形A的法线,立即设置三角形A的三个顶点的纹理
    //vBackLeft
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 0.f, 0.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vFrontLeft);
    
    //vBackRight
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 0.f, 1.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vBackLeft);
    
    //vFrontRight
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    //参数:
    //(1)texture:第几个纹理!!!而不是把纹理放上去,是纹理的叠加的时候,这个纹理是第几个。要用哪个
    //(2)s:纹理的横坐标
    //(3)t:纹理的总坐标
    pyramidBatch.MultiTexCoord2f(0, 1.f, 1.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vBackRight);
    
    
    
/********************************************设置三角形B******************************************/
    //三角形B的三个点:vBackRight,vFrontRight,vFrontLeft
    //找到三角形B的法线
    m3dFindNormal(vNormal, vBackRight, vFrontRight, vFrontLeft);
    
    //设置三角形B的三个顶点纹理
    //vFrontLeft
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 1.f, 1.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vBackRight);
    
    //vBackLeft
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 1.f, 0.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vFrontRight);
    
    //vFrontRight
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 0.f, 0.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vFrontLeft);
    
    
    
/********************************************设置正面三角形****************************************/
    //顶点:vApex,vFrontLeft,vFrontRight
    //找到正面三角形的法线
    m3dFindNormal(vNormal, vApex, vFrontLeft, vFrontRight);
   
    //设置正面三角形的顶点纹理
    //vApex
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vApex);
    
    //vFrontLeft
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 0.f, 0.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vFrontLeft);
    
    //vFrontRight
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 1.f, 0.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vFrontRight);


    
/********************************************设置左面三角形****************************************/
    //顶点:vApex,vFrontLeft,vBackLeft
    //找到左面三角形的法线
    m3dFindNormal(vNormal, vApex, vBackLeft, vFrontLeft);
    
    //设置左面三角形顶点纹理
    //vApex
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vApex);
    
    //vFrontLeft
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 1.f, 0.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vFrontLeft);
    
    //vBackLeft
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 0.f, 0.f);
    //设置顶点
    pyramidBatch.Vertex3fv(vBackLeft);
    
    
    
/********************************************设置右面三角形****************************************/
    //顶点:vApex,vFrontRight,vBackRight
    //找到右面三角形的法线
    m3dFindNormal(vNormal, vApex, vFrontRight, vBackRight);
    
    //设置右面三角形顶点纹理
    //vApex
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vApex);
    
    //vFrontRight
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 1.f, 0.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vFrontRight);
    
    //vBackRight
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 0.f, 0.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vBackRight);
    
    
    
/********************************************设置后面三角形****************************************/
    //顶点:vApex,vBackLeft,vBackRight
    //找到后面三角形的法线
    m3dFindNormal(vNormal, vApex, vBackRight, vBackLeft);
    //设置后面三角形顶点纹理
    //vApex
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vApex);

    //vBackLeft
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 1.f, 0.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vBackLeft);

    //vBackRight
    //设置法线
    pyramidBatch.Normal3fv(vNormal);
    //设置纹理坐标
    pyramidBatch.MultiTexCoord2f(0, 0.f, 0.f);
    //设置顶点坐标
    pyramidBatch.Vertex3fv(vBackRight);
    
/***********************************************************************************************/
    
    //结束三角形批次类的设置
    pyramidBatch.End();
    
    
}

//设置渲染环境
void SetUpRC ()
{
    
    //设置清屏颜色
    glClearColor(0.3f, 0.3f, 0.3f, 1.f);
    
    //初始化着色器管理器
    shaderManager.InitializeStockShaders();
    
    //开启深度测试
    glEnable(GL_DEPTH_TEST);
    
    //观察者参考帧偏移一定的位置,不要和原点重合
    cameraFrame.MoveForward(-10.f);
    
    //分配纹理对象
    //参数:(1)要使用几个纹理 (2)纹理对象的指针
    glGenTextures(1, &textureID);
    
    //绑定纹理状态
    //参数:(1)纹理的状态 (2)纹理对象
    glBindTexture(GL_TEXTURE_2D, textureID);
    
    //读取、加载、设置纹理参数
    //这是一个自己写的函数,主要是把这三步合成一步
    //就是将TGA文件加载成2D纹理
    LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);
    
    //设置金字塔顶点及纹理数据等
    MakePyramid(pyramidBatch);
    
}

//清理
void ShutDownRC ()
{
    
    //
    glDeleteTextures(1, &textureID);
    
}

//渲染
void RenderScene ()
{
    
    //清理缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    //光源位置
    static GLfloat vLightPos[] = {1.f,1.f,0.f};
    
    //颜色
    static GLfloat vColor[] = {1.f,1.f,1.f,1.f};
    
    //压栈
    mvMatrixStack.PushMatrix();
    
    //获取观察者矩阵
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    mvMatrixStack.MultMatrix(mCamera);
    
    //获取物体矩阵
    M3DMatrix44f mObj;
    objFrame.GetMatrix(mObj);
    mvMatrixStack.MultMatrix(mObj);
    
    //绑定纹理。因为这里我们只有一个纹理,是可以不用再绑定的,但是多个纹理的时候,绑定一下保证纹理不错
    glBindTexture(GL_TEXTURE_2D, textureID);
    
    //设置着色器
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vLightPos,vColor,0);
    
    //绘制
    pyramidBatch.Draw();
    
    //出栈
    mvMatrixStack.PopMatrix();
    
    //交换缓冲区
    glutSwapBuffers();
    
    
}

//特殊键位
void SpecialKeys (int key , int x ,int y)
{
    switch (key) {
            
        case GLUT_KEY_UP:
            objFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
            break;
            
        case GLUT_KEY_DOWN:
            objFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
            break;
            
        case GLUT_KEY_RIGHT:
            objFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
            break;
            
        case GLUT_KEY_LEFT:
            objFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
            break;
            
    }
    glutPostRedisplay();
    
}

#pragma mark - main

int main (int argc , char *argv[])
{
    
    //设置工作目录和项目目录一致在/Resource下,glut已经设置,手动安全
    gltSetWorkingDirectory(argv[0]);
    
    //初始化glut
    glutInit(&argc, argv);
    
    //初始化显示模式
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    
    //设置窗口大小
    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();
    
    ShutDownRC();
    
    return 0;
    
}


执行结果如图1.1:

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