本文为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: