OpenGL植物建模(附完整代码注释清晰分步讲解)
Posted 鲨鱼小猫
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenGL植物建模(附完整代码注释清晰分步讲解)相关的知识,希望对你有一定的参考价值。
目录
- 完整代码在我上传的资源处下载
- 完整代码在我上传的资源处下载
- 完整代码在我上传的资源处下载
1、成果
2、myInit初始化函数
该函数中是一个初始化环境的函数,负责开启深度测试、自动法向、加载纹理数据、调用光照函数、材质设置以及调用生成列表函数。
void myInit()
glClearColor(1.0, 1.0, 1.0, 1.0);
glDisable(GL_CULL_FACE);
//自动法向防止变形
glEnable(GL_NORMALIZE);
glEnable(GL_DEPTH_TEST);
//输入的深度值小于参考值,则通过
glDepthFunc(GL_LESS);
//纹理载入
……
//打开纹理
glEnable(GL_TEXTURE_2D);
//调用打开光照函数
……
//调用设置材质函数
……
//调用显示列表函数
……
3、加载纹理数据
在myInit函数中设计for循环,依次图片路径数组读取图片,并依次加载进纹理数组中并分配编号,供后续贴图使用。每个编号均使用宏定义,更语义化。同时使用auxDIBImageLoad读取图片的宽高以及数据,不过VS2019中需要对图片路径字符转换为宽字符才能使用。
for (int i = 0; i < 9; i++)
//生成纹理编号
glGenTextures(1, &ImagesIDs[i]);
glBindTexture(GL_TEXTURE_2D, ImagesIDs[i]);
WCHAR wfilename[256];
memset(wfilename, 0, sizeof(wfilename));
//该函数映射一个字符串到一个宽字符(unicode)的字符串
MultiByteToWideChar(CP_ACP, 0, szFiles[i], strlen(szFiles[i]) + 1, wfilename, sizeof(wfilename) / sizeof(wfilename[0]));
Images[i] = auxDIBImageLoad(wfilename); //加载图片
//Images[i] = auxDIBImageLoadA(szFiles[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Images[i]->sizeX, Images[i]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Images[i]->data);
//纹理被放大(一个纹元对应多个像素)
//选择距像素中心最近的四个texel的加权平均值作为像素的颜色
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//纹理被缩小(多个纹元对应一个像素)
//选择距像素中心最近的四个texel的加权平均值作为像素的颜色
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//纹理第一维坐标大于1小于0时,表现为裁剪
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
//纹理第二维坐标大于1小于0时,表现为裁剪
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
//纹理环境
//直接使用纹理图片替换
glTexEnvf(GL_TEXTURE_2D, GL_TEXTURE_ENV_MODE, GL_REPLACE);
4、打开光照和材质
设置光照和材质的函数都比较常规。在光照函数mySetupLights中,要设置环境光为白色,同时光源的位置中,最后一个参数为0,这样的光就是平行的光,更能模拟自然的太阳。
//灯光参数
void mySetupLights()
//环境光:白色的太阳光
GLfloat ambientLight[] = 1.0, 1.0, 1.0, 1.0 ;
//glColor3f(0.91f, 0.56f, 0.64f);
//光源的颜色与物体的颜色值相乘(叉乘)
GLfloat diffuseLight[] = 0.9, 0.9, 0.9, 1.0 ; //散射光
//镜面光:白色
GLfloat specularLight[] = 1.0, 1.0, 1.0, 1.0 ;
//阳光,模拟平行,第四个参数为0.0,定义相应的光源是定向光源,光线几乎是互相平行
GLfloat posLight[] = 0.0, 0.0, 0.0, 0.0 ;
GLfloat local_view[] = 0.0 ;
//平滑模式
glShadeModel(GL_SMOOTH);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
glLightfv(GL_LIGHT0, GL_POSITION, posLight);
glEnable(GL_LIGHT0);
//镜面反射角度
glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);
//材质相关函数
void myMaterial()
glEnable(GL_COLOR_MATERIAL);
//物体对各种光的强度
GLfloat mat_ambient[] = 1.0, 0.0, 0.0, 1.0 ;
GLfloat mat_diffuse[] = 0.9, 0.38, 0.1, 1.0 ;
GLfloat mat_specular[] = 1.0, 0.0, 0.0, 1.0 ;
GLfloat env_ambient[] = 0.9, 0.38, 0.1, 1.0 ;
GLfloat mat_shinness[] = 50.0 ;
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shinness);
//全局设置整个场景的环境光强度
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, env_ambient);
5、显示列表封装绘制函数
把绘制模型的函数,通过显示列表封装在myCreateList函数中,并提前在myInit函数中加载好,后续会在myTree函数中反复调用生成模型。每一个显示列表都用使用了glPushMatrix与glPopMatrix,这样所作变换操作就不会影响下一个变换,有些列表还使用了glPushAttrib与glPopAttrib,保证顶点处理时候的一些属性不影响后续顶点。
void myCreateList(void)
//创建树干绘制显示列表
glNewList(TRUNK, GL_COMPILE);
……
//保护现场,不污染其他绘制
glPushMatrix();
……
glPopMatrix();
//删除曲面
glEndList();
//创建花朵绘制显示列表,八个大部分
glNewList(FLOWER, GL_COMPILE);
glPushMatrix();
//保护花朵的颜色等属性不污染其他绘制
glPushAttrib(GL_ALL_ATTRIB_BITS);
……
glPopAttrib();
glPopMatrix();
glEndList();
//花朵和树干连接
glNewList(FLOWERANDTRUNK, GL_COMPILE);
glPushMatrix();
glPushAttrib(GL_LIGHTING_BIT);
……
glPopAttrib();
glPopMatrix();
glEndList();
//地板的显示列表
glNewList(LAND, GL_COMPILE);
glPushMatrix();
……
glPopMatrix();
glEndList();
//天空盒的显示列表
glNewList(SKYBOX, GL_COMPILE);
glPushMatrix();
……
glPopMatrix();
glEndList();
5.1显示列表封装绘制函数:绘制树干
通过gluNewQuadric函数可以绘制出底大顶小的圆柱体,但是需要注意的是默认绘制的圆柱轴与世界坐标系中z轴是平行的,如图8中的Ⅰ,所以在树干进行旋转变换(所有的模型变换都是基于局部坐标系)glRotatef(-90, 1.0, 0.0, 0.0)。这个时候树干在坐标系中效果为图8-Ⅲ、图8-Ⅳ。
图8 树干局部坐标系的变换
//创建树干绘制显示列表
glNewList(TRUNK, GL_COMPILE);
//二次曲面
cylquad = gluNewQuadric();
//绑定纹理
glBindTexture(GL_TEXTURE_2D, ImagesIDs[0]);
//二次曲面绑定纹理
gluQuadricTexture(cylquad, GL_TRUE);
//保护现场
glPushMatrix();
//默认绘制的圆柱轴与z轴平行
glRotatef(-90, 1.0, 0.0, 0.0);
//绘制圆柱
gluCylinder(cylquad, 0.1, 0.08, 1.7, 10, 10);
glPopMatrix();
//删除曲面
gluDeleteQuadric(cylquad);
glEndList();
5.2显示列表封装绘制函数:绘制花朵
使用在线抠图网站,把模型描边(图9)扣出来。使用ppt,让坐标系图片与模型描边叠值(图10),人工读取花瓣特征坐标。为了贴图方便每一朵花瓣都切割为多个四边形,根据每片花瓣的长势,最少划分为2部分,最后划分为5部分。
//创建花朵绘制显示列表,八个大部分
glNewList(FLOWER, GL_COMPILE);
glPushMatrix();
//保护花朵的颜色
glPushAttrib(GL_ALL_ATTRIB_BITS);
glBindTexture(GL_TEXTURE_2D, ImagesIDs[1]);
//粉色
glColor3f(0.91f, 0.56f, 0.64f);
glBegin(GL_QUADS); //part1-1
glTexCoord2f(0.0, 0.22); glVertex3f(0.0, 0.0, 0.0);
glTexCoord2f(0.6, 0.044); glVertex3f(0.30, -0.09, 0.0);
glTexCoord2f(0.8, 0.22); glVertex3f(0.44, 0.0, 0.0);
glTexCoord2f(1.0, 1.0); glVertex3f(0.46, 0.19, 0.0);
glEnd();
glBegin(GL_QUADS); //part1-2
……
glEnd();
glBegin(GL_QUADS); //part2-1
……
glEnd();
//省略了很多,详情见工程文件
……
……
glBegin(GL_QUADS);//part8-5
……
glEnd();
glPopAttrib();
glPopMatrix();
glEndList();
图11 绘制的花朵
5.3显示列表封装绘制函数:花与树干的连接
从这里开始就体现了glPushMatrix与glPopMatrix的作用。因为绘制树干与花朵都是各自的glPushMatrix与glPopMatrix中,则树干与花朵都是基于世界坐标系(0,0,0)建模。在封装花朵与树干的连接的时候,先绘制树干,然后进行glTranslatef(0.0, 1.7, 0.0)把花朵的局部坐标系向y轴平移1.7个单位,这个时候花朵才是在树干头顶开始绘制。坐标系如图12、13、14。
//花朵和树干连接
glNewList(FLOWERANDTRUNK, GL_COMPILE);
glPushMatrix();
glPushAttrib(GL_LIGHTING_BIT);
glCallList(TRUNK);
glTranslatef(0.0, 1.7, 0.0);
glPushMatrix();
glRotatef(90, 0.0, 1.0, 0.0);
glRotatef(50, 1.0, 0.0, 0.0);
glCallList(FLOWER);
glPopMatrix();
glPushMatrix();
glRotatef(180, 0.0, 1.0, 0.0);
glRotatef(60, 1.0, 0.0, 0.0);
glCallList(FLOWER);
glPopMatrix();
glPopAttrib();
glPopMatrix();
glEndList();
图12花朵未平移图图13 花朵平移后图
图14 花朵平移坐标系示意图
5.4显示列表封装绘制函数:天空盒和地板
天空盒就是一个正六面体,然后按照顺序贴图。地板就是一块平行于世界坐标系的x0y面的正方形区域
//天空盒的显示列表
glNewList(SKYBOX, GL_COMPILE); //天空背景
glPushMatrix();
glBindTexture(GL_TEXTURE_2D, ImagesIDs[3]);
/** 绘制背面 */
glBegin(GL_QUADS);
/** 指定纹理坐标和顶点坐标 */
glTexCoord2f(1.0f, 0.0f); glVertex3f(10, -10, -10);
glTexCoord2f(1.0f, 1.0f); glVertex3f(10, 10, -10);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-10, 10, -10);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-10, -10, -10);
glEnd();
glPopMatrix();
/** 绘制前面 */
glBindTexture(GL_TEXTURE_2D, ImagesIDs[4]);
glBegin(GL_QUADS);
/** 指定纹理坐标和顶点坐标 */
glTexCoord2f(1.0f, 0.0f); glVertex3f(10, -10, 10);
glTexCoord2f(1.0f, 1.0f); glVertex3f(10, 10, 10);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-10, 10, 10);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-10, -10, 10);
glEnd();
/** 绘制底面 */
glBindTexture(GL_TEXTURE_2D, ImagesIDs[5]);
glBegin(GL_QUADS);
/** 指定纹理坐标和顶点坐标 */
glTexCoord2f(1.0f, 0.0f); glVertex3f(10, -10, 10);
glTexCoord2f(1.0f, 1.0f); glVertex3f(10, -10, -10);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-10, -10, -10);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-10, -10, 10);
glEnd();
/** 绘制顶面 */
glBindTexture(GL_TEXTURE_2D, ImagesIDs[6]);
glBegin(GL_QUADS);
/** 指定纹理坐标和顶点坐标 */
glTexCoord2f(1.0f, 0.0f); glVertex3f(10, 10, 10);
glTexCoord2f(1.0f, 1.0f); glVertex3f(10, 10, -10);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-10, 10, -10);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-10, 10, 10);
glEnd();
/** 绘制左面 */
glBindTexture(GL_TEXTURE_2D, ImagesIDs[7]);
glBegin(GL_QUADS);
/** 指定纹理坐标和顶点坐标 */
glTexCoord2f(1.0f, 1.0f); glVertex3f(-10, 10, -10);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-10, 10, 10);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-10, -10, 10);
glTexCoord2f(1.0f, 0.0f); glVertex3f(-10, -10, -10);
glEnd();
///** 绘制右面 */
glBindTexture(GL_TEXTURE_2D, ImagesIDs[8]);
glBegin(GL_QUADS);
/** 指定纹理坐标和顶点坐标 */
glTexCoord2f(0.0f, 1.0f); glVertex3f(10, 10, -10);
glTexCoord2f(1.0f, 1.0f); glVertex3f(10, 10, 10);
glTexCoord2f(1.0f, 0.0f); glVertex3f(10, OpenGL植物建模(附完整代码注释清晰分步讲解)
机器学习完整项目实战附代码:探索型数据分析+特征工程+建模+报告
2021 数学建模“华为杯”B题:空气质量预报二次建模 2 方案设计附实现代码
2021 数学建模“华为杯”B题:空气质量预报二次建模 2 方案设计附实现代码