Author
This is the work of Emil Persson, aka Humus.
http://www.humus.name
License
This work is licensed under a Creative Commons Attribution 3.0 Unported License.
http://creativecommons.org/licenses/by/3.0/
main.cpp
#include <QApplication>
#include "glwidget.h"
int main(int argc, char **argv)
{
QApplication a(argc, argv);
GLWidget glw;
glw.resize(600, 600);
glw.show();
return a.exec();
}
glwidget.cpp
#include "glwidget.h"
#include "skybox.h"
#include "torus.h"
#include <QMatrix4x4>
struct GLWidgetData
{
QMatrix4x4 projectionMatrix;
QMatrix4x4 viewMatrix;
SkyBox skyBox;
Torus torus;
};
GLWidget::GLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
d = new GLWidgetData;
QSurfaceFormat format;
format.setSamples(4);
setFormat(format);
}
GLWidget::~GLWidget()
{
delete d;
}
void GLWidget::initializeGL()
{
QOpenGLFunctions::initializeOpenGLFunctions();
glClearColor(0.25, 0.35, 0.45, 1);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_POLYGON_OFFSET_LINE);
glPolygonOffset(-0.03125f, -0.03125f);
}
void GLWidget::resizeGL(int w, int h)
{
glViewport(0, 0, w, h);
d->projectionMatrix.setToIdentity();
d->projectionMatrix.perspective(60.0, float(w)/float(h), 0.1f, 20.0f);
d->viewMatrix.setToIdentity();
}
void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
d->skyBox.render(d->viewMatrix, d->projectionMatrix);
d->torus.render(d->viewMatrix, d->projectionMatrix);
}
skybox.cpp
#include "skybox.h"
#include <QMatrix4x4>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
struct SkyBoxData
{
SkyBoxData() {
for(int i=0; i<6; i++)
this->textures[i] = 0;
this->vertexBuffer = 0;
this->indexBuffer = 0;
this->shader = 0;
}
~SkyBoxData() {
delete this->shader;
delete this->indexBuffer;
delete this->vertexBuffer;
for(int i=5; i>=0; i--)
delete this->textures[i];
}
QOpenGLTexture *textures[6];
QOpenGLBuffer *vertexBuffer;
QOpenGLBuffer *indexBuffer;
QOpenGLShaderProgram *shader;
QMatrix4x4 matrix;
};
SkyBox::SkyBox()
{
d = new SkyBoxData;
}
SkyBox::~SkyBox()
{
delete d;
}
void SkyBox::render(const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix)
{
this->init();
d->shader->bind();
d->vertexBuffer->bind();
d->indexBuffer->bind();
d->shader->enableAttributeArray("qt_Vertex");
d->shader->setAttributeBuffer("qt_Vertex", GL_FLOAT, 0, 3, 0);
d->shader->enableAttributeArray("qt_MultiTexCoord0");
const int texCoordsOffset = 24 * sizeof(QVector3D);
d->shader->setAttributeBuffer("qt_MultiTexCoord0", GL_FLOAT, texCoordsOffset, 2, 0);
d->matrix.rotate(-1, 0, 1, 0);
QMatrix4x4 modelMatrix = d->matrix;
modelMatrix.scale(10, 10, 10);
const QMatrix4x4 mvp = projectionMatrix * viewMatrix * modelMatrix;
d->shader->setUniformValue("qt_ModelViewProjectionMatrix", mvp);
for(int i=0; i<6; i++)
{
d->textures[i]->bind(i+1);
d->shader->setUniformValue("qt_Texture0", (i+1));
const uint triangleOffset = i*6*sizeof(uint);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)triangleOffset);
d->textures[i]->release(i+1);
}
d->indexBuffer->release();
d->vertexBuffer->release();
d->shader->release();
}
void SkyBox::init()
{
if(d->shader)
return;
QOpenGLFunctions::initializeOpenGLFunctions();
// Load all texture images
const QImage posx = QImage(":/images/posx.jpg").mirrored();
const QImage posy = QImage(":/images/posy.jpg").mirrored();
const QImage posz = QImage(":/images/posz.jpg").mirrored();
const QImage negx = QImage(":/images/negx.jpg").mirrored();
const QImage negy = QImage(":/images/negy.jpg").mirrored();
const QImage negz = QImage(":/images/negz.jpg").mirrored();
// Load images as independent texture objects
d->textures[0] = new QOpenGLTexture(posx);
d->textures[1] = new QOpenGLTexture(posy);
d->textures[2] = new QOpenGLTexture(posz);
d->textures[3] = new QOpenGLTexture(negx);
d->textures[4] = new QOpenGLTexture(negy);
d->textures[5] = new QOpenGLTexture(negz);
for(int i=0; i<6; i++)
{
d->textures[i]->setWrapMode(QOpenGLTexture::ClampToEdge);
d->textures[i]->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
d->textures[i]->setMagnificationFilter(QOpenGLTexture::Linear);
}
// Construct a template square of size 2x2
const QVector3D p1(-1, 1, 0); // top-left
const QVector3D p2(-1, -1, 0); // bottom-left
const QVector3D p3(1, -1, 0); // bottom-right
const QVector3D p4(1, 1, 0); // top-right
// Array for storing geometry of the cube
QVector<QVector3D> geometry;
geometry.reserve(24);
// Transform p1 ... p4 for posx
QMatrix4x4 mat;
mat.translate(1, 0, 0);
mat.rotate(-90, 0, 1, 0);
geometry << mat.map(p1) << mat.map(p2)
<< mat.map(p3) << mat.map(p4);
// Transform p1 ... p4 for posy
mat.setToIdentity();
mat.translate(0, 1, 0);
mat.rotate(90, 1, 0, 0);
geometry << mat.map(p1) << mat.map(p2)
<< mat.map(p3) << mat.map(p4);
// Transform p2 ... p4 for posz
mat.setToIdentity();
mat.translate(0, 0, -1);
geometry << mat.map(p1) << mat.map(p2)
<< mat.map(p3) << mat.map(p4);
// Transform p2 ... p4 for negx
mat.setToIdentity();
mat.translate(-1, 0, 0);
mat.rotate(90, 0, 1, 0);
geometry << mat.map(p1) << mat.map(p2)
<< mat.map(p3) << mat.map(p4);
// Transform p2 ... p4 for negy
mat.setToIdentity();
mat.translate(0, -1, 0);
mat.rotate(-90, 1, 0, 0);
geometry << mat.map(p1) << mat.map(p2)
<< mat.map(p3) << mat.map(p4);
// Transform p2 ... p4 for negz
mat.setToIdentity();
mat.translate(0, 0, 1);
mat.rotate(180, 0, 1, 0);
geometry << mat.map(p1) << mat.map(p2)
<< mat.map(p3) << mat.map(p4);
// Texture coordinates
QVector<QVector2D> texCoords;
texCoords.reserve(24);
for(int i=0; i<6; i++)
{
texCoords << QVector2D(0, 1)
<< QVector2D(0, 0)
<< QVector2D(1, 0)
<< QVector2D(1, 1);
}
// Triangles
QVector<uint> triangles;
triangles.reserve(36);
for(int i=0; i<6; i++)
{
const int base = i*4;
triangles << base << base+1 << base+2;
triangles << base << base+2 << base+3;
}
// Store the arrays in buffers
d->vertexBuffer = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
d->vertexBuffer->create();
d->vertexBuffer->bind();
d->vertexBuffer->allocate( geometry.size()*sizeof(QVector3D) +
texCoords.size()*sizeof(QVector2D) );
d->vertexBuffer->write(0, (const void *)geometry.constData(),
geometry.size()*sizeof(QVector3D) );
d->vertexBuffer->write(geometry.size()*sizeof(QVector3D),
(const void *)texCoords.constData(),
texCoords.size()*sizeof(QVector2D) );
d->vertexBuffer->release();
d->indexBuffer = new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
d->indexBuffer->create();
d->indexBuffer->bind();
d->indexBuffer->allocate((const void*)triangles.constData(),
triangles.size()*sizeof(uint));
d->indexBuffer->release();
// Create shaders
d->shader = new QOpenGLShaderProgram;
d->shader->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/skybox_vertex.glsl");
d->shader->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/skybox_fragment.glsl");
d->shader->link();
}
torus.cpp
#include "torus.h"
#include <QColor>
#include <QMatrix4x4>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
struct TorusData
{
TorusData() :
tubeRadius(0.3), torusRadius(1.0),
tubeResolution(20), torusResolution(40),
vertexBuffer(0), indexBuffer(0), shader(0),
angle(0) { }
~TorusData() {
delete vertexBuffer;
delete indexBuffer;
delete shader;
}
const float tubeRadius;
const float torusRadius;
const int tubeResolution;
const int torusResolution;
QOpenGLTexture *environment;
QOpenGLBuffer *vertexBuffer;
QOpenGLBuffer *indexBuffer;
QOpenGLShaderProgram *shader;
float angle;
};
Torus::Torus()
{
d = new TorusData;
}
Torus::~Torus()
{
delete d;
}
void Torus::render(const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix)
{
this->init();
d->shader->bind();
d->vertexBuffer->bind();
d->indexBuffer->bind();
d->shader->enableAttributeArray("qt_Vertex");
d->shader->setAttributeBuffer("qt_Vertex", GL_FLOAT, 0, 3, 2*sizeof(QVector3D));
d->shader->enableAttributeArray("qt_Normal");
d->shader->setAttributeBuffer("qt_Normal", GL_FLOAT, sizeof(QVector3D), 3, 2*sizeof(QVector3D));
QMatrix4x4 modelMatrix;
modelMatrix.translate(0, 0, -3.0);
modelMatrix.rotate(d->angle, 0, 1, 0);
d->angle += 5;
const QMatrix4x4 mvMat = viewMatrix * modelMatrix;
const QMatrix4x4 normalMat = mvMat.inverted().transposed();
d->shader->setUniformValue("qt_ModelViewMatrix", mvMat);
d->shader->setUniformValue("qt_ProjectionMatrix", projectionMatrix);
d->shader->setUniformValue("qt_NormalMatrix", normalMat);
d->shader->setUniformValue("qt_EyePosition", QVector3D(0,0,-1));
d->shader->setUniformValue("qt_Light.direction", QVector3D(1,1,1));
d->shader->setUniformValue("qt_Light.ambientColor", QColor(Qt::gray));
d->shader->setUniformValue("qt_Light.diffuseColor", QColor(Qt::white));
d->shader->setUniformValue("qt_Light.specularColor", QColor(Qt::white));
d->shader->setUniformValue("qt_Material.ambientColor", QColor(Qt::gray).darker());
d->shader->setUniformValue("qt_Material.diffuseColor", QColor(Qt::gray).lighter());
d->shader->setUniformValue("qt_Material.specularColor", QColor(Qt::white));
d->shader->setUniformValue("qt_Material.specularPower", 0.9f);
d->shader->setUniformValue("qt_Material.opacity", 1.0f);
d->shader->setUniformValue("qt_Material.brightness", 2.0f);
d->environment->bind(0);
d->shader->setUniformValue("qt_Environment", 0);
glEnable(GL_CULL_FACE);
glCullFace(GL_CCW);
const int nrIndicies = d->torusResolution * d->tubeResolution * 6;
glDrawElements(GL_TRIANGLES, nrIndicies, GL_UNSIGNED_INT, (void*)0);
glDisable(GL_CULL_FACE);
d->indexBuffer->release();
d->vertexBuffer->release();
d->environment->release(0);
d->shader->release();
}
void Torus::init()
{
if(d->shader)
return;
QOpenGLFunctions::initializeOpenGLFunctions();
d->vertexBuffer = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
d->vertexBuffer->create();
d->vertexBuffer->bind();
d->vertexBuffer->allocate(d->tubeResolution * d->torusResolution * sizeof(QVector3D) * 2);
d->indexBuffer = new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
d->indexBuffer->create();
d->indexBuffer->bind();
d->indexBuffer->allocate( d->torusResolution * d->tubeResolution * 6 * sizeof(uint) );
QMatrix4x4 matrix;
const float tubeAngleStep = 360.0f / float(d->tubeResolution-1);
const float torusAngleStep = 360.0f / float(d->torusResolution-1);
int vertexBufferOffset = 0;
for(int i=0; i<d->torusResolution; i++)
{
QMatrix4x4 tmp = matrix;
tmp.translate(d->torusRadius, 0, 0);
const QVector3D centerPt = tmp.map(QVector3D(0,0,0));
for(int j=0; j<d->tubeResolution; j++)
{
const QVector3D pt = tmp.map( QVector3D(d->tubeRadius, 0, 0) );
d->vertexBuffer->write(vertexBufferOffset, &pt, sizeof(pt));
vertexBufferOffset += sizeof(pt);
const QVector3D normal = (pt - centerPt).normalized();
d->vertexBuffer->write(vertexBufferOffset, &normal, sizeof(normal));
vertexBufferOffset += sizeof(normal);
tmp.rotate(tubeAngleStep, QVector3D(0,1,0));
}
matrix.rotate(torusAngleStep, QVector3D(0,0,1));
}
int indexBufferOffset = 0;
int base = 0;
const int nrPoints = (d->torusResolution * d->tubeResolution);
#define P(x) (x)%nrPoints
for(int i=0; i<d->torusResolution; i++)
{
for(int j=0; j<d->tubeResolution; j++)
{
const uint tgls[] = {
P(base),
P(base+1),
P(base+1+d->tubeResolution),
P(base),
P(base+1+d->tubeResolution),
P(base+d->tubeResolution)
};
d->indexBuffer->write(indexBufferOffset, (const void*)tgls, sizeof(uint)*6);
indexBufferOffset += sizeof(uint)*6;
++base;
}
#undef P
}
d->indexBuffer->release();
d->vertexBuffer->release();
// Load all environment texture images
const QImage posx = QImage(":/images/posx.jpg").mirrored().convertToFormat(QImage::Format_RGBA8888);
const QImage posy = QImage(":/images/posy.jpg").mirrored().convertToFormat(QImage::Format_RGBA8888);
const QImage posz = QImage(":/images/posz.jpg").mirrored().convertToFormat(QImage::Format_RGBA8888);
const QImage negx = QImage(":/images/negx.jpg").mirrored().convertToFormat(QImage::Format_RGBA8888);
const QImage negy = QImage(":/images/negy.jpg").mirrored().convertToFormat(QImage::Format_RGBA8888);
const QImage negz = QImage(":/images/negz.jpg").mirrored().convertToFormat(QImage::Format_RGBA8888);
// Construct the environment texture
d->environment = new QOpenGLTexture(QOpenGLTexture::TargetCubeMap);
d->environment->create();
d->environment->setSize(posx.width(), posx.height(), posx.depth());
d->environment->setFormat(QOpenGLTexture::RGBA8_UNorm);
d->environment->allocateStorage();
d->environment->setData(0, 0, QOpenGLTexture::CubeMapPositiveX,
QOpenGLTexture::RGBA, QOpenGLTexture::UInt8,
(const void*)posx.constBits(), 0);
d->environment->setData(0, 0, QOpenGLTexture::CubeMapPositiveY,
QOpenGLTexture::RGBA, QOpenGLTexture::UInt8,
(const void*)posy.constBits(), 0);
d->environment->setData(0, 0, QOpenGLTexture::CubeMapPositiveZ,
QOpenGLTexture::RGBA, QOpenGLTexture::UInt8,
(const void*)posz.constBits(), 0);
d->environment->setData(0, 0, QOpenGLTexture::CubeMapNegativeX,
QOpenGLTexture::RGBA, QOpenGLTexture::UInt8,
(const void*)negx.constBits(), 0);
d->environment->setData(0, 0, QOpenGLTexture::CubeMapNegativeY,
QOpenGLTexture::RGBA, QOpenGLTexture::UInt8,
(const void*)negy.constBits(), 0);
d->environment->setData(0, 0, QOpenGLTexture::CubeMapNegativeZ,
QOpenGLTexture::RGBA, QOpenGLTexture::UInt8,
(const void*)negz.constBits(), 0);
d->environment->generateMipMaps();
d->environment->setWrapMode(QOpenGLTexture::ClampToEdge);
d->environment->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
d->environment->setMagnificationFilter(QOpenGLTexture::LinearMipMapLinear);
// Load and link shader programs
d->shader = new QOpenGLShaderProgram;
d->shader->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/torus_vertex.glsl");
d->shader->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/torus_fragment.glsl");
d->shader->link();
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
}
skybox_fragment.glsl
uniform sampler2D qt_Texture0;
varying vec4 qt_TexCoord0;
void main(void)
{
gl_FragColor = texture2D(qt_Texture0, qt_TexCoord0.st);
}
skybox_vertex.glsl
attribute vec4 qt_Vertex;
attribute vec4 qt_MultiTexCoord0;
uniform mat4 qt_ModelViewProjectionMatrix;
varying vec4 qt_TexCoord0;
void main(void)
{
gl_Position = qt_ModelViewProjectionMatrix * qt_Vertex;
qt_TexCoord0 = qt_MultiTexCoord0;
}
torus_vertex.glsl
attribute vec3 qt_Vertex;
attribute vec3 qt_Normal;
uniform mat4 qt_NormalMatrix;
uniform mat4 qt_ModelViewMatrix;
uniform mat4 qt_ProjectionMatrix;
varying vec3 v_Position;
varying vec3 v_Normal;
varying vec3 v_TexCoord;
const float c_zero = 0.0;
const float c_one = 1.0;
void main(void)
{
vec4 normal = normalize( qt_NormalMatrix * vec4(qt_Normal, c_zero) );
v_Normal = normal.xyz;
vec4 position = qt_ModelViewMatrix * vec4(qt_Vertex, c_one);
v_Position = position.xyz;
v_TexCoord = v_Normal;
mat4 mat = qt_ProjectionMatrix * qt_ModelViewMatrix;
gl_Position = mat * vec4(qt_Vertex, c_one);
}
torus_fragment.glsl
struct directional_light
{
vec3 direction;
vec4 ambientColor;
vec4 diffuseColor;
vec4 specularColor;
};
struct material_properties
{
vec4 ambientColor;
vec4 diffuseColor;
vec4 specularColor;
float specularPower;
float opacity;
float brightness;
};
uniform directional_light qt_Light;
uniform material_properties qt_Material;
uniform vec3 qt_EyePosition;
uniform samplerCube qt_Environment;
varying vec3 v_Position;
varying vec3 v_Normal;
varying vec3 v_TexCoord;
const float c_zero = 0.0;
const float c_one = 1.0;
vec4 evaluateColor(in vec3 normal, in vec3 texCoord)
{
// Start with black color
vec3 finalColor = vec3(c_zero, c_zero, c_zero);
// Upgrade black color to the base ambient color
finalColor += qt_Light.ambientColor.rgb * qt_Material.ambientColor.rgb;
// Add diffuse component to it
vec3 lightDir = normalize(qt_Light.direction);
float diffuseFactor = max( c_zero, dot(lightDir, normal) );
if(diffuseFactor > c_zero)
{
finalColor += qt_Light.diffuseColor.rgb *
qt_Material.diffuseColor.rgb *
diffuseFactor * qt_Material.brightness;
}
// Add specular component to it
vec3 viewDir = normalize(qt_EyePosition - v_Position);
vec3 reflectionVec = reflect(viewDir, normal);
const vec3 blackColor = vec3(c_zero, c_zero, c_zero);
if( !(qt_Material.specularColor.rgb == blackColor ||
qt_Light.specularColor.rgb == blackColor ||
qt_Material.specularPower == c_zero) )
{
float specularFactor = max( c_zero, dot(reflectionVec, -viewDir) );
if(specularFactor > c_zero)
{
specularFactor = pow( specularFactor, qt_Material.specularPower );
finalColor += qt_Light.specularColor.rgb *
qt_Material.specularColor.rgb *
specularFactor;
}
}
// Texture mapping
finalColor *= textureCube(qt_Environment, reflectionVec).rgb;
// All done!
return vec4( finalColor, c_one );
}
void main(void)
{
gl_FragColor = evaluateColor(v_Normal, v_TexCoord);
}