From 6c2b4f6f04f2d94e4f64dc731c9ef2615d75ec01 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Mon, 29 May 2023 04:37:23 -0400 Subject: [PATCH] sky rendering (still needs work) wireframe slightly easier to work with --- common/imglib.cc | 9 +- include/common/imglib.hh | 2 +- lightpreview/glview.cpp | 261 ++++++++++++++++++++++++++++++++++----- lightpreview/glview.h | 32 ++++- 4 files changed, 261 insertions(+), 43 deletions(-) diff --git a/common/imglib.cc b/common/imglib.cc index cc63dc16..dcf8c3fd 100644 --- a/common/imglib.cc +++ b/common/imglib.cc @@ -325,16 +325,17 @@ qvec3b calculate_average(const std::vector &pixels) } std::tuple, fs::resolve_result, fs::data> load_texture( - const std::string_view &name, bool meta_only, const gamedef_t *game, const settings::common_settings &options) + const std::string_view &name, bool meta_only, const gamedef_t *game, const settings::common_settings &options, + bool no_prefix) { - fs::path prefix; + fs::path prefix {}; - if (game->id == GAME_QUAKE_II) { + if (!no_prefix && game->id == GAME_QUAKE_II) { prefix = "textures"; } for (auto &ext : img::extension_list) { - fs::path p = (prefix / name) += ext.suffix; + fs::path p = (no_prefix ? fs::path(name) : (prefix / name)) += ext.suffix; if (auto pos = fs::where(p, options.filepriority.value() == settings::search_priority_t::LOOSE)) { if (auto data = fs::load(pos)) { diff --git a/include/common/imglib.hh b/include/common/imglib.hh index a2855d3d..dbb5dcd5 100644 --- a/include/common/imglib.hh +++ b/include/common/imglib.hh @@ -114,7 +114,7 @@ constexpr struct // Attempt to load a texture from the specified name. std::tuple, fs::resolve_result, fs::data> load_texture( - const std::string_view &name, bool meta_only, const gamedef_t *game, const settings::common_settings &options); + const std::string_view &name, bool meta_only, const gamedef_t *game, const settings::common_settings &options, bool no_prefix = false); enum class meta_ext { diff --git a/lightpreview/glview.cpp b/lightpreview/glview.cpp index 478b779c..9495ecbb 100644 --- a/lightpreview/glview.cpp +++ b/lightpreview/glview.cpp @@ -171,8 +171,90 @@ void main() { } )"; +static const char *s_skyboxFragShader = R"( +#version 330 core + +in vec3 uv; +in vec2 lightmap_uv; +in vec3 normal; +flat in vec3 flat_color; +flat in uint styles; + +out vec4 color; + +uniform samplerCube texture_sampler; +uniform sampler2DArray lightmap_sampler; +uniform bool lightmap_only; +uniform bool fullbright; +uniform bool drawnormals; +uniform bool drawflat; +uniform float style_scalars[256]; + +void main() { + if (drawnormals) { + // remap -1..+1 to 0..1 + color = vec4((normal + vec3(1.0)) / vec3(2.0), 1.0); + } else if (drawflat) { + color = vec4(flat_color, 1.0); + } else { + if (!fullbright && lightmap_only) + { + vec3 lmcolor = vec3(0.5); + + for (uint i = 0u; i < 32u; i += 8u) + { + uint style = (styles >> i) & 0xFFu; + + if (style == 0xFFu) + break; + + lmcolor += texture(lightmap_sampler, vec3(lightmap_uv, (float) style)).rgb * style_scalars[style]; + } + + // 2.0 for overbright + color = vec4(lmcolor * 2.0, 1.0); + } + else + color = vec4(texture(texture_sampler, uv).rgb, 1.0); + } +} +)"; + +static const char *s_skyboxVertShader = R"( +#version 330 core + +layout (location = 0) in vec3 position; +layout (location = 1) in vec2 vertex_uv; +layout (location = 2) in vec2 vertex_lightmap_uv; +layout (location = 3) in vec3 vertex_normal; +layout (location = 4) in vec3 vertex_flat_color; +layout (location = 5) in uint vertex_styles; + +smooth out vec3 uv; +out vec2 lightmap_uv; +out vec3 normal; +flat out vec3 flat_color; +flat out uint styles; + +uniform mat4 MVP; +uniform vec3 eye_origin; + +void main() { + gl_Position = MVP * vec4(position.x, position.y, position.z, 1.0); + + uv = normalize(position - eye_origin); + lightmap_uv = vertex_lightmap_uv; + normal = vertex_normal; + flat_color = vertex_flat_color; + styles = vertex_styles; +} +)"; + void GLView::handleLoggedMessage(const QOpenGLDebugMessage &debugMessage) { + if (debugMessage.type() == QOpenGLDebugMessage::ErrorType) + __debugbreak(); + qDebug() << debugMessage.message(); } @@ -186,7 +268,7 @@ void GLView::initializeGL() logger->initialize(); // initializes in the current context, i.e. ctx connect(logger, &QOpenGLDebugLogger::messageLogged, this, &GLView::handleLoggedMessage); - logger->startLogging(); + logger->startLogging(QOpenGLDebugLogger::SynchronousLogging); // set up shader @@ -195,6 +277,11 @@ void GLView::initializeGL() m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, s_fragShader); assert(m_program->link()); + m_skybox_program = new QOpenGLShaderProgram(); + m_skybox_program->addShaderFromSourceCode(QOpenGLShader::Vertex, s_skyboxVertShader); + m_skybox_program->addShaderFromSourceCode(QOpenGLShader::Fragment, s_skyboxFragShader); + assert(m_skybox_program->link()); + m_program_wireframe = new QOpenGLShaderProgram(); m_program_wireframe->addShaderFromSourceCode(QOpenGLShader::Vertex, s_vertShader_Wireframe); m_program_wireframe->addShaderFromSourceCode(QOpenGLShader::Fragment, s_fragShader_Wireframe); @@ -212,6 +299,19 @@ void GLView::initializeGL() m_program_style_scalars_location = m_program->uniformLocation("style_scalars"); m_program->release(); + m_skybox_program->bind(); + m_skybox_program_mvp_location = m_skybox_program->uniformLocation("MVP"); + m_skybox_program_eye_direction_location = m_skybox_program->uniformLocation("eye_origin"); + m_skybox_program_texture_sampler_location = m_skybox_program->uniformLocation("texture_sampler"); + m_skybox_program_lightmap_sampler_location = m_skybox_program->uniformLocation("lightmap_sampler"); + m_skybox_program_opacity_location = m_skybox_program->uniformLocation("opacity"); + m_skybox_program_lightmap_only_location = m_skybox_program->uniformLocation("lightmap_only"); + m_skybox_program_fullbright_location = m_skybox_program->uniformLocation("fullbright"); + m_skybox_program_drawnormals_location = m_skybox_program->uniformLocation("drawnormals"); + m_skybox_program_drawflat_location = m_skybox_program->uniformLocation("drawflat"); + m_skybox_program_style_scalars_location = m_skybox_program->uniformLocation("style_scalars"); + m_skybox_program->release(); + m_program_wireframe->bind(); m_program_wireframe_mvp_location = m_program_wireframe->uniformLocation("MVP"); m_program_wireframe->release(); @@ -257,6 +357,10 @@ void GLView::paintGL() m_program_wireframe->bind(); m_program_wireframe->setUniformValue(m_program_wireframe_mvp_location, MVP); + glEnable(GL_POLYGON_OFFSET_FILL); + glEnable(GL_POLYGON_OFFSET_LINE); + glPolygonOffset(-0.8, 1.0); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao); @@ -265,10 +369,14 @@ void GLView::paintGL() reinterpret_cast(draw.first_index * sizeof(uint32_t))); } glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glDisable(GL_POLYGON_OFFSET_FILL); + glDisable(GL_POLYGON_OFFSET_LINE); m_program_wireframe->release(); } + QOpenGLShaderProgram *active_program = nullptr; + m_program->bind(); m_program->setUniformValue(m_program_mvp_location, MVP); m_program->setUniformValue(m_program_texture_sampler_location, 0 /* texture unit */); @@ -279,11 +387,27 @@ void GLView::paintGL() m_program->setUniformValue(m_program_drawnormals_location, m_drawNormals); m_program->setUniformValue(m_program_drawflat_location, m_drawFlat); + m_skybox_program->bind(); + m_skybox_program->setUniformValue(m_skybox_program_mvp_location, MVP); + m_skybox_program->setUniformValue(m_skybox_program_eye_direction_location, m_cameraOrigin); + m_skybox_program->setUniformValue(m_skybox_program_texture_sampler_location, 0 /* texture unit */); + m_skybox_program->setUniformValue(m_skybox_program_lightmap_sampler_location, 1 /* texture unit */); + m_skybox_program->setUniformValue(m_skybox_program_opacity_location, 1.0f); + m_skybox_program->setUniformValue(m_skybox_program_lightmap_only_location, m_lighmapOnly); + m_skybox_program->setUniformValue(m_skybox_program_fullbright_location, m_fullbright); + m_skybox_program->setUniformValue(m_skybox_program_drawnormals_location, m_drawNormals); + m_skybox_program->setUniformValue(m_skybox_program_drawflat_location, m_drawFlat); + // opaque draws for (auto &draw : m_drawcalls) { - if (draw.opacity != 1.0f) + if (draw.key.opacity != 1.0f) continue; + if (active_program != draw.key.program) { + active_program = draw.key.program; + active_program->bind(); + } + draw.texture->bind(0 /* texture unit */); lightmap_texture->bind(1 /* texture unit */); @@ -299,13 +423,22 @@ void GLView::paintGL() glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); for (auto &draw : m_drawcalls) { - if (draw.opacity == 1.0f) + if (draw.key.opacity == 1.0f) continue; + if (active_program != draw.key.program) { + active_program = draw.key.program; + active_program->bind(); + } + draw.texture->bind(0 /* texture unit */); lightmap_texture->bind(1 /* texture unit */); - m_program->setUniformValue(m_program_opacity_location, draw.opacity); + if (active_program == m_program) { + m_program->setUniformValue(m_program_opacity_location, draw.key.opacity); + } else { + m_skybox_program->setUniformValue(m_skybox_program_opacity_location, draw.key.opacity); + } QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao); @@ -417,7 +550,9 @@ void GLView::renderBSP(const QString &file, const mbsp_t &bsp, const bspxentries // clear old data lightmap_texture.reset(); m_drawcalls.clear(); + m_vbo.bind(); m_vbo.allocate(0); + m_indexBuffer.bind(); m_indexBuffer.allocate(0); int32_t highest_depth = 0; @@ -430,16 +565,13 @@ void GLView::renderBSP(const QString &file, const mbsp_t &bsp, const bspxentries { const auto &lm_tex = lightmap.style_to_lightmap_atlas.begin()->second; - lightmap_texture = std::make_unique(QOpenGLTexture::Target2DArray); + lightmap_texture = std::make_shared(QOpenGLTexture::Target2DArray); lightmap_texture->setSize(lm_tex.width, lm_tex.height); lightmap_texture->setLayers(highest_depth + 1); - + lightmap_texture->setFormat(QOpenGLTexture::TextureFormat::RGBA8_UNorm); lightmap_texture->setAutoMipMapGenerationEnabled(false); lightmap_texture->setMagnificationFilter(QOpenGLTexture::Linear); lightmap_texture->setMinificationFilter(QOpenGLTexture::Linear); - - lightmap_texture->setFormat(QOpenGLTexture::TextureFormat::RGBAFormat); - lightmap_texture->allocateStorage(); for (auto &style : lightmap.style_to_lightmap_atlas) { @@ -448,17 +580,6 @@ void GLView::renderBSP(const QString &file, const mbsp_t &bsp, const bspxentries } } - // this determines what can be batched together in a draw call - struct material_key - { - std::string texname; - float opacity; - - auto as_tuple() const { return std::make_tuple(texname, opacity); } - - bool operator<(const material_key &other) const { return as_tuple() < other.as_tuple(); } - }; - struct face_payload { const mface_t *face; @@ -468,6 +589,8 @@ void GLView::renderBSP(const QString &file, const mbsp_t &bsp, const bspxentries // collect faces grouped by material_key std::map> faces_by_material_key; + bool needs_skybox = false; + // collect entity bmodels for (int mi = 0; mi < bsp.dmodels.size(); mi++) { qvec3d origin{}; @@ -504,27 +627,78 @@ void GLView::renderBSP(const QString &file, const mbsp_t &bsp, const bspxentries if (!texinfo) continue; // FIXME: render as checkerboard? + QOpenGLShaderProgram *program = m_program; + // determine opacity float opacity = 1.0f; + if (bsp.loadversion->game->id == GAME_QUAKE_II) { - if (texinfo->flags.native & (Q2_SURF_NODRAW | Q2_SURF_SKY)) { + if (texinfo->flags.native & Q2_SURF_NODRAW) { continue; } - if (texinfo->flags.native & Q2_SURF_TRANS33) { - opacity = 0.33f; - } - if (texinfo->flags.native & Q2_SURF_TRANS66) { - opacity = 0.66f; + if (texinfo->flags.native & Q2_SURF_SKY) { + program = m_skybox_program; + needs_skybox = true; + } else { + if (texinfo->flags.native & Q2_SURF_TRANS33) { + opacity = 0.33f; + } + if (texinfo->flags.native & Q2_SURF_TRANS66) { + opacity = 0.66f; + } } } - material_key k = {.texname = t, .opacity = opacity}; + material_key k = {.program = program, .texname = t, .opacity = opacity}; faces_by_material_key[k].push_back({.face = &f, .model_offset = origin}); } } + std::shared_ptr skybox_texture; + + if (needs_skybox) { + // load skybox + std::string skybox = "unit1_"; // TODO: game-specific defaults + + if (entities[0].has("sky")) { + skybox = entities[0].get("sky"); + } + + auto up = img::load_texture(fmt::format("env/{}up", skybox), false, bsp.loadversion->game, settings, true); + auto down = img::load_texture(fmt::format("env/{}dn", skybox), false, bsp.loadversion->game, settings, true); + auto left = img::load_texture(fmt::format("env/{}lf", skybox), false, bsp.loadversion->game, settings, true); + auto right = img::load_texture(fmt::format("env/{}rt", skybox), false, bsp.loadversion->game, settings, true); + auto front = img::load_texture(fmt::format("env/{}ft", skybox), false, bsp.loadversion->game, settings, true); + auto back = img::load_texture(fmt::format("env/{}bk", skybox), false, bsp.loadversion->game, settings, true); + + skybox_texture = std::make_shared(QOpenGLTexture::TargetCubeMap); + + skybox_texture->setSize(std::get<0>(up)->width, std::get<0>(up)->height); + skybox_texture->setFormat(QOpenGLTexture::TextureFormat::RGBA8_UNorm); + skybox_texture->setAutoMipMapGenerationEnabled(true); + skybox_texture->setMagnificationFilter(QOpenGLTexture::Linear); + skybox_texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + skybox_texture->setMaximumAnisotropy(16); + skybox_texture->allocateStorage(); + + skybox_texture->setWrapMode(QOpenGLTexture::ClampToEdge); + + skybox_texture->setData(0, 0, QOpenGLTexture::CubeMapNegativeZ, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, + std::get<0>(up)->pixels.data(), nullptr); + skybox_texture->setData(0, 0, QOpenGLTexture::CubeMapPositiveZ, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, + std::get<0>(down)->pixels.data(), nullptr); + skybox_texture->setData(0, 0, QOpenGLTexture::CubeMapNegativeY, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, + std::get<0>(left)->pixels.data(), nullptr); + skybox_texture->setData(0, 0, QOpenGLTexture::CubeMapPositiveY, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, + std::get<0>(right)->pixels.data(), nullptr); + skybox_texture->setData(0, 0, QOpenGLTexture::CubeMapNegativeX, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, + std::get<0>(front)->pixels.data(), nullptr); + skybox_texture->setData(0, 0, QOpenGLTexture::CubeMapPositiveX, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, + std::get<0>(back)->pixels.data(), nullptr); + } + // populate the vertex/index buffers struct vertex_t { @@ -548,15 +722,14 @@ void GLView::renderBSP(const QString &file, const mbsp_t &bsp, const bspxentries continue; } - std::unique_ptr qtexture = - std::make_unique(QImage(reinterpret_cast(texture->pixels.data()), - texture->width, texture->height, QImage::Format_RGBA8888)); - - qtexture->setMaximumAnisotropy(16); - qtexture->setAutoMipMapGenerationEnabled(true); + std::shared_ptr qtexture; const size_t dc_first_index = indexBuffer.size(); + if (k.program == m_skybox_program) { + qtexture = skybox_texture; + } + for (const auto &[f, model_offset] : faces) { const int fnum = Face_GetNum(&bsp, f); const auto plane_normal = Face_Normal(&bsp, f); @@ -601,9 +774,29 @@ void GLView::renderBSP(const QString &file, const mbsp_t &bsp, const bspxentries } } + if (!qtexture) { + qtexture = std::make_shared(QOpenGLTexture::Target2D); + + qtexture->setSize(texture->width, texture->height); + qtexture->setFormat(QOpenGLTexture::TextureFormat::RGBA8_UNorm); + + qtexture->allocateStorage(); + + // ???? + // FIXME: debug why this doesn't work, so we can remove the QImage garbage + qtexture->setData(QOpenGLTexture::RGBA, QOpenGLTexture::UInt8, + reinterpret_cast(texture->pixels.data())); + + qtexture->setMaximumAnisotropy(16); + qtexture->setAutoMipMapGenerationEnabled(true); + + qtexture->setMagnificationFilter(QOpenGLTexture::Linear); + qtexture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + } + const size_t dc_index_count = indexBuffer.size() - dc_first_index; - drawcall_t dc = {.opacity = k.opacity, + drawcall_t dc = {.key = k, .texture = std::move(qtexture), .first_index = dc_first_index, .index_count = dc_index_count}; diff --git a/lightpreview/glview.h b/lightpreview/glview.h index 588e8b64..7880be50 100644 --- a/lightpreview/glview.h +++ b/lightpreview/glview.h @@ -85,17 +85,29 @@ private: QOpenGLBuffer m_vbo; QOpenGLBuffer m_indexBuffer; - std::unique_ptr lightmap_texture; + // this determines what can be batched together in a draw call + struct material_key + { + QOpenGLShaderProgram *program; + std::string texname; + float opacity = 1.f; + + auto as_tuple() const { return std::make_tuple(program, texname, opacity); } + + bool operator<(const material_key &other) const { return as_tuple() < other.as_tuple(); } + }; + + std::shared_ptr lightmap_texture; struct drawcall_t { - float opacity = 1.0f; - std::unique_ptr texture; + material_key key; + std::shared_ptr texture; size_t first_index = 0; size_t index_count = 0; }; std::vector m_drawcalls; - QOpenGLShaderProgram *m_program = nullptr; + QOpenGLShaderProgram *m_program = nullptr, *m_skybox_program = nullptr; QOpenGLShaderProgram *m_program_wireframe = nullptr; // uniform locations @@ -109,6 +121,18 @@ private: int m_program_drawflat_location = 0; int m_program_style_scalars_location = 0; + // uniform locations + int m_skybox_program_mvp_location = 0; + int m_skybox_program_eye_direction_location = 0; + int m_skybox_program_texture_sampler_location = 0; + int m_skybox_program_lightmap_sampler_location = 0; + int m_skybox_program_opacity_location = 0; + int m_skybox_program_lightmap_only_location = 0; + int m_skybox_program_fullbright_location = 0; + int m_skybox_program_drawnormals_location = 0; + int m_skybox_program_drawflat_location = 0; + int m_skybox_program_style_scalars_location = 0; + // uniform locations (wireframe program) int m_program_wireframe_mvp_location = 0;