OpenGL:glfw+glad实现仿真模拟车削

github仓库:livingsu/OpenGL-simulate-turning

之前用的一直是旧OpenGL的固定管线(glut库),但是当接触了新OpenGL以后,感觉着色器、VAO、VBO实在是太强大了,不仅更加灵活,而且利用缓存可以极大提升渲染性能。

主要参考**Learn-OpenGL-CN官网**,项目中加载模型用到的Assimp库,加载图片用的stb_image库,另外还用到了learn-OpenGL作者写的一些类:camera.h,shader.h,model.h都是及其方便的类。

实现的主要功能:

  1. 显示背景图片,圆柱形原料、车刀模型的绘制
  2. 通过鼠标移动车刀,完成切削
  3. 切削时有粒子系统模拟飞溅效果
  4. 可以选择圆柱体材质(金属或者木质),使用的是pbr光照模型
  5. 切削后圆柱体光照材质发生变化
  6. 可以创建三次贝塞尔曲线作为切削的约束线

效果预览:
在这里插入图片描述

1.背景图片

画两个三角形,设置纹理图片即可,注意将z值设置和w一样,那么其深度就是最大的z/w=1.0,就会显示在所有物体的后面。

// background.vs
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

void main(){
    
    
    TexCoords = aTexCoords;
    gl_Position =  vec4(aPos.xy,1.0,1.0);     //设置深度为最大
}

要注意画背景之前打开glDepthFunc(GL_LEQUAL)并在之后恢复默认值。

        // 画背景图片
		// -------------
		glDepthFunc(GL_LEQUAL);   //设置背景在所有物体的最后面(最大深度)绘制
		bgShader.use();
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, bgTexture);
		glBindVertexArray(bgVAO);
		glDrawArrays(GL_TRIANGLES, 0, 6);
		glBindVertexArray(0);
		glDepthFunc(GL_LESS);

2.圆柱体原料

圆柱体拥有很多信息

//圆柱体信息,注意应该用double而不是float,避免精度不够导致除法出错
const double cylinderRadius = 0.4;
const double cylinderLength = 1.6;
const int angleStep = 2;			//切分角度增量
const double lengthStep = 0.001;	//切分长度增量
const double radiusStep = 0.001;	//半径增量
vector<int> radiusArray;			//半径数组,均为radiusStep的整数倍
vector<int> radiusMinArray;			//半径最小值数组,规定半径的最小值,用于贝塞尔曲线的限制
vector<glm::vec3> allPoints;		//所有点数组,包括顶点、法向量和纹理坐标
vector<unsigned int> indices;		//索引数据

unsigned int cylinderVAO;
unsigned int cylinderVBO;
unsigned int vertexNum; //顶点数量

绘制的主要思想:

只需要绘制圆柱体侧面,将侧面展开得到一个矩形,将矩形沿底边分成slices(=360/angleStep)份,沿z轴分成stacks(=cylinderLength/lengthStep)份,得到的小矩形用两个三角形绘制即可。由于相同的点在不同的小矩形中重复利用,故用EBO进行索引缓存即可。

void initCylinder() {
    
    
	int slices = 360 / angleStep;  //围绕z轴的细分
	int stacks = cylinderLength / lengthStep;  //沿z轴的细分

	//初始化半径数组和半径限制数组
	unsigned int initRadius = cylinderRadius / radiusStep;
	for (int i = 0; i <= stacks; ++i) {
    
    
		radiusArray.push_back(initRadius);
	}

	for (int i = 0; i <= stacks; ++i) {
    
    
		radiusMinArray.push_back(0);  //开始时,半径最小均可以取0
	}

	float R, alpha, x, y, z, texX, texY;

	for (int i = 0; i <= slices; i++) {
    
    
		for (int j = 0; j <= stacks; j++) {
    
    
			R = radiusArray[j] * radiusStep;
			alpha = i * angleStep;
			x = R * (float)glm::cos(glm::radians(alpha));
			y = R * (float)glm::sin(glm::radians(alpha));
			z = lengthStep * j;
			texX = (float)i / (float)slices;  //纹理坐标
			texY = (float)j / (float)stacks;

			//顶点
			glm::vec3 V(x, y, z);

			//侧面
			allPoints.push_back(V);
			allPoints.push_back(glm::vec3(V.x, V.y, 0.0f)); //法向量
			allPoints.push_back(glm::vec3(texX, texY, 0.0f));//2d纹理坐标+是否被切削(0为否,1为是)


			//添加索引坐标
			if (i < slices && j < stacks) {
    
    
				unsigned int leftDown, leftUp, rightDown, rightUp;
				leftDown = i * (stacks + 1) + j;
				leftUp = i * (stacks + 1) + (j + 1);
				rightDown = (i + 1) * (stacks + 1) + j;
				rightUp = (i + 1) * (stacks + 1) + (j + 1);

				indices.push_back(leftDown);
				indices.push_back(leftUp);
				indices.push_back(rightUp);
				indices.push_back(leftDown);
				indices.push_back(rightUp);
				indices.push_back(rightDown);
			}
		}
	}

	vertexNum = indices.size();

	unsigned int VAO, VBO, EBO;
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &VBO);
	glGenBuffers(1, &EBO);
	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float)*allPoints.size() * 3, &allPoints[0], GL_STREAM_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int)*vertexNum, &indices[0], GL_STATIC_DRAW);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void*)(6 * sizeof(float)));
	glEnableVertexAttribArray(2);
	glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void*)(8 * sizeof(float)));
	glEnableVertexAttribArray(3);

	cylinderVAO = VAO;
	cylinderVBO = VBO;
}

