实现一遍在任意给定的两个点之间画个圆柱...
为啥不用OpenGL的实用库glu.h中方法
gluCylinder(
GLUquadric*quad,GLdoublebase,GLdoubletop,GLdoubleheight,GLintslices,GLintstacks)
而要自己实现一遍哩?两个原因:
1)gluCylinder是生成从(0, 0, 0)沿着z轴方向生成圆柱,并不能依据给定的两点生成;
2)glu.h提供的方法多用于固定管线,不适太用于modern opengl管线;
思路很简单,先依据给定的两点生成所有顶点,然后生成三角面片索引,最后依索引渲染。这里只说生成顶点和索引的方法。
除了两点 a, b 的三维坐标,还需要有半径 radius,顶部圆形的细分slices,圆柱纵向的细分stacks.
注意哟:这里有一点要说明的是,我们先默认从(0, 0, 0)沿着z轴方向,按照给定的radius,slices, stacks以及两点间间距 生成需要的顶点,然后按照从 (0, 0, z)方向到(b.x-a.x, b.y-a.y, b.z-a.z)得到旋转和平移量,因为这一小段cylinder是刚性变换,所以直接给到所有顶点就行了。
直接贴代码和注释:
// 调用了glm库
// 该方法是我的一个类方法,可以生成由一系列圆柱连接而成的管道。几个成员变量说一下:
// [Input] std::vector<float> vertices_; 事先赋好值,是所有中点的 x, y, z 坐标
// [Input] subdivisions_; 环形细分,就是上文的slices
// [Input] stacks_; 纵向细分
// [Output] std::vector<glm::vec3> cylinder_vertices_; 依据 vertices_ 里的连续的两点之间填充一个圆柱的所有顶点
// [Output] std::vector<unsigned int> indices_; 依据 cylinder_vertices_ 绘制三角面片需要的顶点排列
void CylinderData::_gen_cylinder()
{
cylinder_vertices_.clear();
indices_.clear();
// for setting indices
unsigned int stack_step = subdivisions_ + 1; // 用于每一stack的步进,由于加上了中心点,故而多加一个1
unsigned int vertex_number_each_cylinder = stack_step * stacks_; // 用于每一cylinder的步进
// generate vertices and indices
for (int i = 0; i < vertices_.size() / 3 - 1; i++)
{
// 获得连续的两个点,计算各分量差
double hx = vertices_[i * 3 + 3] - vertices_[i * 3];
double hy = vertices_[i * 3 + 4] - vertices_[i * 3 + 1];
double hz = vertices_[i * 3 + 5] - vertices_[i * 3 + 2];
if (hz == 0) hz = 0.00000001;
double length = std::sqrt(hx*hx + hy*hy + hz*hz); // 该cylinder的高度
// 下面几行是计算旋转角度和旋转轴 依据罗德里格公式描述旋转
double ax = acos(hz / length); // 旋转角度
// 旋转轴由前后两向量叉乘得到
if (hz < 0.0)
ax = -ax;
double rx = -hy * hz;
double ry = hx * hz;
glm::mat4 translate = glm::translate(glm::vec3(vertices_[i * 3], vertices_[i * 3 + 1], vertices_[i * 3 + 2]));
glm::mat4 rotate = glm::rotate((float)ax, glm::vec3((float)rx, (float)ry, 0.0f));
// 沿着 z 轴 按照给定的radius,slices, stacks以及两点间间距,生成顶点
double z_step = length / (stacks_ - 1); // 用于纵向细分步进
double r_step = M_PI * 2 / subdivisions_; // 用于环形细分步进
for (int stack = 0; stack < stacks_; stack++)
{
double z = z_step * stack;
for (int slice = 0; slice < subdivisions_; slice++)
{
double r = r_step * slice;
double x = radius_ * std::cos(r);
double y = radius_ * std::sin(r);
glm::vec3 vertex = translate * rotate * glm::vec4(x, y, z, 1.0); // 先旋转后平移
cylinder_vertices_.push_back(vertex);
}
glm::vec3 center = translate * rotate * glm::vec4(0, 0, z, 1.0); // 中心点,同样的 先旋转后平移
cylinder_vertices_.push_back(center);
}
// 然后再便利一遍设置下索引号
unsigned int cylinder_step = i * vertex_number_each_cylinder;
for (int stack = 0; stack < stacks_ - 1; stack++)
{
for (int slice = 0; slice < subdivisions_; slice++)
{
// i0, i1, i2, i3 对应画出两个三角面片绘制一个矩形面
// 绕一圈,以此来构成该stack层级的圆柱体侧面
unsigned int i0 = slice;
unsigned int i1 = (i0 + 1) % subdivisions_; // 这里取模主要是为了最后一个点能与开始点连上形成闭环
unsigned int i2 = i1 + stack_step;
unsigned int i3 = i0 + stack_step;
// c0, c1 是上下两层stack的圆形中心点
unsigned int c0 = subdivisions_;
unsigned int c1 = c0 + stack_step;
i0 += stack * stack_step + cylinder_step;
i1 += stack * stack_step + cylinder_step;
i2 += stack * stack_step + cylinder_step;
i3 += stack * stack_step + cylinder_step;
c0 += stack * stack_step + cylinder_step;
c1 += stack * stack_step + cylinder_step;
// sides
indices_.push_back(i0);
indices_.push_back(i1);
indices_.push_back(i2);
indices_.push_back(i2);
indices_.push_back(i3);
indices_.push_back(i0);
// caps
// 那这里其实重复绘制了,更好的是判定最外层两侧画上盖就好
indices_.push_back(i0);
indices_.push_back(i1);
indices_.push_back(c0);
indices_.push_back(i3);
indices_.push_back(i2);
indices_.push_back(c1);
}
}
}
}
特性:
其实有些重复绘制了,但是这样写看着比较清楚;
该function特性在于可以生成一串由多个小圆柱构成的管道,即可以实现依据给定的曲线来实现宽窄和细分可控的管道模型;
如果只需要实现一个圆柱,那么输入的 vertices_ 存两个点就好了,比较灵活。