opengl L系统 三维分形树(有纹理)

最近老师留作业,要求实现一棵分形树,花时间研究了一下,实现的效果还不错。

运行结果如下,可以鼠标控制旋转,键盘按键控制放大缩小,以及控制叶片大小,树干粗细



首先对文法进行迭代,得到最终的规则。

根据规则计算出每个树枝的起始点与终止点。

树干部分,我用圆柱来画,通过对圆柱绑定纹理,实现树干的绘制,绘制时,每个圆柱的半径由L系统计算得出。

树叶部分,我按照每个树枝的方向贴一张树叶的纹理,我对树叶的纹理进行了处理,将其空余的白色变为透明。






下面来说一下具体实现的方法。

文法分析部分如下:

我这里使用的文法是:

string grammar = "FA[*+X][-/&X][/%X]B";//文法
里面的X代表的是最末端的树干,通过迭代使得树的枝干越来越多。
void Grammar::Iteration() {
	string temprule = grammar;
	for (int i = 1; i <= level; i++)
	{
		int curlen = temprule.length();
		int j = 0;
		while (j < curlen)
		{
			if (temprule[j] == 'X')//迭代,替换成文法模型
			{
				rule += grammar;
				j++;
			}
			else //保留转角
			{
				rule += temprule[j];
				j++;
			}
		}
		temprule = rule;
		rule.clear();
	}
	rule = temprule;//迭代好之后的文法规则
}

L系统的规则如下

vector<TrunkPosition> trunks; //  所有分支
vector<LeafPosition> leaves;//所有叶子结点

void LSystem::generateFractal()   // 利用加载过的文法,创建分形树
{
	trunks.clear();
	leaves.clear();
	curState.pos = Node(0, 0, 0);
	curState.dir = Node(0, 1, 0);

	State stack[3000]; // 状态栈
	for (int i = 0; i <3000; i++)
	{
		stack[i].pos.x = 0.0;
		stack[i].pos.y = 0.0;
		stack[i].pos.z = 0.0;
		stack[i].dir.x = 0.0;
		stack[i].dir.y = 0.0;
		stack[i].dir.z = 0.0;
	}
	size_t i = 0;
	while (i <grammar.getRule().length()) {
		TrunkPosition tmp_trunk;//单个分支
		LeafPosition tmp_leaf;
		switch (grammar.getRule()[i])
		{
		case 'F':
			tmp_trunk.pos1 = curState.pos;
			curState.pos.x += length * curState.dir.x;
			curState.pos.y += length * curState.dir.y;
			curState.pos.z += length * curState.dir.z;
			tmp_trunk.pos2 = curState.pos;
			/*glColor3f(1.0, 0.0, 0.0);
			glLineWidth(3);
			glBegin(GL_LINES);
			glVertex3f(tmp_trunk.pos1.x, tmp_trunk.pos1.y, tmp_trunk.pos1.z);
			glVertex3f(tmp_trunk.pos2.x, tmp_trunk.pos2.y, tmp_trunk.pos2.z);
			glEnd();*/
			tmp_trunk.radius = radius;
			trunks.push_back(tmp_trunk);
			break;
		case 'X': //叶子
			tmp_leaf.pos1 = curState.pos;
			tmp_trunk.pos1 = curState.pos;
			curState.pos.x += length * curState.dir.x;
			curState.pos.y += length * curState.dir.y;
			curState.pos.z += length * curState.dir.z;
			tmp_trunk.pos2 = curState.pos;
			tmp_leaf.pos2 = curState.pos;
			tmp_leaf.radius = leafRadius;
			tmp_trunk.radius = radius;
			trunks.push_back(tmp_trunk);
			leaves.push_back(tmp_leaf);
			break;
		case 'A':
			length = length * lengthFactor;
			radius = radius * radiusFactor;
			break;
		case 'B':
			length = length / lengthFactor;
			radius = radius / radiusFactor;
			break;
		case '[':
			stack[stackpointer] = curState;
			stackpointer++;;
			break;
		case ']':
			curState = stack[stackpointer - 1];
			stackpointer--;
			break;
		case '+': //绕X轴		 
			trans.set(curState.dir);
			curState.dir = trans.Rotate('X', dx);
			break;
		case '-':
			trans.set(curState.dir);
			curState.dir = trans.Rotate('X', -dx);
			break;
		case '*': //绕y轴
			trans.set(curState.dir);
			curState.dir = trans.Rotate('Y', dy);
			break;
		case '/':
			trans.set(curState.dir);
			curState.dir = trans.Rotate('Y', -dy);
			break;
		case '&'://绕z轴
			trans.set(curState.dir);
			curState.dir = trans.Rotate('Z', dz);
			break;
		case '%':
			trans.set(curState.dir);
			curState.dir = trans.Rotate('Z', -dz);
			break;
		default:
			;
		}
		i++;
	}
}

画树干的方法如下:

