/* QGL RendererBase Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #define GL_GLEXT_PROTOTYPES #include "renderer_base.h" #include "glmesh.h" #include "glshaders_headers.h" #include "gltexture_manager.h" #include "qglview.h" #include "renderer.h" #include 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] = QVector4D(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 & 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 & 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 maps[2]; QMap 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 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->_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"; 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> & lights) { lights_start.clear(); lights_start[Light::Omni] = 0; QMapIterator> 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; } 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); pos = m * pos; dir = (m * QVector4D(QVector3D(0, 0, -1), 0)).normalized(); so.position = pos; so.direction = dir; } 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 data(size * size); int ind = -1; for (int x = 0; x < size; ++x) { for (int y = 0; y < size; ++y) { QColor p = im.pixelColor(x, y); data[++ind] = QVector2D(p.redF(), p.greenF()); } } createCoeffTexture(tex_coeff[0], data.constData(), size, 2); } 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); }