From e509644203b9b019a813e824e3026fea9dc89a9e Mon Sep 17 00:00:00 2001 From: Giacomo Nazzaro Date: Sat, 15 Aug 2020 11:51:28 +0200 Subject: [PATCH 1/3] update yocto_gui --- libs/yocto_gui/yocto_gui.cpp | 340 +++++++++++++++++++++++++++++++++-- libs/yocto_gui/yocto_gui.h | 89 +++++++++ 2 files changed, 418 insertions(+), 11 deletions(-) diff --git a/libs/yocto_gui/yocto_gui.cpp b/libs/yocto_gui/yocto_gui.cpp index 82ff0144b..d68f3a708 100644 --- a/libs/yocto_gui/yocto_gui.cpp +++ b/libs/yocto_gui/yocto_gui.cpp @@ -108,6 +108,10 @@ void set_ogl_viewport(const vec4i& viewport) { glViewport(viewport.x, viewport.y, viewport.z, viewport.w); } +void set_ogl_viewport(const vec2i& viewport) { + glViewport(0, 0, viewport.x, viewport.y); +} + void set_ogl_wireframe(bool enabled) { if (enabled) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); @@ -148,7 +152,7 @@ void set_texture(ogl_texture* texture, const vec2i& size, int nchannels, {4, GL_RGBA}, }; assert_ogl_error(); - if (size == zero2i || img == nullptr) { + if (size == zero2i) { clear_texture(texture); return; } @@ -202,7 +206,7 @@ void set_texture(ogl_texture* texture, const vec2i& size, int nchannels, {4, GL_RGBA}, }; assert_ogl_error(); - if (!img) { + if (size == zero2i) { clear_texture(texture); return; } @@ -284,6 +288,210 @@ void set_texture(ogl_texture* texture, const image& img, bool as_float, linear, mipmap); } +void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, + const array& images, bool as_srgb, bool linear, bool mipmap) { + static auto sformat = unordered_map{ + {1, GL_SRGB}, + {2, GL_SRGB}, + {3, GL_SRGB}, + {4, GL_SRGB_ALPHA}, + }; + static auto iformat = unordered_map{ + {1, GL_RGB}, + {2, GL_RGB}, + {3, GL_RGB}, + {4, GL_RGBA}, + }; + static auto cformat = unordered_map{ + {1, GL_RED}, + {2, GL_RG}, + {3, GL_RGB}, + {4, GL_RGBA}, + }; + assert_ogl_error(); + if (size == 0) { + clear_cubemap(cubemap); + return; + } + + if (!cubemap->cubemap_id) glGenTextures(1, &cubemap->cubemap_id); + if (cubemap->size != size || cubemap->nchannels != nchannels || + cubemap->is_srgb != as_srgb || cubemap->is_float == true || + cubemap->linear != linear || cubemap->mipmap != mipmap) { + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap->cubemap_id); + + for (auto i = 0; i < 6; i++) { + if (!images[i]) { + throw std::runtime_error("cannot initialize cubemap from empty image"); + return; + } + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, + as_srgb ? sformat.at(nchannels) : iformat.at(nchannels), size, size, + 0, cformat.at(nchannels), GL_UNSIGNED_BYTE, images[i]); + } + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, + mipmap ? (linear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST) + : (linear ? GL_LINEAR : GL_NEAREST)); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, + linear ? GL_LINEAR : GL_NEAREST); + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + if (mipmap) { + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + } + } else { + throw std::runtime_error("cannot modify initialized cubemap"); + // glBindTexture(GL_TEXTURE_2D, cubemap->cubemap_id); + // glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size.x, size.y, + // cformat.at(nchannels), GL_UNSIGNED_BYTE, img); + // if (mipmap) glGenerateMipmap(GL_TEXTURE_2D); + } + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + cubemap->size = size; + cubemap->nchannels = nchannels; + cubemap->is_srgb = as_srgb; + cubemap->is_float = false; + cubemap->linear = linear; + cubemap->mipmap = mipmap; + assert_ogl_error(); +} + +void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, + const array& images, bool as_float, bool linear, bool mipmap) { + static auto fformat = unordered_map{ + {1, GL_RGB16F}, + {2, GL_RGB16F}, + {3, GL_RGB16F}, + {4, GL_RGBA32F}, + }; + static auto iformat = unordered_map{ + {1, GL_RGB}, + {2, GL_RGB}, + {3, GL_RGB}, + {4, GL_RGBA}, + }; + static auto cformat = unordered_map{ + {1, GL_RED}, + {2, GL_RG}, + {3, GL_RGB}, + {4, GL_RGBA}, + }; + assert_ogl_error(); + if (size == 0) { + clear_cubemap(cubemap); + return; + } + + if (!cubemap->cubemap_id) glGenTextures(1, &cubemap->cubemap_id); + if (cubemap->size != size || cubemap->nchannels != nchannels || + cubemap->is_float != as_float || cubemap->is_srgb == true || + cubemap->linear != linear || cubemap->mipmap != mipmap) { + glGenTextures(1, &cubemap->cubemap_id); + + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap->cubemap_id); + + for (auto i = 0; i < 6; i++) { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, + as_float ? fformat.at(nchannels) : iformat.at(nchannels), size, size, + 0, iformat.at(nchannels), GL_FLOAT, images[i]); + } + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, + mipmap ? (linear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST) + : (linear ? GL_LINEAR : GL_NEAREST)); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, + linear ? GL_LINEAR : GL_NEAREST); + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + if (mipmap) { + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + } + + } else { + // TODO(giacomo): handle this case. + throw std::runtime_error("cannot modify initialized cubemap"); + + // glBindTexture(GL_TEXTURE_2D, cubemap->cubemap_id); + // glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size, size, + // iformat.at(nchannels), GL_FLOAT, img); + // if (mipmap) glGenerateMipmap(GL_TEXTURE_2D); + } + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + cubemap->size = size; + cubemap->nchannels = nchannels; + cubemap->is_srgb = false; + cubemap->is_float = as_float; + cubemap->linear = linear; + cubemap->mipmap = mipmap; + assert_ogl_error(); +} + +// check if cubemap is initialized +bool is_initialized(ogl_cubemap* cubemap) { return cubemap->cubemap_id != 0; } + +// clear cubemap +void clear_cubemap(ogl_cubemap* cubemap) { + if (cubemap->cubemap_id) glDeleteTextures(1, &cubemap->cubemap_id); + *cubemap = ogl_cubemap{}; +} + +void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, + int nchannels, bool as_srgb, bool linear, bool mipmap) { + auto data = array{(byte*)img[0].data(), (byte*)img[1].data(), + (byte*)img[2].data(), (byte*)img[3].data(), (byte*)img[4].data(), + (byte*)img[5].data()}; + set_cubemap( + cubemap, img[0].imsize().x, nchannels, data, as_srgb, linear, mipmap); +} +void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, + int nchannels, bool as_float, bool linear, bool mipmap) { + auto data = array{(float*)img[0].data(), (float*)img[1].data(), + (float*)img[2].data(), (float*)img[3].data(), (float*)img[4].data(), + (float*)img[5].data()}; + set_cubemap( + cubemap, img[0].imsize().x, nchannels, data, as_float, linear, mipmap); +} +void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, + int nchannels, bool as_srgb, bool linear, bool mipmap) { + auto data = array{(byte*)img[0].data(), (byte*)img[1].data(), + (byte*)img[2].data(), (byte*)img[3].data(), (byte*)img[4].data(), + (byte*)img[5].data()}; + set_cubemap( + cubemap, img[0].imsize().x, nchannels, data, as_srgb, linear, mipmap); +} +void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, + int nchannels, bool as_float, bool linear, bool mipmap) { + auto data = array{(float*)img[0].data(), (float*)img[1].data(), + (float*)img[2].data(), (float*)img[3].data(), (float*)img[4].data(), + (float*)img[5].data()}; + set_cubemap( + cubemap, img[0].imsize().x, nchannels, data, as_float, linear, mipmap); +} +void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, + int nchannels, bool as_srgb, bool linear, bool mipmap) { + auto data = array{(byte*)img[0].data(), (byte*)img[1].data(), + (byte*)img[2].data(), (byte*)img[3].data(), (byte*)img[4].data(), + (byte*)img[5].data()}; + set_cubemap( + cubemap, img[0].imsize().x, nchannels, data, as_srgb, linear, mipmap); +} +void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, + int nchannels, bool as_float, bool linear, bool mipmap) { + auto data = array{(float*)img[0].data(), (float*)img[1].data(), + (float*)img[2].data(), (float*)img[3].data(), (float*)img[4].data(), + (float*)img[5].data()}; + set_cubemap( + cubemap, img[0].imsize().x, nchannels, data, as_float, linear, mipmap); +} + +// check if buffer is initialized +bool is_initialized(ogl_arraybuffer* buffer) { return buffer->buffer_id != 0; } + // set buffer void set_arraybuffer(ogl_arraybuffer* buffer, size_t size, int esize, const float* data, bool dynamic) { @@ -307,9 +515,6 @@ void set_arraybuffer(ogl_arraybuffer* buffer, size_t size, int esize, assert_ogl_error(); } -// check if buffer is initialized -bool is_initialized(ogl_arraybuffer* buffer) { return buffer->buffer_id != 0; } - // clear buffer void clear_arraybuffer(ogl_arraybuffer* buffer) { assert_ogl_error(); @@ -455,17 +660,23 @@ bool init_program(ogl_program* program, const string& vertex, glAttachShader(program->program_id, program->vertex_id); glAttachShader(program->program_id, program->fragment_id); glLinkProgram(program->program_id); - glValidateProgram(program->program_id); glGetProgramiv(program->program_id, GL_LINK_STATUS, &errflags); if (!errflags) { glGetProgramInfoLog(program->program_id, 10000, 0, errbuf); return program_error("program not linked", errbuf); } - glGetProgramiv(program->program_id, GL_VALIDATE_STATUS, &errflags); - if (!errflags) { - glGetProgramInfoLog(program->program_id, 10000, 0, errbuf); - return program_error("program not validated", errbuf); - } + // TODO(giacomo): Apparently validation must be done just before drawing. + // https://community.khronos.org/t/samplers-of-different-types-use-the-same-textur/66329 + // If done here, validation fails when using cubemaps and textures in the same + // shader. We should create a function validate_program() anc call it + // separately. + // + // glValidateProgram(program->program_id); + // glGetProgramiv(program->program_id, GL_VALIDATE_STATUS, &errflags); + // if (!errflags) { + // glGetProgramInfoLog(program->program_id, 10000, 0, errbuf); + // return program_error("program not validated", errbuf); + // } assert_ogl_error(); // done @@ -795,6 +1006,39 @@ void set_uniform(ogl_program* program, const char* name, const char* name_on, get_uniform_location(program, name_on), texture, unit); } +// set uniform cubemap +void set_uniform( + ogl_program* program, int location, const ogl_cubemap* cubemap, int unit) { + assert_ogl_error(); + glActiveTexture(GL_TEXTURE0 + unit); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap->cubemap_id); + glUniform1i(location, unit); + assert_ogl_error(); +} +void set_uniform(ogl_program* program, const char* name, + const ogl_cubemap* cubemap, int unit) { + return set_uniform( + program, get_uniform_location(program, name), cubemap, unit); +} +void set_uniform(ogl_program* program, int location, int location_on, + const ogl_cubemap* cubemap, int unit) { + assert_ogl_error(); + if (cubemap && cubemap->cubemap_id) { + glActiveTexture(GL_TEXTURE0 + unit); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap->cubemap_id); + glUniform1i(location, unit); + glUniform1i(location_on, 1); + } else { + glUniform1i(location_on, 0); + } + assert_ogl_error(); +} +void set_uniform(ogl_program* program, const char* name, const char* name_on, + const ogl_cubemap* cubemap, int unit) { + return set_uniform(program, get_uniform_location(program, name), + get_uniform_location(program, name_on), cubemap, unit); +} + // get attribute location int get_attribute_location(ogl_program* program, const char* name) { return glGetAttribLocation(program->program_id, name); @@ -844,6 +1088,78 @@ void draw_elements(ogl_elementbuffer* buffer) { elements.at(buffer->element), buffer->size, GL_UNSIGNED_INT, nullptr); } +void set_framebuffer(ogl_framebuffer* framebuffer, const vec2i& size) { + assert(!framebuffer->framebuffer_id); + assert(!framebuffer->renderbuffer_id); + + glGenFramebuffers(1, &framebuffer->framebuffer_id); + glGenRenderbuffers(1, &framebuffer->renderbuffer_id); + framebuffer->size = size; + + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->framebuffer_id); + glBindRenderbuffer(GL_RENDERBUFFER, framebuffer->renderbuffer_id); + + // create render buffer for depth and stencil + // TODO(giacomo): Why do we need to put STENCIL8 to make things work on Mac?? + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, + framebuffer->size.x, framebuffer->size.y); + + // bind together frame buffer and render buffer + // TODO(giacomo): We put STENCIL here for the same reason... + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, framebuffer->renderbuffer_id); + assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + assert_ogl_error(); +} + +inline void set_framebuffer_texture(const ogl_framebuffer* framebuffer, + uint texture_id, uint target, uint mipmap_level) { + // TODO(giacomo): We change the state of the framebuffer, but we don't store + // this information anywhere, unlike the rest of the library. + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->framebuffer_id); + glFramebufferTexture2D( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, texture_id, mipmap_level); + // glBindFramebuffer(GL_FRAMEBUFFER, ogl_framebuffer::bound_framebuffer_id); + assert_ogl_error(); +} + +bool is_framebuffer_bound(const ogl_framebuffer* framebuffer) { + return framebuffer->framebuffer_id == ogl_framebuffer::bound_framebuffer_id; +} + +void set_framebuffer_texture(const ogl_framebuffer* framebuffer, + const ogl_texture* texture, uint mipmap_level) { + set_framebuffer_texture( + framebuffer, texture->texture_id, GL_TEXTURE_2D, mipmap_level); +} + +void set_framebuffer_texture(const ogl_framebuffer* framebuffer, + const ogl_cubemap* cubemap, uint face, uint mipmap_level) { + set_framebuffer_texture(framebuffer, cubemap->cubemap_id, + GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmap_level); +} + +void bind_framebuffer(const ogl_framebuffer* framebuffer) { + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->framebuffer_id); + ogl_framebuffer::bound_framebuffer_id = framebuffer->framebuffer_id; + assert_ogl_error(); +} + +void unbind_framebuffer() { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + ogl_framebuffer::bound_framebuffer_id = 0; + assert_ogl_error(); +} + +void clear_framebuffer(ogl_framebuffer* framebuffer) { + glDeleteFramebuffers(1, &framebuffer->framebuffer_id); + glDeleteRenderbuffers(1, &framebuffer->renderbuffer_id); + *framebuffer = {}; +} + } // namespace yocto // ----------------------------------------------------------------------------- @@ -1188,6 +1504,8 @@ void clear_scene(ogl_scene* scene) { for (auto texture : scene->textures) clear_texture(texture); for (auto shape : scene->shapes) clear_shape(shape); clear_program(scene->program); + clear_program(scene->environment_program); + clear_cubemap(scene->environment_cubemap); } // add camera diff --git a/libs/yocto_gui/yocto_gui.h b/libs/yocto_gui/yocto_gui.h index 20f4ae711..e744fcf9f 100644 --- a/libs/yocto_gui/yocto_gui.h +++ b/libs/yocto_gui/yocto_gui.h @@ -67,6 +67,7 @@ void assert_ogl_error(); bool check_ogl_error(string& error); void clear_ogl_framebuffer(const vec4f& color, bool clear_depth = true); void set_ogl_viewport(const vec4i& viewport); +void set_ogl_viewport(const vec2i& viewport); void set_ogl_wireframe(bool enabled); void set_ogl_blending(bool enabled); void set_ogl_point_size(int size); @@ -118,6 +119,56 @@ void set_texture(ogl_texture* texture, const image& img, void set_texture(ogl_texture* texture, const image& img, bool as_float = false, bool linear = true, bool mipmap = true); +struct ogl_cubemap { + // Cubemap properties + int size = 0; + int nchannels = 0; + bool is_srgb = false; + bool is_float = false; + bool linear = false; + bool mipmap = false; + + // OpenGL state + uint cubemap_id = 0; +}; + +// set cubemap +void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, + const array& img, bool as_srgb = false, bool linear = true, + bool mipmap = true); +void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, + const array& img, bool as_float = false, bool linear = true, + bool mipmap = true); + +template +void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, + bool as_float = false, bool linear = true, bool mipmap = true) { + auto img = array{0, 0, 0, 0, 0, 0}; + set_cubemap(cubemap, size, nchannels, img, as_float, mipmap); +} + +// check if cubemap is initialized +bool is_initialized(ogl_cubemap* cubemap); + +// clear cubemap +void clear_cubemap(ogl_cubemap* cubemap); + +void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, + int nchannels, bool as_srgb = true, bool linear = true, bool mipmap = true); +void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, + int nchannels, bool as_float = false, bool linear = true, + bool mipmap = true); +void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, + int nchannels, bool as_srgb = true, bool linear = true, bool mipmap = true); +void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, + int nchannels, bool as_float = false, bool linear = true, + bool mipmap = true); +void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, + int nchannels, bool as_srgb = true, bool linear = true, bool mipmap = true); +void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, + int nchannels, bool as_float = false, bool linear = true, + bool mipmap = true); + // Opengl array/element buffer struct ogl_arraybuffer { // buffer data @@ -239,6 +290,16 @@ void set_uniform(ogl_program* program, int location, int location_on, void set_uniform(ogl_program* program, const char* name, const char* name_on, const ogl_texture* texture, int unit); +// set uniform cubemap +void set_uniform( + ogl_program* program, int location, const ogl_cubemap* cubemap, int unit); +void set_uniform(ogl_program* program, const char* name, + const ogl_cubemap* cubemap, int unit); +void set_uniform(ogl_program* program, int location, int location_on, + const ogl_cubemap* cubemap, int unit); +void set_uniform(ogl_program* program, const char* name, const char* name_on, + const ogl_cubemap* cubemap, int unit); + // get attribute location int get_attribute_location(ogl_program* program, const char* name); @@ -325,6 +386,28 @@ struct ogl_image_params { // draw image void draw_image(ogl_image* image, const ogl_image_params& params); +struct ogl_framebuffer { + vec2i size = {0, 0}; + uint framebuffer_id = 0; + uint renderbuffer_id = 0; + + static inline uint bound_framebuffer_id = 0; + // TODO(giacomo): Maybe the following is better + // static inline ogl_framebuffer* bound_framebuffer = nullptr; +}; + +void set_framebuffer(ogl_framebuffer* framebuffer, const vec2i& size); + +void set_framebuffer_texture(const ogl_framebuffer* framebuffer, + const ogl_texture* texture, uint mipmap_level = 0); + +void set_framebuffer_texture(const ogl_framebuffer* framebuffer, + const ogl_cubemap* cubemap, uint face, uint mipmap_level = 0); + +void bind_framebuffer(const ogl_framebuffer* target); +void unbind_framebuffer(); +void clear_framebuffer(ogl_framebuffer* target); + } // namespace yocto // ----------------------------------------------------------------------------- @@ -422,6 +505,12 @@ struct ogl_scene { // OpenGL state ogl_program* program = new ogl_program{}; + + ogl_program* environment_program = new ogl_program{}; + ogl_cubemap* environment_cubemap = new ogl_cubemap{}; + ogl_cubemap* irradiance_map = new ogl_cubemap{}; + ogl_cubemap* prefiltered_map = new ogl_cubemap{}; + ogl_texture* brdf_lut = new ogl_texture{}; }; // Shading type From c7c8e1f97f238cdfc8d26d08320da7e392d86f0b Mon Sep 17 00:00:00 2001 From: giacomonazzaro Date: Sat, 22 Aug 2020 23:30:16 +0200 Subject: [PATCH 2/3] many updates to real-time renderers --- apps/ysceneview/ysceneview.cpp | 66 +- apps/ysceneviews/ysceneviews.cpp | 59 +- apps/yshapeview/yshapeview.cpp | 168 ++- libs/yocto_gui/yocto_draw.cpp | 2268 ++++++++++++++++-------------- libs/yocto_gui/yocto_draw.h | 142 +- libs/yocto_gui/yocto_opengl.cpp | 421 +++--- libs/yocto_gui/yocto_opengl.h | 219 ++- 7 files changed, 1704 insertions(+), 1639 deletions(-) diff --git a/apps/ysceneview/ysceneview.cpp b/apps/ysceneview/ysceneview.cpp index 41be56bb4..5387b82d8 100644 --- a/apps/ysceneview/ysceneview.cpp +++ b/apps/ysceneview/ysceneview.cpp @@ -129,36 +129,6 @@ void load_scene_async( if (!apps->selected) apps->selected = app; } -void update_lights(gui_scene* glscene, sceneio_scene* ioscene) { - clear_lights(glscene); - for (auto ioobject : ioscene->instances) { - if (has_max_lights(glscene)) break; - if (ioobject->material->emission == zero3f) continue; - auto ioshape = ioobject->shape; - auto bbox = invalidb3f; - for (auto p : ioshape->positions) bbox = merge(bbox, p); - auto pos = (bbox.max + bbox.min) / 2; - auto area = 0.0f; - if (!ioshape->triangles.empty()) { - for (auto t : ioshape->triangles) - area += triangle_area(ioshape->positions[t.x], ioshape->positions[t.y], - ioshape->positions[t.z]); - } else if (!ioshape->quads.empty()) { - for (auto q : ioshape->quads) - area += quad_area(ioshape->positions[q.x], ioshape->positions[q.y], - ioshape->positions[q.z], ioshape->positions[q.w]); - } else if (!ioshape->lines.empty()) { - for (auto l : ioshape->lines) - area += line_length(ioshape->positions[l.x], ioshape->positions[l.y]); - } else { - area += ioshape->positions.size(); - } - auto ke = ioobject->material->emission * area; - set_light(add_light(glscene), transform_point(ioobject->frame, pos), ke, - ogl_light_type::point, false); - } -} - void init_glscene(gui_scene* glscene, sceneio_scene* ioscene, gui_camera*& glcamera, sceneio_camera* iocamera, progress_callback progress_cb) { @@ -184,7 +154,7 @@ void init_glscene(gui_scene* glscene, sceneio_scene* ioscene, } // textures - auto texture_map = unordered_map{}; + auto texture_map = unordered_map{}; texture_map[nullptr] = nullptr; for (auto iotexture : ioscene->textures) { if (progress_cb) progress_cb("convert texture", progress.x++, progress.y); @@ -222,36 +192,30 @@ void init_glscene(gui_scene* glscene, sceneio_scene* ioscene, } // shapes - auto shape_map = unordered_map{}; + auto shape_map = unordered_map{}; shape_map[nullptr] = nullptr; for (auto ioshape : ioscene->shapes) { if (progress_cb) progress_cb("convert shape", progress.x++, progress.y); - auto glshape = add_shape(glscene); - set_positions(glshape, ioshape->positions); - set_normals(glshape, ioshape->normals); - set_texcoords(glshape, ioshape->texcoords); - set_colors(glshape, ioshape->colors); - set_points(glshape, ioshape->points); - set_lines(glshape, ioshape->lines); - set_triangles(glshape, ioshape->triangles); - set_quads(glshape, ioshape->quads); - set_edges(glshape, ioshape->triangles, ioshape->quads); + auto glshape = add_shape(glscene, ioshape->points, ioshape->lines, + ioshape->triangles, ioshape->quads, ioshape->positions, + ioshape->normals, ioshape->texcoords, ioshape->colors); shape_map[ioshape] = glshape; } // shapes for (auto ioinstance : ioscene->instances) { if (progress_cb) progress_cb("convert instance", progress.x++, progress.y); - auto globject = add_instance(glscene); - set_frame(globject, ioinstance->frame); - set_shape(globject, shape_map.at(ioinstance->shape)); - set_material(globject, material_map.at(ioinstance->material)); + auto instance = add_instance(glscene); + set_frame(instance, ioinstance->frame); + set_shape(instance, shape_map.at(ioinstance->shape)); + set_material(instance, material_map.at(ioinstance->material)); } // bake prefiltered environments if (ioscene->environments.size()) { - ibl::init_ibl_data( - glscene, texture_map[ioscene->environments[0]->emission_tex]); + auto environment = ioscene->environments[0]; + init_ibl_data( + glscene, texture_map[environment->emission_tex], environment->emission); } // done @@ -460,7 +424,8 @@ void draw_widgets(gui_window* win, app_states* apps, const gui_input& input) { draw_combobox(win, "shading", (int&)params.shading, gui_shading_names); draw_checkbox(win, "wireframe", params.wireframe); continue_line(win); - draw_checkbox(win, "edges", params.edges); + draw_checkbox(win, "faceted", params.faceted); + // draw_checkbox(win, "edges", params.edges); continue_line(win); draw_checkbox(win, "double sided", params.double_sided); draw_slider(win, "exposure", params.exposure, -10, 10); @@ -590,8 +555,6 @@ void draw_widgets(gui_window* win, app_states* apps, const gui_input& input) { void draw(gui_window* win, app_states* apps, const gui_input& input) { if (!apps->selected || !apps->selected->ok) return; auto app = apps->selected; - if (app->drawgl_prms.shading == gui_shading_type::eyelight) - update_lights(app->glscene, app->ioscene); draw_scene(app->glscene, app->glcamera, input.framebuffer_viewport, app->drawgl_prms); } @@ -615,7 +578,6 @@ void update(gui_window* win, app_states* apps) { if (app->loader_error.empty()) { init_glscene(app->glscene, app->ioscene, app->glcamera, app->iocamera, progress_cb); - update_lights(app->glscene, app->ioscene); app->ok = true; app->status = "ok"; } else { diff --git a/apps/ysceneviews/ysceneviews.cpp b/apps/ysceneviews/ysceneviews.cpp index 116aeb6e7..2e74a8bcf 100644 --- a/apps/ysceneviews/ysceneviews.cpp +++ b/apps/ysceneviews/ysceneviews.cpp @@ -90,36 +90,6 @@ struct app_state { } }; -void update_lights(gui_scene* glscene, sceneio_scene* ioscene) { - clear_lights(glscene); - for (auto ioobject : ioscene->instances) { - if (has_max_lights(glscene)) break; - if (ioobject->material->emission == zero3f) continue; - auto ioshape = ioobject->shape; - auto bbox = invalidb3f; - for (auto p : ioshape->positions) bbox = merge(bbox, p); - auto pos = (bbox.max + bbox.min) / 2; - auto area = 0.0f; - if (!ioshape->triangles.empty()) { - for (auto t : ioshape->triangles) - area += triangle_area(ioshape->positions[t.x], ioshape->positions[t.y], - ioshape->positions[t.z]); - } else if (!ioshape->quads.empty()) { - for (auto q : ioshape->quads) - area += quad_area(ioshape->positions[q.x], ioshape->positions[q.y], - ioshape->positions[q.z], ioshape->positions[q.w]); - } else if (!ioshape->lines.empty()) { - for (auto l : ioshape->lines) - area += line_length(ioshape->positions[l.x], ioshape->positions[l.y]); - } else { - area += ioshape->positions.size(); - } - auto ke = ioobject->material->emission * area; - set_light(add_light(glscene), transform_point(ioobject->frame, pos), ke, - ogl_light_type::point, false); - } -} - void init_glscene(gui_scene* glscene, sceneio_scene* ioscene, gui_camera*& glcamera, sceneio_camera* iocamera, progress_callback progress_cb) { @@ -145,7 +115,7 @@ void init_glscene(gui_scene* glscene, sceneio_scene* ioscene, } // textures - auto texture_map = unordered_map{}; + auto texture_map = unordered_map{}; texture_map[nullptr] = nullptr; for (auto iotexture : ioscene->textures) { if (progress_cb) progress_cb("convert texture", progress.x++, progress.y); @@ -183,20 +153,13 @@ void init_glscene(gui_scene* glscene, sceneio_scene* ioscene, } // shapes - auto shape_map = unordered_map{}; + auto shape_map = unordered_map{}; shape_map[nullptr] = nullptr; for (auto ioshape : ioscene->shapes) { if (progress_cb) progress_cb("convert shape", progress.x++, progress.y); - auto glshape = add_shape(glscene); - set_positions(glshape, ioshape->positions); - set_normals(glshape, ioshape->normals); - set_texcoords(glshape, ioshape->texcoords); - set_colors(glshape, ioshape->colors); - set_points(glshape, ioshape->points); - set_lines(glshape, ioshape->lines); - set_triangles(glshape, ioshape->triangles); - set_quads(glshape, ioshape->quads); - set_edges(glshape, ioshape->triangles, ioshape->quads); + auto glshape = add_shape(glscene, ioshape->points, ioshape->lines, + ioshape->triangles, ioshape->quads, ioshape->positions, + ioshape->normals, ioshape->texcoords, ioshape->colors); shape_map[ioshape] = glshape; } @@ -212,8 +175,9 @@ void init_glscene(gui_scene* glscene, sceneio_scene* ioscene, // bake prefiltered environments // TODO(giacomo): what if there's more than 1 environment? if (ioscene->environments.size()) { - ibl::init_ibl_data( - glscene, texture_map[ioscene->environments[0]->emission_tex]); + auto environment = ioscene->environments[0]; + init_ibl_data( + glscene, texture_map[environment->emission_tex], environment->emission); } // done @@ -264,8 +228,6 @@ int main(int argc, const char* argv[]) { clear_scene(app->glscene); }; callbacks.draw_cb = [app](gui_window* win, const gui_input& input) { - if (app->drawgl_prms.shading == gui_shading_type::eyelight) - update_lights(app->glscene, app->ioscene); draw_scene(app->glscene, app->glcamera, input.framebuffer_viewport, app->drawgl_prms); }; @@ -280,11 +242,12 @@ int main(int argc, const char* argv[]) { auto& params = app->drawgl_prms; draw_slider(win, "resolution", params.resolution, 0, 4096); draw_checkbox(win, "wireframe", params.wireframe); - draw_combobox(win, "shading", (int&)params.shading, gui_shading_names); continue_line(win); - draw_checkbox(win, "edges", params.edges); + draw_checkbox(win, "faceted", params.faceted); continue_line(win); draw_checkbox(win, "double sided", params.double_sided); + draw_combobox(win, "shading", (int&)params.shading, gui_shading_names); + // draw_checkbox(win, "edges", params.edges); draw_slider(win, "exposure", params.exposure, -10, 10); draw_slider(win, "gamma", params.gamma, 0.1f, 4); draw_slider(win, "near", params.near, 0.01f, 1.0f); diff --git a/apps/yshapeview/yshapeview.cpp b/apps/yshapeview/yshapeview.cpp index 65fa7299b..18f500e45 100644 --- a/apps/yshapeview/yshapeview.cpp +++ b/apps/yshapeview/yshapeview.cpp @@ -167,8 +167,10 @@ quads_shape make_cylinders(const vector& lines, return shape; } -void init_glscene( - gui_scene* glscene, generic_shape* ioshape, progress_callback progress_cb) { +const char* draw_instanced_vertex_code(); + +void init_glscene(app_state* app, gui_scene* glscene, generic_shape* ioshape, + progress_callback progress_cb) { // handle progress auto progress = vec2i{0, 4}; @@ -196,25 +198,70 @@ void init_glscene( // shapes if (progress_cb) progress_cb("convert shape", progress.x++, progress.y); - auto glshape = add_shape(glscene, ioshape->points, ioshape->lines, + auto model_shape = add_shape(glscene, ioshape->points, ioshape->lines, ioshape->triangles, ioshape->quads, ioshape->positions, ioshape->normals, ioshape->texcoords, ioshape->colors, true); - auto edges = make_cylinders(get_edges(ioshape->triangles, ioshape->quads), - ioshape->positions, 0.0003, {4, 1, 1}); - auto glshapee = add_shape(glscene, {}, {}, {}, edges.quads, edges.positions, - edges.normals, edges.texcoords, {}); - auto vertices = make_spheres(ioshape->positions, 0.001, 2); - auto glshapev = add_shape(glscene, {}, {}, {}, vertices.quads, + set_vertex_attribute(model_shape, vec3f{0, 0, 0}, 5); + set_vertex_attribute(model_shape, vec3f{0, 0, 0}, 6); + + auto cylinder = make_uvcylinder({4, 1, 1}, {0.0003, 1}); + for (auto& p : cylinder.positions) { + p.z = p.z * 0.5 + 0.5; + } + + auto edges_shape = add_shape(glscene, {}, {}, {}, cylinder.quads, + cylinder.positions, cylinder.normals, cylinder.texcoords, {}); + + auto edges = get_edges(ioshape->triangles, ioshape->quads); + auto froms = vector(); + auto tos = vector(); + froms.reserve(edges.size()); + tos.reserve(edges.size()); + for (auto& edge : edges) { + froms.push_back(ioshape->positions[edge.x]); + tos.push_back(ioshape->positions[edge.y]); + } + set_vertex_attribute(edges_shape, froms, 5); + set_instance_buffer(edges_shape, 5); + set_vertex_attribute(edges_shape, tos, 6); + set_instance_buffer(edges_shape, 6); + + auto vertices = make_spheres(ioshape->positions, 0.001, 2); + auto vertices_shape = add_shape(glscene, {}, {}, {}, vertices.quads, vertices.positions, vertices.normals, vertices.texcoords, {}); + set_vertex_attribute(vertices_shape, vec3f{0, 0, 0}, 5); + set_vertex_attribute(vertices_shape, vec3f{0, 0, 0}, 6); // shapes if (progress_cb) progress_cb("convert instance", progress.x++, progress.y); - add_instance(glscene, identity3x4f, glshape, glmaterial); - add_instance(glscene, identity3x4f, glshapee, glmateriale, true); - add_instance(glscene, identity3x4f, glshapev, glmaterialv, true); + + add_instance(glscene, identity3x4f, model_shape, glmaterial); + + auto edges_instance = add_instance( + glscene, identity3x4f, edges_shape, glmateriale, true); + edges_instance->shading_type = 0; + + auto points_instance = add_instance( + glscene, identity3x4f, vertices_shape, glmaterialv, true); + points_instance->shading_type = 0; + + auto error = string{}; + auto errorb = string{}; + auto vert = draw_instanced_vertex_code(); + auto frag = draw_instances_eyelight_fragment_code(); + init_program(glscene->eyelight_program, vert, frag, error, errorb); // done if (progress_cb) progress_cb("convert done", progress.x++, progress.y); + + // init_program(glscene->ibl_program, vertex_source, + // draw_instances_ibl_fragment_code(), error, errorb); + + // auto img = image{}; + // load_image("apps/yshapeview/env.hdr", img, error); + // auto texture = new ogl_texture{}; + // set_texture(texture, img, true, true, true); + // init_ibl_data(glscene, texture, {1, 1, 1}); } // draw with shading @@ -258,31 +305,22 @@ void draw_widgets(gui_window* win, app_states* apps, const gui_input& input) { if (!apps->selected->ok) return; auto app = apps->selected; if (begin_header(win, "view")) { - auto ioshape = app->ioshape; - auto glshape = app->glscene->shapes.front(); - auto glmaterial = app->glscene->materials.front(); - auto smooth = is_initialized(glshape->normals); - if (draw_checkbox(win, "smooth", smooth)) { - if (smooth) { - set_normals(glshape, !ioshape->normals.empty() - ? ioshape->normals - : compute_normals(*ioshape)); - } else { - set_normals(glshape, {}); - } + auto glshape = app->glscene->shapes.front(); + auto glmaterial = app->glscene->materials.front(); + auto& params = app->drawgl_prms; + if (!is_initialized(get_normals(glshape))) { + params.faceted = true; } + draw_checkbox(win, "faceted", params.faceted); continue_line(win); draw_checkbox(win, "lines", app->glscene->instances[1]->hidden, true); continue_line(win); draw_checkbox(win, "points", app->glscene->instances[2]->hidden, true); draw_coloredit(win, "color", glmaterial->color); - auto& params = app->drawgl_prms; draw_slider(win, "resolution", params.resolution, 0, 4096); draw_combobox(win, "shading", (int&)params.shading, gui_shading_names); draw_checkbox(win, "wireframe", params.wireframe); continue_line(win); - draw_checkbox(win, "edges", params.edges); - continue_line(win); draw_checkbox(win, "double sided", params.double_sided); draw_slider(win, "exposure", params.exposure, -10, 10); draw_slider(win, "gamma", params.gamma, 0.1f, 4); @@ -338,7 +376,7 @@ void update(gui_window* win, app_states* apps) { }; app->loader.get(); if (app->loader_error.empty()) { - init_glscene(app->glscene, app->ioshape, progress_cb); + init_glscene(app, app->glscene, app->ioshape, progress_cb); app->glcamera = app->glscene->cameras.front(); app->ok = true; app->status = "ok"; @@ -403,14 +441,86 @@ int main(int argc, const char* argv[]) { dolly = (input.mouse_pos.x - input.mouse_last.x) / 100.0f; if (input.mouse_left && input.modifier_shift) pan = (input.mouse_pos - input.mouse_last) / 100.0f; + pan.x = -pan.x; + rotate.y = -rotate.y; + std::tie(app->glcamera->frame, app->glcamera->focus) = camera_turntable( app->glcamera->frame, app->glcamera->focus, rotate, dolly, pan); } }; // run ui - run_ui({1280 + 320, 720}, "ysceneview", callbacks); + run_ui({1280 + 320, 720}, "yshapeview", callbacks); // done return 0; } + +const char* draw_instanced_vertex_code() { + static const char* code = R"( +#version 330 + +layout(location = 0) in vec3 positions; +layout(location = 1) in vec3 normals; +layout(location = 2) in vec2 texcoords; +layout(location = 3) in vec4 colors; +layout(location = 4) in vec4 tangents; +layout(location = 5) in vec3 instance_from; +layout(location = 6) in vec3 instance_to; + +uniform mat4 frame; +uniform mat4 frameit; +uniform float offset = 0; + +uniform mat4 view; +uniform mat4 projection; + +out vec3 position; +out vec3 normal; +out vec2 texcoord; +out vec4 color; +out vec4 tangsp; + +// main function +void main() { + // copy values + position = positions; + normal = normals; + tangsp = tangents; + texcoord = texcoords; + color = colors; + + // normal offset + if (offset != 0) { + position += offset * normal; + } + + // world projection + position = (frame * vec4(position, 1)).xyz; + normal = (frameit * vec4(normal, 0)).xyz; + tangsp.xyz = (frame * vec4(tangsp.xyz, 0)).xyz; + + if (instance_from != instance_to) { + vec3 dir = instance_to - instance_from; + + vec3 up = abs(dir.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, dir)); + vec3 bitangent = normalize(cross(dir, tangent)); + + mat3 mat; + mat[2] = dir; + mat[0] = tangent; + mat[1] = bitangent; + position = mat * position; + normal = mat * normal; + tangent = mat * tangent; + bitangent = mat * bitangent; + } + position += instance_from; + + // clip + gl_Position = projection * view * vec4(position, 1); +} +)"; + return code; +} diff --git a/libs/yocto_gui/yocto_draw.cpp b/libs/yocto_gui/yocto_draw.cpp index 86af2bd1f..b2a290713 100644 --- a/libs/yocto_gui/yocto_draw.cpp +++ b/libs/yocto_gui/yocto_draw.cpp @@ -60,755 +60,950 @@ namespace yocto { #pragma GCC diagnostic ignored "-Woverlength-strings" #endif -static const char* glscene_vertex = - R"( -#version 330 - -layout(location = 0) in vec3 positions; // vertex position (in mesh coordinate frame) -layout(location = 1) in vec3 normals; // vertex normal (in mesh coordinate frame) -layout(location = 2) in vec2 texcoords; // vertex texcoords -layout(location = 3) in vec4 colors; // vertex color -layout(location = 4) in vec4 tangents; // vertex tangent space - -uniform mat4 frame; // shape transform -uniform mat4 frameit; // shape transform -uniform float offset; // shape normal offset - -uniform mat4 view; // inverse of the camera frame (as a matrix) -uniform mat4 projection; // camera projection - -out vec3 position; // [to fragment shader] vertex position (in world coordinate) -out vec3 normal; // [to fragment shader] vertex normal (in world coordinate) -out vec2 texcoord; // [to fragment shader] vertex texture coordinates -out vec4 color; // [to fragment shader] vertex color -out vec4 tangsp; // [to fragment shader] vertex tangent space - -// main function -void main() { - // copy values - position = positions; - normal = normals; - tangsp = tangents; - texcoord = texcoords; - color = colors; +#ifndef _WIN32 +#pragma GCC diagnostic pop +#endif - // normal offset - if(offset != 0) { - position += offset * normal; +ogl_arraybuffer* get_positions(gui_shape* shape) { + if (shape->vertex_buffers.size() <= 0) return nullptr; + return &shape->vertex_buffers[0]; +} +ogl_arraybuffer* get_normals(gui_shape* shape) { + if (shape->vertex_buffers.size() <= 1) return nullptr; + return &shape->vertex_buffers[1]; +} +ogl_arraybuffer* get_texcoords(gui_shape* shape) { + if (shape->vertex_buffers.size() <= 2) return nullptr; + return &shape->vertex_buffers[2]; +} +ogl_arraybuffer* get_colors(gui_shape* shape) { + if (shape->vertex_buffers.size() <= 3) return nullptr; + return &shape->vertex_buffers[3]; +} +ogl_arraybuffer* get_tangents(gui_shape* shape) { + if (shape->vertex_buffers.size() <= 4) return nullptr; + return &shape->vertex_buffers[4]; +} + +void set_positions(gui_shape* shape, const vector& positions) { + if (positions.empty()) + set_vertex_attribute(shape, vec3f{0, 0, 0}, 0); + else + set_vertex_attribute(shape, positions, 0); +} +void set_normals(gui_shape* shape, const vector& normals) { + if (normals.empty()) + set_vertex_attribute(shape, vec3f{0, 0, 1}, 1); + else + set_vertex_attribute(shape, normals, 1); +} +void set_texcoords(gui_shape* shape, const vector& texcoords) { + if (texcoords.empty()) + set_vertex_attribute(shape, vec2f{0, 0}, 2); + else + set_vertex_attribute(shape, texcoords, 2); +} +void set_colors(gui_shape* shape, const vector& colors) { + if (colors.empty()) + set_vertex_attribute(shape, vec4f{1, 1, 1, 1}, 3); + else + set_vertex_attribute(shape, colors, 3); +} +void set_tangents(gui_shape* shape, const vector& tangents) { + if (tangents.empty()) + set_vertex_attribute(shape, vec4f{0, 0, 1, 1}, 4); + else + set_vertex_attribute(shape, tangents, 4); +} + +void set_points(gui_shape* shape, const vector& points) { + set_index_buffer(shape, points); +} +void set_lines(gui_shape* shape, const vector& lines) { + set_index_buffer(shape, lines); +} +void set_triangles(gui_shape* shape, const vector& triangles) { + set_index_buffer(shape, triangles); +} +void set_quads(gui_shape* shape, const vector& quads) { + auto triangles = vector{}; + triangles.reserve(quads.size() * 2); + for (auto& q : quads) { + triangles.push_back({q.x, q.y, q.w}); + if (q.z != q.w) triangles.push_back({q.z, q.w, q.y}); } - - // world projection - position = (frame * vec4(position,1)).xyz; - normal = (frameit * vec4(normal,0)).xyz; - tangsp.xyz = (frame * vec4(tangsp.xyz,0)).xyz; - - // clip - gl_Position = projection * view * vec4(position,1); + set_index_buffer(shape, triangles); } -)"; -static const char* glscene_fragment = - R"( -#version 330 +gui_scene::~gui_scene() { + clear_scene(this); + for (auto camera : cameras) delete camera; + for (auto shape : shapes) delete shape; + for (auto material : materials) delete material; + for (auto texture : textures) delete texture; + delete eyelight_program; + delete ibl_program; + delete environment_program; + delete environment_cubemap; + delete diffuse_cubemap; + delete specular_cubemap; + delete brdf_lut; +} -float pif = 3.14159265; +const char* bake_brdf_vertex_code(); +const char* bake_brdf_fragment_code(); -uniform bool eyelight; // eyelight shading -uniform vec3 lamb; // ambient light -uniform int lnum; // number of lights -uniform int ltype[16]; // light type (0 -> point, 1 -> directional) -uniform vec3 lpos[16]; // light positions -uniform vec3 lke[16]; // light intensities +const char* bake_cubemap_vertex_code(); +const char* bake_environment_fragment_code(); +const char* bake_irradiance_fragment_code(); +const char* bake_reflections_fragment_code(); -void evaluate_light(int lid, vec3 position, out vec3 cl, out vec3 wi) { - cl = vec3(0,0,0); - wi = vec3(0,0,0); - if(ltype[lid] == 0) { - // compute point light color at position - cl = lke[lid] / pow(length(lpos[lid]-position),2); - // compute light direction at position - wi = normalize(lpos[lid]-position); - } - else if(ltype[lid] == 1) { - // compute light color - cl = lke[lid]; - // compute light direction - wi = normalize(lpos[lid]); - } +// Initialize an OpenGL scene +void init_scene(gui_scene* scene) { + if (is_initialized(scene->eyelight_program)) return; + auto error = ""s, errorlog = ""s; + auto vert = draw_instances_vertex_code(); + auto frag = draw_instances_eyelight_fragment_code(); + init_program(scene->eyelight_program, vert, frag, error, errorlog); } -vec3 brdfcos(int etype, vec3 ke, vec3 kd, vec3 ks, float rs, float op, - vec3 n, vec3 wi, vec3 wo) { - if(etype == 0) return vec3(0); - vec3 wh = normalize(wi+wo); - float ns = 2/(rs*rs)-2; - float ndi = dot(wi,n), ndo = dot(wo,n), ndh = dot(wh,n); - if(etype == 1) { - return ((1+dot(wo,wi))/2) * kd/pif; - } else if(etype == 2) { - float si = sqrt(1-ndi*ndi); - float so = sqrt(1-ndo*ndo); - float sh = sqrt(1-ndh*ndh); - if(si <= 0) return vec3(0); - vec3 diff = si * kd / pif; - if(sh<=0) return diff; - float d = ((2+ns)/(2*pif)) * pow(si,ns); - vec3 spec = si * ks * d / (4*si*so); - return diff+spec; - } else if(etype == 3) { - if(ndi<=0 || ndo <=0) return vec3(0); - vec3 diff = ndi * kd / pif; - if(ndh<=0) return diff; - float cos2 = ndh * ndh; - float tan2 = (1 - cos2) / cos2; - float alpha2 = rs * rs; - float d = alpha2 / (pif * cos2 * cos2 * (alpha2 + tan2) * (alpha2 + tan2)); - float lambda_o = (-1 + sqrt(1 + (1 - ndo * ndo) / (ndo * ndo))) / 2; - float lambda_i = (-1 + sqrt(1 + (1 - ndi * ndi) / (ndi * ndi))) / 2; - float g = 1 / (1 + lambda_o + lambda_i); - vec3 spec = ndi * ks * d * g / (4*ndi*ndo); - return diff+spec; - } +bool is_initialized(gui_scene* scene) { + return scene && is_initialized(scene->eyelight_program); } -uniform int etype; -uniform bool faceted; -uniform vec4 highlight; // highlighted color - -uniform int mtype; // material type -uniform vec3 emission; // material ke -uniform vec3 diffuse; // material kd -uniform vec3 specular; // material ks -uniform float roughness; // material rs -uniform float opacity; // material op - -uniform bool emission_tex_on; // material ke texture on -uniform sampler2D emission_tex; // material ke texture -uniform bool diffuse_tex_on; // material kd texture on -uniform sampler2D diffuse_tex; // material kd texture -uniform bool specular_tex_on; // material ks texture on -uniform sampler2D specular_tex; // material ks texture -uniform bool roughness_tex_on; // material rs texture on -uniform sampler2D roughness_tex; // material rs texture -uniform bool opacity_tex_on; // material op texture on -uniform sampler2D opacity_tex; // material op texture - -uniform bool mat_norm_tex_on; // material normal texture on -uniform sampler2D mat_norm_tex; // material normal texture - -uniform bool double_sided; // double sided rendering - -uniform mat4 frame; // shape transform -uniform mat4 frameit; // shape transform - -bool evaluate_material(vec2 texcoord, vec4 color, out vec3 ke, - out vec3 kd, out vec3 ks, out float rs, out float op) { - if(mtype == 0) { - ke = emission; - kd = vec3(0,0,0); - ks = vec3(0,0,0); - op = 1; - return false; - } - - ke = color.xyz * emission; - kd = color.xyz * diffuse; - ks = color.xyz * specular; - rs = roughness; - op = color.w * opacity; - - vec4 ke_tex = (emission_tex_on) ? texture(emission_tex,texcoord) : vec4(1,1,1,1); - vec4 kd_tex = (diffuse_tex_on) ? texture(diffuse_tex,texcoord) : vec4(1,1,1,1); - vec4 ks_tex = (specular_tex_on) ? texture(specular_tex,texcoord) : vec4(1,1,1,1); - vec4 rs_tex = (roughness_tex_on) ? texture(roughness_tex,texcoord) : vec4(1,1,1,1); - vec4 op_tex = (opacity_tex_on) ? texture(opacity_tex,texcoord) : vec4(1,1,1,1); - - // get material color from textures and adjust values - ke *= ke_tex.xyz; - vec3 kb = kd * kd_tex.xyz; - float km = ks.x * ks_tex.z; - kd = kb * (1 - km); - ks = kb * km + vec3(0.04) * (1 - km); - rs *= ks_tex.y; - rs = rs*rs; - op *= kd_tex.w; - - return true; +// Clear an OpenGL scene +void clear_scene(gui_scene* scene) { + for (auto texture : scene->textures) clear_texture(texture); + for (auto shape : scene->shapes) clear_shape(shape); + clear_program(scene->eyelight_program); + clear_program(scene->ibl_program); + clear_program(scene->environment_program); + clear_cubemap(scene->environment_cubemap); + clear_cubemap(scene->diffuse_cubemap); + clear_cubemap(scene->specular_cubemap); + clear_texture(scene->brdf_lut); } -vec3 apply_normal_map(vec2 texcoord, vec3 normal, vec4 tangsp) { - if(!mat_norm_tex_on) return normal; - vec3 tangu = normalize((frame * vec4(normalize(tangsp.xyz),0)).xyz); - vec3 tangv = normalize(cross(normal, tangu)); - if(tangsp.w < 0) tangv = -tangv; - vec3 texture = 2 * pow(texture(mat_norm_tex,texcoord).xyz, vec3(1/2.2)) - 1; - // texture.y = -texture.y; - return normalize( tangu * texture.x + tangv * texture.y + normal * texture.z ); +// add camera +gui_camera* add_camera(gui_scene* scene) { + return scene->cameras.emplace_back(new gui_camera{}); } - -in vec3 position; // [from vertex shader] position in world space -in vec3 normal; // [from vertex shader] normal in world space (need normalization) -in vec2 texcoord; // [from vertex shader] texcoord -in vec4 color; // [from vertex shader] color -in vec4 tangsp; // [from vertex shader] tangent space - -uniform vec3 eye; // camera position -uniform mat4 view; // inverse of the camera frame (as a matrix) -uniform mat4 projection; // camera projection - -uniform float exposure; -uniform float gamma; - -out vec4 frag_color; - -vec3 triangle_normal(vec3 position) { - vec3 fdx = dFdx(position); - vec3 fdy = dFdy(position); - return normalize((frame * vec4(normalize(cross(fdx, fdy)), 0)).xyz); +void set_frame(gui_camera* camera, const frame3f& frame) { + camera->frame = frame; +} +void set_lens(gui_camera* camera, float lens, float aspect, float film) { + camera->lens = lens; + camera->aspect = aspect; + camera->film = film; +} +void set_nearfar(gui_camera* camera, float near, float far) { + camera->near = near; + camera->far = far; } -// main -void main() { - // view vector - vec3 wo = normalize(eye - position); - - // prepare normals - vec3 n; - if(faceted) { - n = triangle_normal(position); - } else { - n = normalize(normal); - } - - // apply normal map - n = apply_normal_map(texcoord, n, tangsp); - - // use faceforward to ensure the normals points toward us - if(double_sided) n = faceforward(n,-wo,n); - - // get material color from textures - vec3 brdf_ke, brdf_kd, brdf_ks; float brdf_rs, brdf_op; - bool has_brdf = evaluate_material(texcoord, color, brdf_ke, brdf_kd, brdf_ks, brdf_rs, brdf_op); - - // exit if needed - if(brdf_op < 0.005) discard; - - // check const color - if(etype == 0) { - frag_color = vec4(brdf_ke,brdf_op); - return; - } +// add texture +gui_texture* add_texture(gui_scene* scene) { + return scene->textures.emplace_back(new gui_texture{}); +} - // emission - vec3 c = brdf_ke; +// add shape +gui_shape* add_shape(gui_scene* scene) { + auto shape = new gui_shape{}; + shape->vertex_buffers.resize(5); + set_shape(shape); + scene->shapes.push_back(shape); + return shape; +} - // check early exit - if(brdf_kd != vec3(0,0,0) || brdf_ks != vec3(0,0,0)) { - // eyelight shading - if(eyelight) { - vec3 wi = wo; - c += pif * brdfcos((has_brdf) ? etype : 0, brdf_ke, brdf_kd, brdf_ks, brdf_rs, brdf_op, n,wi,wo); - } else { - // accumulate ambient - c += lamb * brdf_kd; - // foreach light - for(int lid = 0; lid < lnum; lid ++) { - vec3 cl = vec3(0,0,0); vec3 wi = vec3(0,0,0); - evaluate_light(lid, position, cl, wi); - c += cl * brdfcos((has_brdf) ? etype : 0, brdf_ke, brdf_kd, brdf_ks, brdf_rs, brdf_op, n,wi,wo); - } - } - } +// add instance +gui_instance* add_instance(gui_scene* scene) { + return scene->instances.emplace_back(new gui_instance{}); +} +void set_frame(gui_instance* instance, const frame3f& frame) { + instance->frame = frame; +} +void set_shape(gui_instance* instance, gui_shape* shape) { + instance->shape = shape; +} +void set_material(gui_instance* instance, gui_material* material) { + instance->material = material; +} +void set_hidden(gui_instance* instance, bool hidden) { + instance->hidden = hidden; +} +void set_highlighted(gui_instance* instance, bool highlighted) { + instance->highlighted = highlighted; +} - // final color correction - c = pow(c * pow(2,exposure), vec3(1/gamma)); +// add material +gui_material* add_material(gui_scene* scene) { + return scene->materials.emplace_back(new gui_material{}); +} +void set_emission( + gui_material* material, const vec3f& emission, gui_texture* emission_tex) { + material->emission = emission; + material->emission_tex = emission_tex; +} +void set_color( + gui_material* material, const vec3f& color, gui_texture* color_tex) { + material->color = color; + material->color_tex = color_tex; +} +void set_specular( + gui_material* material, float specular, gui_texture* specular_tex) { + material->specular = specular; + material->specular_tex = specular_tex; +} +void set_roughness( + gui_material* material, float roughness, gui_texture* roughness_tex) { + material->roughness = roughness; + material->roughness_tex = roughness_tex; +} +void set_opacity( + gui_material* material, float opacity, gui_texture* opacity_tex) { + material->opacity = opacity; +} +void set_metallic( + gui_material* material, float metallic, gui_texture* metallic_tex) { + material->metallic = metallic; + material->metallic_tex = metallic_tex; +} +void set_normalmap(gui_material* material, gui_texture* normal_tex) { + material->normal_tex = normal_tex; +} - // highlighting - if(highlight.w > 0) { - if(mod(int(gl_FragCoord.x)/4 + int(gl_FragCoord.y)/4, 2) == 0) - c = highlight.xyz * highlight.w + c * (1-highlight.w); +gui_shape* add_shape(gui_scene* scene, const vector& points, + const vector& lines, const vector& triangles, + const vector& quads, const vector& positions, + const vector& normals, const vector& texcoords, + const vector& colors, bool edges) { + auto shape = add_shape(scene); + if (points.size() != 0) { + set_points(shape, points); + } else if (lines.size() != 0) { + set_lines(shape, lines); + } else if (triangles.size() != 0) { + set_triangles(shape, triangles); + } else if (quads.size() != 0) { + set_quads(shape, quads); } - - // output final color by setting gl_FragColor - frag_color = vec4(c,brdf_op); + set_positions(shape, positions); + set_normals(shape, normals); + set_texcoords(shape, texcoords); + set_colors(shape, colors); + return shape; } -)"; - -static const char* glibl_fragment = R"( -#version 330 - -in vec3 position; // [from vertex shader] position in world space -in vec3 normal; // [from vertex shader] normal in world space -in vec2 texcoord; // [from vertex shader] texcoord -in vec4 color; // [from vertex shader] color -in vec4 tangsp; // [from vertex shader] tangent space - -float pif = 3.14159265; -uniform int etype; -uniform bool faceted; -uniform int shading_type; +// shortcuts +gui_camera* add_camera(gui_scene* scene, const frame3f& frame, float lens, + float aspect, float film, float near, float far) { + auto camera = add_camera(scene); + set_frame(camera, frame); + set_lens(camera, lens, aspect, film); + set_nearfar(camera, near, far); + return camera; +} +gui_material* add_material(gui_scene* scene, const vec3f& emission, + const vec3f& color, float specular, float metallic, float roughness, + gui_texture* emission_tex, gui_texture* color_tex, + gui_texture* specular_tex, gui_texture* metallic_tex, + gui_texture* roughness_tex, gui_texture* normalmap_tex) { + auto material = add_material(scene); + set_emission(material, emission, emission_tex); + set_color(material, color, color_tex); + set_specular(material, specular, specular_tex); + set_metallic(material, metallic, metallic_tex); + set_roughness(material, roughness, roughness_tex); + set_normalmap(material, normalmap_tex); + return material; +} -uniform vec3 emission; // material ke -uniform vec3 diffuse; // material kd -uniform vec3 specular; // material ks -uniform float roughness; // material rs -uniform float opacity; // material op +gui_instance* add_instance(gui_scene* scene, const frame3f& frame, + gui_shape* shape, gui_material* material, bool hidden, bool highlighted) { + auto instance = add_instance(scene); + set_frame(instance, frame); + set_shape(instance, shape); + set_material(instance, material); + set_hidden(instance, hidden); + set_highlighted(instance, highlighted); + return instance; +} -// baked textures for image based lighting -uniform samplerCube irradiance_cubemap; -uniform samplerCube reflection_cubemap; -uniform sampler2D brdf_lut; +void set_scene_view_uniforms(ogl_program* program, const gui_scene_view& view) { + set_uniform(program, "eye", view.camera_frame.o); + set_uniform(program, "view", view.view_matrix); + set_uniform(program, "projection", view.projection_matrix); + set_uniform(program, "exposure", view.params.exposure); + set_uniform(program, "gamma", view.params.gamma); + set_uniform(program, "faceted", view.params.faceted); + set_uniform(program, "double_sided", view.params.double_sided); +} -uniform bool emission_tex_on; // material ke texture on -uniform sampler2D emission_tex; // material ke texture -uniform bool diffuse_tex_on; // material kd texture on -uniform sampler2D diffuse_tex; // material kd texture -uniform bool specular_tex_on; // material ks texture on -uniform sampler2D specular_tex; // material ks texture -uniform bool roughness_tex_on; // material rs texture on -uniform sampler2D roughness_tex; // material rs texture -uniform bool opacity_tex_on; // material op texture on -uniform sampler2D opacity_tex; // material op texture +// Draw a shape +void set_instance_uniforms(ogl_program* program, const gui_instance* instance, + const gui_scene_view& view) { + auto shape_xform = frame_to_mat(instance->frame); + auto shape_inv_xform = transpose( + frame_to_mat(inverse(instance->frame, view.params.non_rigid_frames))); + set_uniform(program, "frame", shape_xform); + set_uniform(program, "frameit", shape_inv_xform); + set_uniform(program, "offset", 0.0f); + if (instance->highlighted) { + set_uniform(program, "highlight", vec4f{1, 1, 0, 1}); + } else { + set_uniform(program, "highlight", vec4f{0, 0, 0, 0}); + } + assert_ogl_error(); -uniform bool mat_norm_tex_on; // material normal texture on -uniform sampler2D mat_norm_tex; // material normal texture + auto material = instance->material; + auto mtype = instance->shading_type; + set_uniform(program, "mtype", mtype); + set_uniform(program, "emission", material->emission); + set_uniform(program, "diffuse", material->color); + set_uniform(program, "specular", + vec3f{material->metallic, material->metallic, material->metallic}); + set_uniform(program, "roughness", material->roughness); + set_uniform(program, "opacity", material->opacity); + set_uniform(program, "double_sided", (int)view.params.double_sided); + set_uniform( + program, "emission_tex", "emission_tex_on", material->emission_tex, 0); + set_uniform(program, "diffuse_tex", "diffuse_tex_on", material->color_tex, 1); + set_uniform( + program, "specular_tex", "specular_tex_on", material->metallic_tex, 2); + set_uniform( + program, "roughness_tex", "roughness_tex_on", material->roughness_tex, 3); + set_uniform( + program, "opacity_tex", "opacity_tex_on", material->opacity_tex, 4); + set_uniform( + program, "mat_norm_tex", "mat_norm_tex_on", material->normal_tex, 5); -uniform bool double_sided; // double sided rendering + assert_ogl_error(); -uniform mat4 frame; // shape transform -uniform mat4 frameit; // shape transform + auto shape = instance->shape; + auto type = shape->index_buffer.element; + if (type == ogl_element_type::points) set_uniform(program, "etype", 1); + if (type == ogl_element_type::lines) set_uniform(program, "etype", 2); + if (type == ogl_element_type::triangles) set_uniform(program, "etype", 3); + assert_ogl_error(); +} -uniform vec3 eye; // camera position -uniform mat4 view; // inverse of the camera frame (as a matrix) -uniform mat4 projection; // camera projection +void draw_environment(gui_scene* scene, const gui_scene_view& view) { + auto program = scene->environment_program; + if (program->program_id == 0) return; -uniform float exposure; -uniform float gamma; + bind_program(program); -out vec4 frag_color; + set_scene_view_uniforms(program, view); + set_uniform(program, "environment", scene->environment_cubemap, 0); -struct brdf_struct { - vec3 emission; - vec3 diffuse; - vec3 specular; - float roughness; - float opacity; -} brdf; + auto cube = cube_shape(); + draw_shape(cube); -vec3 eval_brdf_color(vec3 value, sampler2D tex, bool tex_on) { - vec3 result = value; - if (tex_on) result *= texture(tex, texcoord).rgb; - return result; -} -float eval_brdf_value(float value, sampler2D tex, bool tex_on) { - float result = value; - if (tex_on) result *= texture(tex, texcoord).r; - return result; + unbind_program(); } -brdf_struct compute_brdf() { - brdf_struct brdf; - brdf.emission = eval_brdf_color(emission, emission_tex, emission_tex_on); - brdf.diffuse = eval_brdf_color(diffuse, diffuse_tex, diffuse_tex_on); - brdf.specular = eval_brdf_color(specular, specular_tex, specular_tex_on); - brdf.roughness = eval_brdf_value(roughness, roughness_tex, roughness_tex_on); - brdf.opacity = eval_brdf_value(opacity, opacity_tex, opacity_tex_on); - return brdf; -} +void set_eyelight_uniforms(ogl_program* program, const gui_scene_view& view) { + // Opengl light + struct gui_light { + vec3f position = {0, 0, 0}; + vec3f emission = {0, 0, 0}; + ogl_light_type type = ogl_light_type::point; + bool camera = false; + }; -vec3 apply_normal_map(vec2 texcoord, vec3 normal, vec4 tangsp) { - if (!mat_norm_tex_on) return normal; - vec3 tangu = normalize((frame * vec4(normalize(tangsp.xyz), 0)).xyz); - vec3 tangv = normalize(cross(normal, tangu)); - if (tangsp.w < 0) tangv = -tangv; - vec3 texture = 2 * pow(texture(mat_norm_tex, texcoord).xyz, vec3(1 / 2.2)) - - 1; - // texture.y = -texture.y; - return normalize(tangu * texture.x + tangv * texture.y + normal * texture.z); + static auto camera_light0 = gui_light{normalize(vec3f{1, 1, 1}), + vec3f{pif / 2, pif / 2, pif / 2}, ogl_light_type::directional, true}; + static auto camera_light1 = gui_light{normalize(vec3f{-1, 1, 1}), + vec3f{pif / 2, pif / 2, pif / 2}, ogl_light_type::directional, true}; + static auto camera_light2 = gui_light{normalize(vec3f{-1, -1, 1}), + vec3f{pif / 4, pif / 4, pif / 4}, ogl_light_type::directional, true}; + static auto camera_light3 = gui_light{normalize(vec3f{0.1, 0.5, -1}), + vec3f{pif / 4, pif / 4, pif / 4}, ogl_light_type::directional, true}; + static auto camera_lights = vector{ + &camera_light0, &camera_light1, &camera_light2, &camera_light3}; + + auto& lights = camera_lights; + set_uniform(program, "lamb", vec3f{0, 0, 0}); + set_uniform(program, "lnum", (int)lights.size()); + auto lid = 0; + for (auto light : lights) { + auto is = std::to_string(lid); + if (light->camera) { + auto position = light->type == ogl_light_type::directional + ? transform_direction( + view.camera_frame, light->position) + : transform_point(view.camera_frame, light->position); + set_uniform(program, ("lpos[" + is + "]").c_str(), position); + } else { + set_uniform(program, ("lpos[" + is + "]").c_str(), light->position); + } + set_uniform(program, ("lke[" + is + "]").c_str(), light->emission); + set_uniform(program, ("ltype[" + is + "]").c_str(), (int)light->type); + lid++; + } + assert_ogl_error(); } -vec3 triangle_normal(vec3 position) { - vec3 fdx = dFdx(position); - vec3 fdy = dFdy(position); - return normalize((frame * vec4(normalize(cross(fdx, fdy)), 0)).xyz); +void set_ibl_uniforms(ogl_program* program, const gui_scene* scene) { + set_uniform(program, "irradiance_cubemap", scene->diffuse_cubemap, 6); + set_uniform(program, "reflection_cubemap", scene->specular_cubemap, 7); + set_uniform(program, "brdf_lut", scene->brdf_lut, 8); } -#define etype_points 1 -#define etype_lines 2 -#define etype_triangles 3 -#define etype_quads 3 +void draw_instances(gui_scene* scene, const gui_scene_view& view) { + auto program = scene->eyelight_program; + if (is_initialized(scene->environment_cubemap) && + view.params.shading == gui_shading_type::environment) { + program = scene->ibl_program; + } -vec3 compute_normal(vec3 V) { - vec3 N; - if (etype == etype_triangles) { - if (faceted) { - N = triangle_normal(position); - } else { - N = normalize(normal); - } + bind_program(program); + set_scene_view_uniforms(program, view); + if (view.params.shading == gui_shading_type::eyelight) { + set_eyelight_uniforms(program, view); + } else { + set_ibl_uniforms(program, scene); } - if (etype == etype_lines) { - // normal of lines is coplanar with view vector and direction tangent to the - // line - vec3 tangent = normalize(normal); - N = normalize(V - tangent * dot(V, tangent)); + set_ogl_wireframe(view.params.wireframe); + for (auto instance : scene->instances) { + if (instance->hidden) continue; + set_instance_uniforms(program, instance, view); + draw_shape(instance->shape); } + unbind_program(); +} - // apply normal map - N = apply_normal_map(texcoord, N, tangsp); +gui_scene_view make_scene_view( + gui_camera* camera, const vec4i& viewport, const gui_scene_params& params) { + auto camera_aspect = (float)viewport.z / (float)viewport.w; + auto camera_yfov = + camera_aspect >= 0 + ? (2 * atan(camera->film / (camera_aspect * 2 * camera->lens))) + : (2 * atan(camera->film / (2 * camera->lens))); + auto view_matrix = frame_to_mat(inverse(camera->frame)); + auto projection_matrix = perspective_mat( + camera_yfov, camera_aspect, params.near, params.far); - // use faceforward to ensure the normals points toward us - if (double_sided) N = faceforward(N, -V, N); - return N; + auto view = gui_scene_view{}; + view.camera_frame = camera->frame; + view.view_matrix = view_matrix; + view.projection_matrix = projection_matrix; + view.params = params; + return view; } -vec3 sample_prefiltered_refleciton(vec3 L, float roughness) { - int MAX_REFLECTION_LOD = 5; - float lod = sqrt(roughness) * MAX_REFLECTION_LOD; - return textureLod(reflection_cubemap, L, lod).rgb; +void draw_scene(gui_scene* scene, gui_camera* camera, const vec4i& viewport, + const gui_scene_params& params) { + assert_ogl_error(); + clear_ogl_framebuffer(params.background); + set_ogl_viewport(viewport); + assert_ogl_error(); + + auto view = make_scene_view(camera, viewport, params); + draw_instances(scene, view); + draw_environment(scene, view); } -// main -void main() { - vec3 V = normalize(eye - position); - vec3 N = compute_normal(V); +// image based lighting + +// Using 6 render passes, bake a cubemap given a sampler for the environment. +// The input sampler can be either a cubemap or a latlong texture. +template +inline void bake_cubemap(ogl_cubemap* cubemap, const Sampler* environment, + ogl_program* program, int size, int num_mipmap_levels = 1, + const vec3f& emission = {1, 1, 1}) { + // init cubemap with no data + set_cubemap(cubemap, size, 3, true, true, true); + auto cube = cube_shape(); + + auto framebuffer = ogl_framebuffer{}; + set_framebuffer(&framebuffer, {size, size}); - brdf_struct brdf = compute_brdf(); - if (brdf.opacity < 0.005) discard; + // clang-format off + frame3f cameras[6] = { + lookat_frame({0, 0, 0}, { 1, 0, 0}, {0, 1, 0}), + lookat_frame({0, 0, 0}, {-1, 0, 0}, {0, 1, 0}), + lookat_frame({0, 0, 0}, { 0,-1, 0}, {0, 0,-1}), + lookat_frame({0, 0, 0}, { 0, 1, 0}, {0, 0, 1}), + lookat_frame({0, 0, 0}, { 0, 0,-1}, {0, 1, 0}), + lookat_frame({0, 0, 0}, { 0, 0, 1}, {0, 1, 0}) + }; + // clang-format on - // emission - vec3 radiance = brdf.emission; + bind_framebuffer(&framebuffer); + bind_program(program); + for (int mipmap_level = 0; mipmap_level < num_mipmap_levels; mipmap_level++) { + // resize render buffer and viewport + set_framebuffer(&framebuffer, {size, size}); + set_ogl_viewport(vec2i{size, size}); - // diffuse - radiance += brdf.diffuse * textureLod(irradiance_cubemap, N, 0).rgb; + for (auto i = 0; i < 6; ++i) { + // perspective_mat(fov, aspect, near, far) + auto camera_proj = perspective_mat(radians(90), 1, 1, 100); + auto camera_view = frame_to_mat(inverse(cameras[i])); - // specular - vec3 L = normalize(reflect(-V, N)); - vec3 reflection = sample_prefiltered_refleciton(L, brdf.roughness); - vec2 env_brdf = texture(brdf_lut, vec2(max(dot(N, V), 0.0), roughness)).rg; - radiance += reflection * (brdf.specular * env_brdf.x + env_brdf.y); + set_framebuffer_texture(&framebuffer, cubemap, i, mipmap_level); + clear_ogl_framebuffer({0, 0, 0, 0}, true); - // final color correction - radiance = pow(radiance * pow(2, exposure), vec3(1 / gamma)); + set_uniform(program, "view", camera_view); + set_uniform(program, "projection", camera_proj); + set_uniform(program, "eye", vec3f{0, 0, 0}); + set_uniform(program, "mipmap_level", mipmap_level); + set_uniform(program, "emission", emission); + set_uniform(program, "environment", environment, 0); - // output final color by setting gl_FragColor - frag_color = vec4(radiance, brdf.opacity); + draw_shape(cube); + } + size /= 2; + } + unbind_program(); + unbind_framebuffer(); } -)"; -static const char* bake_brdf_vertex = R"( -#version 330 +inline void bake_specular_brdf_texture(gui_texture* texture) { + auto size = 512; + auto framebuffer = ogl_framebuffer{}; + auto screen_quad = quad_shape(); -layout(location = 0) in vec3 positions; // vertex position + auto program = ogl_program{}; + auto error = ""s, errorlog = ""s; + auto vert = bake_brdf_vertex_code(); + auto frag = bake_brdf_fragment_code(); + init_program(&program, vert, frag, error, errorlog); -out vec3 position; // vertex position (in world coordinate) + assert_ogl_error(); -// main function -void main() { - position = positions; + texture->is_float = true; + texture->linear = true; + texture->num_channels = 3; + texture->size = {size, size}; + glGenTextures(1, &texture->texture_id); - gl_Position = vec4(position, 1); -} -)"; + glBindTexture(GL_TEXTURE_2D, texture->texture_id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + // TODO(giacomo): mipmaps? -static const char* bake_brdf_fragment = R"( -#version 330 + assert_ogl_error(); -out vec3 frag_color; + set_framebuffer(&framebuffer, {size, size}); + set_framebuffer_texture(&framebuffer, texture, 0); -in vec3 position; // position in world space + bind_framebuffer(&framebuffer); + bind_program(&program); -const float pif = 3.14159265359; + set_ogl_viewport(vec2i{size, size}); + clear_ogl_framebuffer({0, 0, 0, 0}, true); -float radical_inverse(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 -} + draw_shape(screen_quad); + assert_ogl_error(); -vec2 hammersley(uint i, uint N) { - return vec2(float(i) / float(N), radical_inverse(i)); + unbind_program(); + unbind_framebuffer(); + clear_framebuffer(&framebuffer); + clear_program(&program); } -float geometry_schlick_ggx(float NdotV, float roughness) { - float a = roughness; - float k = (a * a) / 2.0; +void init_ibl_data(gui_scene* scene, const gui_texture* environment_texture, + const vec3f& emission) { + auto load_program = [](ogl_program* program, const string& vertex, + const string& fragment) { + auto error = ""s, errorlog = ""s; + if (!init_program(program, vertex, fragment, error, errorlog)) { + printf("error: %s\n", error.c_str()); + printf("errorlog: %s\n", errorlog.c_str()); + } + }; - float nom = NdotV; - float denom = NdotV * (1.0 - k) + k; + // shader with IBL shading + { + auto vert = draw_instances_vertex_code(); + auto frag = draw_instances_ibl_fragment_code(); + load_program(scene->ibl_program, vert, frag); + } - return nom / denom; -} + // shader for drawing the environment + { + auto vert = bake_cubemap_vertex_code(); + auto frag = draw_enivronment_fragment_code(); + load_program(scene->environment_program, vert, frag); + } -float geometry_smith(vec3 N, vec3 V, vec3 L, float roughness) { - float NdotV = max(dot(N, V), 0.0); - float NdotL = max(dot(N, L), 0.0); - float ggx2 = geometry_schlick_ggx(NdotV, roughness); - float ggx1 = geometry_schlick_ggx(NdotL, roughness); + // bake cubemap from environment texture + { + auto size = environment_texture->size.y; + auto program = new ogl_program{}; + auto vert = bake_cubemap_vertex_code(); + auto frag = bake_environment_fragment_code(); + load_program(program, vert, frag); + bake_cubemap(scene->environment_cubemap, environment_texture, program, size, + 1, emission); + clear_program(program); + delete program; + } - return ggx1 * ggx2; + // bake irradiance map + { + auto program = new ogl_program{}; + auto vert = bake_cubemap_vertex_code(); + auto frag = bake_irradiance_fragment_code(); + load_program(program, vert, frag); + bake_cubemap( + scene->diffuse_cubemap, scene->environment_cubemap, program, 64); + clear_program(program); + delete program; + } + + // bake specular map + { + auto program = new ogl_program{}; + auto vert = bake_cubemap_vertex_code(); + auto frag = bake_reflections_fragment_code(); + load_program(program, vert, frag); + bake_cubemap( + scene->specular_cubemap, scene->environment_cubemap, program, 256, 6); + clear_program(program); + delete program; + } + + bake_specular_brdf_texture(scene->brdf_lut); } -vec3 importance_sample_ggx(vec2 Xi, vec3 N, float roughness) { - float a = roughness * roughness; +const char* draw_instances_vertex_code() { + static const char* code = + R"( +#version 330 - float phi = 2.0 * pif * Xi.x; - float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a * a - 1.0) * Xi.y)); - float sinTheta = sqrt(1.0 - cosTheta * cosTheta); +layout(location = 0) in vec3 positions; // vertex position (in mesh coordinate frame) +layout(location = 1) in vec3 normals; // vertex normal (in mesh coordinate frame) +layout(location = 2) in vec2 texcoords; // vertex texcoords +layout(location = 3) in vec4 colors; // vertex color +layout(location = 4) in vec4 tangents; // vertex tangent space - // from spherical coordinates to cartesian coordinates - vec3 H; - H.x = cos(phi) * sinTheta; - H.y = sin(phi) * sinTheta; - H.z = cosTheta; +uniform mat4 frame; // shape transform +uniform mat4 frameit; // shape transform +uniform float offset; // shape normal offset - // from tangent-space vector to world-space sample vector - vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); - vec3 tangent = normalize(cross(up, N)); - vec3 bitangent = cross(N, tangent); +uniform mat4 view; // inverse of the camera frame (as a matrix) +uniform mat4 projection; // camera projection - vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; - return normalize(sampleVec); -} +out vec3 position; // [to fragment shader] vertex position (in world coordinate) +out vec3 normal; // [to fragment shader] vertex normal (in world coordinate) +out vec2 texcoord; // [to fragment shader] vertex texture coordinates +out vec4 color; // [to fragment shader] vertex color +out vec4 tangsp; // [to fragment shader] vertex tangent space -vec2 integrate_brdf(float NdotV, float roughness) { - vec3 V; - V.x = sqrt(1.0 - NdotV * NdotV); - V.y = 0.0; - V.z = NdotV; +// main function +void main() { + // copy values + position = positions; + normal = normals; + tangsp = tangents; + texcoord = texcoords; + color = colors; - float A = 0.0; - float B = 0.0; + // normal offset + if(offset != 0) { + position += offset * normal; + } - vec3 N = vec3(0.0, 0.0, 1.0); + // world projection + position = (frame * vec4(position,1)).xyz; + normal = (frameit * vec4(normal,0)).xyz; + tangsp.xyz = (frame * vec4(tangsp.xyz,0)).xyz; - const uint SAMPLE_COUNT = 1024u; - for (uint i = 0u; i < SAMPLE_COUNT; ++i) { - vec2 Xi = hammersley(i, SAMPLE_COUNT); - vec3 H = importance_sample_ggx(Xi, N, roughness); - vec3 L = normalize(2.0 * dot(V, H) * H - V); + // clip + gl_Position = projection * view * vec4(position,1); +} +)"; + return code; +} - float NdotL = max(L.z, 0.0); - float NdotH = max(H.z, 0.0); - float VdotH = max(dot(V, H), 0.0); +const char* draw_instances_eyelight_fragment_code() { + static const char* code = + R"( +#version 330 - if (NdotL > 0.0) { - float G = geometry_smith(N, V, L, roughness); - float G_Vis = (G * VdotH) / (NdotH * NdotV); - float Fc = pow(1.0 - VdotH, 5.0); +float pif = 3.14159265; - A += (1.0 - Fc) * G_Vis; - B += Fc * G_Vis; - } +uniform bool eyelight; // eyelight shading +uniform vec3 lamb; // ambient light +uniform int lnum; // number of lights +uniform int ltype[16]; // light type (0 -> point, 1 -> directional) +uniform vec3 lpos[16]; // light positions +uniform vec3 lke[16]; // light intensities + +void evaluate_light(int lid, vec3 position, out vec3 cl, out vec3 wi) { + cl = vec3(0,0,0); + wi = vec3(0,0,0); + if(ltype[lid] == 0) { + // compute point light color at position + cl = lke[lid] / pow(length(lpos[lid]-position),2); + // compute light direction at position + wi = normalize(lpos[lid]-position); + } + else if(ltype[lid] == 1) { + // compute light color + cl = lke[lid]; + // compute light direction + wi = normalize(lpos[lid]); } - A /= float(SAMPLE_COUNT); - B /= float(SAMPLE_COUNT); - return vec2(A, B); } -void main() { - vec2 uv = position.xy * 0.5 + 0.5; - vec2 integrated_brdf = integrate_brdf(uv.x, uv.y); - frag_color = vec3(integrated_brdf, 0.0); +vec3 brdfcos(int etype, vec3 ke, vec3 kd, vec3 ks, float rs, float op, + vec3 n, vec3 wi, vec3 wo) { + if(etype == 0) return vec3(0); + vec3 wh = normalize(wi+wo); + float ns = 2/(rs*rs)-2; + float ndi = dot(wi,n), ndo = dot(wo,n), ndh = dot(wh,n); + if(etype == 1) { + return ((1+dot(wo,wi))/2) * kd/pif; + } else if(etype == 2) { + float si = sqrt(1-ndi*ndi); + float so = sqrt(1-ndo*ndo); + float sh = sqrt(1-ndh*ndh); + if(si <= 0) return vec3(0); + vec3 diff = si * kd / pif; + if(sh<=0) return diff; + float d = ((2+ns)/(2*pif)) * pow(si,ns); + vec3 spec = si * ks * d / (4*si*so); + return diff+spec; + } else if(etype == 3) { + if(ndi<=0 || ndo <=0) return vec3(0); + vec3 diff = ndi * kd / pif; + if(ndh<=0) return diff; + float cos2 = ndh * ndh; + float tan2 = (1 - cos2) / cos2; + float alpha2 = rs * rs; + float d = alpha2 / (pif * cos2 * cos2 * (alpha2 + tan2) * (alpha2 + tan2)); + float lambda_o = (-1 + sqrt(1 + (1 - ndo * ndo) / (ndo * ndo))) / 2; + float lambda_i = (-1 + sqrt(1 + (1 - ndi * ndi) / (ndi * ndi))) / 2; + float g = 1 / (1 + lambda_o + lambda_i); + vec3 spec = ndi * ks * d * g / (4*ndi*ndo); + return diff+spec; + } } -)"; -static const char* cubemap_vertex = R"( -#version 330 - -layout(location = 0) in vec3 positions; // vertex position - -uniform mat4 view; // inverse of the camera frame (as a matrix) -uniform mat4 projection; // camera projection +uniform int etype; +uniform bool faceted; +uniform vec4 highlight; // highlighted color -out vec3 position; // vertex position (in world coordinate) -out vec2 texcoord; // vertex texture coordinates +uniform int mtype; // material type +uniform vec3 emission; // material ke +uniform vec3 diffuse; // material kd +uniform vec3 specular; // material ks +uniform float roughness; // material rs +uniform float opacity; // material op -// main function -void main() { - // copy values - position = positions; +uniform bool emission_tex_on; // material ke texture on +uniform sampler2D emission_tex; // material ke texture +uniform bool diffuse_tex_on; // material kd texture on +uniform sampler2D diffuse_tex; // material kd texture +uniform bool specular_tex_on; // material ks texture on +uniform sampler2D specular_tex; // material ks texture +uniform bool roughness_tex_on; // material rs texture on +uniform sampler2D roughness_tex; // material rs texture +uniform bool opacity_tex_on; // material op texture on +uniform sampler2D opacity_tex; // material op texture - // clip - vec3 view_no_transform = (view * vec4(position * 100.0, 0)).xyz; - gl_Position = projection * vec4(view_no_transform, 1); -} -)"; +uniform bool mat_norm_tex_on; // material normal texture on +uniform sampler2D mat_norm_tex; // material normal texture -static const char* bake_environment_frag = R"( -#version 330 +uniform bool double_sided; // double sided rendering -out vec3 frag_color; +uniform mat4 frame; // shape transform +uniform mat4 frameit; // shape transform -in vec3 position; // position in world space +bool evaluate_material(vec2 texcoord, vec4 color, out vec3 ke, + out vec3 kd, out vec3 ks, out float rs, out float op) { + if(mtype == 0) { + ke = emission; + kd = vec3(0,0,0); + ks = vec3(0,0,0); + op = 1; + return false; + } -uniform vec3 eye; // camera position -uniform mat4 view; // inverse of the camera frame (as a matrix) -uniform mat4 projection; // camera projection + ke = color.xyz * emission; + kd = color.xyz * diffuse; + ks = color.xyz * specular; + rs = roughness; + op = color.w * opacity; -uniform sampler2D environment; + vec4 ke_tex = (emission_tex_on) ? texture(emission_tex,texcoord) : vec4(1,1,1,1); + vec4 kd_tex = (diffuse_tex_on) ? texture(diffuse_tex,texcoord) : vec4(1,1,1,1); + vec4 ks_tex = (specular_tex_on) ? texture(specular_tex,texcoord) : vec4(1,1,1,1); + vec4 rs_tex = (roughness_tex_on) ? texture(roughness_tex,texcoord) : vec4(1,1,1,1); + vec4 op_tex = (opacity_tex_on) ? texture(opacity_tex,texcoord) : vec4(1,1,1,1); -const float PI = 3.14159265359; + // get material color from textures and adjust values + ke *= ke_tex.xyz; + vec3 kb = kd * kd_tex.xyz; + float km = ks.x * ks_tex.z; + kd = kb * (1 - km); + ks = kb * km + vec3(0.04) * (1 - km); + rs *= ks_tex.y; + rs = rs*rs; + op *= kd_tex.w; -vec2 sample_spherical_map(vec3 v) { - vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); - uv *= vec2(0.1591, 0.3183); // inv atan - uv += 0.5; - uv.x = 1 - uv.x; - return uv; + return true; } -void main() { - vec3 normal = normalize(position); - vec2 uv = sample_spherical_map(normal); - vec3 color = texture(environment, uv).rgb; - - // TODO(giacomo): We skip gamma correction, assuming the environment is stored - // in linear space. Is it always true? Probably not. - frag_color = vec3(color); +vec3 apply_normal_map(vec2 texcoord, vec3 normal, vec4 tangsp) { + if(!mat_norm_tex_on) return normal; + vec3 tangu = normalize((frame * vec4(normalize(tangsp.xyz),0)).xyz); + vec3 tangv = normalize(cross(normal, tangu)); + if(tangsp.w < 0) tangv = -tangv; + vec3 texture = 2 * pow(texture(mat_norm_tex,texcoord).xyz, vec3(1/2.2)) - 1; + // texture.y = -texture.y; + return normalize( tangu * texture.x + tangv * texture.y + normal * texture.z ); } -)"; - -static const char* bake_diffuse_frag = R"( -#version 330 - -out vec3 frag_color; -in vec3 position; // position in world space +in vec3 position; // [from vertex shader] position in world space +in vec3 normal; // [from vertex shader] normal in world space (need normalization) +in vec2 texcoord; // [from vertex shader] texcoord +in vec4 color; // [from vertex shader] color +in vec4 tangsp; // [from vertex shader] tangent space -uniform vec3 eye; // camera position -uniform mat4 view; // inverse of the camera frame (as a matrix) -uniform mat4 projection; // camera projection +uniform vec3 eye; // camera position +uniform mat4 view; // inverse of the camera frame (as a matrix) +uniform mat4 projection; // camera projection -uniform samplerCube environment; +uniform float exposure; +uniform float gamma; -const float pif = 3.14159265359; +out vec4 frag_color; -vec3 direction(float phi, float theta) { - return vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); +vec3 triangle_normal(vec3 position) { + vec3 fdx = dFdx(position); + vec3 fdy = dFdy(position); + return normalize((frame * vec4(normalize(cross(fdx, fdy)), 0)).xyz); } +// main void main() { - vec3 normal = normalize(position); - // TODO: Why do we need these flips? - normal.z *= -1; - normal.y *= -1; - - vec3 up = vec3(0.0, 1.0, 0.0); - vec3 right = normalize(cross(up, normal)); - up = normalize(cross(normal, right)); - mat3 rot = mat3(right, up, normal); + if(mtype == 0) { + frag_color = vec4(emission, 1); + return; + } - vec3 irradiance = vec3(0.0); + // view vector + vec3 wo = normalize(eye - position); - int phi_samples = 256; - int theta_samples = 128; - for (int x = 0; x < phi_samples; x++) { - for (int y = 0; y < theta_samples; y++) { - float phi = (2.0 * pif * x) / phi_samples; - float theta = (0.5 * pif * y) / theta_samples; - vec3 sample = rot * direction(phi, theta); - // TODO: Artifacts on Mac if we don't force the LOD. - vec3 environment = textureLod(environment, sample, 0).rgb; - irradiance += environment * cos(theta) * sin(theta); - } + // prepare normals + vec3 n; + if(faceted) { + n = triangle_normal(position); + } else { + n = normalize(normal); } - irradiance *= pif / (phi_samples * theta_samples); - frag_color = irradiance; -} -)"; - -static const char* bake_specular_frag = R"( -#version 330 -out vec3 frag_color; + // apply normal map + n = apply_normal_map(texcoord, n, tangsp); -in vec3 position; // position in world space + // use faceforward to ensure the normals points toward us + if(double_sided) n = faceforward(n,-wo,n); -uniform vec3 eye; // camera position -uniform mat4 view; // inverse of the camera frame (as a matrix) -uniform mat4 projection; // camera projection -uniform int mipmap_level; -uniform int num_samples = 1024; + // get material color from textures + vec3 brdf_ke, brdf_kd, brdf_ks; float brdf_rs, brdf_op; + bool has_brdf = evaluate_material(texcoord, color, brdf_ke, brdf_kd, brdf_ks, brdf_rs, brdf_op); -uniform samplerCube environment; + // exit if needed + if(brdf_op < 0.005) discard; -const float pif = 3.14159265359; + // check const color + if(etype == 0) { + frag_color = vec4(brdf_ke,brdf_op); + return; + } -float radical_inverse(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 -} + // emission + vec3 c = brdf_ke; -vec2 hammersley(uint i, int N) { - return vec2(float(i) / float(N), radical_inverse(i)); -} + // check early exit + if(brdf_kd != vec3(0,0,0) || brdf_ks != vec3(0,0,0)) { + // eyelight shading + if(eyelight) { + vec3 wi = wo; + c += pif * brdfcos((has_brdf) ? etype : 0, brdf_ke, brdf_kd, brdf_ks, brdf_rs, brdf_op, n,wi,wo); + } else { + // accumulate ambient + c += lamb * brdf_kd; + // foreach light + for(int lid = 0; lid < lnum; lid ++) { + vec3 cl = vec3(0,0,0); vec3 wi = vec3(0,0,0); + evaluate_light(lid, position, cl, wi); + c += cl * brdfcos((has_brdf) ? etype : 0, brdf_ke, brdf_kd, brdf_ks, brdf_rs, brdf_op, n,wi,wo); + } + } + } -vec3 sample_ggx(vec2 rn, vec3 N, float roughness) { - float a = roughness * roughness; + // final color correction + c = pow(c * pow(2,exposure), vec3(1/gamma)); - float phi = 2.0 * pif * rn.x; - float cos_theta = sqrt((1.0 - rn.y) / (1.0 + (a * a - 1.0) * rn.y)); - float sin_theta = sqrt(1.0 - cos_theta * cos_theta); + // highlighting + if(highlight.w > 0) { + if(mod(int(gl_FragCoord.x)/4 + int(gl_FragCoord.y)/4, 2) == 0) + c = highlight.xyz * highlight.w + c * (1-highlight.w); + } - // from spherical coordinates to cartesian coordinates - vec3 H; - H.x = cos(phi) * sin_theta; - H.y = sin(phi) * sin_theta; - H.z = cos_theta; + // output final color by setting gl_FragColor + frag_color = vec4(c,brdf_op); +} +)"; + return code; +} - // from tangent-space vector to world-space sample vector - vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); - vec3 tangent = normalize(cross(up, N)); - vec3 bitangent = normalize(cross(N, tangent)); +const char* draw_instances_ibl_fragment_code() { + static const char* code = R"( +#version 330 - vec3 result = tangent * H.x + bitangent * H.y + N * H.z; - return normalize(result); -} +in vec3 position; // [from vertex shader] position in world space +in vec3 normal; // [from vertex shader] normal in world space +in vec2 texcoord; // [from vertex shader] texcoord +in vec4 color; // [from vertex shader] color +in vec4 tangsp; // [from vertex shader] tangent space -void main() { - vec3 N = normalize(position); - N.z *= -1; - N.y *= -1; - vec3 R = N; - vec3 V = N; +float pif = 3.14159265; - float roughness = float(mipmap_level) / 5.0; - roughness *= roughness; +uniform int etype; +uniform int mtype; +uniform bool faceted; +uniform int shading_type; - float total_weight = 0.0; - vec3 result = vec3(0.0); - for (uint i = 0u; i < uint(num_samples); i++) { - vec2 rn = hammersley(i, num_samples); - vec3 H = sample_ggx(rn, N, roughness); - vec3 L = normalize(reflect(-V, H)); - float NdotL = dot(N, L); - if (NdotL > 0.0) { - result += textureLod(environment, L, 0).rgb * NdotL; - total_weight += NdotL; - } - } - result = result / total_weight; +uniform vec3 emission; // material ke +uniform vec3 diffuse; // material kd +uniform vec3 specular; // material ks +uniform float roughness; // material rs +uniform float opacity; // material op - frag_color = vec3(result); -} -)"; +// baked textures for image based lighting +uniform samplerCube irradiance_cubemap; +uniform samplerCube reflection_cubemap; +uniform sampler2D brdf_lut; -static const char* environment_fragment = R"( -#version 330 +uniform bool emission_tex_on; // material ke texture on +uniform sampler2D emission_tex; // material ke texture +uniform bool diffuse_tex_on; // material kd texture on +uniform sampler2D diffuse_tex; // material kd texture +uniform bool specular_tex_on; // material ks texture on +uniform sampler2D specular_tex; // material ks texture +uniform bool roughness_tex_on; // material rs texture on +uniform sampler2D roughness_tex; // material rs texture +uniform bool opacity_tex_on; // material op texture on +uniform sampler2D opacity_tex; // material op texture -out vec3 frag_color; +uniform bool mat_norm_tex_on; // material normal texture on +uniform sampler2D mat_norm_tex; // material normal texture -in vec3 position; // position in world space +uniform bool double_sided; // double sided rendering + +uniform mat4 frame; // shape transform +uniform mat4 frameit; // shape transform uniform vec3 eye; // camera position uniform mat4 view; // inverse of the camera frame (as a matrix) @@ -817,548 +1012,475 @@ uniform mat4 projection; // camera projection uniform float exposure; uniform float gamma; -uniform samplerCube environment; - -void main() { - vec3 v = normalize(position); +out vec4 frag_color; - vec3 radiance = texture(environment, v).rgb; +struct brdf_struct { + vec3 emission; + vec3 diffuse; + vec3 specular; + float roughness; + float opacity; +} brdf; - // final color correction - radiance = pow(radiance * pow(2, exposure), vec3(1 / gamma)); - frag_color = radiance; +vec3 eval_brdf_color(vec3 value, sampler2D tex, bool tex_on) { + vec3 result = value; + if (tex_on) result *= texture(tex, texcoord).rgb; + return result; } -)"; - -#ifndef _WIN32 -#pragma GCC diagnostic pop -#endif - -gui_scene::~gui_scene() { - clear_scene(this); - for (auto camera : cameras) delete camera; - for (auto shape : shapes) delete shape; - for (auto material : materials) delete material; - for (auto texture : textures) delete texture; - for (auto light : lights) delete light; - delete lights_program; - delete ibl_program; - delete environment_program; - delete environment_cubemap; - delete diffuse_cubemap; - delete specular_cubemap; - delete brdf_lut; +float eval_brdf_value(float value, sampler2D tex, bool tex_on) { + float result = value; + if (tex_on) result *= texture(tex, texcoord).r; + return result; } -// Initialize an OpenGL scene -void init_scene(gui_scene* scene) { - if (is_initialized(scene->lights_program)) return; - auto error = ""s, errorlog = ""s; - init_program( - scene->lights_program, glscene_vertex, glscene_fragment, error, errorlog); -} -bool is_initialized(gui_scene* scene) { - return is_initialized(scene->lights_program); +brdf_struct compute_brdf() { + brdf_struct brdf; + brdf.emission = eval_brdf_color(emission, emission_tex, emission_tex_on); + brdf.diffuse = eval_brdf_color(diffuse, diffuse_tex, diffuse_tex_on); + brdf.specular = eval_brdf_color(specular, specular_tex, specular_tex_on); + brdf.roughness = eval_brdf_value(roughness, roughness_tex, roughness_tex_on); + brdf.opacity = eval_brdf_value(opacity, opacity_tex, opacity_tex_on); + return brdf; } -// Clear an OpenGL scene -void clear_scene(gui_scene* scene) { - for (auto texture : scene->textures) clear_texture(texture); - for (auto shape : scene->shapes) clear_shape(shape); - clear_program(scene->lights_program); - clear_program(scene->ibl_program); - clear_program(scene->environment_program); - clear_cubemap(scene->environment_cubemap); - clear_cubemap(scene->diffuse_cubemap); - clear_cubemap(scene->specular_cubemap); - clear_texture(scene->brdf_lut); +vec3 apply_normal_map(vec2 texcoord, vec3 normal, vec4 tangsp) { + if (!mat_norm_tex_on) return normal; + vec3 tangu = normalize((frame * vec4(normalize(tangsp.xyz), 0)).xyz); + vec3 tangv = normalize(cross(normal, tangu)); + if (tangsp.w < 0) tangv = -tangv; + vec3 texture = 2 * pow(texture(mat_norm_tex, texcoord).xyz, vec3(1 / 2.2)) - + 1; + // texture.y = -texture.y; + return normalize(tangu * texture.x + tangv * texture.y + normal * texture.z); } -// add camera -gui_camera* add_camera(gui_scene* scene) { - return scene->cameras.emplace_back(new gui_camera{}); -} -void set_frame(gui_camera* camera, const frame3f& frame) { - camera->frame = frame; -} -void set_lens(gui_camera* camera, float lens, float aspect, float film) { - camera->lens = lens; - camera->aspect = aspect; - camera->film = film; -} -void set_nearfar(gui_camera* camera, float near, float far) { - camera->near = near; - camera->far = far; +vec3 triangle_normal(vec3 position) { + vec3 fdx = dFdx(position); + vec3 fdy = dFdy(position); + return normalize((frame * vec4(normalize(cross(fdx, fdy)), 0)).xyz); } -// add texture -ogl_texture* add_texture(gui_scene* scene) { - return scene->textures.emplace_back(new ogl_texture{}); -} +#define etype_points 1 +#define etype_lines 2 +#define etype_triangles 3 +#define etype_quads 3 -// add shape -ogl_shape* add_shape(gui_scene* scene) { - auto shape = new ogl_shape{}; - set_shape(shape); - scene->shapes.push_back(shape); - return shape; -} +vec3 compute_normal(vec3 V) { + vec3 N; + if (etype == etype_triangles) { + if (faceted) { + N = triangle_normal(position); + } else { + N = normalize(normal); + } + } -// add instance -gui_instance* add_instance(gui_scene* scene) { - return scene->instances.emplace_back(new gui_instance{}); -} -void set_frame(gui_instance* instance, const frame3f& frame) { - instance->frame = frame; -} -void set_shape(gui_instance* instance, ogl_shape* shape) { - instance->shape = shape; -} -void set_material(gui_instance* instance, gui_material* material) { - instance->material = material; -} -void set_hidden(gui_instance* instance, bool hidden) { - instance->hidden = hidden; -} -void set_highlighted(gui_instance* instance, bool highlighted) { - instance->highlighted = highlighted; -} + if (etype == etype_lines) { + // normal of lines is coplanar with view vector and direction tangent to the + // line + vec3 tangent = normalize(normal); + N = normalize(V - tangent * dot(V, tangent)); + } -// add material -gui_material* add_material(gui_scene* scene) { - return scene->materials.emplace_back(new gui_material{}); -} -void set_emission( - gui_material* material, const vec3f& emission, ogl_texture* emission_tex) { - material->emission = emission; - material->emission_tex = emission_tex; -} -void set_color( - gui_material* material, const vec3f& color, ogl_texture* color_tex) { - material->color = color; - material->color_tex = color_tex; -} -void set_specular( - gui_material* material, float specular, ogl_texture* specular_tex) { - material->specular = specular; - material->specular_tex = specular_tex; -} -void set_roughness( - gui_material* material, float roughness, ogl_texture* roughness_tex) { - material->roughness = roughness; - material->roughness_tex = roughness_tex; -} -void set_opacity( - gui_material* material, float opacity, ogl_texture* opacity_tex) { - material->opacity = opacity; -} -void set_metallic( - gui_material* material, float metallic, ogl_texture* metallic_tex) { - material->metallic = metallic; - material->metallic_tex = metallic_tex; + // apply normal map + N = apply_normal_map(texcoord, N, tangsp); + + // use faceforward to ensure the normals points toward us + if (double_sided) N = faceforward(N, -V, N); + return N; } -void set_normalmap(gui_material* material, ogl_texture* normal_tex) { - material->normal_tex = normal_tex; + +vec3 sample_prefiltered_refleciton(vec3 L, float roughness) { + int MAX_REFLECTION_LOD = 5; + float lod = sqrt(roughness) * MAX_REFLECTION_LOD; + return textureLod(reflection_cubemap, L, lod).rgb; } -ogl_shape* add_shape(gui_scene* scene, const vector& points, - const vector& lines, const vector& triangles, - const vector& quads, const vector& positions, - const vector& normals, const vector& texcoords, - const vector& colors, bool edges) { - auto shape = add_shape(scene); - set_points(shape, points); - set_lines(shape, lines); - set_triangles(shape, triangles); - set_quads(shape, quads); - set_positions(shape, positions); - set_normals(shape, normals); - set_texcoords(shape, texcoords); - set_colors(shape, colors); - if (edges && (!triangles.empty() || !quads.empty())) { - set_edges(shape, triangles, quads); +// main +void main() { + if(mtype == 0) { + frag_color = vec4(emission, 1); + return; } - return shape; + + vec3 V = normalize(eye - position); + vec3 N = compute_normal(V); + + brdf_struct brdf = compute_brdf(); + if (brdf.opacity < 0.005) discard; + + // emission + vec3 radiance = brdf.emission; + + // diffuse + radiance += brdf.diffuse * textureLod(irradiance_cubemap, N, 0).rgb; + + // specular + vec3 L = normalize(reflect(-V, N)); + vec3 reflection = sample_prefiltered_refleciton(L, brdf.roughness); + vec2 env_brdf = texture(brdf_lut, vec2(max(dot(N, V), 0.0), roughness)).rg; + radiance += reflection * (brdf.specular * env_brdf.x + env_brdf.y); + + // final color correction + radiance = pow(radiance * pow(2, exposure), vec3(1 / gamma)); + + // output final color by setting gl_FragColor + frag_color = vec4(radiance, brdf.opacity); +} +)"; + return code; } -// shortcuts -gui_camera* add_camera(gui_scene* scene, const frame3f& frame, float lens, - float aspect, float film, float near, float far) { - auto camera = add_camera(scene); - set_frame(camera, frame); - set_lens(camera, lens, aspect, film); - set_nearfar(camera, near, far); - return camera; +const char* bake_brdf_vertex_code() { + static const char* code = R"( +#version 330 + +layout(location = 0) in vec3 positions; // vertex position + +out vec3 position; // vertex position (in world coordinate) + +// main function +void main() { + position = positions; + + gl_Position = vec4(position, 1); } -gui_material* add_material(gui_scene* scene, const vec3f& emission, - const vec3f& color, float specular, float metallic, float roughness, - ogl_texture* emission_tex, ogl_texture* color_tex, - ogl_texture* specular_tex, ogl_texture* metallic_tex, - ogl_texture* roughness_tex, ogl_texture* normalmap_tex) { - auto material = add_material(scene); - set_emission(material, emission, emission_tex); - set_color(material, color, color_tex); - set_specular(material, specular, specular_tex); - set_metallic(material, metallic, metallic_tex); - set_roughness(material, roughness, roughness_tex); - set_normalmap(material, normalmap_tex); - return material; +)"; + return code; } -gui_instance* add_instance(gui_scene* scene, const frame3f& frame, - ogl_shape* shape, gui_material* material, bool hidden, bool highlighted) { - auto instance = add_instance(scene); - set_frame(instance, frame); - set_shape(instance, shape); - set_material(instance, material); - set_hidden(instance, hidden); - set_highlighted(instance, highlighted); - return instance; +const char* bake_brdf_fragment_code() { + static const char* code = R"( +#version 330 + +out vec3 frag_color; + +in vec3 position; // position in world space + +const float pif = 3.14159265359; + +float radical_inverse(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 } -// add light -gui_light* add_light(gui_scene* scene) { - return scene->lights.emplace_back(new gui_light{}); +vec2 hammersley(uint i, uint N) { + return vec2(float(i) / float(N), radical_inverse(i)); } -void set_light(gui_light* light, const vec3f& position, const vec3f& emission, - ogl_light_type type, bool camera) { - light->position = position; - light->emission = emission; - light->type = type; - light->camera = camera; + +float geometry_schlick_ggx(float NdotV, float roughness) { + float a = roughness; + float k = (a * a) / 2.0; + + float nom = NdotV; + float denom = NdotV * (1.0 - k) + k; + + return nom / denom; } -void clear_lights(gui_scene* scene) { - for (auto light : scene->lights) delete light; - scene->lights.clear(); + +float geometry_smith(vec3 N, vec3 V, vec3 L, float roughness) { + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = geometry_schlick_ggx(NdotV, roughness); + float ggx1 = geometry_schlick_ggx(NdotL, roughness); + + return ggx1 * ggx2; } -bool has_max_lights(gui_scene* scene) { return scene->lights.size() >= 16; } -void add_default_lights(gui_scene* scene) { - clear_lights(scene); - set_light(add_light(scene), normalize(vec3f{1, 1, 1}), - vec3f{pif / 2, pif / 2, pif / 2}, ogl_light_type::directional, true); - set_light(add_light(scene), normalize(vec3f{-1, 1, 1}), - vec3f{pif / 2, pif / 2, pif / 2}, ogl_light_type::directional, true); - set_light(add_light(scene), normalize(vec3f{-1, -1, 1}), - vec3f{pif / 4, pif / 4, pif / 4}, ogl_light_type::directional, true); - set_light(add_light(scene), normalize(vec3f{0.1, 0.5, -1}), - vec3f{pif / 4, pif / 4, pif / 4}, ogl_light_type::directional, true); + +vec3 importance_sample_ggx(vec2 Xi, vec3 N, float roughness) { + float a = roughness * roughness; + + float phi = 2.0 * pif * Xi.x; + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a * a - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta * cosTheta); + + // from spherical coordinates to cartesian coordinates + vec3 H; + H.x = cos(phi) * sinTheta; + H.y = sin(phi) * sinTheta; + H.z = cosTheta; + + // from tangent-space vector to world-space sample vector + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; + return normalize(sampleVec); } -// Draw a shape -void draw_object( - gui_scene* scene, gui_instance* instance, const gui_scene_params& params) { - static auto empty_instances = vector{identity3x4f}; +vec2 integrate_brdf(float NdotV, float roughness) { + vec3 V; + V.x = sqrt(1.0 - NdotV * NdotV); + V.y = 0.0; + V.z = NdotV; - if (instance->hidden) return; + float A = 0.0; + float B = 0.0; - assert_ogl_error(); - auto program = (params.shading == gui_shading_type::environment) - ? scene->ibl_program - : scene->lights_program; + vec3 N = vec3(0.0, 0.0, 1.0); - auto shape_xform = frame_to_mat(instance->frame); - auto shape_inv_xform = transpose( - frame_to_mat(inverse(instance->frame, params.non_rigid_frames))); - set_uniform(program, "frame", shape_xform); - set_uniform(program, "frameit", shape_inv_xform); - set_uniform(program, "offset", 0.0f); - if (instance->highlighted) { - set_uniform(program, "highlight", vec4f{1, 1, 0, 1}); - } else { - set_uniform(program, "highlight", vec4f{0, 0, 0, 0}); + const uint SAMPLE_COUNT = 1024u; + for (uint i = 0u; i < SAMPLE_COUNT; ++i) { + vec2 Xi = hammersley(i, SAMPLE_COUNT); + vec3 H = importance_sample_ggx(Xi, N, roughness); + vec3 L = normalize(2.0 * dot(V, H) * H - V); + + float NdotL = max(L.z, 0.0); + float NdotH = max(H.z, 0.0); + float VdotH = max(dot(V, H), 0.0); + + if (NdotL > 0.0) { + float G = geometry_smith(N, V, L, roughness); + float G_Vis = (G * VdotH) / (NdotH * NdotV); + float Fc = pow(1.0 - VdotH, 5.0); + + A += (1.0 - Fc) * G_Vis; + B += Fc * G_Vis; + } } - assert_ogl_error(); + A /= float(SAMPLE_COUNT); + B /= float(SAMPLE_COUNT); + return vec2(A, B); +} - auto material = instance->material; - auto mtype = 2; - set_uniform(program, "mtype", mtype); - set_uniform(program, "emission", material->emission); - set_uniform(program, "diffuse", material->color); - set_uniform(program, "specular", - vec3f{material->metallic, material->metallic, material->metallic}); - set_uniform(program, "roughness", material->roughness); - set_uniform(program, "opacity", material->opacity); - set_uniform(program, "double_sided", (int)params.double_sided); - set_uniform( - program, "emission_tex", "emission_tex_on", material->emission_tex, 0); - set_uniform(program, "diffuse_tex", "diffuse_tex_on", material->color_tex, 1); - set_uniform( - program, "specular_tex", "specular_tex_on", material->metallic_tex, 2); - set_uniform( - program, "roughness_tex", "roughness_tex_on", material->roughness_tex, 3); - set_uniform( - program, "opacity_tex", "opacity_tex_on", material->opacity_tex, 4); - set_uniform( - program, "mat_norm_tex", "mat_norm_tex_on", material->normal_tex, 5); +void main() { + vec2 uv = position.xy * 0.5 + 0.5; + vec2 integrated_brdf = integrate_brdf(uv.x, uv.y); + frag_color = vec3(integrated_brdf, 0.0); +} +)"; + return code; +} - set_uniform(program, "irradiance_cubemap", scene->diffuse_cubemap, 6); - set_uniform(program, "reflection_cubemap", scene->specular_cubemap, 7); - set_uniform(program, "brdf_lut", scene->brdf_lut, 8); - assert_ogl_error(); +const char* bake_cubemap_vertex_code() { + static const char* code = R"( +#version 330 - auto shape = instance->shape; +layout(location = 0) in vec3 positions; // vertex position - if (is_initialized(shape->points)) { - set_uniform(program, "etype", 1); - } - if (is_initialized(shape->lines)) { - set_uniform(program, "etype", 2); - } - if (is_initialized(shape->triangles)) { - set_uniform(program, "etype", 3); - } - if (is_initialized(shape->quads)) { - set_uniform(program, "etype", 3); - } - draw_shape(shape); +uniform mat4 view; // inverse of the camera frame (as a matrix) +uniform mat4 projection; // camera projection - assert_ogl_error(); +out vec3 position; // vertex position (in world coordinate) +out vec2 texcoord; // vertex texture coordinates - if (is_initialized(shape->edges) && params.edges && !params.wireframe) { - set_uniform(program, "mtype", mtype); - set_uniform(program, "emission", vec3f{0, 0, 0}); - set_uniform(program, "diffuse", vec3f{0, 0, 0}); - set_uniform(program, "specular", vec3f{0, 0, 0}); - set_uniform(program, "roughness", 0.0f); - set_uniform(program, "etype", 3); - draw_shape(shape); - assert_ogl_error(); - } +// main function +void main() { + // copy values + position = positions; + + // clip + vec3 view_no_transform = (view * vec4(position * 100.0, 0)).xyz; + gl_Position = projection * vec4(view_no_transform, 1); +} +)"; + return code; } -// Display a scene -void draw_scene(gui_scene* scene, gui_camera* camera, const vec4i& viewport, - const gui_scene_params& params) { - static auto camera_light0 = gui_light{normalize(vec3f{1, 1, 1}), - vec3f{pif / 2, pif / 2, pif / 2}, ogl_light_type::directional, true}; - static auto camera_light1 = gui_light{normalize(vec3f{-1, 1, 1}), - vec3f{pif / 2, pif / 2, pif / 2}, ogl_light_type::directional, true}; - static auto camera_light2 = gui_light{normalize(vec3f{-1, -1, 1}), - vec3f{pif / 4, pif / 4, pif / 4}, ogl_light_type::directional, true}; - static auto camera_light3 = gui_light{normalize(vec3f{0.1, 0.5, -1}), - vec3f{pif / 4, pif / 4, pif / 4}, ogl_light_type::directional, true}; - static auto camera_lights = vector{ - &camera_light0, &camera_light1, &camera_light2, &camera_light3}; - auto camera_aspect = (float)viewport.z / (float)viewport.w; - auto camera_yfov = - camera_aspect >= 0 - ? (2 * atan(camera->film / (camera_aspect * 2 * camera->lens))) - : (2 * atan(camera->film / (2 * camera->lens))); - auto camera_view = frame_to_mat(inverse(camera->frame)); - auto camera_proj = perspective_mat( - camera_yfov, camera_aspect, params.near, params.far); +const char* bake_environment_fragment_code() { + static const char* code = R"( +#version 330 - assert_ogl_error(); - clear_ogl_framebuffer(params.background); - set_ogl_viewport(viewport); +out vec3 frag_color; - assert_ogl_error(); - auto program = (params.shading == gui_shading_type::environment) - ? scene->ibl_program - : scene->lights_program; +in vec3 position; // position in world space - bind_program(program); - assert_ogl_error(); - set_uniform(program, "eye", camera->frame.o); - set_uniform(program, "view", camera_view); - set_uniform(program, "projection", camera_proj); - set_uniform(program, "exposure", params.exposure); - set_uniform(program, "gamma", params.gamma); - assert_ogl_error(); +uniform vec3 eye; // camera position +uniform mat4 view; // inverse of the camera frame (as a matrix) +uniform mat4 projection; // camera projection - if (params.shading == gui_shading_type::eyelight) { - assert_ogl_error(); - auto& lights = camera_lights; - set_uniform(program, "lamb", vec3f{0, 0, 0}); - set_uniform(program, "lnum", (int)lights.size()); - auto lid = 0; - for (auto light : lights) { - auto is = std::to_string(lid); - if (light->camera) { - auto position = light->type == ogl_light_type::directional - ? transform_direction( - camera->frame, light->position) - : transform_point(camera->frame, light->position); - set_uniform(program, ("lpos[" + is + "]").c_str(), position); - } else { - set_uniform(program, ("lpos[" + is + "]").c_str(), light->position); - } - set_uniform(program, ("lke[" + is + "]").c_str(), light->emission); - set_uniform(program, ("ltype[" + is + "]").c_str(), (int)light->type); - lid++; - } - assert_ogl_error(); - } +uniform sampler2D environment; +uniform vec3 emission = vec3(1); - if (params.wireframe) set_ogl_wireframe(true); - for (auto instance : scene->instances) { - draw_object(scene, instance, params); - } - unbind_program(); +vec2 sample_spherical_map(vec3 v) { + vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); + uv *= vec2(0.1591, 0.3183); // inv atan + uv += 0.5; + uv.x = 1 - uv.x; + return uv; +} - if (!scene->environment_program->program_id) return; +void main() { + vec3 normal = normalize(position); + vec2 uv = sample_spherical_map(normal); + vec3 color = texture(environment, uv).rgb; - bind_program(scene->environment_program); - // set_scene_uniforms - set_uniform(scene->environment_program, "eye", camera->frame.o); - set_uniform(scene->environment_program, "view", camera_view); - set_uniform(scene->environment_program, "projection", camera_proj); - set_uniform(scene->environment_program, "exposure", params.exposure); - set_uniform(scene->environment_program, "gamma", params.gamma); + // TODO(giacomo): We skip gamma correction, assuming the environment is stored + // in linear space. Is it always true? Probably not. + frag_color = emission * color; +} +)"; + return code; +} - set_uniform( - scene->environment_program, "environment", scene->environment_cubemap, 0); - set_uniform(scene->environment_program, "roughness", 0.0f); +const char* bake_irradiance_fragment_code() { + static const char* code = R"( +#version 330 - auto cube = cube_shape(); - draw_shape(cube); +out vec3 frag_color; - unbind_program(); -} +in vec3 position; // position in world space -// image based lighting -namespace ibl { -// Using 6 render passes, bake a cubemap given a sampler for the environment. -// The input sampler can be either a cubemap or a latlong texture. -template -inline void bake_cubemap(ogl_cubemap* cubemap, const Sampler* environment, - ogl_program* program, int size, int num_mipmap_levels = 1) { - // init cubemap with no data - set_cubemap(cubemap, size, 3, true, true, true); - auto cube = cube_shape(); +uniform vec3 eye; // camera position +uniform mat4 view; // inverse of the camera frame (as a matrix) +uniform mat4 projection; // camera projection - auto framebuffer = ogl_framebuffer{}; - set_framebuffer(&framebuffer, {size, size}); +uniform samplerCube environment; - // clang-format off - frame3f cameras[6] = { - lookat_frame({0, 0, 0}, { 1, 0, 0}, {0, 1, 0}), - lookat_frame({0, 0, 0}, {-1, 0, 0}, {0, 1, 0}), - lookat_frame({0, 0, 0}, { 0,-1, 0}, {0, 0,-1}), - lookat_frame({0, 0, 0}, { 0, 1, 0}, {0, 0, 1}), - lookat_frame({0, 0, 0}, { 0, 0,-1}, {0, 1, 0}), - lookat_frame({0, 0, 0}, { 0, 0, 1}, {0, 1, 0}) - }; - // clang-format on +const float pif = 3.14159265359; - bind_framebuffer(&framebuffer); - bind_program(program); - for (int mipmap_level = 0; mipmap_level < num_mipmap_levels; mipmap_level++) { - // resize render buffer and viewport - set_framebuffer(&framebuffer, {size, size}); - set_ogl_viewport(vec2i{size, size}); +vec3 direction(float phi, float theta) { + return vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); +} - for (auto i = 0; i < 6; ++i) { - // perspective_mat(fov, aspect, near, far) - auto camera_proj = perspective_mat(radians(90), 1, 1, 100); - auto camera_view = frame_to_mat(inverse(cameras[i])); +void main() { + vec3 normal = normalize(position); + // TODO: Why do we need these flips? + normal.z *= -1; + normal.y *= -1; - set_framebuffer_texture(&framebuffer, cubemap, i, mipmap_level); - clear_ogl_framebuffer({0, 0, 0, 0}, true); + vec3 up = vec3(0.0, 1.0, 0.0); + vec3 right = normalize(cross(up, normal)); + up = normalize(cross(normal, right)); + mat3 rot = mat3(right, up, normal); - set_uniform(program, "view", camera_view); - set_uniform(program, "projection", camera_proj); - set_uniform(program, "eye", vec3f{0, 0, 0}); - set_uniform(program, "mipmap_level", mipmap_level); - set_uniform(program, "environment", environment, 0); + vec3 irradiance = vec3(0.0); - draw_shape(cube); + int phi_samples = 256; + int theta_samples = 128; + for (int x = 0; x < phi_samples; x++) { + for (int y = 0; y < theta_samples; y++) { + float phi = (2.0 * pif * x) / phi_samples; + float theta = (0.5 * pif * y) / theta_samples; + vec3 sample = rot * direction(phi, theta); + // TODO: Artifacts on Mac if we don't force the LOD. + vec3 environment = textureLod(environment, sample, 0).rgb; + irradiance += environment * cos(theta) * sin(theta); } - size /= 2; } - unbind_program(); - unbind_framebuffer(); + irradiance *= pif / (phi_samples * theta_samples); + frag_color = irradiance; +} +)"; + return code; } -inline void bake_specular_brdf_texture(ogl_texture* texture) { - auto size = 512; - auto framebuffer = ogl_framebuffer{}; - auto screen_quad = quad_shape(); +const char* bake_reflections_fragment_code() { + static const char* code = R"( +#version 330 - auto program = ogl_program{}; - auto error = ""s, errorlog = ""s; - init_program(&program, bake_brdf_vertex, bake_brdf_fragment, error, errorlog); +out vec3 frag_color; - assert_ogl_error(); +in vec3 position; // position in world space - texture->is_float = true; - texture->linear = true; - texture->nchannels = 3; - texture->size = {size, size}; - glGenTextures(1, &texture->texture_id); +uniform vec3 eye; // camera position +uniform mat4 view; // inverse of the camera frame (as a matrix) +uniform mat4 projection; // camera projection +uniform int mipmap_level; +uniform int num_samples = 1024; - glBindTexture(GL_TEXTURE_2D, texture->texture_id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - // TODO(giacomo): mipmaps? +uniform samplerCube environment; - assert_ogl_error(); +const float pif = 3.14159265359; - set_framebuffer(&framebuffer, {size, size}); - set_framebuffer_texture(&framebuffer, texture, 0); +float radical_inverse(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 +} - bind_framebuffer(&framebuffer); - bind_program(&program); +vec2 hammersley(uint i, int N) { + return vec2(float(i) / float(N), radical_inverse(i)); +} - set_ogl_viewport(vec2i{size, size}); - clear_ogl_framebuffer({0, 0, 0, 0}, true); +vec3 sample_ggx(vec2 rn, vec3 N, float roughness) { + float a = roughness * roughness; - draw_shape(screen_quad); - assert_ogl_error(); + float phi = 2.0 * pif * rn.x; + float cos_theta = sqrt((1.0 - rn.y) / (1.0 + (a * a - 1.0) * rn.y)); + float sin_theta = sqrt(1.0 - cos_theta * cos_theta); - unbind_program(); - unbind_framebuffer(); - clear_framebuffer(&framebuffer); - clear_program(&program); + // from spherical coordinates to cartesian coordinates + vec3 H; + H.x = cos(phi) * sin_theta; + H.y = sin(phi) * sin_theta; + H.z = cos_theta; + + // from tangent-space vector to world-space sample vector + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = normalize(cross(N, tangent)); + + vec3 result = tangent * H.x + bitangent * H.y + N * H.z; + return normalize(result); } -void init_ibl_data(gui_scene* scene, const ogl_texture* environment_texture) { - auto load_program = [](ogl_program* program, const char* vertex, - const char* fragment) { - auto error = ""s, errorlog = ""s; - init_program(program, vertex, fragment, error, errorlog); - }; +void main() { + vec3 N = normalize(position); + N.z *= -1; + N.y *= -1; + vec3 R = N; + vec3 V = N; - load_program(scene->ibl_program, glscene_vertex, glibl_fragment); - load_program( - scene->environment_program, cubemap_vertex, environment_fragment); + float roughness = float(mipmap_level) / 5.0; + roughness *= roughness; - // make cubemap from environment texture - { - auto size = environment_texture->size.y; - auto program = new ogl_program{}; - load_program(program, cubemap_vertex, bake_environment_frag); - bake_cubemap( - scene->environment_cubemap, environment_texture, program, size); - clear_program(program); - delete program; + float total_weight = 0.0; + vec3 result = vec3(0.0); + for (uint i = 0u; i < uint(num_samples); i++) { + vec2 rn = hammersley(i, num_samples); + vec3 H = sample_ggx(rn, N, roughness); + vec3 L = normalize(reflect(-V, H)); + float NdotL = dot(N, L); + if (NdotL > 0.0) { + result += textureLod(environment, L, 0).rgb * NdotL; + total_weight += NdotL; + } } + result = result / total_weight; - // bake irradiance map - { - auto program = new ogl_program{}; - load_program(program, cubemap_vertex, bake_diffuse_frag); - bake_cubemap( - scene->diffuse_cubemap, scene->environment_cubemap, program, 64); - clear_program(program); - delete program; - } + frag_color = vec3(result); +} +)"; + return code; +} - // bake specular map - { - auto program = new ogl_program{}; - load_program(program, cubemap_vertex, bake_specular_frag); - bake_cubemap( - scene->specular_cubemap, scene->environment_cubemap, program, 256, 6); - clear_program(program); - delete program; - } +const char* draw_enivronment_fragment_code() { + static const char* code = R"( +#version 330 - bake_specular_brdf_texture(scene->brdf_lut); +out vec3 frag_color; + +in vec3 position; // position in world space + +uniform vec3 eye; // camera position +uniform mat4 view; // inverse of the camera frame (as a matrix) +uniform mat4 projection; // camera projection + +uniform float exposure; +uniform float gamma; + +uniform samplerCube environment; + +void main() { + vec3 v = normalize(position); + + vec3 radiance = texture(environment, v).rgb; + + // final color correction + radiance = pow(radiance * pow(2, exposure), vec3(1 / gamma)); + frag_color = radiance; +} +)"; + return code; } -} // namespace ibl } // namespace yocto diff --git a/libs/yocto_gui/yocto_draw.h b/libs/yocto_gui/yocto_draw.h index 32001a782..c3b0a7009 100644 --- a/libs/yocto_gui/yocto_draw.h +++ b/libs/yocto_gui/yocto_draw.h @@ -36,9 +36,6 @@ #include "yocto_opengl.h" -// forward declaration -struct GLFWwindow; - // ----------------------------------------------------------------------------- // USING DIRECTIVES // ----------------------------------------------------------------------------- @@ -64,6 +61,8 @@ struct gui_camera { float focus = 0; }; +struct gui_texture : ogl_texture {}; + // Opengl material struct gui_material { // material @@ -73,36 +72,51 @@ struct gui_material { float roughness = 0; float specular = 0; float opacity = 1; - ogl_texture* emission_tex = nullptr; - ogl_texture* color_tex = nullptr; - ogl_texture* metallic_tex = nullptr; - ogl_texture* roughness_tex = nullptr; - ogl_texture* specular_tex = nullptr; - ogl_texture* opacity_tex = nullptr; - ogl_texture* normal_tex = nullptr; + gui_texture* emission_tex = nullptr; + gui_texture* color_tex = nullptr; + gui_texture* metallic_tex = nullptr; + gui_texture* roughness_tex = nullptr; + gui_texture* specular_tex = nullptr; + gui_texture* opacity_tex = nullptr; + gui_texture* normal_tex = nullptr; }; +struct gui_shape : ogl_shape { + float points_size = 10; + float line_thickness = 4; +}; + +// shape properties +void set_points(gui_shape* shape, const vector& points); +void set_lines(gui_shape* shape, const vector& lines); +void set_triangles(gui_shape* shape, const vector& triangles); +void set_quads(gui_shape* shape, const vector& quads); + +void set_positions(gui_shape* shape, const vector& positions); +void set_normals(gui_shape* shape, const vector& normals); +void set_texcoords(gui_shape* shape, const vector& texcoords); +void set_colors(gui_shape* shape, const vector& colors); +void set_tangents(gui_shape* shape, const vector& tangents); +ogl_arraybuffer* get_positions(gui_shape* shape); +ogl_arraybuffer* get_normals(gui_shape* shape); +ogl_arraybuffer* get_texcoords(gui_shape* shape); +ogl_arraybuffer* get_colors(gui_shape* shape); +ogl_arraybuffer* get_tangents(gui_shape* shape); + // Opengl instance struct gui_instance { // instance properties - frame3f frame = identity3x4f; - ogl_shape* shape = nullptr; - gui_material* material = nullptr; - bool hidden = false; - bool highlighted = false; + frame3f frame = identity3x4f; + gui_shape* shape = nullptr; + gui_material* material = nullptr; + bool hidden = false; + bool highlighted = false; + int shading_type = 1; }; // Light type enum struct ogl_light_type { point = 0, directional }; -// Opengl light -struct gui_light { - vec3f position = {0, 0, 0}; - vec3f emission = {0, 0, 0}; - ogl_light_type type = ogl_light_type::point; - bool camera = false; -}; - // Opengl scene struct gui_scene { gui_scene() {} @@ -113,13 +127,12 @@ struct gui_scene { // scene objects vector cameras = {}; vector instances = {}; - vector shapes = {}; + vector shapes = {}; vector materials = {}; - vector textures = {}; - vector lights = {}; + vector textures = {}; // OpenGL state - ogl_program* lights_program = new ogl_program{}; + ogl_program* eyelight_program = new ogl_program{}; ogl_program* ibl_program = new ogl_program{}; ogl_program* environment_program = new ogl_program{}; @@ -127,7 +140,7 @@ struct gui_scene { ogl_cubemap* environment_cubemap = new ogl_cubemap{}; ogl_cubemap* diffuse_cubemap = new ogl_cubemap{}; ogl_cubemap* specular_cubemap = new ogl_cubemap{}; - ogl_texture* brdf_lut = new ogl_texture{}; + gui_texture* brdf_lut = new gui_texture{}; }; // Shading type @@ -144,12 +157,10 @@ const auto gui_shading_names = vector{"environment", "camera_lights"}; struct gui_scene_params { int resolution = 1280; bool wireframe = false; - bool edges = false; - float edge_offset = 0.01f; gui_shading_type shading = gui_shading_type::eyelight; float exposure = 0; float gamma = 2.2f; - vec3f ambient = {0, 0, 0}; + bool faceted = false; bool double_sided = true; bool non_rigid_frames = true; float near = 0.01f; @@ -157,25 +168,30 @@ struct gui_scene_params { vec4f background = vec4f{0.15f, 0.15f, 0.15f, 1.0f}; }; +struct gui_scene_view { + frame3f camera_frame = {}; + mat4f view_matrix = {}; + mat4f projection_matrix = {}; + gui_scene_params params = {}; +}; + // Initialize an OpenGL scene void init_scene(gui_scene* scene); bool is_initialized(const gui_scene* scene); -namespace ibl { // Initialize data for image based lighting -void init_ibl_data(gui_scene* scene, const ogl_texture* environment); -} // namespace ibl +void init_ibl_data( + gui_scene* scene, const gui_texture* environment, const vec3f& emission); // Clear an OpenGL scene void clear_scene(gui_scene* scene); // add scene elements gui_camera* add_camera(gui_scene* scene); -ogl_texture* add_texture(gui_scene* scene); +gui_texture* add_texture(gui_scene* scene); gui_material* add_material(gui_scene* scene); -ogl_shape* add_shape(gui_scene* scene); +gui_shape* add_shape(gui_scene* scene); gui_instance* add_instance(gui_scene* scene); -gui_light* add_light(gui_scene* scene); // camera properties void set_frame(gui_camera* camera, const frame3f& frame); @@ -184,20 +200,20 @@ void set_nearfar(gui_camera* camera, float near, float far); // material properties void set_emission(gui_material* material, const vec3f& emission, - ogl_texture* emission_tex = nullptr); + gui_texture* emission_tex = nullptr); void set_color(gui_material* material, const vec3f& color, - ogl_texture* color_tex = nullptr); + gui_texture* color_tex = nullptr); void set_metallic(gui_material* material, float metallic, - ogl_texture* metallic_tex = nullptr); + gui_texture* metallic_tex = nullptr); void set_roughness(gui_material* material, float roughness, - ogl_texture* roughness_tex = nullptr); + gui_texture* roughness_tex = nullptr); void set_specular(gui_material* material, float specular, - ogl_texture* specular_tex = nullptr); + gui_texture* specular_tex = nullptr); void set_opacity( - gui_material* material, float opacity, ogl_texture* opacity_tex = nullptr); -void set_normalmap(gui_material* material, ogl_texture* normal_tex); + gui_material* material, float opacity, gui_texture* opacity_tex = nullptr); +void set_normalmap(gui_material* material, gui_texture* normal_tex); -ogl_shape* add_shape(gui_scene* scene, const vector& points, +gui_shape* add_shape(gui_scene* scene, const vector& points, const vector& lines, const vector& triangles, const vector& quads, const vector& positions, const vector& normals, const vector& texcoords, @@ -205,7 +221,7 @@ ogl_shape* add_shape(gui_scene* scene, const vector& points, // instance properties void set_frame(gui_instance* instance, const frame3f& frame); -void set_shape(gui_instance* instance, ogl_shape* shape); +void set_shape(gui_instance* instance, gui_shape* shape); void set_material(gui_instance* instance, gui_material* material); void set_hidden(gui_instance* instance, bool hidden); void set_highlighted(gui_instance* instance, bool highlighted); @@ -215,31 +231,31 @@ gui_camera* add_camera(gui_scene* scene, const frame3f& frame, float lens, float aspect, float film = 0.036, float near = 0.001, float far = 10000); gui_material* add_material(gui_scene* scene, const vec3f& emission, const vec3f& color, float specular, float metallic, float roughness, - ogl_texture* emission_tex = nullptr, ogl_texture* color_tex = nullptr, - ogl_texture* specular_tex = nullptr, ogl_texture* metallic_tex = nullptr, - ogl_texture* roughness_tex = nullptr, ogl_texture* normalmap_tex = nullptr); -// ogl_shape* _add_shape(gui_scene* scene, const vector& points, -// const vector& lines, const vector& triangles, -// const vector& quads, const vector& positions, -// const vector& normals, const vector& texcoords, -// const vector& colors, bool edges = false); + gui_texture* emission_tex = nullptr, gui_texture* color_tex = nullptr, + gui_texture* specular_tex = nullptr, gui_texture* metallic_tex = nullptr, + gui_texture* roughness_tex = nullptr, gui_texture* normalmap_tex = nullptr); gui_instance* add_instance(gui_scene* scene, const frame3f& frame, - ogl_shape* shape, gui_material* material, bool hidden = false, + gui_shape* shape, gui_material* material, bool hidden = false, bool highlighted = false); -// light properties -void add_default_lights(gui_scene* scene); -void set_light(gui_light* light, const vec3f& position, const vec3f& emission, - ogl_light_type type, bool camera); +void set_scene_view_uniforms(ogl_program* program, const gui_scene_view& view); +void set_instance_uniforms(ogl_program* program, const gui_instance* instance, + const gui_scene_view& view); +void set_eyelight_uniforms(ogl_program* program, const gui_scene_view& view); +void set_ibl_uniforms(ogl_program* program, const gui_scene* scene); -// light size -void clear_lights(gui_scene* scene); -bool has_max_lights(gui_scene* scene); +void draw_instances(gui_scene* scene, const gui_scene_view& view); +void draw_environment(gui_scene* scene, const gui_scene_view& view); -// Draw an OpenGL scene void draw_scene(gui_scene* scene, gui_camera* camera, const vec4i& viewport, const gui_scene_params& params); +// read-only access to defualt shader code +const char* draw_instances_vertex_code(); +const char* draw_instances_eyelight_fragment_code(); +const char* draw_instances_ibl_fragment_code(); +const char* draw_enivronment_fragment_code(); + } // namespace yocto #endif diff --git a/libs/yocto_gui/yocto_opengl.cpp b/libs/yocto_gui/yocto_opengl.cpp index c7e7e01f3..3735f71c6 100644 --- a/libs/yocto_gui/yocto_opengl.cpp +++ b/libs/yocto_gui/yocto_opengl.cpp @@ -94,14 +94,6 @@ GLenum _assert_ogl_error() { } void assert_ogl_error() { assert(_assert_ogl_error() == GL_NO_ERROR); } -bool check_ogl_error(string& error) { - if (glGetError() != GL_NO_ERROR) { - error = ""; - return false; - } - return true; -} - void clear_ogl_framebuffer(const vec4f& color, bool clear_depth) { glClearColor(color.x, color.y, color.z, color.w); if (clear_depth) { @@ -139,7 +131,7 @@ void set_ogl_blending(bool enabled) { void set_ogl_point_size(int size) { glPointSize(size); } -void set_texture(ogl_texture* texture, const vec2i& size, int nchannels, +void set_texture(ogl_texture* texture, const vec2i& size, int num_channels, const byte* img, bool as_srgb, bool linear, bool mipmap) { static auto sformat = unordered_map{ {1, GL_SRGB}, @@ -165,13 +157,13 @@ void set_texture(ogl_texture* texture, const vec2i& size, int nchannels, return; } if (!texture->texture_id) glGenTextures(1, &texture->texture_id); - if (texture->size != size || texture->nchannels != nchannels || + if (texture->size != size || texture->num_channels != num_channels || texture->is_srgb != as_srgb || texture->is_float == true || texture->linear != linear || texture->mipmap != mipmap) { glBindTexture(GL_TEXTURE_2D, texture->texture_id); glTexImage2D(GL_TEXTURE_2D, 0, - as_srgb ? sformat.at(nchannels) : iformat.at(nchannels), size.x, size.y, - 0, cformat.at(nchannels), GL_UNSIGNED_BYTE, img); + as_srgb ? sformat.at(num_channels) : iformat.at(num_channels), size.x, + size.y, 0, cformat.at(num_channels), GL_UNSIGNED_BYTE, img); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmap ? (linear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST) : (linear ? GL_LINEAR : GL_NEAREST)); @@ -181,19 +173,19 @@ void set_texture(ogl_texture* texture, const vec2i& size, int nchannels, } else { glBindTexture(GL_TEXTURE_2D, texture->texture_id); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size.x, size.y, - cformat.at(nchannels), GL_UNSIGNED_BYTE, img); + cformat.at(num_channels), GL_UNSIGNED_BYTE, img); if (mipmap) glGenerateMipmap(GL_TEXTURE_2D); } - texture->size = size; - texture->nchannels = nchannels; - texture->is_srgb = as_srgb; - texture->is_float = false; - texture->linear = linear; - texture->mipmap = mipmap; + texture->size = size; + texture->num_channels = num_channels; + texture->is_srgb = as_srgb; + texture->is_float = false; + texture->linear = linear; + texture->mipmap = mipmap; assert_ogl_error(); } -void set_texture(ogl_texture* texture, const vec2i& size, int nchannels, +void set_texture(ogl_texture* texture, const vec2i& size, int num_channels, const float* img, bool as_float, bool linear, bool mipmap) { static auto fformat = unordered_map{ {1, GL_RGB16F}, @@ -219,14 +211,14 @@ void set_texture(ogl_texture* texture, const vec2i& size, int nchannels, return; } if (!texture->texture_id) glGenTextures(1, &texture->texture_id); - if (texture->size != size || texture->nchannels != nchannels || + if (texture->size != size || texture->num_channels != num_channels || texture->is_float != as_float || texture->is_srgb == true || texture->linear != linear || texture->mipmap != mipmap) { glGenTextures(1, &texture->texture_id); glBindTexture(GL_TEXTURE_2D, texture->texture_id); glTexImage2D(GL_TEXTURE_2D, 0, - as_float ? fformat.at(nchannels) : iformat.at(nchannels), size.x, - size.y, 0, iformat.at(nchannels), GL_FLOAT, img); + as_float ? fformat.at(num_channels) : iformat.at(num_channels), size.x, + size.y, 0, iformat.at(num_channels), GL_FLOAT, img); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmap ? (linear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST) : (linear ? GL_LINEAR : GL_NEAREST)); @@ -236,31 +228,33 @@ void set_texture(ogl_texture* texture, const vec2i& size, int nchannels, } else { glBindTexture(GL_TEXTURE_2D, texture->texture_id); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size.x, size.y, - iformat.at(nchannels), GL_FLOAT, img); + iformat.at(num_channels), GL_FLOAT, img); if (mipmap) glGenerateMipmap(GL_TEXTURE_2D); } - texture->size = size; - texture->nchannels = nchannels; - texture->is_srgb = false; - texture->is_float = as_float; - texture->linear = linear; - texture->mipmap = mipmap; + texture->size = size; + texture->num_channels = num_channels; + texture->is_srgb = false; + texture->is_float = as_float; + texture->linear = linear; + texture->mipmap = mipmap; assert_ogl_error(); } // check if texture is initialized -bool is_initialized(ogl_texture* texture) { return texture->texture_id != 0; } +bool is_initialized(const ogl_texture* texture) { + return texture && texture->texture_id != 0; +} // clear texture void clear_texture(ogl_texture* texture) { if (texture->texture_id) glDeleteTextures(1, &texture->texture_id); - texture->texture_id = 0; - texture->size = {0, 0}; - texture->nchannels = 0; - texture->is_srgb = false; - texture->is_float = false; - texture->linear = false; - texture->mipmap = false; + texture->texture_id = 0; + texture->size = {0, 0}; + texture->num_channels = 0; + texture->is_srgb = false; + texture->is_float = false; + texture->linear = false; + texture->mipmap = false; } void set_texture(ogl_texture* texture, const image& img, bool as_srgb, @@ -296,7 +290,7 @@ void set_texture(ogl_texture* texture, const image& img, bool as_float, linear, mipmap); } -void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, +void set_cubemap(ogl_cubemap* cubemap, int size, int num_channels, const array& images, bool as_srgb, bool linear, bool mipmap) { static auto sformat = unordered_map{ {1, GL_SRGB}, @@ -323,7 +317,7 @@ void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, } if (!cubemap->cubemap_id) glGenTextures(1, &cubemap->cubemap_id); - if (cubemap->size != size || cubemap->nchannels != nchannels || + if (cubemap->size != size || cubemap->num_channels != num_channels || cubemap->is_srgb != as_srgb || cubemap->is_float == true || cubemap->linear != linear || cubemap->mipmap != mipmap) { glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap->cubemap_id); @@ -334,8 +328,8 @@ void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, return; } glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, - as_srgb ? sformat.at(nchannels) : iformat.at(nchannels), size, size, - 0, cformat.at(nchannels), GL_UNSIGNED_BYTE, images[i]); + as_srgb ? sformat.at(num_channels) : iformat.at(num_channels), size, + size, 0, cformat.at(num_channels), GL_UNSIGNED_BYTE, images[i]); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, mipmap ? (linear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST) @@ -354,20 +348,20 @@ void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, throw std::runtime_error{"cannot modify initialized cubemap"}; // glBindTexture(GL_TEXTURE_2D, cubemap->cubemap_id); // glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size.x, size.y, - // cformat.at(nchannels), GL_UNSIGNED_BYTE, img); + // cformat.at(num_channels), GL_UNSIGNED_BYTE, img); // if (mipmap) glGenerateMipmap(GL_TEXTURE_2D); } glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); - cubemap->size = size; - cubemap->nchannels = nchannels; - cubemap->is_srgb = as_srgb; - cubemap->is_float = false; - cubemap->linear = linear; - cubemap->mipmap = mipmap; + cubemap->size = size; + cubemap->num_channels = num_channels; + cubemap->is_srgb = as_srgb; + cubemap->is_float = false; + cubemap->linear = linear; + cubemap->mipmap = mipmap; assert_ogl_error(); } -void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, +void set_cubemap(ogl_cubemap* cubemap, int size, int num_channels, const array& images, bool as_float, bool linear, bool mipmap) { static auto fformat = unordered_map{ {1, GL_RGB16F}, @@ -394,7 +388,7 @@ void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, } if (!cubemap->cubemap_id) glGenTextures(1, &cubemap->cubemap_id); - if (cubemap->size != size || cubemap->nchannels != nchannels || + if (cubemap->size != size || cubemap->num_channels != num_channels || cubemap->is_float != as_float || cubemap->is_srgb == true || cubemap->linear != linear || cubemap->mipmap != mipmap) { glGenTextures(1, &cubemap->cubemap_id); @@ -403,8 +397,8 @@ void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, for (auto i = 0; i < 6; i++) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, - as_float ? fformat.at(nchannels) : iformat.at(nchannels), size, size, - 0, iformat.at(nchannels), GL_FLOAT, images[i]); + as_float ? fformat.at(num_channels) : iformat.at(num_channels), size, + size, 0, iformat.at(num_channels), GL_FLOAT, images[i]); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, mipmap ? (linear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST) @@ -426,21 +420,23 @@ void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, // glBindTexture(GL_TEXTURE_2D, cubemap->cubemap_id); // glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size, size, - // iformat.at(nchannels), GL_FLOAT, img); + // iformat.at(num_channels), GL_FLOAT, img); // if (mipmap) glGenerateMipmap(GL_TEXTURE_2D); } glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); - cubemap->size = size; - cubemap->nchannels = nchannels; - cubemap->is_srgb = false; - cubemap->is_float = as_float; - cubemap->linear = linear; - cubemap->mipmap = mipmap; + cubemap->size = size; + cubemap->num_channels = num_channels; + cubemap->is_srgb = false; + cubemap->is_float = as_float; + cubemap->linear = linear; + cubemap->mipmap = mipmap; assert_ogl_error(); } // check if cubemap is initialized -bool is_initialized(ogl_cubemap* cubemap) { return cubemap->cubemap_id != 0; } +bool is_initialized(const ogl_cubemap* cubemap) { + return cubemap && cubemap->cubemap_id != 0; +} // clear cubemap void clear_cubemap(ogl_cubemap* cubemap) { @@ -449,56 +445,58 @@ void clear_cubemap(ogl_cubemap* cubemap) { } void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, - int nchannels, bool as_srgb, bool linear, bool mipmap) { + int num_channels, bool as_srgb, bool linear, bool mipmap) { auto data = array{(byte*)img[0].data(), (byte*)img[1].data(), (byte*)img[2].data(), (byte*)img[3].data(), (byte*)img[4].data(), (byte*)img[5].data()}; set_cubemap( - cubemap, img[0].imsize().x, nchannels, data, as_srgb, linear, mipmap); + cubemap, img[0].imsize().x, num_channels, data, as_srgb, linear, mipmap); } void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, - int nchannels, bool as_float, bool linear, bool mipmap) { + int num_channels, bool as_float, bool linear, bool mipmap) { auto data = array{(float*)img[0].data(), (float*)img[1].data(), (float*)img[2].data(), (float*)img[3].data(), (float*)img[4].data(), (float*)img[5].data()}; set_cubemap( - cubemap, img[0].imsize().x, nchannels, data, as_float, linear, mipmap); + cubemap, img[0].imsize().x, num_channels, data, as_float, linear, mipmap); } void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, - int nchannels, bool as_srgb, bool linear, bool mipmap) { + int num_channels, bool as_srgb, bool linear, bool mipmap) { auto data = array{(byte*)img[0].data(), (byte*)img[1].data(), (byte*)img[2].data(), (byte*)img[3].data(), (byte*)img[4].data(), (byte*)img[5].data()}; set_cubemap( - cubemap, img[0].imsize().x, nchannels, data, as_srgb, linear, mipmap); + cubemap, img[0].imsize().x, num_channels, data, as_srgb, linear, mipmap); } void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, - int nchannels, bool as_float, bool linear, bool mipmap) { + int num_channels, bool as_float, bool linear, bool mipmap) { auto data = array{(float*)img[0].data(), (float*)img[1].data(), (float*)img[2].data(), (float*)img[3].data(), (float*)img[4].data(), (float*)img[5].data()}; set_cubemap( - cubemap, img[0].imsize().x, nchannels, data, as_float, linear, mipmap); + cubemap, img[0].imsize().x, num_channels, data, as_float, linear, mipmap); } void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, - int nchannels, bool as_srgb, bool linear, bool mipmap) { + int num_channels, bool as_srgb, bool linear, bool mipmap) { auto data = array{(byte*)img[0].data(), (byte*)img[1].data(), (byte*)img[2].data(), (byte*)img[3].data(), (byte*)img[4].data(), (byte*)img[5].data()}; set_cubemap( - cubemap, img[0].imsize().x, nchannels, data, as_srgb, linear, mipmap); + cubemap, img[0].imsize().x, num_channels, data, as_srgb, linear, mipmap); } void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, - int nchannels, bool as_float, bool linear, bool mipmap) { + int num_channels, bool as_float, bool linear, bool mipmap) { auto data = array{(float*)img[0].data(), (float*)img[1].data(), (float*)img[2].data(), (float*)img[3].data(), (float*)img[4].data(), (float*)img[5].data()}; set_cubemap( - cubemap, img[0].imsize().x, nchannels, data, as_float, linear, mipmap); + cubemap, img[0].imsize().x, num_channels, data, as_float, linear, mipmap); } // check if buffer is initialized -bool is_initialized(ogl_arraybuffer* buffer) { return buffer->buffer_id != 0; } +bool is_initialized(const ogl_arraybuffer* buffer) { + return buffer && buffer->buffer_id != 0; +} // set buffer void set_arraybuffer(ogl_arraybuffer* buffer, size_t size, int esize, @@ -576,8 +574,8 @@ void set_elementbuffer(ogl_elementbuffer* buffer, size_t size, } // check if buffer is initialized -bool is_initialized(ogl_elementbuffer* buffer) { - return buffer->buffer_id != 0; +bool is_initialized(const ogl_elementbuffer* buffer) { + return buffer && buffer->buffer_id != 0; } // clear buffer @@ -685,18 +683,46 @@ bool init_program(ogl_program* program, const string& vertex, return true; } +bool load_program(ogl_program* program, const string& vertex_filename, + const string& fragment_filename) { + auto error = ""s; + auto vertex_source = ""s; + auto fragment_source = ""s; + + if (!load_text(vertex_filename, vertex_source, error)) { + printf("error loading vertex shader (%s): \n%s\n", vertex_filename.c_str(), + error.c_str()); + return false; + } + if (!load_text(fragment_filename, fragment_source, error)) { + printf("error loading fragment shader (%s): \n%s\n", + fragment_filename.c_str(), error.c_str()); + return false; + } + + auto error_buf = ""s; + if (!init_program( + program, vertex_source, fragment_source, error, error_buf)) { + printf("\nerror: %s\n", error.c_str()); + printf(" %s\n", error_buf.c_str()); + return false; + } + return true; +} + // clear program void clear_program(ogl_program* program) { if (program->program_id) glDeleteProgram(program->program_id); if (program->vertex_id) glDeleteShader(program->vertex_id); - if (program->fragment_id) glDeleteProgram(program->fragment_id); + if (program->fragment_id) glDeleteShader(program->fragment_id); program->program_id = 0; program->vertex_id = 0; program->fragment_id = 0; + assert_ogl_error(); } bool is_initialized(const ogl_program* program) { - return program->program_id != 0; + return program && program->program_id != 0; } // bind program @@ -705,8 +731,7 @@ void bind_program(ogl_program* program) { glUseProgram(program->program_id); assert_ogl_error(); } -// unbind program -void unbind_program(ogl_program* program) { glUseProgram(0); } + // unbind program void unbind_program() { glUseProgram(0); } @@ -821,104 +846,106 @@ void main() { #endif // set uniforms -void set_uniform(ogl_program* program, int location, int value) { +void set_uniform(const ogl_program* program, int location, int value) { assert_ogl_error(); glUniform1i(location, value); assert_ogl_error(); } -void set_uniform(ogl_program* program, int location, const vec2i& value) { +void set_uniform(const ogl_program* program, int location, const vec2i& value) { assert_ogl_error(); glUniform2i(location, value.x, value.y); assert_ogl_error(); } -void set_uniform(ogl_program* program, int location, const vec3i& value) { +void set_uniform(const ogl_program* program, int location, const vec3i& value) { assert_ogl_error(); glUniform3i(location, value.x, value.y, value.z); assert_ogl_error(); } -void set_uniform(ogl_program* program, int location, const vec4i& value) { +void set_uniform(const ogl_program* program, int location, const vec4i& value) { assert_ogl_error(); glUniform4i(location, value.x, value.y, value.z, value.w); assert_ogl_error(); } -void set_uniform(ogl_program* program, int location, float value) { +void set_uniform(const ogl_program* program, int location, float value) { assert_ogl_error(); glUniform1f(location, value); assert_ogl_error(); } -void set_uniform(ogl_program* program, int location, const vec2f& value) { +void set_uniform(const ogl_program* program, int location, const vec2f& value) { assert_ogl_error(); glUniform2f(location, value.x, value.y); assert_ogl_error(); } -void set_uniform(ogl_program* program, int location, const vec3f& value) { +void set_uniform(const ogl_program* program, int location, const vec3f& value) { assert_ogl_error(); glUniform3f(location, value.x, value.y, value.z); assert_ogl_error(); } -void set_uniform(ogl_program* program, int location, const vec4f& value) { +void set_uniform(const ogl_program* program, int location, const vec4f& value) { assert_ogl_error(); glUniform4f(location, value.x, value.y, value.z, value.w); assert_ogl_error(); } -void set_uniform(ogl_program* program, int location, const mat2f& value) { +void set_uniform(const ogl_program* program, int location, const mat2f& value) { assert_ogl_error(); glUniformMatrix2fv(location, 1, false, &value.x.x); assert_ogl_error(); } -void set_uniform(ogl_program* program, int location, const mat3f& value) { +void set_uniform(const ogl_program* program, int location, const mat3f& value) { assert_ogl_error(); glUniformMatrix3fv(location, 1, false, &value.x.x); assert_ogl_error(); } -void set_uniform(ogl_program* program, int location, const mat4f& value) { +void set_uniform(const ogl_program* program, int location, const mat4f& value) { assert_ogl_error(); glUniformMatrix4fv(location, 1, false, &value.x.x); assert_ogl_error(); } -void set_uniform(ogl_program* program, int location, const frame2f& value) { +void set_uniform( + const ogl_program* program, int location, const frame2f& value) { assert_ogl_error(); glUniformMatrix3x2fv(location, 1, false, &value.x.x); assert_ogl_error(); } -void set_uniform(ogl_program* program, int location, const frame3f& value) { +void set_uniform( + const ogl_program* program, int location, const frame3f& value) { assert_ogl_error(); glUniformMatrix4x3fv(location, 1, false, &value.x.x); assert_ogl_error(); } // get uniform location -int get_uniform_location(ogl_program* program, const char* name) { +int get_uniform_location(const ogl_program* program, const char* name) { return glGetUniformLocation(program->program_id, name); } // set uniform texture -void set_uniform( - ogl_program* program, int location, const ogl_texture* texture, int unit) { +void set_uniform(const ogl_program* program, int location, + const ogl_texture* texture, int unit) { assert_ogl_error(); glActiveTexture(GL_TEXTURE0 + unit); glBindTexture(GL_TEXTURE_2D, texture->texture_id); glUniform1i(location, unit); assert_ogl_error(); } -void set_uniform(ogl_program* program, const char* name, +void set_uniform(const ogl_program* program, const char* name, const ogl_texture* texture, int unit) { return set_uniform( program, get_uniform_location(program, name), texture, unit); } -void set_uniform(ogl_program* program, int location, int location_on, +void set_uniform(const ogl_program* program, int location, int location_on, const ogl_texture* texture, int unit) { assert_ogl_error(); if (texture && texture->texture_id) { @@ -931,27 +958,27 @@ void set_uniform(ogl_program* program, int location, int location_on, } assert_ogl_error(); } -void set_uniform(ogl_program* program, const char* name, const char* name_on, - const ogl_texture* texture, int unit) { +void set_uniform(const ogl_program* program, const char* name, + const char* name_on, const ogl_texture* texture, int unit) { return set_uniform(program, get_uniform_location(program, name), get_uniform_location(program, name_on), texture, unit); } // set uniform cubemap -void set_uniform( - ogl_program* program, int location, const ogl_cubemap* cubemap, int unit) { +void set_uniform(const ogl_program* program, int location, + const ogl_cubemap* cubemap, int unit) { assert_ogl_error(); glActiveTexture(GL_TEXTURE0 + unit); glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap->cubemap_id); glUniform1i(location, unit); assert_ogl_error(); } -void set_uniform(ogl_program* program, const char* name, +void set_uniform(const ogl_program* program, const char* name, const ogl_cubemap* cubemap, int unit) { return set_uniform( program, get_uniform_location(program, name), cubemap, unit); } -void set_uniform(ogl_program* program, int location, int location_on, +void set_uniform(const ogl_program* program, int location, int location_on, const ogl_cubemap* cubemap, int unit) { assert_ogl_error(); if (cubemap && cubemap->cubemap_id) { @@ -964,8 +991,8 @@ void set_uniform(ogl_program* program, int location, int location_on, } assert_ogl_error(); } -void set_uniform(ogl_program* program, const char* name, const char* name_on, - const ogl_cubemap* cubemap, int unit) { +void set_uniform(const ogl_program* program, const char* name, + const char* name_on, const ogl_cubemap* cubemap, int unit) { return set_uniform(program, get_uniform_location(program, name), get_uniform_location(program, name_on), cubemap, unit); } @@ -1007,19 +1034,6 @@ void set_attribute(ogl_program* program, int location, const vec4f& value) { glVertexAttrib4f(location, value.x, value.y, value.z, value.w); } -// draw elements -void draw_elements(ogl_elementbuffer* buffer) { - static auto elements = unordered_map{ - {ogl_element_type::points, GL_POINTS}, - {ogl_element_type::lines, GL_LINES}, - {ogl_element_type::triangles, GL_TRIANGLES}, - }; - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->buffer_id); - glDrawElements(elements.at(buffer->element), (GLsizei)buffer->size, - GL_UNSIGNED_INT, nullptr); - assert_ogl_error(); -} - void set_framebuffer(ogl_framebuffer* framebuffer, const vec2i& size) { if (!framebuffer->framebuffer_id) { glGenFramebuffers(1, &framebuffer->framebuffer_id); @@ -1105,163 +1119,64 @@ void set_shape(ogl_shape* shape) { // Clear an OpenGL shape void clear_shape(ogl_shape* shape) { - clear_arraybuffer(shape->positions); - clear_arraybuffer(shape->normals); - clear_arraybuffer(shape->texcoords); - clear_arraybuffer(shape->colors); - clear_arraybuffer(shape->tangents); - clear_elementbuffer(shape->points); - clear_elementbuffer(shape->lines); - clear_elementbuffer(shape->triangles); - clear_elementbuffer(shape->quads); - clear_elementbuffer(shape->edges); + for (auto& buffer : shape->vertex_buffers) { + clear_arraybuffer(&buffer); + } + clear_elementbuffer(&shape->index_buffer); glDeleteVertexArrays(1, &shape->shape_id); shape->shape_id = 0; } -ogl_shape::~ogl_shape() { - clear_shape(this); - if (positions) delete positions; - if (normals) delete normals; - if (texcoords) delete texcoords; - if (colors) delete colors; - if (tangents) delete tangents; - if (points) delete points; - if (lines) delete lines; - if (triangles) delete triangles; - if (quads) delete quads; - if (edges) delete edges; -} +ogl_shape::~ogl_shape() { clear_shape(this); } -void set_vertex_attribute(int location, float value) { +void set_vertex_attribute_const(int location, float value) { glVertexAttrib1f(location, value); } -void set_vertex_attribute(int location, const vec2f& value) { +void set_vertex_attribute_const(int location, const vec2f& value) { glVertexAttrib2f(location, value.x, value.y); } -void set_vertex_attribute(int location, const vec3f& value) { +void set_vertex_attribute_const(int location, const vec3f& value) { glVertexAttrib3f(location, value.x, value.y, value.z); } -void set_vertex_attribute(int location, const vec4f& value) { +void set_vertex_attribute_const(int location, const vec4f& value) { glVertexAttrib4f(location, value.x, value.y, value.z, value.w); } void set_vertex_attribute(int location, const ogl_arraybuffer* buffer) { assert_ogl_error(); - assert(buffer->buffer_id); // == 0) return; + assert(buffer->buffer_id); glBindBuffer(GL_ARRAY_BUFFER, buffer->buffer_id); glEnableVertexAttribArray(location); glVertexAttribPointer(location, buffer->esize, GL_FLOAT, false, 0, nullptr); assert_ogl_error(); } -template -void set_vertex_attribute(ogl_shape* shape, ogl_arraybuffer* attribute, - const vector& data, int location) { - assert(!data.empty()); - set_arraybuffer(attribute, data, false); - assert_ogl_error(); - bind_shape(shape); - set_vertex_attribute(location, attribute); - assert_ogl_error(); -} - -template -void set_vertex_attribute(ogl_shape* shape, const T& attribute, int location) { - assert_ogl_error(); - bind_shape(shape); - set_vertex_attribute(location, attribute); - assert_ogl_error(); -} - -void set_positions(ogl_shape* shape, const vector& positions) { - if (positions.empty()) - set_vertex_attribute(shape, vec3f{0, 0, 0}, 0); - else - set_vertex_attribute(shape, shape->positions, positions, 0); -} -void set_normals(ogl_shape* shape, const vector& normals) { - if (normals.empty()) - set_vertex_attribute(shape, vec3f{0, 0, 1}, 1); - else - set_vertex_attribute(shape, shape->normals, normals, 1); -} -void set_texcoords(ogl_shape* shape, const vector& texcoords) { - if (texcoords.empty()) - set_vertex_attribute(shape, vec2f{0, 0}, 2); - else - set_vertex_attribute(shape, shape->texcoords, texcoords, 2); -} -void set_colors(ogl_shape* shape, const vector& colors) { - if (colors.empty()) - set_vertex_attribute(shape, vec4f{1, 1, 1, 1}, 3); - else - set_vertex_attribute(shape, shape->colors, colors, 3); -} -void set_tangents(ogl_shape* shape, const vector& tangents) { - if (tangents.empty()) - set_vertex_attribute(shape, vec4f{0, 0, 1, 1}, 4); - else - set_vertex_attribute(shape, shape->tangents, tangents, 4); -} - -template -void set_index_buffer( - ogl_shape* shape, ogl_elementbuffer* elements, const vector& data) { +void set_instance_buffer(ogl_shape* shape, int location) { bind_shape(shape); - set_arraybuffer(elements, data); + glVertexAttribDivisor(location, 1); + shape->num_instances = (int)shape->vertex_buffers[location].size / + shape->vertex_buffers[location].esize; assert_ogl_error(); } -void set_points(ogl_shape* shape, const vector& points) { - set_elementbuffer(shape->points, points); -} -void set_lines(ogl_shape* shape, const vector& lines) { - set_elementbuffer(shape->lines, lines); -} -void set_triangles(ogl_shape* shape, const vector& triangles) { - set_elementbuffer(shape->triangles, triangles); -} -void set_quads(ogl_shape* shape, const vector& quads) { - auto triangles = vector{}; - triangles.reserve(quads.size() * 2); - for (auto& q : quads) { - triangles.push_back({q.x, q.y, q.w}); - if (q.z != q.w) triangles.push_back({q.z, q.w, q.y}); - } - set_elementbuffer(shape->quads, triangles); -} -void set_edges(ogl_shape* shape, const vector& triangles, - const vector& quads) { - auto edgemap = unordered_set{}; - for (auto t : triangles) { - edgemap.insert({min(t.x, t.y), max(t.x, t.y)}); - edgemap.insert({min(t.y, t.z), max(t.y, t.z)}); - edgemap.insert({min(t.z, t.x), max(t.z, t.x)}); - } - for (auto t : quads) { - edgemap.insert({min(t.x, t.y), max(t.x, t.y)}); - edgemap.insert({min(t.y, t.z), max(t.y, t.z)}); - edgemap.insert({min(t.z, t.w), max(t.z, t.w)}); - edgemap.insert({min(t.w, t.x), max(t.w, t.x)}); - } - auto edges = vector(edgemap.begin(), edgemap.end()); - set_elementbuffer(shape->edges, edges); -} - void draw_shape(const ogl_shape* shape) { bind_shape(shape); - if (is_initialized(shape->points)) { - glPointSize(shape->points_size); - draw_elements(shape->points); - } else if (is_initialized(shape->lines)) { - draw_elements(shape->lines); - } else if (is_initialized(shape->triangles)) { - draw_elements(shape->triangles); - } else if (is_initialized(shape->quads)) { - draw_elements(shape->quads); + static auto elements = unordered_map{ + {ogl_element_type::points, GL_POINTS}, + {ogl_element_type::lines, GL_LINES}, + {ogl_element_type::triangles, GL_TRIANGLES}, + }; + auto& buffer = shape->index_buffer; + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.buffer_id); + if (shape->num_instances == 0) { + glDrawElements(elements.at(buffer.element), (GLsizei)buffer.size, + GL_UNSIGNED_INT, nullptr); + } else { + glDrawElementsInstanced(elements.at(buffer.element), (GLsizei)buffer.size, + GL_UNSIGNED_INT, nullptr, shape->num_instances); } + assert_ogl_error(); } ogl_shape* cube_shape() { @@ -1280,8 +1195,8 @@ ogl_shape* cube_shape() { // clang-format on cube = new ogl_shape{}; set_shape(cube); - set_positions(cube, cube_positions); - set_triangles(cube, cube_triangles); + set_vertex_attribute(cube, cube_positions, 0); + set_index_buffer(cube, cube_triangles); } return cube; } @@ -1299,8 +1214,8 @@ ogl_shape* quad_shape() { // clang-format on quad = new ogl_shape{}; set_shape(quad); - set_positions(quad, quad_positions); - set_triangles(quad, quad_triangles); + set_vertex_attribute(quad, quad_positions, 0); + set_index_buffer(quad, quad_triangles); } return quad; } @@ -1325,8 +1240,8 @@ bool init_image(ogl_image* image) { auto texcoords = vector{{0, 0}, {0, 1}, {1, 1}, {1, 0}}; auto triangles = vector{{0, 1, 2}, {0, 2, 3}}; set_shape(image->quad); - set_vertex_attribute(image->quad, image->quad->texcoords, texcoords, 0); - set_triangles(image->quad, triangles); + set_vertex_attribute(image->quad, texcoords, 0); + set_index_buffer(image->quad, triangles); return true; } @@ -1366,7 +1281,7 @@ void draw_image(ogl_image* image, const ogl_image_params& params) { set_uniform(image->program, "image_center", params.center); set_uniform(image->program, "image_scale", params.scale); draw_shape(image->quad); - unbind_program(image->program); + unbind_program(); assert_ogl_error(); } diff --git a/libs/yocto_gui/yocto_opengl.h b/libs/yocto_gui/yocto_opengl.h index 60bf008ba..7d43a5ecd 100644 --- a/libs/yocto_gui/yocto_opengl.h +++ b/libs/yocto_gui/yocto_opengl.h @@ -75,12 +75,12 @@ void set_ogl_point_size(int size); // OpenGL texture struct ogl_texture { // Texture properties - vec2i size = {0, 0}; - int nchannels = 0; - bool is_srgb = false; - bool is_float = false; - bool linear = false; - bool mipmap = false; + vec2i size = {0, 0}; + int num_channels = 0; + bool is_srgb = false; + bool is_float = false; + bool linear = false; + bool mipmap = false; // OpenGL state uint texture_id = 0; @@ -92,15 +92,15 @@ struct ogl_texture { }; // set texture -void set_texture(ogl_texture* texture, const vec2i& size, int nchannels, +void set_texture(ogl_texture* texture, const vec2i& size, int num_channels, const byte* img, bool as_srgb = false, bool linear = true, bool mipmap = true); -void set_texture(ogl_texture* texture, const vec2i& size, int nchannels, +void set_texture(ogl_texture* texture, const vec2i& size, int num_channels, const float* img, bool as_float = false, bool linear = true, bool mipmap = true); // check if texture is initialized -bool is_initialized(ogl_texture* texture); +bool is_initialized(const ogl_texture* texture); // clear texture void clear_texture(ogl_texture* texture); @@ -121,52 +121,55 @@ void set_texture(ogl_texture* texture, const image& img, struct ogl_cubemap { // Cubemap properties - int size = 0; - int nchannels = 0; - bool is_srgb = false; - bool is_float = false; - bool linear = false; - bool mipmap = false; + int size = 0; + int num_channels = 0; + bool is_srgb = false; + bool is_float = false; + bool linear = false; + bool mipmap = false; // OpenGL state uint cubemap_id = 0; }; // set cubemap -void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, +void set_cubemap(ogl_cubemap* cubemap, int size, int num_channels, const array& img, bool as_srgb = false, bool linear = true, bool mipmap = true); -void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, +void set_cubemap(ogl_cubemap* cubemap, int size, int num_channels, const array& img, bool as_float = false, bool linear = true, bool mipmap = true); template -void set_cubemap(ogl_cubemap* cubemap, int size, int nchannels, +void set_cubemap(ogl_cubemap* cubemap, int size, int num_channels, bool as_float = false, bool linear = true, bool mipmap = true) { auto img = array{0, 0, 0, 0, 0, 0}; - set_cubemap(cubemap, size, nchannels, img, as_float, mipmap); + set_cubemap(cubemap, size, num_channels, img, as_float, mipmap); } // check if cubemap is initialized -bool is_initialized(ogl_cubemap* cubemap); +bool is_initialized(const ogl_cubemap* cubemap); // clear cubemap void clear_cubemap(ogl_cubemap* cubemap); void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, - int nchannels, bool as_srgb = true, bool linear = true, bool mipmap = true); + int num_channels, bool as_srgb = true, bool linear = true, + bool mipmap = true); void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, - int nchannels, bool as_float = false, bool linear = true, + int num_channels, bool as_float = false, bool linear = true, bool mipmap = true); void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, - int nchannels, bool as_srgb = true, bool linear = true, bool mipmap = true); + int num_channels, bool as_srgb = true, bool linear = true, + bool mipmap = true); void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, - int nchannels, bool as_float = false, bool linear = true, + int num_channels, bool as_float = false, bool linear = true, bool mipmap = true); void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, - int nchannels, bool as_srgb = true, bool linear = true, bool mipmap = true); + int num_channels, bool as_srgb = true, bool linear = true, + bool mipmap = true); void set_cubemap(ogl_cubemap* cubemap, const array, 6>& img, - int nchannels, bool as_float = false, bool linear = true, + int num_channels, bool as_float = false, bool linear = true, bool mipmap = true); // Opengl array/element buffer @@ -184,7 +187,7 @@ void set_arraybuffer(ogl_arraybuffer* buffer, size_t size, int esize, const float* data, bool dynamic = false); // check if buffer is initialized -bool is_initialized(ogl_arraybuffer* buffer); +bool is_initialized(const ogl_arraybuffer* buffer); // clear buffer void clear_arraybuffer(ogl_arraybuffer* buffer); @@ -217,7 +220,7 @@ void set_elementbuffer(ogl_elementbuffer* buffer, size_t size, ogl_element_type element, const int* data, bool dynamic = false); // check if buffer is initialized -bool is_initialized(ogl_elementbuffer* buffer); +bool is_initialized(const ogl_elementbuffer* buffer); // clear buffer void clear_elementbuffer(ogl_elementbuffer* buffer); @@ -244,6 +247,8 @@ struct ogl_program { // initialize program bool init_program(ogl_program* program, const string& vertex, const string& fragment, string& error, string& errorlog); +bool load_program(ogl_program* program, const string& vertex_filename, + const string& fragment_filename); bool is_initialized(const ogl_program* program); // clear program @@ -251,92 +256,55 @@ void clear_program(ogl_program* program); // bind program void bind_program(ogl_program* program); -// unbind program -void unbind_program(ogl_program* program); + // unbind program void unbind_program(); // get uniform location -int get_uniform_location(ogl_program* program, const char* name); +int get_uniform_location(const ogl_program* program, const char* name); // set uniforms -void set_uniform(ogl_program* program, int location, int value); -void set_uniform(ogl_program* program, int location, const vec2i& value); -void set_uniform(ogl_program* program, int location, const vec3i& value); -void set_uniform(ogl_program* program, int location, const vec4i& value); -void set_uniform(ogl_program* program, int location, float value); -void set_uniform(ogl_program* program, int location, const vec2f& value); -void set_uniform(ogl_program* program, int location, const vec3f& value); -void set_uniform(ogl_program* program, int location, const vec4f& value); -void set_uniform(ogl_program* program, int location, const mat2f& value); -void set_uniform(ogl_program* program, int location, const mat3f& value); -void set_uniform(ogl_program* program, int location, const mat4f& value); -void set_uniform(ogl_program* program, int location, const frame2f& value); -void set_uniform(ogl_program* program, int location, const frame3f& value); +void set_uniform(const ogl_program* program, int location, int value); +void set_uniform(const ogl_program* program, int location, const vec2i& value); +void set_uniform(const ogl_program* program, int location, const vec3i& value); +void set_uniform(const ogl_program* program, int location, const vec4i& value); +void set_uniform(const ogl_program* program, int location, float value); +void set_uniform(const ogl_program* program, int location, const vec2f& value); +void set_uniform(const ogl_program* program, int location, const vec3f& value); +void set_uniform(const ogl_program* program, int location, const vec4f& value); +void set_uniform(const ogl_program* program, int location, const mat2f& value); +void set_uniform(const ogl_program* program, int location, const mat3f& value); +void set_uniform(const ogl_program* program, int location, const mat4f& value); +void set_uniform( + const ogl_program* program, int location, const frame2f& value); +void set_uniform( + const ogl_program* program, int location, const frame3f& value); + template inline void set_uniform( - ogl_program* program, const char* name, const T& value) { + const ogl_program* program, const char* name, const T& value) { return set_uniform(program, get_uniform_location(program, name), value); } // set uniform texture -void set_uniform( - ogl_program* program, int location, const ogl_texture* texture, int unit); -void set_uniform(ogl_program* program, const char* name, +void set_uniform(const ogl_program* program, int location, const ogl_texture* texture, int unit); -void set_uniform(ogl_program* program, int location, int location_on, +void set_uniform(const ogl_program* program, const char* name, const ogl_texture* texture, int unit); -void set_uniform(ogl_program* program, const char* name, const char* name_on, +void set_uniform(const ogl_program* program, int location, int location_on, const ogl_texture* texture, int unit); +void set_uniform(const ogl_program* program, const char* name, + const char* name_on, const ogl_texture* texture, int unit); // set uniform cubemap -void set_uniform( - ogl_program* program, int location, const ogl_cubemap* cubemap, int unit); -void set_uniform(ogl_program* program, const char* name, +void set_uniform(const ogl_program* program, int location, + const ogl_cubemap* cubemap, int unit); +void set_uniform(const ogl_program* program, const char* name, const ogl_cubemap* cubemap, int unit); void set_uniform(ogl_program* program, int location, int location_on, const ogl_cubemap* cubemap, int unit); void set_uniform(ogl_program* program, const char* name, const char* name_on, const ogl_cubemap* cubemap, int unit); - -// get attribute location -int get_attribute_location(ogl_program* program, const char* name); - -// set vertex attributes -void set_attribute(ogl_program* program, int location, ogl_arraybuffer* buffer); -void set_attribute( - ogl_program* program, const char* name, ogl_arraybuffer* buffer); - -// set vertex attributes -void set_attribute(ogl_program* program, int location, float value); -void set_attribute(ogl_program* program, int location, const vec2f& value); -void set_attribute(ogl_program* program, int location, const vec3f& value); -void set_attribute(ogl_program* program, int location, const vec4f& value); -template -inline void set_attribute( - ogl_program* program, const char* name, const T& value) { - return set_attribute(program, get_attribute_location(program, name), value); -} - -// set vertex attributes -template -inline void set_attribute(ogl_program* program, int location, - ogl_arraybuffer* buffer, const T& value) { - if (buffer && is_initialized(buffer)) { - return set_attribute(program, location, buffer); - } else { - set_attribute(program, location, value); - } -} -template -inline void set_attribute(ogl_program* program, const char* name, - ogl_arraybuffer* buffer, const T& def) { - set_attribute(program, get_attribute_location(program, name), buffer, def); -} - -// draw elements -void draw_elements(ogl_elementbuffer* buffer); - } // namespace yocto // ----------------------------------------------------------------------------- @@ -350,8 +318,6 @@ struct ogl_framebuffer { uint renderbuffer_id = 0; static inline uint bound_framebuffer_id = 0; - // TODO(giacomo): Maybe the following is better - // static inline ogl_framebuffer* bound_framebuffer = nullptr; }; void set_framebuffer(ogl_framebuffer* framebuffer, const vec2i& size); @@ -373,21 +339,11 @@ void clear_framebuffer(ogl_framebuffer* target); namespace yocto { // Opengl shape struct ogl_shape { - // vertex buffers - ogl_arraybuffer* positions = new ogl_arraybuffer{}; - ogl_arraybuffer* normals = new ogl_arraybuffer{}; - ogl_arraybuffer* texcoords = new ogl_arraybuffer{}; - ogl_arraybuffer* colors = new ogl_arraybuffer{}; - ogl_arraybuffer* tangents = new ogl_arraybuffer{}; - ogl_elementbuffer* points = new ogl_elementbuffer{}; - ogl_elementbuffer* lines = new ogl_elementbuffer{}; - ogl_elementbuffer* triangles = new ogl_elementbuffer{}; - ogl_elementbuffer* quads = new ogl_elementbuffer{}; - ogl_elementbuffer* edges = new ogl_elementbuffer{}; - float points_size = 10; - float line_thickness = 4; - - uint shape_id = 0; + uint shape_id = 0; + vector vertex_buffers = {}; + ogl_elementbuffer index_buffer = {}; + + int num_instances = 0; ogl_shape() {} ogl_shape(const ogl_shape&) = delete; @@ -399,18 +355,39 @@ void set_shape(ogl_shape* shape); void clear_shape(ogl_shape* shape); void bind_shape(const ogl_shape* shape); -// shape properties -void set_points(ogl_shape* shape, const vector& points); -void set_lines(ogl_shape* shape, const vector& lines); -void set_triangles(ogl_shape* shape, const vector& triangles); -void set_quads(ogl_shape* shape, const vector& quads); -void set_edges(ogl_shape* shape, const vector& triangles, - const vector& quads); -void set_positions(ogl_shape* shape, const vector& positions); -void set_normals(ogl_shape* shape, const vector& normals); -void set_texcoords(ogl_shape* shape, const vector& texcoords); -void set_colors(ogl_shape* shape, const vector& colors); -void set_tangents(ogl_shape* shape, const vector& tangents); +void set_vertex_attribute_const(int location, float value); +void set_vertex_attribute_const(int location, const vec2f& value); +void set_vertex_attribute_const(int location, const vec3f& value); +void set_vertex_attribute_const(int location, const vec4f& value); +void set_vertex_attribute(int location, const ogl_arraybuffer* buffer); + +template +void set_vertex_attribute( + ogl_shape* shape, const vector& data, int location) { + if (shape->vertex_buffers.size() <= location) { + shape->vertex_buffers.resize(location + 1); + } + set_arraybuffer(&shape->vertex_buffers[location], data, false); + assert_ogl_error(); + bind_shape(shape); + set_vertex_attribute(location, &shape->vertex_buffers[location]); + assert_ogl_error(); +} + +template +void set_vertex_attribute(ogl_shape* shape, const T& attribute, int location) { + assert_ogl_error(); + bind_shape(shape); + set_vertex_attribute_const(location, attribute); + assert_ogl_error(); +} + +void set_instance_buffer(ogl_shape* shape, int location); + +template +void set_index_buffer(ogl_shape* shape, const vector& primitives) { + set_elementbuffer(&shape->index_buffer, primitives); +} void draw_shape(const ogl_shape* shape); From 000124302ad878eea626f502e0fef8ca0f493346 Mon Sep 17 00:00:00 2001 From: Giacomo Nazzaro Date: Sun, 23 Aug 2020 00:22:27 +0200 Subject: [PATCH 3/3] update --- apps/yshapeview/yshapeview.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/yshapeview/yshapeview.cpp b/apps/yshapeview/yshapeview.cpp index 18f500e45..79fecee86 100644 --- a/apps/yshapeview/yshapeview.cpp +++ b/apps/yshapeview/yshapeview.cpp @@ -201,6 +201,9 @@ void init_glscene(app_state* app, gui_scene* glscene, generic_shape* ioshape, auto model_shape = add_shape(glscene, ioshape->points, ioshape->lines, ioshape->triangles, ioshape->quads, ioshape->positions, ioshape->normals, ioshape->texcoords, ioshape->colors, true); + if (!is_initialized(get_normals(model_shape))) { + app->drawgl_prms.faceted = true; + } set_vertex_attribute(model_shape, vec3f{0, 0, 0}, 5); set_vertex_attribute(model_shape, vec3f{0, 0, 0}, 6); @@ -305,12 +308,8 @@ void draw_widgets(gui_window* win, app_states* apps, const gui_input& input) { if (!apps->selected->ok) return; auto app = apps->selected; if (begin_header(win, "view")) { - auto glshape = app->glscene->shapes.front(); auto glmaterial = app->glscene->materials.front(); auto& params = app->drawgl_prms; - if (!is_initialized(get_normals(glshape))) { - params.faceted = true; - } draw_checkbox(win, "faceted", params.faceted); continue_line(win); draw_checkbox(win, "lines", app->glscene->instances[1]->hidden, true);