Files
qad/qglengine/renderer_base.cpp

443 lines
15 KiB
C++

/*
QGLView
Copyright (C) 2020 Ivan Pelipenko peri4ko@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define GL_GLEXT_PROTOTYPES
#include <QOpenGLExtraFunctions>
#include "renderer_base.h"
#include "renderer.h"
#include "qglview.h"
#include "glmesh.h"
#include "gltexture_manager.h"
#include "glshaders_headers.h"
using namespace QGLEngineShaders;
RendererBase::RendererBase(QGLView * view_):
view(view_),
buffer_materials (GL_UNIFORM_BUFFER, GL_STREAM_DRAW),
buffer_lights (GL_UNIFORM_BUFFER, GL_STREAM_DRAW),
buffer_lights_pos(GL_UNIFORM_BUFFER, GL_STREAM_DRAW),
textures_empty(false),
textures_maps(true)
{
textures_manager = new TextureManager(view);
maps_size = QSize(512, 512);
maps_hash = 0;
tex_coeff[0] = tex_coeff[1] = 0;
}
RendererBase::~RendererBase() {
delete textures_manager;
}
void RendererBase::initTextureArrays() {
QOpenGLExtraFunctions * f = view;
textures_maps.init(f);
textures_empty.init(f);
textures_empty.resize(f, QSize(1, 1), 2);
textures_empty.bind(f);
QImage im(1, 1, QImage::Format_RGBA8888);
im.fill(0xFFFFFFFF);
textures_empty.load(f, im, emrWhite);
im.fill(0xFF8080);
textures_empty.load(f, im, emrBlue);
}
void RendererBase::initUniformBuffer(QOpenGLShaderProgram * prog, Buffer * buffer, int bind_point, const char * blockName) {
if (!prog || !buffer) return;
if (!prog->isLinked()) return;
QOpenGLExtraFunctions * f = view;
buffer->init(f);
//glClearError();
GLint ubo_ind = f->glGetUniformBlockIndex(prog->programId(), blockName);
f->glUniformBlockBinding(prog->programId(), ubo_ind, bind_point);
f->glBindBufferBase(GL_UNIFORM_BUFFER, bind_point, buffer->ID());
//qDebug() << "initUBO" << QString::number(f->glGetError(), 16);
}
void RendererBase::setUniformHalo(QOpenGLShaderProgram * prog, const char * type, QColor color, float fill) {
prog->setUniformValue((QString(type) + "_color").toLatin1().constData(), color);
prog->setUniformValue((QString(type) + "_fill" ).toLatin1().constData(), fill);
}
void RendererBase::setUniformMaps(QOpenGLShaderProgram * prog) {
prog->setUniformValue("qgl_texture_array[0]", (int)tarEmpty);
prog->setUniformValue("qgl_texture_array[1]", (int)tarMaps );
}
void RendererBase::setUniformCamera(QOpenGLShaderProgram * prog, Camera * cam, bool matrices, QSize viewport) {
double w = view->width(), h = view->height();
if (viewport.isValid()) {
w = viewport.width();
h = viewport.height();
}
QMatrix4x4 mat_view, mat_proj;
if (cam) {
if (matrices) {
mat_view = cam->fullViewMatrix();
mat_proj = cam->projectionMatrix(w / h);
}
prog->setUniformValue("z_near", cam->depthStart());
}
prog->setUniformValue("dt", QVector2D(1. / w, 1. / h));
prog->setUniformValue("qgl_ViewMatrix" , mat_view);
prog->setUniformValue("qgl_ViewProjMatrix", mat_proj * mat_view);
}
void RendererBase::setUniformViewCorners(QOpenGLShaderProgram * prog, Camera * cam, QSize viewport) {
double w = view->width(), h = view->height();
if (viewport.isValid()) {
w = viewport.width();
h = viewport.height();
}
QMatrix4x4 mproji = cam->projectionMatrix(w / h).inverted();
QMatrix4x4 mviewi = cam->viewMatrix().inverted();
QVector4D corner_dirs[4], world_dirs[4];
corner_dirs[0] = (mproji * QVector4D(-1, -1, 0, 1));
corner_dirs[1] = (mproji * QVector4D(-1, 1, 0, 1));
corner_dirs[2] = (mproji * QVector4D( 1, 1, 0, 1));
corner_dirs[3] = (mproji * QVector4D( 1, -1, 0, 1));
for (int i = 0; i < 4; ++i) {
world_dirs[i] = mviewi.mapVector(corner_dirs[i].toVector3D());
prog->setUniformValue(QString("view_corners[%1]").arg(i).toLatin1().constData(), corner_dirs[i]);
prog->setUniformValue(QString("world_corners[%1]").arg(i).toLatin1().constData(), world_dirs[i]);
}
}
void RendererBase::fillSelectionsBuffer(QVector<uchar> & buffer, const ObjectBaseList & ol) {
buffer.resize(ol.size());
for (int i = 0; i < ol.size(); ++i) {
buffer[i] = (ol[i]->isSelected(true) ? 1 : 0);
}
}
void RendererBase::fillSelectionsBuffer(QVector<uchar> & buffer, bool yes, int size) {
buffer.resize(size);
for (int i = 0; i < size; ++i)
buffer[i] = (yes ? 1 : 0);
}
void RendererBase::reloadMaterials(Scene & scene) {
//qDebug() << "reloadMaterias";
QList<Map*> maps[2];
QMap<QString, int> tex_layers[2];
foreach (Material * m, scene.materials) {
if (m->map_diffuse .hasBitmap()) maps[0] << &(m->map_diffuse );
if (m->map_normal .hasBitmap()) maps[1] << &(m->map_normal );
if (m->map_metalness.hasBitmap()) maps[0] << &(m->map_metalness);
if (m->map_roughness.hasBitmap()) maps[0] << &(m->map_roughness);
if (m->map_emission .hasBitmap()) maps[0] << &(m->map_emission );
if (m->map_relief .hasBitmap()) maps[0] << &(m->map_relief );
}
for (int i = 0; i < 2; ++i) {
foreach (Map * m, maps[i])
tex_layers[i][m->bitmap_path] = 0;
}
int layers_count = tex_layers[0].size() + tex_layers[1].size(), cl = -1;
uint cur_maps_hash = qHash(tex_layers[0].keys()) ^ (qHash(tex_layers[1].keys()) + 0xF00FF00F);
if (maps_hash != cur_maps_hash) {
maps_hash = cur_maps_hash;
textures_maps.resize(view, maps_size, layers_count);
textures_maps.bind(view);
for (int i = 0; i < 2; ++i) {
QMutableMapIterator<QString, int> it(tex_layers[i]);
while (it.hasNext()) {
it.next();
QImage im = textures_manager->loadTextureImage(it.key(), i == 1);
textures_maps.load(view, im, ++cl);
it.value() = cl;
}
foreach (Map * m, maps[i]) {
m->_layer = tex_layers[i].value(m->bitmap_path);
//qDebug() << "assign" << m->bitmap_path << "layer" << m->_layer;
}
}
textures_maps.mipmaps(view);
// qDebug() << "load" << (cl+1) << "bitmaps";
}
QGLMaterial glm;
cur_materials_.clear();
cur_materials_ << glm;
foreach (Material * m, scene.materials) {
if (cur_materials_.size() >= max_materials) {
qDebug() << "[QGLEngine] Warning: Too many materials! Maximum" << max_materials;
break;
}
//m->load(textures_manager);
m->_index = cur_materials_.size();
m->_changed = false;
glm.color_diffuse = QColor2QVector(m->color_diffuse );
glm.color_emission = QColor2QVector(m->color_emission);
glm.transparency = m->transparency;
glm.reflectivity = m->reflectivity;
glm.iof = m->iof ;
glm.dispersion = m->dispersion ;
m->map_diffuse .copyToQGLMap(glm.map[mtDiffuse ]);
m->map_normal .copyToQGLMap(glm.map[mtNormal ]);
m->map_metalness.copyToQGLMap(glm.map[mtMetalness ]);
m->map_roughness.copyToQGLMap(glm.map[mtRoughness]);
m->map_emission .copyToQGLMap(glm.map[mtEmission ]);
m->map_relief .copyToQGLMap(glm.map[mtRelief ]);
cur_materials_ << glm;
}
//qDebug() << "load" << cur_materials_.size() << "materials";
//textures_maps.resize(maps_size, );
//cur_materials_[0].color_diffuse = QColor2QVector(Qt::red);
buffer_materials.bind(view);
buffer_materials.resize(view, cur_materials_.size() * sizeof(QGLMaterial));
buffer_materials.load(view, cur_materials_.constData(), cur_materials_.size() * sizeof(QGLMaterial));
scene.need_reload_materials = false;
}
void RendererBase::reloadLightsParameters(const QMap<int, QList<Light*>> & lights) {
lights_start.clear();
lights_start[Light::Omni] = 0;
QMapIterator<int, QList<Light*>> it(lights);
current_lights.clear();
while (it.hasNext()) {
it.next();
lights_start[it.key()] = current_lights.size();
current_lights.append(it.value());
}
cur_lights_params_.resize(qMin(current_lights.size(), max_lights));
//qDebug() << "reloadLightsParameters" << cur_lights_params_.size();
for (int i = 0; i < cur_lights_params_.size(); ++i) {
QGLLightParameter & so(cur_lights_params_[i]);
Light * l = current_lights[i];
double ang_start = l->angle_start / 2.f, ang_end = l->angle_end / 2.f;
if (l->light_type == Light::Omni)
ang_start = ang_end = 180.;
//qDebug() << "light" << light->name() << ulightn << pos;
so.color = QColor2QVector(l->color_);
so.angles[0] = ang_start;
so.angles[1] = cos(ang_start * deg2rad);
so.angles[2] = ang_end;
so.angles[3] = cos(ang_end * deg2rad);
so.decay_intensity[0] = l->decay_const;
so.decay_intensity[1] = l->decay_linear;
so.decay_intensity[2] = l->decay_quadratic;
so.decay_intensity[3] = l->intensity;
//so.shadow = shadow;
//so.shadowColor = shadow;
}
buffer_lights.bind(view);
buffer_lights.resize(view, cur_lights_params_.size() * sizeof(QGLLightParameter));
buffer_lights.load(view, cur_lights_params_.constData(), cur_lights_params_.size() * sizeof(QGLLightParameter));
}
void RendererBase::reloadLightsPositions(Camera * cam) {
cur_lights_pos_.resize(qMin(current_lights.size(), max_lights));
QMatrix4x4 mat = cam->viewMatrix() * cam->offsetMatrix();
for (int i = 0; i < cur_lights_pos_.size(); ++i) {
QGLLightPosition & so(cur_lights_pos_[i]);
Light * l = current_lights[i];
QMatrix4x4 m = mat * l->worldTransform();
QVector4D pos(0, 0, 0, 1.), dir(QVector3D(0,0,-1), 1);//, dir0(light->dir0), dir1(light->dir1);
pos = m * pos;
dir = (m * QVector4D(QVector3D(0,0,-1),0)).normalized();//((m * dir) - pos).normalized();
so.position = pos;
so.direction = dir;
//so.shadowMatrix = l->shadow_matrix;
}
buffer_lights_pos.bind(view);
buffer_lights_pos.resize(view, cur_lights_pos_.size() * sizeof(QGLLightPosition));
buffer_lights_pos.load(view, cur_lights_pos_.constData(), cur_lights_pos_.size() * sizeof(QGLLightPosition));
}
void RendererBase::markReloadTextures() {
maps_hash = 0;
textures_manager->clearImageCache();
view->scene_->need_reload_materials = true;
}
void RendererBase::setMapsSize(QSize sz) {
maps_size = sz;
markReloadTextures();
}
void RendererBase::initQuad(Mesh * mesh, QMatrix4x4 mat) {
QGLEngineShaders::Object quab_object;
mat.transposed().copyDataTo(quab_object.modelmatrix);
mesh->init(view);
mesh->loadObject(view, quab_object);
}
void RendererBase::renderQuad(QOpenGLShaderProgram * prog, Mesh * mesh, Camera * cam, bool uniforms) {
glDisableDepth();
if (uniforms)
setUniformCamera(prog, cam, false);
mesh->draw(view, 1);
}
float RadicalInverse_VdC(uint bits) {
bits = (bits << 16u) | (bits >> 16u);
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
return float(bits) * 2.3283064365386963e-10; // / 0x100000000
}
// ----------------------------------------------------------------------------
QVector2D Hammersley(uint i, uint N) {
return QVector2D(float(i)/float(N), RadicalInverse_VdC(i));
}
QVector3D ImportanceSampleGGX(QVector2D Xi, QVector3D N, float roughness) {
float a = roughness*roughness;
float phi = 2.0 * M_PI * Xi[0];
float cosTheta = sqrt((1.0 - Xi[1]) / (1.0 + (a*a - 1.0) * Xi[1]));
float sinTheta = sqrt(1.0 - cosTheta*cosTheta);
// преобразование из сферических в декартовы координаты
QVector3D H;
H[0] = cos(phi) * sinTheta;
H[1] = sin(phi) * sinTheta;
H[2] = cosTheta;
// преобразование из касательного пространства в мировые координаты
QVector3D up = qAbs(N[2]) < 0.999 ? QVector3D(0.0, 0.0, 1.0) : QVector3D(1.0, 0.0, 0.0);
QVector3D tangent = QVector3D::crossProduct(up, N).normalized();
QVector3D bitangent = QVector3D::crossProduct(N, tangent);
QVector3D sampleVec = tangent * H[0] + bitangent * H[1] + N * H[2];
return sampleVec.normalized();
}
float GeometrySchlickGGX(float NdotV, float roughness) {
float k = (roughness * roughness) / 2.0;
float nom = NdotV;
float denom = NdotV * (1.0 - k) + k;
return nom / denom;
}
float GeometrySmith(QVector3D N, QVector3D V, QVector3D L, float roughness) {
float NdotV = piMax(QVector3D::dotProduct(N, V), 0.f);
float NdotL = piMax(QVector3D::dotProduct(N, L), 0.f);
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
return ggx1 * ggx2;
}
QVector2D IntegrateBRDF(float NdotV, float roughness) {
QVector3D V;
V[0] = sqrt(1.f - NdotV*NdotV);
V[1] = 0.f;
V[2] = NdotV;
float A = 0.f;
float B = 0.f;
QVector3D N = QVector3D(0.f, 0.f, 1.f);
const uint SAMPLE_COUNT = 256u;
for(uint i = 0u; i < SAMPLE_COUNT; ++i) {
QVector2D Xi = Hammersley(i, SAMPLE_COUNT);
QVector3D H = ImportanceSampleGGX(Xi, N, roughness);
QVector3D L = (2.f * QVector3D::dotProduct(V, H) * H - V).normalized();
float NdotL = piMax(L[2], 0.f);
float NdotH = piMax(H[2], 0.f);
float VdotH = piMax(QVector3D::dotProduct(V, H), 0.f);
if(NdotL > 0.f) {
float G = GeometrySmith(N, V, L, roughness);
float G_Vis = (G * VdotH) / (NdotH * NdotV);
float Fc = pow(1.f - VdotH, 5.f);
A += (1.f - Fc) * G_Vis;
B += Fc * G_Vis;
}
}
A /= float(SAMPLE_COUNT);
B /= float(SAMPLE_COUNT);
return QVector2D(A, B);
}
void RendererBase::initCoeffTextures() {
QImage im = QImage(":/coeffs_brdf.png").mirrored();
int size = im.width();
QVector<QVector2D> data(size*size);
int ind = -1;
for (int x = 0; x < size; ++x) {
//float c = x / double(size - 1) + 1.E-3;
for (int y = 0; y < size; ++y) {
//float r = y / double(size - 1);
QColor p = im.pixelColor(x, y);
data[++ind] = QVector2D(p.redF(), p.greenF());//IntegrateBRDF(c, 1.f - r + 1.E-3);
}
}
createCoeffTexture(tex_coeff[0], data.constData(), size, 2);
/*const int size = 512;
QVector<float> data_diff(size*size), data_spec(size*size);
double r, c, c2;
int ind = -1;
for (int x = 0; x < size; ++x) {
c = x / double(size - 1);
c = sqrt(c);
c = piMax(1.E-16, c);
c2 = c*c;
for (int y = 0; y < size; ++y) {
r = y / double(size - 1);
double r_d = r;
double r_s = r*r*r;
r_d = piMax(1.E-16, r_d);
r_s = piMax(1.E-16, r_s);
double ndlc = (1. - c2) / c2;
double diff = 2. / (1. + sqrt(1. + (1. - r_d) * ndlc));
double spec = c2 * (r_s + ndlc);
spec = r_s / (spec * spec) / M_PI;
++ind;
data_diff[ind] = piClamp(diff, 1.E-12, 1.E+36);
data_spec[ind] = piClamp(spec, 1.E-12, 1.E+36);
}
}
createCoeffTexture(tex_coeff[0], data_diff, size);
createCoeffTexture(tex_coeff[1], data_spec, size);*/
}
void RendererBase::createCoeffTexture(GLuint & id, const void * data, int size, int channels) {
QOpenGLExtraFunctions * f = view;
deleteGLTexture(f, id);
f->glGenTextures(1, &id);
f->glBindTexture(GL_TEXTURE_2D, id);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
GLenum iformat = GL_R16F, format = GL_RED;
if (channels == 2) {iformat = GL_RG16F; format = GL_RG;}
if (channels == 3) {iformat = GL_RGB16F; format = GL_RGB;}
if (channels == 4) {iformat = GL_RGBA16F; format = GL_RGBA;}
f->glTexImage2D(GL_TEXTURE_2D, 0, iformat, size, size, 0, format, GL_FLOAT, data);
}