圆柱的顶面和底面都是一个圆形,其顶点坐标为(R*cos(弧度),y,R*sin(弧度))其中R分别为圆柱高的峰值,比如正放于中心时y就是h/2或者-h/2,h/2表示顶面,-h/2表示底面。其纹理坐标为(0.5-0.5*cos(弧度),0.5-0.5*sin(弧度)),法向量为垂直向上或向下的单位向量。
圆柱的侧面可以将其分割为n份,用GL_TRIANGLE_FAN绘制三角形的方式将其绘出;我们可以以弧度为分割标准,将其分成n份,其中分成的小三角形为两个相邻弧度求出的上、下顶点的四个坐标;纹理坐标可理解为弧度占整个圆周的比例,以此来从0-1中取值得到纹理坐标;法向量则为其顶点坐标。
了解了圆柱的顶点生成原理后,我们就可以写出渲染器了,其实现如下
#ifndef CYLINDERRENDER_H
#define CYLINDERRENDER_H
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include <QOpenGLExtraFunctions>
#define PI 3.14159265f
class CylinderRender
{
public:
CylinderRender() = default;
void initsize(QImage &ce,QImage &top,QImage &bottom,float r,float h);
void render(QOpenGLExtraFunctions *f,QMatrix4x4 &pMatrix,QMatrix4x4 &vMatrix,QMatrix4x4 &mMatrix,QVector3D &light,QVector3D &camera);
private:
QOpenGLShaderProgram program_;
QOpenGLBuffer vbo_;
QVector<GLfloat> vertVec_,textVec_,normalVec_;
QVector<GLfloat> ceVec,ceTextVec,ceNorVec,topVec,topTexVec,topNorVec,bottomVec,bottomTexVec,bottomNorVec;
QOpenGLTexture *ceTexture_{nullptr},*topTexture_{nullptr},*bottomTexture_{nullptr};
};
#endif // CYLINDERRENDER_H
#include "cylinderrender.h"
void CylinderRender::initsize(QImage &ce, QImage &top, QImage &bottom, float r, float h)
{
program_.addCacheableShaderFromSourceFile(QOpenGLShader::Vertex,"vsrc.vert");
program_.addCacheableShaderFromSourceFile(QOpenGLShader::Fragment,"fsrc.frag");
program_.link();
topTexture_ = new QOpenGLTexture(top);
bottomTexture_ = new QOpenGLTexture(bottom);
ceTexture_ = new QOpenGLTexture(ce);
topTexture_->setWrapMode(QOpenGLTexture::ClampToEdge);
topTexture_->setMinMagFilters(QOpenGLTexture::NearestMipMapNearest,QOpenGLTexture::LinearMipMapNearest);
bottomTexture_->setWrapMode(QOpenGLTexture::ClampToEdge);
bottomTexture_->setMinMagFilters(QOpenGLTexture::NearestMipMapNearest,QOpenGLTexture::LinearMipMapNearest);
ceTexture_->setWrapMode(QOpenGLTexture::ClampToEdge);
ceTexture_->setMinMagFilters(QOpenGLTexture::NearestMipMapNearest,QOpenGLTexture::LinearMipMapNearest);
float angleSpan = 5;
topVec << 0 << h/2 << 0;
topTexVec << 0.5 << 0.5;
topNorVec << 0 << 1 << 0;
bottomVec << 0 << -h/2 << 0;
bottomTexVec << 0.5 << 0.5;
bottomNorVec << 0 << -1 << 0;
for(float angle = 0; angle <= 360; angle += angleSpan){
//侧面
float curRad = angle * PI / 180;
float x1 = r * ::cos(curRad);
float y1 = - h/2;
float z1 = r * ::sin(curRad);
float hx1 = (angle/360);
float hy1 = 0;
float nx1 = x1;
float ny1 = 0;
float nz1 = z1;
float x2 = x1;
float y2 = h/2;
float z2 = z1;
float hx2 = hx1;
float hy2 = 1;
float nx2 = x2;
float ny2 = 0;
float nz2 = z2;
float nextRad = angle + angleSpan;
float x3 = r * ::cos(nextRad * PI / 180);
float y3 = h/2;
float z3 = r * ::sin(nextRad * PI / 180);
float hx3 = (nextRad/360);
float hy3 = 1;
float nx3 = x3;
float ny3 = 0;
float nz3 = z3;
float x4 = x3;
float y4 = -h/2;
float z4 = z3;
float hx4 = hx3;
float hy4 = 0;
float nx4 = x4;
float ny4 = 0;
float nz4 = z4;
ceVec << x1 << y1 << z1 << x2 << y2 << z2 << x3 << y3 << z3 << x4 << y4 << z4;
ceTextVec << hx2 << hy2 << hx1 << hy1<< hx4 << hy4 << hx3 << hy3;
ceNorVec << nx1 << ny1 << nz1 << nx2 << ny2 << nz2 << nx3 << ny3 << nz3 << nx4 << ny4 << nz4;
//顶面
x2 = r * ::cos(-curRad);
z2 = r * ::sin(-curRad);
topVec << x2 << y2 << z2;
float topTx1 = 0.5 - 0.5 * ::cos(curRad);
float topTy1 = 0.5 - 0.5 * ::sin(-curRad);
topTexVec << topTx1 << topTy1;
topNorVec << 0 << 1 << 0;
//底面
bottomVec << x1 << y1 << z1;
bottomTexVec << topTx1 << topTy1;
bottomNorVec << 0 << -1 << 0;
}
vertVec_ << ceVec << topVec << bottomVec;
textVec_ << ceTextVec << topTexVec << bottomTexVec;
normalVec_ << ceNorVec << topNorVec << bottomNorVec;
QVector<GLfloat> bytesVec;
bytesVec << vertVec_ << textVec_ << normalVec_;
vbo_.create();
vbo_.bind();
vbo_.allocate(bytesVec.data(),bytesVec.count() * sizeof GLfloat);
}
void CylinderRender::render(QOpenGLExtraFunctions *f, QMatrix4x4 &pMatrix, QMatrix4x4 &vMatrix, QMatrix4x4 &mMatrix, QVector3D &light, QVector3D &camera)
{
f->glEnable(GL_DEPTH_TEST);
f->glEnable(GL_CULL_FACE);
program_.bind();
vbo_.bind();
f->glActiveTexture(GL_TEXTURE0 + 0);
program_.setUniformValue("uPMatrix",pMatrix);
program_.setUniformValue("uVMatrix",vMatrix);
program_.setUniformValue("uMMatrix",mMatrix);
program_.setUniformValue("uLightLocation",light);
program_.setUniformValue("uCamera",camera);
program_.setUniformValue("sTextures",0);
program_.enableAttributeArray(0);
program_.enableAttributeArray(1);
program_.enableAttributeArray(2);
program_.setAttributeBuffer(0,GL_FLOAT,0,3,3*sizeof(GLfloat));
program_.setAttributeBuffer(1,GL_FLOAT,vertVec_.count() * sizeof(GLfloat),2,2*sizeof(GLfloat));
program_.setAttributeBuffer(2,GL_FLOAT,(vertVec_.count() + textVec_.count())*sizeof(GLfloat),3,3*sizeof(GLfloat));
ceTexture_->bind();
f->glDrawArrays(GL_QUADS,0,ceVec.count()/3);
topTexture_->bind();
f->glDrawArrays(GL_TRIANGLE_FAN,ceVec.count()/3,topVec.count()/3);
bottomTexture_->bind();
f->glDrawArrays(GL_TRIANGLE_FAN,(ceVec.count() + topVec.count())/3,bottomVec.count()/3);
program_.disableAttributeArray(0);
program_.disableAttributeArray(1);
program_.disableAttributeArray(2);
topTexture_->release();
ceTexture_->release();
bottomTexture_->release();
vbo_.release();
program_.release();
f->glDisable(GL_CULL_FACE);
f->glDisable(GL_DEPTH_TEST);
}
其shader和平时的差不多,只不过用了上、下、侧三个纹理
#version 330
uniform mat4 uPMatrix,uVMatrix,uMMatrix;
uniform vec3 uCamera,uLightLocation;
layout (location = 0)in vec3 aPosition;
layout (location = 1)in vec2 aTexture;
layout (location = 2)in vec3 aNormal;
smooth out vec3 vPosition;
smooth out vec2 vTexture;
smooth out vec4 vAmbient,vDiffuse,vSpecular;
void pointLight(in vec3 normal,inout vec4 ambient,inout vec4 diffuse,inout vec4 specular,in vec4 lightAmbient,in vec4 lightDiffuse,in vec4 lightSpecular,in float shininess){
ambient = lightAmbient;
vec3 normalTarget = aPosition + normal;
vec3 newNormal = normalize((uMMatrix * vec4(normalTarget,1)).xyz - (uMMatrix * vec4(aPosition,1)).xyz);
vec3 eye = normalize(uCamera - (uMMatrix * vec4(aPosition,1)).xyz);
vec3 vp = normalize(uLightLocation - (uMMatrix * vec4(aPosition,1)).xyz);
vec3 halfVector = normalize(eye + vp);
float nDotViewPotision = max(0.0,dot(newNormal,vp));
diffuse = lightDiffuse * nDotViewPotision;
float nDotViewHalfVector = dot(newNormal,halfVector);
float powerFactor = max(0.0,pow(nDotViewHalfVector,shininess));
specular = lightSpecular * powerFactor;
}
void main(void)
{
gl_Position = uPMatrix * uVMatrix * uMMatrix * vec4(aPosition,1);
vec4 ambient = vec4(0.0,0.0,0.0,0.0),diffuse = vec4(0.0,0.0,0.0,0.0),specular = vec4(0.0,0.0,0.0,0.0);
pointLight(aNormal,ambient,diffuse,specular,vec4(0.5,0.5,0.5,1),vec4(0.8,0.8,0.8,1),vec4(0.7,0.7,0.7,1),50.0);
vPosition = aPosition;
vTexture = aTexture;
vAmbient = ambient;
vDiffuse = diffuse;
vSpecular = specular;
}
#version 330
uniform sampler2D sTextures;
in vec3 vPosition;
in vec4 vAmbient,vDiffuse,vSpecular;
in vec2 vTexture;
out vec4 fragColor;
void main(void)
{
vec4 color = vec4(0.0,0.0,0.0,1.0);
color = texture2D(sTextures,vTexture);
fragColor = color * (vAmbient + vDiffuse + vSpecular);
}
其使用和前面一样了,传入参数即可
#ifndef WIDGET_H
#define WIDGET_H
#include <QOpenGLWidget>
#include <QTimer>
#include "cylinderrender.h"
class Widget : public QOpenGLWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
protected:
void paintGL() override;
void resizeGL(int w,int h) override;
void initializeGL() override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
private:
QMatrix4x4 pMatrix_;
QVector3D lightLocation_,camera_;
CylinderRender render_;
QTimer tm;
qreal angleX_ = 0,angleY_ = 0,angleZ_ = 0;
private slots:
void slotTimeout();
};
#endif // WIDGET_H
#include <QTimer>
#include "widget.h"
Widget::Widget(QWidget *parent)
: QOpenGLWidget(parent)
{
connect(&tm,SIGNAL(timeout()),this,SLOT(slotTimeout()));
tm.start(40);
}
Widget::~Widget()
{
}
void Widget::paintGL()
{
QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
f->glClearColor(0.0,0.0,0.0,1.0);
f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
QMatrix4x4 vMatrix;
vMatrix.lookAt(camera_,QVector3D(0,0,0),QVector3D(0,1,0));
QMatrix4x4 mMatrix;
mMatrix.rotate(angleX_,1,0,0);
mMatrix.rotate(angleY_,0,1,0);
mMatrix.rotate(angleZ_,0,0,1);
render_.render(f,pMatrix_,vMatrix,mMatrix,lightLocation_,camera_);
}
void Widget::resizeGL(int w, int h)
{
pMatrix_.setToIdentity();
pMatrix_.perspective(45,float(w)/h,0.01f,100.0f);
}
void Widget::initializeGL()
{
render_.initsize(QImage("rect.jpg"),QImage("circle.jpg"),QImage("circle.jpg"),0.8,0.8);
camera_.setX(0);
camera_.setY(0);
camera_.setZ(3);
lightLocation_.setX(5);
lightLocation_.setY(2);
lightLocation_.setZ(1);
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
Q_UNUSED(event)
angleX_ += 5;
angleY_ += 5;
angleZ_ += 5;
update();
}
void Widget::mousePressEvent(QMouseEvent *event)
{
tm.stop();
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
tm.start();
}
void Widget::slotTimeout()
{
angleX_ += 5;
angleY_ += 5;
angleZ_ += 5;
update();
}
到此结束。