根据前面的实践,已能通过代码生成球面的顶点,要将2维图贴到球表面;只要能计算出每个顶点对应的纹理坐标就可以了;关于2维图贴到球面的公式如下:
float pi = 3.14159265;
U = arcsin(z / R)/pi + 0.5;
V = arctan(y/x)/2/pi;
x、y、z分别为顶点的三个坐标。由此我们便可以贴图了,另外我们还可以利用shader函数帮我们计算,以减少cpu消耗。
我在贴图的同时,加入了光照效果;其渲染器实现如下:
#ifndef BALLTEXTURERENDER_H
#define BALLTEXTURERENDER_H
#include <QOpenGLExtraFunctions>
#include <QOpenGLTexture>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QImage>
#define PI 3.14159265f
class BallTextureRender
{
public:
BallTextureRender() = default;
~BallTextureRender();
void initsize(float r,QImage &img); //根据图片,生成纹理数据
void render(QOpenGLExtraFunctions *f,QMatrix4x4 &pMatrix,QMatrix4x4 &vMatrix,QMatrix4x4 &mMatrix,QVector3D &lightLocation,QVector3D &camera); //渲染
private:
QOpenGLShaderProgram program_;
QOpenGLTexture *texture_{nullptr};
QOpenGLBuffer vbo_;
float r_ = 0.0f;
QVector<GLfloat> points_;
};
#endif // BALLTEXTURERENDER_H
#include "balltexturerender.h"
BallTextureRender::~BallTextureRender()
{
if(texture_){
texture_->destroy();
delete texture_;
}
}
void BallTextureRender::initsize(float r, QImage &img)
{
program_.addCacheableShaderFromSourceFile(QOpenGLShader::Vertex,"vsrc.vert");
program_.addCacheableShaderFromSourceFile(QOpenGLShader::Fragment,"fsrc.frag");
program_.link();
r_ = r; //生成球面顶点
int angleSpan = 10; //弧度 = 角度 * PI / 180
for(int vAngle = -90; vAngle < 90; vAngle = vAngle + angleSpan){ //生成球面顶点
for(int hAngle = 0; hAngle <= 360; hAngle = hAngle + angleSpan){
float x0 = r * ::cos(vAngle * PI / 180) * ::cos(hAngle * PI / 180);
float y0 = r * ::cos(vAngle * PI / 180) * ::sin(hAngle * PI / 180);
float z0 = r * ::sin(vAngle * PI / 180);
float x1 = r * ::cos(vAngle * PI / 180) * ::cos((hAngle + angleSpan) * PI / 180);
float y1 = r * ::cos(vAngle * PI / 180) * ::sin((hAngle + angleSpan) * PI / 180);
float z1 = r * ::sin(vAngle * PI / 180);
float x2 = r * ::cos((vAngle + angleSpan) * PI / 180) * ::cos((hAngle + angleSpan) * PI / 180);
float y2 = r * ::cos((vAngle + angleSpan) * PI / 180) * ::sin((hAngle + angleSpan) * PI / 180);
float z2 = r * ::sin((vAngle + angleSpan) * PI / 180);
float x3 = r * ::cos((vAngle + angleSpan) * PI / 180) * ::cos(hAngle * PI / 180);
float y3 = r * ::cos((vAngle + angleSpan) * PI / 180) * ::sin(hAngle * PI / 180);
float z3 = r * ::sin((vAngle + angleSpan) * PI / 180);
points_ << x1 << y1 << z1 << x3 << y3 << z3
<< x0 << y0 << z0 << x1 << y1 << z1
<< x2 << y2 << z2 << x3 << y3 << z3;
}
}
texture_ = new QOpenGLTexture(img); //生成纹理数据
vbo_.create();
vbo_.bind();
vbo_.allocate(points_.constData(),points_.count() * sizeof GLfloat);
}
void BallTextureRender::render(QOpenGLExtraFunctions *f, QMatrix4x4 &pMatrix, QMatrix4x4 &vMatrix, QMatrix4x4 &mMatrix, QVector3D &lightLocation, QVector3D &camera)
{
f->glEnable(GL_DEPTH_TEST);
f->glEnable(GL_CULL_FACE);
program_.bind();
vbo_.bind();
f->glActiveTexture(GL_TEXTURE0 + 0); //使用0号纹理
program_.setUniformValue("uPMatrix",pMatrix);
program_.setUniformValue("uVMatrix",vMatrix);
program_.setUniformValue("uMMatrix",mMatrix);
program_.setUniformValue("uLightLocation",lightLocation);
program_.setUniformValue("uCamera",camera);
program_.setUniformValue("uR",r_);
program_.setUniformValue("sTexture",0);
program_.enableAttributeArray(0);
program_.enableAttributeArray(1);
texture_->bind(0); //球面上的顶点,同时也是法向量
program_.setAttributeBuffer(0,GL_FLOAT,0,3,3 * sizeof(GLfloat));
program_.setAttributeBuffer(1,GL_FLOAT,0,3,3 * sizeof(GLfloat));
f->glDrawArrays(GL_TRIANGLES,0,points_.count() / 3);
program_.disableAttributeArray(0);
program_.disableAttributeArray(1);
texture_->release();
vbo_.release();
program_.release();
f->glDisable(GL_DEPTH_TEST);
f->glDisable(GL_CULL_FACE);
}
其shader如下,光照部分采用的混合光,和前面的一样。
#version 330
uniform mat4 uPMatrix,uVMatrix,uMMatrix; //投影,视图,转换矩阵
uniform vec3 uLightLocation,uCamera; //光源位置,摄像机位置
uniform float uR; //球半径
layout (location = 0) in vec3 aPosition;
layout (location = 1) in vec3 aNormal;
smooth out vec2 vTextureCood; //每个顶点对应的纹理坐标
smooth out vec4 vAmbient; //环境光强度,传递到片元着色器
smooth out vec4 vDiffuse; //散射光强度,传递到片元着色器
smooth out vec4 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.15,0.15,0.15,1.0),vec4(0.8,0.8,0.8,1.0),vec4(0.7,0.7,0.7,1),50);
vec2 textureCood = vec2(0.0,0.0);
float pi = 3.14159265;
textureCood.x = asin(aPosition.z / uR)/pi + 0.5; //根据公式计算u坐标
textureCood.y = atan(aPosition.y/aPosition.x)/2/pi; //根据公式计算v坐标
vTextureCood = textureCood;
vAmbient = ambient;
vDiffuse = diffuse;
vSpecular = specular;
}
通过以上代码渲染器就完成了,接下来是使用。我让球面在自转的同时也执行了公转。实现如下
#ifndef WIDGET_H
#define WIDGET_H
#include <QOpenGLWidget>
#include <QTimer>
#include "balltexturerender.h"
class Widget : public QOpenGLWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
protected:
void initializeGL() override;
void paintGL() override;
void resizeGL(int w,int h) override;
private:
BallTextureRender render_; //渲染器
QVector3D lightLocation_,camera_; //光源位置和摄像机位置
QMatrix4x4 pMatrix; //投影矩阵
QTimer tm_;
float angle_ = 0; //旋转角度
private slots:
void slotTimeout();
};
#endif // WIDGET_H
#include "widget.h"
Widget::Widget(QWidget *parent)
: QOpenGLWidget(parent)
{
connect(&tm_,SIGNAL(timeout()),this,SLOT(slotTimeout()));
tm_.start(30);
}
Widget::~Widget()
{
}
void Widget::initializeGL()
{
render_.initsize(1.0,QImage("test.jpg")); //生成顶点和纹理数据
lightLocation_ = QVector3D(50,10,0);
camera_ = QVector3D(0,0,6);
}
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,-1),QVector3D(0,1,0));
QMatrix4x4 mMatrix;
//先x轴旋转30度,然后再y轴旋转一个递增角度,最后平移,实现公转和自转
mMatrix.rotate(30,1,0,0);
mMatrix.rotate(angle_,0,1,0);
mMatrix.translate(1.5,0,0);
render_.render(f,pMatrix,vMatrix,mMatrix,lightLocation_,camera_);
angle_ += 5;
}
void Widget::resizeGL(int w, int h)
{
pMatrix.setToIdentity();
pMatrix.perspective(45,float(w)/h,0.01f,100.0f);
}
void Widget::slotTimeout()
{
update();
}
工程下载地址https://download.csdn.net/download/wanghualin033/10698789