绘制(没加上着色器):

glBindVertexArray(cylinderVAO);
glDrawElements(GL_TRIANGLES, vertexNum, GL_UNSIGNED_INT, 0);

3.车刀的建模和载入

我用的是3dmax建模,车刀的建模比较简单,加上金属材质,然后导出为3ds文件格式或者是obj格式均可,然后用learn-OpenGL作者写的model类读取模型即可。

4.鼠标控制车刀移动,模拟切削

这里通过将鼠标的屏幕坐标(原点在左上角)转化为标准化设备坐标(原点在屏幕中央,范围是-1~1),将车刀移至指定位置上即可。

        //直接将车刀移至指定的标准化设备坐标上
        model = glm::translate(model, glm::vec3(clipX, clipY, 0.0f));
		//旋转到正确方位
		model = glm::rotate(model, glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f));
		model = glm::rotate(model, glm::radians(90.0f), glm::vec3(1.0f, 0.0f, 0.0f));
		model = glm::scale(model, glm::vec3(0.02f));
		modelShader.setMat4("model", model);
		myModel.Draw(modelShader);

模拟切削


void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    
    
	//将屏幕坐标转化为裁剪坐标(-1~1范围)
	double newClipX = xpos * 2.0 / (double)(WIN_WIDTH - 1) - 1.0;
	double newClipY = -ypos * 2.0 / (double)(WIN_HEIGHT - 1) + 1.0;

	//模拟切削
	if (mode == 1) {
    
       //切削模式
		if (newClipY > clipY0) {
    
     //模型不得高于圆心位置
			newClipY = clipY0;
		}

		if (newClipX < clipX0 || clipX < clipX0) {
    
    
			int zStart = (clipX0 - (clipX > newClipX ? clipX : newClipX)) / lengthStep; //圆柱体z轴方向下标
			int zEnd = (clipX0 - (clipX > newClipX ? newClipX : clipX)) / lengthStep;
			int zGap = zEnd - zStart;
			//开始部分对应的半径是radiusStep的整数倍
			int R_start = (clipY0 - (clipX > newClipX ? clipY : newClipY)) / radiusStep;
			int R_end = (clipY0 - (clipX > newClipX ? newClipY : clipY)) / radiusStep;
			int R_gap = R_end - R_start;

			int stacks = cylinderLength / lengthStep;
			if (zStart >= 0 && zStart <= stacks && zEnd >= 0 && zEnd <= stacks && zGap >= 0 && R_start >= 0) {
    
    
				isCut = false;
				for (int i = zStart; i <= zEnd; ++i) {
    
    
					int newRadius;
					if (zGap == 0)
						newRadius = R_start;
					else
						newRadius = R_start + R_gap * (float)(i - zStart) / (float)zGap;

					if (newRadius < 0) newRadius = 0;
					//更新半径数组
					if (radiusArray[i] > newRadius&&radiusArray[i] > radiusMinArray[i]) {
    
      //最小半径不能比现有半径小
						isCut = true;
						radiusArray[i] = newRadius < radiusMinArray[i] ? radiusMinArray[i] : newRadius; //新半径必须比现有半径小,且>=规定的最小半径
					}
				}

				//更新VBO缓冲区
				glBindBuffer(GL_ARRAY_BUFFER, cylinderVBO);
				if (isCut) {
    
    
					if (newClipX < clipX) {
    
     //车刀左移,粒子速度方向应向右
						isLeft = false;
					}
					else {
    
    
						isLeft = true;
					}

					int slices = 360 / angleStep;
					for (int i = 0; i <= slices; ++i) {
    
    
						for (int j = zStart; j <= zEnd; ++j) {
    
    
							int pointOffset = (i*(stacks + 1) + j) * 3;
							glm::vec3 newPoint = allPoints[pointOffset];
							float alpha = i * angleStep;
							float newR = radiusArray[j] * radiusStep;
							newPoint.x = newR * (float)glm::cos(glm::radians(alpha));
							newPoint.y = newR * (float)glm::sin(glm::radians(alpha));
							float isCut = 1.0f;
							glBufferSubData(GL_ARRAY_BUFFER, 3 * sizeof(float) * pointOffset, 2 * sizeof(float), &newPoint);
							glBufferSubData(GL_ARRAY_BUFFER, 3 * sizeof(float) * pointOffset + 2 * sizeof(glm::vec3) +2* sizeof(float), sizeof(float), &isCut);
							allPoints[pointOffset] = newPoint;
						}

					}
				}
				glBindBuffer(GL_ARRAY_BUFFER, 0);
			}
		}
	}


	//车刀跟随鼠标移动
	clipX = newClipX;
	clipY = newClipY;
}

结束语

通过对新OpenGL的学习,我对于图形学的原理有了更深入的理解。之前学的都是十多年前的固定管线,所以对原理一知半解。而着色器、VAO、VBO、EBO不仅提升了渲染性能,而且灵活易变。

猜你喜欢

转载自blog.csdn.net/livingsu/article/details/113572714
今日推荐