301 lines
9.4 KiB
C++
301 lines
9.4 KiB
C++
#include <QOpenGLFramebufferObjectFormat>
|
|
#include <QThread>
|
|
#include <QDebug>
|
|
#include <QtGlobal>
|
|
#include <QGuiApplication>
|
|
#define LIGHTMAPPER_IMPLEMENTATION
|
|
#include "qtlightmapper.h"
|
|
#include "ambientocclusionbaker.h"
|
|
|
|
// This file is modfied from the example code of Andreas Mantler' lightmapper
|
|
// https://github.com/ands/lightmapper/blob/master/example/example.c
|
|
|
|
AmbientOcclusionBaker::AmbientOcclusionBaker(QScreen *targetScreen) :
|
|
QOffscreenSurface(targetScreen),
|
|
m_context(nullptr),
|
|
m_bakeWidth(256),
|
|
m_bakeHeight(256),
|
|
m_ambientOcclusionImage(nullptr),
|
|
m_colorImage(nullptr),
|
|
m_textureImage(nullptr),
|
|
m_borderImage(nullptr),
|
|
m_guideImage(nullptr),
|
|
m_imageUpdateVersion(0),
|
|
m_resultMesh(nullptr)
|
|
{
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
|
|
m_useCore = QSurfaceFormat::defaultFormat().profile() == QSurfaceFormat::CoreProfile;
|
|
#endif
|
|
create();
|
|
m_context = new QOpenGLContext();
|
|
|
|
QSurfaceFormat fmt = format();
|
|
fmt.setAlphaBufferSize(8);
|
|
fmt.setSamples(4);
|
|
setFormat(fmt);
|
|
|
|
m_context->setFormat(fmt);
|
|
m_context->create();
|
|
}
|
|
|
|
void AmbientOcclusionBaker::setRenderThread(QThread *thread)
|
|
{
|
|
m_context->moveToThread(thread);
|
|
}
|
|
|
|
void AmbientOcclusionBaker::setBorderImage(const QImage &borderImage)
|
|
{
|
|
delete m_borderImage;
|
|
m_borderImage = new QImage(borderImage);
|
|
}
|
|
|
|
void AmbientOcclusionBaker::setImageUpdateVersion(unsigned long long version)
|
|
{
|
|
m_imageUpdateVersion = version;
|
|
}
|
|
|
|
unsigned long long AmbientOcclusionBaker::getImageUpdateVersion()
|
|
{
|
|
return m_imageUpdateVersion;
|
|
}
|
|
|
|
void AmbientOcclusionBaker::setColorImage(const QImage &colorImage)
|
|
{
|
|
delete m_colorImage;
|
|
m_colorImage = new QImage(colorImage);
|
|
}
|
|
|
|
QImage *AmbientOcclusionBaker::takeAmbientOcclusionImage()
|
|
{
|
|
QImage *resultImage = m_ambientOcclusionImage;
|
|
m_ambientOcclusionImage = nullptr;
|
|
return resultImage;
|
|
}
|
|
|
|
QImage *AmbientOcclusionBaker::takeTextureImage()
|
|
{
|
|
QImage *resultImage = m_textureImage;
|
|
m_textureImage = nullptr;
|
|
return resultImage;
|
|
}
|
|
|
|
QImage *AmbientOcclusionBaker::takeGuideImage()
|
|
{
|
|
QImage *resultImage = m_guideImage;
|
|
m_guideImage = nullptr;
|
|
return resultImage;
|
|
}
|
|
|
|
MeshLoader *AmbientOcclusionBaker::takeResultMesh()
|
|
{
|
|
MeshLoader *resultMesh = m_resultMesh;
|
|
m_resultMesh = nullptr;
|
|
return resultMesh;
|
|
}
|
|
|
|
AmbientOcclusionBaker::~AmbientOcclusionBaker()
|
|
{
|
|
delete m_context;
|
|
m_context = nullptr;
|
|
destroy();
|
|
delete m_ambientOcclusionImage;
|
|
delete m_colorImage;
|
|
delete m_textureImage;
|
|
delete m_borderImage;
|
|
delete m_guideImage;
|
|
delete m_resultMesh;
|
|
}
|
|
|
|
void AmbientOcclusionBaker::setInputMesh(const MeshResultContext &meshResultContext)
|
|
{
|
|
m_meshResultContext = meshResultContext;
|
|
}
|
|
|
|
void AmbientOcclusionBaker::setBakeSize(int width, int height)
|
|
{
|
|
m_bakeWidth = width;
|
|
m_bakeHeight = height;
|
|
}
|
|
|
|
void AmbientOcclusionBaker::process()
|
|
{
|
|
bake();
|
|
|
|
if (m_colorImage) {
|
|
m_textureImage = new QImage(*m_colorImage);
|
|
QPainter mergeTexturePainter(m_textureImage);
|
|
mergeTexturePainter.setCompositionMode(QPainter::CompositionMode_Multiply);
|
|
if (m_ambientOcclusionImage)
|
|
mergeTexturePainter.drawImage(0, 0, *m_ambientOcclusionImage);
|
|
mergeTexturePainter.end();
|
|
} else {
|
|
if (m_ambientOcclusionImage)
|
|
m_textureImage = new QImage(*m_ambientOcclusionImage);
|
|
}
|
|
|
|
if (m_textureImage) {
|
|
m_guideImage = new QImage(*m_textureImage);
|
|
QPainter mergeGuidePainter(m_guideImage);
|
|
mergeGuidePainter.setCompositionMode(QPainter::CompositionMode_Multiply);
|
|
if (m_borderImage)
|
|
mergeGuidePainter.drawImage(0, 0, *m_borderImage);
|
|
mergeGuidePainter.end();
|
|
|
|
m_resultMesh = new MeshLoader(m_meshResultContext);
|
|
m_resultMesh->setTextureImage(new QImage(*m_textureImage));
|
|
}
|
|
|
|
this->moveToThread(QGuiApplication::instance()->thread());
|
|
emit finished();
|
|
}
|
|
|
|
void AmbientOcclusionBaker::bake()
|
|
{
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
|
|
if (m_meshResultContext.parts().empty())
|
|
return;
|
|
|
|
m_context->makeCurrent(this);
|
|
|
|
initializeOpenGLFunctions();
|
|
|
|
scene_t sceneStruct;
|
|
memset(&sceneStruct, 0, sizeof(sceneStruct));
|
|
scene_t *scene = &sceneStruct;
|
|
|
|
scene->w = m_bakeWidth;
|
|
scene->h = m_bakeHeight;
|
|
|
|
std::vector<vertex_t> vertices;
|
|
std::vector<int> indicies;
|
|
std::vector<QVector3D> normals;
|
|
for (const auto &part: m_meshResultContext.parts()) {
|
|
int i = 0;
|
|
int startIndex = vertices.size();
|
|
for (const auto &it: part.second.vertices) {
|
|
vertex_t vertex;
|
|
vertex.p[0] = it.position.x();
|
|
vertex.p[1] = it.position.y();
|
|
vertex.p[2] = it.position.z();
|
|
vertex.t[0] = part.second.vertexUvs[i].uv[0];
|
|
vertex.t[1] = part.second.vertexUvs[i].uv[1];
|
|
vertices.push_back(vertex);
|
|
normals.push_back(QVector3D());
|
|
i++;
|
|
}
|
|
for (const auto &it: part.second.triangles) {
|
|
normals[startIndex + it.indicies[0]] += it.normal;
|
|
normals[startIndex + it.indicies[1]] += it.normal;
|
|
normals[startIndex + it.indicies[2]] += it.normal;
|
|
indicies.push_back(startIndex + it.indicies[0]);
|
|
indicies.push_back(startIndex + it.indicies[1]);
|
|
indicies.push_back(startIndex + it.indicies[2]);
|
|
}
|
|
}
|
|
for (auto &it: normals) {
|
|
it.normalize();
|
|
}
|
|
|
|
scene->vertices = (vertex_t *)malloc(sizeof(vertex_t) * vertices.size());
|
|
for (auto i = 0u; i < vertices.size(); i++) {
|
|
scene->vertices[i] = vertices[i];
|
|
}
|
|
scene->vertexCount = vertices.size();
|
|
|
|
scene->indices = (unsigned short *)malloc(sizeof(unsigned short) * indicies.size());
|
|
for (auto i = 0u; i < indicies.size(); i++) {
|
|
scene->indices[i] = indicies[i];
|
|
}
|
|
scene->indexCount = indicies.size();
|
|
|
|
float *sceneNormals = (float *)malloc(sizeof(float) * 3 * normals.size());
|
|
int sceneNormalItemIndex = 0;
|
|
for (auto i = 0u; i < normals.size(); i++) {
|
|
sceneNormals[sceneNormalItemIndex + 0] = normals[i].x();
|
|
sceneNormals[sceneNormalItemIndex + 1] = normals[i].y();
|
|
sceneNormals[sceneNormalItemIndex + 2] = normals[i].z();
|
|
sceneNormalItemIndex += 3;
|
|
}
|
|
|
|
initScene(scene);
|
|
|
|
lm_context *ctx = lmCreate(
|
|
64, // hemisphere resolution (power of two, max=512)
|
|
0.001f, 100.0f, // zNear, zFar of hemisphere cameras
|
|
1.0f, 1.0f, 1.0f, // background color (white for ambient occlusion)
|
|
2, 0.01f, // lightmap interpolation threshold (small differences are interpolated rather than sampled)
|
|
// check debug_interpolation.tga for an overview of sampled (red) vs interpolated (green) pixels.
|
|
5.0f); // modifier for camera-to-surface distance for hemisphere rendering.
|
|
// tweak this to trade-off between interpolated normals quality and other artifacts (see declaration).
|
|
|
|
int w = scene->w, h = scene->h;
|
|
int c = 4;
|
|
float *data = (float *)calloc(w * h * c, sizeof(float));
|
|
lmSetTargetLightmap(ctx, data, w, h, c);
|
|
|
|
lmSetGeometry(ctx, NULL, // no transformation in this example
|
|
LM_FLOAT, (unsigned char*)scene->vertices + offsetof(vertex_t, p), sizeof(vertex_t),
|
|
LM_FLOAT, (unsigned char*)sceneNormals, sizeof(float) * 3, // no interpolated normals in this example
|
|
LM_FLOAT, (unsigned char*)scene->vertices + offsetof(vertex_t, t), sizeof(vertex_t),
|
|
scene->indexCount, LM_UNSIGNED_SHORT, scene->indices);
|
|
|
|
int vp[4];
|
|
float view[16], projection[16];
|
|
while (lmBegin(ctx, vp, view, projection))
|
|
{
|
|
// render to lightmapper framebuffer
|
|
glViewport(vp[0], vp[1], vp[2], vp[3]);
|
|
drawScene(scene, view, projection);
|
|
|
|
lmEnd(ctx);
|
|
}
|
|
//printf("\rFinished baking %d triangles.\n", scene->indexCount / 3);
|
|
|
|
lmDestroy(ctx);
|
|
|
|
// postprocess texture
|
|
float *temp = (float *)calloc(w * h * 4, sizeof(float));
|
|
for (int i = 0; i < 16; i++)
|
|
{
|
|
lmImageDilate(data, temp, w, h, 4);
|
|
lmImageDilate(temp, data, w, h, 4);
|
|
}
|
|
lmImageSmooth(data, temp, w, h, 4);
|
|
lmImageDilate(temp, data, w, h, 4);
|
|
lmImagePower(data, w, h, 4, 1.0f / 2.2f, 0x7); // gamma correct color channels
|
|
free(temp);
|
|
|
|
{
|
|
unsigned char *temp = (unsigned char *)LM_CALLOC(w * h * c, sizeof(unsigned char));
|
|
lmImageFtoUB(data, temp, w, h, c, 1.0f);
|
|
m_ambientOcclusionImage = new QImage(w, h, QImage::Format_ARGB32);
|
|
m_ambientOcclusionImage->fill(Qt::black);
|
|
int tempOffset = 0;
|
|
for (auto y = 0; y < h; y++) {
|
|
for (auto x = 0; x < w; x++) {
|
|
uint rgb = 0;
|
|
unsigned int r = temp[tempOffset + 0];
|
|
unsigned int g = temp[tempOffset + 1];
|
|
unsigned int b = temp[tempOffset + 2];
|
|
unsigned int a = temp[tempOffset + 3];
|
|
rgb |= b << 0; // B
|
|
rgb |= g << 8; // G
|
|
rgb |= r << 16; // R
|
|
rgb |= a << 24; // A
|
|
m_ambientOcclusionImage->setPixel(x, y, rgb);
|
|
tempOffset += 4;
|
|
}
|
|
}
|
|
LM_FREE(temp);
|
|
}
|
|
|
|
free(data);
|
|
|
|
destroyScene(scene);
|
|
|
|
free(sceneNormals);
|
|
|
|
m_context->doneCurrent();
|
|
#endif
|
|
}
|