根据两点,画一个圆柱,为了效果更好,我把圆柱的上面的半径根据半径收缩率缩小了,这样看起来效果更好,两个枝干连接处没有缝隙。

//以yuandian两端点,r为半径
void drawcone(double r,double h) {
	glBindTexture(GL_TEXTURE_2D, texin);
	for (int i = 0; i <360; i += 50) { //在原点画了一个圆柱
		float temp = i * Pi1 / 180;
		float temp1 = (i + 50)*Pi1 / 180;
		glBegin(GL_QUADS);
		glTexCoord2f((temp*r*tree.trunk.radius_shrink)/(2 * Pi1), 0.0f); glVertex3f(r*cos(temp)*tree.trunk.radius_shrink, 0, r*sin(temp)*tree.trunk.radius_shrink);
		glTexCoord2f((temp*r)/(2*Pi1), 1.0f); glVertex3f(r*cos(temp), h, r*sin(temp)); //竖着2倍 横着1/2
		glTexCoord2f((temp1*r)/(2 * Pi1), 1.0f); glVertex3f(r*cos(temp1), h, r*sin(temp1));
		glTexCoord2f((temp1*r*tree.trunk.radius_shrink) / (2 * Pi1), 0.0f); glVertex3f(r*cos(temp1)*tree.trunk.radius_shrink, 0.0f, r*sin(temp1)*tree.trunk.radius_shrink);
		glEnd();
	}
}
void  DrawChannel(Node A, Node B, double r)
{
	// 起始线段:以(0,1,0)为起点,它的长度(distance)通过目标线段计算,  
	//           终点坐标为(0,1-distance,0)  
	// 目标线段:以(x1,y1,z1)为起点,以(x2,y2,z2)为终点  
	// 计算目标向量  
	GLfloat   dx = B.x - A.x;
	GLfloat   dy = B.y - A.y;
	GLfloat   dz = B.z - A.z;
	// 算出目标向量模(即AB长度)  
	GLfloat   distance = sqrt(dx*dx + dy * dy + dz * dz);
	// 计算平移量  
	GLfloat  px = A.x;
	GLfloat  py = A.y - 1;
	GLfloat  pz = A.z;
	// 起始线段的末端点  
	GLfloat  bx = px;
	GLfloat  by = (1 - distance) + py;
	GLfloat  bz = pz;
	// 计算起始向量  
	GLfloat  sx = bx - A.x;
	GLfloat  sy = by - A.y;
	GLfloat  sz = bz - A.z;
	// 计算向量(sx,sy,sz)与向量(dx,dy,dz)的法向量(sy*dz - dy*sz,sz*dx - sx*dz,sx*dy - dx*sy)  
	GLfloat fx = sy * dz - dy * sz;
	GLfloat fy = sz * dx - sx * dz;
	GLfloat fz = sx * dy - dx * sy;
	// 求两向量间的夹角  
	// 计算第三条边的长度  
	GLfloat ax = fabs(B.x - bx);
	GLfloat ay = fabs(B.y - by);
	GLfloat az = fabs(B.z - bz);
	GLfloat length = sqrt(ax*ax + ay * ay + az * az);
	// 根据余弦定理计算夹角  
	GLfloat angle = acos((distance*distance * 2 - length * length) / (2 * distance*distance))*180.0f / 3.14159;
	// 绘制圆柱   
	glPushMatrix();
	// 变换的顺序与函数书写的顺序相反,  
	// 即先平移(0,-1,0),再绕法向量旋转,最后再平移  
	glTranslatef(A.x, A.y, A.z);
	glRotatef(angle, fx, fy, fz);
	glTranslatef(0, -distance, 0);
	drawcone(r, distance);
	glPopMatrix();
}

画树叶的方法如下:

沿着树干末端的方向画一个正方形,然后把树叶的纹理贴上去。

void drawsquare(double r) {
	glRotated(180, 0, 0, 1);
	glBindTexture(GL_TEXTURE_2D, texout);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f); glVertex3f(r / 2, 0.0f, 0.0f);
	glTexCoord2f(0.0f, 1.0f); glVertex3f(r / 2, r, 0);
	glTexCoord2f(1.0f, 1.0f); glVertex3f(-r / 2, r, 0);
	glTexCoord2f(1.0f, 0.0f); glVertex3f(-r / 2, 0, 0);
	
	glEnd();
}
void DrawLeaf(Node A, Node B, double r) {
	// 起始线段:以(0,1,0)为起点,它的长度(distance)通过目标线段计算,  
	//           终点坐标为(0,1-distance,0)  
	// 目标线段:以(x1,y1,z1)为起点,以(x2,y2,z2)为终点  
	// 计算目标向量  
	GLfloat   dx = B.x - A.x;
	GLfloat   dy = B.y - A.y;
	GLfloat   dz = B.z - A.z;
	// 算出目标向量模(即AB长度)  
	GLfloat   distance = sqrt(dx*dx + dy * dy + dz * dz);
	// 计算平移量  
	GLfloat  px = A.x;
	GLfloat  py = A.y - 1;
	GLfloat  pz = A.z;
	// 起始线段的末端点  
	GLfloat  bx = px;
	GLfloat  by = (1 - distance) + py;
	GLfloat  bz = pz;
	// 计算起始向量  
	GLfloat  sx = bx - A.x;
	GLfloat  sy = by - A.y;
	GLfloat  sz = bz - A.z;
	// 计算向量(sx,sy,sz)与向量(dx,dy,dz)的法向量(sy*dz - dy*sz,sz*dx - sx*dz,sx*dy - dx*sy)  
	GLfloat fx = sy * dz - dy * sz;
	GLfloat fy = sz * dx - sx * dz;
	GLfloat fz = sx * dy - dx * sy;
	// 求两向量间的夹角  
	// 计算第三条边的长度  
	GLfloat ax = fabs(B.x - bx);
	GLfloat ay = fabs(B.y - by);
	GLfloat az = fabs(B.z - bz);
	GLfloat length = sqrt(ax*ax + ay * ay + az * az);
	// 根据余弦定理计算夹角  
	GLfloat angle = acos((distance*distance * 2 - length * length) / (2 * distance*distance))*180.0f / 3.14159;
	glPushMatrix();
	// 变换的顺序与函数书写的顺序相反,  
	// 即先平移(0,-1,0),再绕法向量旋转,最后再平移  
	glTranslatef(A.x, A.y, A.z);
	glRotatef(angle, fx, fy, fz);
	glTranslatef(0, -distance, 0);
	drawsquare(r);
	glPopMatrix();
}

处理bmp图像 将白色部分变为透明的方法如下:

int LoadGLTextures(char *Filename, GLuint *ttexture, int i)//ttexture[i]为用来绑定纹理数据的整数,可更改  
{
	FILE *File;
	BITMAPINFOHEADER header;
	BYTE *texture;//texture长宽和像素RGBA texture[width][height][4]  
	int width, height, m, j;
	unsigned char *image;
	File = fopen(Filename, "rb");
	//读取BMP信息头,跳过14字节文件头  
	if (File) {
		fseek(File, 14, SEEK_SET);
		fread(&header, sizeof(BITMAPINFOHEADER), 1, File);
	}
	else return FALSE;
	//读取长宽  
	width = header.biWidth;
	height = header.biHeight;
	//为image分配像素空间,读取图片数据,为texture分配width*height*4的四位空间用来生成纹理  
	image = (unsigned char *)malloc(width*height * 3);
	fread(image, sizeof(unsigned char), width*height * 3, File);//唯一的不足之处在于将3字节像素读为一维字符串  
	texture = (BYTE *)malloc(width*height * 4);// 唯一的不足之处在于将4字节像素读为一维字符串  
											   //以下代码将对texture重排列,一般来说为Blue Green Red Alpha格式(24位图格式),生成纹理使用BGRA模式  
											   /****************************************修改模块,通过像素任意修改图片,示例:*************************************************************/
											   //本段代码将纯白色像素点透明度(Alpha)设为100%,其余颜色设为0%  
	for (m = 0; m<width; m++)
	{
		for (j = 0; j<height; j++)
		{
			//把颜色值写入   
			texture[m*width * 4 + j * 4] = image[m*width * 3 + j * 3];
			texture[m*width * 4 + j * 4 + 1] = image[m*width * 3 + j * 3 + 1];
			texture[m*width * 4 + j * 4 + 2] = image[m*width * 3 + j * 3 + 2];
			//设置alpha值,假设白色为透明色   
	
			if (texture[m*width * 4 + j * 4] >= 200 && texture[m*width * 4 + j * 4 + 1] >= 200 && texture[m*width * 4 + j * 4 + 2] >= 200) {
				texture[m*width * 4 + j * 4 + 3] = 0;                 //透明,alpha=0   
			}
			else
				texture[m*width * 4 + j * 4 + 3] = 255;           //不透明,alpha=255  
		}
	}
	//ttexture[i]为绑定纹理的整数  
	//下面生成纹理以及纹理处理  
	glGenTextures(1, &ttexture[i]);
	glBindTexture(GL_TEXTURE_2D, ttexture[i]);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexImage2D(GL_TEXTURE_2D, 0, 4, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture);
	gluBuild2DMipmaps(GL_TEXTURE_2D, 4, width, height, GL_BGRA_EXT, GL_UNSIGNED_BYTE, texture);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glAlphaFunc(GL_GREATER, 0.5);//使Alpha值生效  
								 //纹理生成完毕,下面释放空间  
	free(texture);
	free(image);                        // 释放图像数据  
	return TRUE;                                // 返回 Status  
}
  • 12
    点赞
  • 118
    收藏
    觉得还不错? 一键收藏
  • 45
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 45
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值