diff --git a/apps/ysceneview/ysceneview.cpp b/apps/ysceneview/ysceneview.cpp index 9a245c182..87f5bcb16 100644 --- a/apps/ysceneview/ysceneview.cpp +++ b/apps/ysceneview/ysceneview.cpp @@ -32,8 +32,8 @@ #include #include #include +#include #include -#include using namespace yocto; #include @@ -56,15 +56,15 @@ struct app_state { string name = ""; // options - ogl_scene_params drawgl_prms = {}; + gui_scene_params drawgl_prms = {}; // scene sceneio_scene* ioscene = new sceneio_scene{}; sceneio_camera* iocamera = nullptr; // rendering state - ogl_scene* glscene = new ogl_scene{}; - ogl_camera* glcamera = nullptr; + gui_scene* glscene = new gui_scene{}; + gui_camera* glcamera = nullptr; // editing sceneio_camera* selected_camera = nullptr; @@ -97,7 +97,7 @@ struct app_states { std::deque loading = {}; // default options - ogl_scene_params drawgl_prms = {}; + gui_scene_params drawgl_prms = {}; // cleanup ~app_states() { @@ -129,7 +129,7 @@ void load_scene_async( if (!apps->selected) apps->selected = app; } -void update_lights(ogl_scene* glscene, sceneio_scene* ioscene) { +void update_lights(gui_scene* glscene, sceneio_scene* ioscene) { clear_lights(glscene); for (auto ioobject : ioscene->instances) { if (has_max_lights(glscene)) break; @@ -159,8 +159,8 @@ void update_lights(ogl_scene* glscene, sceneio_scene* ioscene) { } } -void init_glscene(ogl_scene* glscene, sceneio_scene* ioscene, - ogl_camera*& glcamera, sceneio_camera* iocamera, +void init_glscene(gui_scene* glscene, sceneio_scene* ioscene, + gui_camera*& glcamera, sceneio_camera* iocamera, progress_callback progress_cb) { // handle progress auto progress = vec2i{ @@ -172,7 +172,7 @@ void init_glscene(ogl_scene* glscene, sceneio_scene* ioscene, init_scene(glscene); // camera - auto camera_map = unordered_map{}; + auto camera_map = unordered_map{}; camera_map[nullptr] = nullptr; for (auto iocamera : ioscene->cameras) { if (progress_cb) progress_cb("convert camera", progress.x++, progress.y); @@ -198,7 +198,7 @@ void init_glscene(ogl_scene* glscene, sceneio_scene* ioscene, } // material - auto material_map = unordered_map{}; + auto material_map = unordered_map{}; material_map[nullptr] = nullptr; for (auto iomaterial : ioscene->materials) { if (progress_cb) progress_cb("convert material", progress.x++, progress.y); @@ -248,6 +248,12 @@ void init_glscene(ogl_scene* glscene, sceneio_scene* ioscene, set_material(globject, material_map.at(ioinstance->material)); } + // bake prefiltered environments + if (ioscene->environments.size()) { + ibl::init_ibl_data( + glscene, texture_map[ioscene->environments[0]->emission_tex]); + } + // done if (progress_cb) progress_cb("convert done", progress.x++, progress.y); @@ -450,7 +456,7 @@ void draw_widgets(gui_window* win, app_states* apps, const gui_input& input) { } auto& params = app->drawgl_prms; draw_slider(win, "resolution", params.resolution, 0, 4096); - draw_combobox(win, "shading", (int&)params.shading, ogl_shading_names); + 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); @@ -583,7 +589,7 @@ 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 == ogl_shading_type::lights) + 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); @@ -631,7 +637,7 @@ int main(int argc, const char* argv[]) { add_option(cli, "--resolution,-r", apps->drawgl_prms.resolution, "Image resolution."); add_option(cli, "--shading", apps->drawgl_prms.shading, "Shading type.", - ogl_shading_names); + gui_shading_names); add_option(cli, "scenes", filenames, "Scene filenames", true); parse_cli(cli, argc, argv); diff --git a/apps/ysceneviews/ysceneviews.cpp b/apps/ysceneviews/ysceneviews.cpp index 21f801c00..116aeb6e7 100644 --- a/apps/ysceneviews/ysceneviews.cpp +++ b/apps/ysceneviews/ysceneviews.cpp @@ -32,8 +32,8 @@ #include #include #include +#include #include -#include using namespace yocto; #include @@ -57,15 +57,15 @@ struct app_state { string name = ""; // options - ogl_scene_params drawgl_prms = {}; + gui_scene_params drawgl_prms = {}; // scene sceneio_scene* ioscene = new sceneio_scene{}; sceneio_camera* iocamera = nullptr; // rendering state - ogl_scene* glscene = new ogl_scene{}; - ogl_camera* glcamera = nullptr; + gui_scene* glscene = new gui_scene{}; + gui_camera* glcamera = nullptr; // editing sceneio_camera* selected_camera = nullptr; @@ -90,7 +90,7 @@ struct app_state { } }; -void update_lights(ogl_scene* glscene, sceneio_scene* ioscene) { +void update_lights(gui_scene* glscene, sceneio_scene* ioscene) { clear_lights(glscene); for (auto ioobject : ioscene->instances) { if (has_max_lights(glscene)) break; @@ -120,8 +120,8 @@ void update_lights(ogl_scene* glscene, sceneio_scene* ioscene) { } } -void init_glscene(ogl_scene* glscene, sceneio_scene* ioscene, - ogl_camera*& glcamera, sceneio_camera* iocamera, +void init_glscene(gui_scene* glscene, sceneio_scene* ioscene, + gui_camera*& glcamera, sceneio_camera* iocamera, progress_callback progress_cb) { // handle progress auto progress = vec2i{ @@ -133,7 +133,7 @@ void init_glscene(ogl_scene* glscene, sceneio_scene* ioscene, init_scene(glscene); // camera - auto camera_map = unordered_map{}; + auto camera_map = unordered_map{}; camera_map[nullptr] = nullptr; for (auto iocamera : ioscene->cameras) { if (progress_cb) progress_cb("convert camera", progress.x++, progress.y); @@ -159,7 +159,7 @@ void init_glscene(ogl_scene* glscene, sceneio_scene* ioscene, } // material - auto material_map = unordered_map{}; + auto material_map = unordered_map{}; material_map[nullptr] = nullptr; for (auto iomaterial : ioscene->materials) { if (progress_cb) progress_cb("convert material", progress.x++, progress.y); @@ -209,6 +209,13 @@ void init_glscene(ogl_scene* glscene, sceneio_scene* ioscene, set_material(globject, material_map.at(ioobject->material)); } + // 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]); + } + // done if (progress_cb) progress_cb("convert done", progress.x++, progress.y); @@ -228,7 +235,7 @@ int main(int argc, const char* argv[]) { add_option( cli, "--resolution,-r", app->drawgl_prms.resolution, "Image resolution."); add_option(cli, "--shading", app->drawgl_prms.shading, "Eyelight rendering.", - ogl_shading_names); + gui_shading_names); add_option(cli, "scene", app->filename, "Scene filename", true); parse_cli(cli, argc, argv); @@ -257,7 +264,7 @@ 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 == ogl_shading_type::lights) + 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); @@ -273,7 +280,7 @@ 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, ogl_shading_names); + draw_combobox(win, "shading", (int&)params.shading, gui_shading_names); continue_line(win); draw_checkbox(win, "edges", params.edges); continue_line(win); diff --git a/apps/yshapeview/yshapeview.cpp b/apps/yshapeview/yshapeview.cpp index 355c36112..6570f46bd 100644 --- a/apps/yshapeview/yshapeview.cpp +++ b/apps/yshapeview/yshapeview.cpp @@ -33,7 +33,7 @@ #include #include #include -#include +#include using namespace yocto; #include @@ -56,14 +56,14 @@ struct app_state { string name = ""; // options - ogl_scene_params drawgl_prms = {}; + gui_scene_params drawgl_prms = {}; // scene generic_shape* ioshape = new generic_shape{}; // rendering state - ogl_scene* glscene = new ogl_scene{}; - ogl_camera* glcamera = nullptr; + gui_scene* glscene = new gui_scene{}; + gui_camera* glcamera = nullptr; // loading status std::atomic ok = false; @@ -88,7 +88,7 @@ struct app_states { std::deque loading = {}; // default options - ogl_scene_params drawgl_prms = {}; + gui_scene_params drawgl_prms = {}; // cleanup ~app_states() { @@ -170,7 +170,7 @@ quads_shape make_cylinders(const vector& lines, return shape; } -void init_glscene(ogl_scene* glscene, const generic_shape* ioshape, +void init_glscene(gui_scene* glscene, const generic_shape* ioshape, progress_callback progress_cb) { // handle progress auto progress = vec2i{0, 4}; @@ -280,7 +280,7 @@ void draw_widgets(gui_window* win, app_states* apps, const gui_input& input) { 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, ogl_shading_names); + 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); @@ -364,7 +364,7 @@ int main(int argc, const char* argv[]) { add_option(cli, "--resolution,-r", apps->drawgl_prms.resolution, "Image resolution."); add_option(cli, "--shading", apps->drawgl_prms.shading, "Shading type.", - ogl_shading_names); + gui_shading_names); add_option(cli, "shapes", filenames, "Shape filenames", true); parse_cli(cli, argc, argv); diff --git a/libs/yocto_gui/CMakeLists.txt b/libs/yocto_gui/CMakeLists.txt index 69c643c3a..b358d6be3 100644 --- a/libs/yocto_gui/CMakeLists.txt +++ b/libs/yocto_gui/CMakeLists.txt @@ -10,6 +10,8 @@ if(YOCTO_OPENGL) add_library(yocto_gui yocto_opengl.h yocto_opengl.cpp + yocto_draw.h + yocto_draw.cpp yocto_imgui.h yocto_imgui.cpp ext/imgui/imgui.cpp diff --git a/libs/yocto_gui/yocto_draw.cpp b/libs/yocto_gui/yocto_draw.cpp new file mode 100644 index 000000000..81e28d86c --- /dev/null +++ b/libs/yocto_gui/yocto_draw.cpp @@ -0,0 +1,1364 @@ +// +// Utilities for real-time rendering of a scene. +// + +// +// LICENSE: +// +// Copyright (c) 2016 -- 2020 Fabio Pellacini +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +#include "yocto_draw.h" + +#include + +#include +#include + +#include "ext/glad/glad.h" + +#ifdef _WIN32 +#undef near +#undef far +#endif + +// ----------------------------------------------------------------------------- +// USING DIRECTIVES +// ----------------------------------------------------------------------------- +namespace yocto { + +// using directives +using std::unordered_map; +using std::unordered_set; +using namespace std::string_literals; + +} // namespace yocto + +namespace yocto { + +#ifndef _WIN32 +#pragma GCC diagnostic push +#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; + + // 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; + + // clip + gl_Position = projection * view * vec4(position,1); +} +)"; + +static const char* glscene_fragment = + R"( +#version 330 + +float pif = 3.14159265; + +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]); + } +} + +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; + } +} + +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; +} + +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 ); +} + +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); +} + +// 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; + } + + // emission + vec3 c = brdf_ke; + + // 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); + } + } + } + + // final color correction + c = pow(c * pow(2,exposure), vec3(1/gamma)); + + // 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); + } + + // output final color by setting gl_FragColor + frag_color = vec4(c,brdf_op); +} +)"; + +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; + +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 + +// baked textures for image based lighting +uniform samplerCube irradiance_cubemap; +uniform samplerCube reflection_cubemap; +uniform sampler2D brdf_lut; + +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 + +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; + +struct brdf_struct { + vec3 emission; + vec3 diffuse; + vec3 specular; + float roughness; + float opacity; +} brdf; + +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; +} + +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; +} + +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); +} + +vec3 triangle_normal(vec3 position) { + vec3 fdx = dFdx(position); + vec3 fdy = dFdy(position); + return normalize((frame * vec4(normalize(cross(fdx, fdy)), 0)).xyz); +} + +#define etype_points 1 +#define etype_lines 2 +#define etype_triangles 3 +#define etype_quads 3 + +vec3 compute_normal(vec3 V) { + vec3 N; + if (etype == etype_triangles) { + if (faceted) { + N = triangle_normal(position); + } else { + N = normalize(normal); + } + } + + 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)); + } + + // 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; +} + +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; +} + +// main +void main() { + 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); +} +)"; + +static const char* bake_brdf_vertex = 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); +} +)"; + +static const char* bake_brdf_fragment = 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 +} + +vec2 hammersley(uint i, uint N) { + return vec2(float(i) / float(N), radical_inverse(i)); +} + +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; +} + +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; +} + +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); +} + +vec2 integrate_brdf(float NdotV, float roughness) { + vec3 V; + V.x = sqrt(1.0 - NdotV * NdotV); + V.y = 0.0; + V.z = NdotV; + + float A = 0.0; + float B = 0.0; + + vec3 N = vec3(0.0, 0.0, 1.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; + } + } + 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); +} +)"; + +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 + +out vec3 position; // vertex position (in world coordinate) +out vec2 texcoord; // vertex texture coordinates + +// 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); +} +)"; + +static const char* bake_environment_frag = R"( +#version 330 + +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 sampler2D environment; + +const float PI = 3.14159265359; + +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; +} + +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); +} +)"; + +static const char* bake_diffuse_frag = R"( +#version 330 + +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 samplerCube environment; + +const float pif = 3.14159265359; + +vec3 direction(float phi, float theta) { + return vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); +} + +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); + + vec3 irradiance = vec3(0.0); + + 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); + } + } + irradiance *= pif / (phi_samples * theta_samples); + frag_color = irradiance; +} +)"; + +static const char* bake_specular_frag = R"( +#version 330 + +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 int mipmap_level; +uniform int num_samples = 1024; + +uniform samplerCube environment; + +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 +} + +vec2 hammersley(uint i, int N) { + return vec2(float(i) / float(N), radical_inverse(i)); +} + +vec3 sample_ggx(vec2 rn, vec3 N, float roughness) { + float a = roughness * roughness; + + 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); + + // 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 main() { + vec3 N = normalize(position); + N.z *= -1; + N.y *= -1; + vec3 R = N; + vec3 V = N; + + float roughness = float(mipmap_level) / 5.0; + roughness *= roughness; + + 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; + + frag_color = vec3(result); +} +)"; + +static const char* environment_fragment = R"( +#version 330 + +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; +} +)"; + +#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; +} + +// 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); +} + +// 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); +} + +// 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; +} + +// add texture +ogl_texture* add_texture(gui_scene* scene) { + return scene->textures.emplace_back(new ogl_texture{}); +} + +// add shape +ogl_shape* add_shape(gui_scene* scene) { + auto shape = new ogl_shape{}; + set_shape(shape); + scene->shapes.push_back(shape); + return shape; +} + +// 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; +} + +// 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; +} +void set_normalmap(gui_material* material, ogl_texture* normal_tex) { + material->normal_tex = normal_tex; +} + +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); + } + return shape; +} + +// 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, + 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; +} + +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; +} + +// add light +gui_light* add_light(gui_scene* scene) { + return scene->lights.emplace_back(new gui_light{}); +} +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; +} +void clear_lights(gui_scene* scene) { + for (auto light : scene->lights) delete light; + scene->lights.clear(); +} +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); +} + +// Draw a shape +void draw_object( + gui_scene* scene, gui_instance* instance, const gui_scene_params& params) { + static auto empty_instances = vector{identity3x4f}; + + if (instance->hidden) return; + + assert_ogl_error(); + auto program = (params.shading == gui_shading_type::environment) + ? scene->ibl_program + : scene->lights_program; + + 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}); + } + assert_ogl_error(); + + 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); + + 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(); + + auto shape = instance->shape; + + 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); + + assert_ogl_error(); + + 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); + set_uniform(program, "etype", 3); + draw_shape(shape); + assert_ogl_error(); + } +} + +// 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); + + assert_ogl_error(); + clear_ogl_framebuffer(params.background); + set_ogl_viewport(viewport); + + assert_ogl_error(); + auto program = (params.shading == gui_shading_type::environment) + ? scene->ibl_program + : scene->lights_program; + + 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(); + + 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(); + } + + if (params.wireframe) set_ogl_wireframe(true); + for (auto instance : scene->instances) { + draw_object(scene, instance, params); + } + unbind_program(); + + if (!scene->environment_program->program_id) return; + + 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); + + set_uniform( + scene->environment_program, "environment", scene->environment_cubemap, 0); + set_uniform(scene->environment_program, "roughness", 0.0f); + + auto cube = cube_shape(); + draw_shape(cube); + + unbind_program(); +} + +// 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(); + + auto framebuffer = ogl_framebuffer{}; + set_framebuffer(&framebuffer, {size, size}); + + // 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 + + 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}); + + 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])); + + set_framebuffer_texture(&framebuffer, cubemap, i, mipmap_level); + clear_ogl_framebuffer({0, 0, 0, 0}, true); + + 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); + + draw_shape(cube); + } + size /= 2; + } + unbind_program(); + unbind_framebuffer(); +} + +inline void bake_specular_brdf_texture(ogl_texture* texture) { + auto size = 512; + auto framebuffer = ogl_framebuffer{}; + auto screen_quad = quad_shape(); + + auto program = ogl_program{}; + auto error = ""s, errorlog = ""s; + init_program(&program, bake_brdf_vertex, bake_brdf_fragment, error, errorlog); + + assert_ogl_error(); + + texture->is_float = true; + texture->linear = true; + texture->nchannels = 3; + texture->size = {size, size}; + glGenTextures(1, &texture->texture_id); + + 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? + + assert_ogl_error(); + + set_framebuffer(&framebuffer, {size, size}); + set_framebuffer_texture(&framebuffer, texture, 0); + + bind_framebuffer(&framebuffer); + bind_program(&program); + + set_ogl_viewport(vec2i{size, size}); + clear_ogl_framebuffer({0, 0, 0, 0}, true); + + draw_shape(screen_quad); + assert_ogl_error(); + + unbind_program(); + unbind_framebuffer(); + clear_framebuffer(&framebuffer); + clear_program(&program); +} + +void init_ibl_data(gui_scene* scene, const ogl_texture* environment_texture) { + auto load_program = [scene](ogl_program* program, const char* vertex, + const char* fragment) { + auto error = ""s, errorlog = ""s; + init_program(program, vertex, fragment, error, errorlog); + }; + + load_program(scene->ibl_program, glscene_vertex, glibl_fragment); + load_program( + scene->environment_program, cubemap_vertex, environment_fragment); + + // 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; + } + + // 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; + } + + // 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; + } + + bake_specular_brdf_texture(scene->brdf_lut); +} +} // namespace ibl + +} // namespace yocto diff --git a/libs/yocto_gui/yocto_draw.h b/libs/yocto_gui/yocto_draw.h new file mode 100644 index 000000000..32001a782 --- /dev/null +++ b/libs/yocto_gui/yocto_draw.h @@ -0,0 +1,245 @@ +// +// Yocto/Draw: Utilities for real-time reandering of a scene. +// + +// +// LICENSE: +// +// Copyright (c) 2016 -- 2020 Fabio Pellacini +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +#ifndef _YOCTO_DRAW_ +#define _YOCTO_DRAW_ + +// ----------------------------------------------------------------------------- +// INCLUDES +// ----------------------------------------------------------------------------- + +#include "yocto_opengl.h" + +// forward declaration +struct GLFWwindow; + +// ----------------------------------------------------------------------------- +// USING DIRECTIVES +// ----------------------------------------------------------------------------- +namespace yocto { + +// using directives +using std::string; +using std::vector; + +} // namespace yocto + +namespace yocto { + +// Opengl caemra +struct gui_camera { + frame3f frame = identity3x4f; + float lens = 0.050; + float aspect = 1.000; + float film = 0.036; + float near = 0.001; + float far = 10000; + float aperture = 0; + float focus = 0; +}; + +// Opengl material +struct gui_material { + // material + vec3f emission = {0, 0, 0}; + vec3f color = {0, 0, 0}; + float metallic = 0; + 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; +}; + +// Opengl instance +struct gui_instance { + // instance properties + frame3f frame = identity3x4f; + ogl_shape* shape = nullptr; + gui_material* material = nullptr; + bool hidden = false; + bool highlighted = false; +}; + +// 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() {} + gui_scene(const gui_scene&) = delete; + gui_scene& operator=(const gui_scene&) = delete; + ~gui_scene(); + + // scene objects + vector cameras = {}; + vector instances = {}; + vector shapes = {}; + vector materials = {}; + vector textures = {}; + vector lights = {}; + + // OpenGL state + ogl_program* lights_program = new ogl_program{}; + ogl_program* ibl_program = new ogl_program{}; + ogl_program* environment_program = new ogl_program{}; + + // IBL data + 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{}; +}; + +// Shading type +enum struct gui_shading_type { + environment, + eyelight, + // scene_lights +}; + +// Shading name +const auto gui_shading_names = vector{"environment", "camera_lights"}; + +// Draw options +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 double_sided = true; + bool non_rigid_frames = true; + float near = 0.01f; + float far = 10000.0f; + vec4f background = vec4f{0.15f, 0.15f, 0.15f, 1.0f}; +}; + +// 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 + +// 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_material* add_material(gui_scene* scene); +ogl_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); +void set_lens(gui_camera* camera, float lens, float aspect, float film); +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); +void set_color(gui_material* material, const vec3f& color, + ogl_texture* color_tex = nullptr); +void set_metallic(gui_material* material, float metallic, + ogl_texture* metallic_tex = nullptr); +void set_roughness(gui_material* material, float roughness, + ogl_texture* roughness_tex = nullptr); +void set_specular(gui_material* material, float specular, + ogl_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); + +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); + +// instance properties +void set_frame(gui_instance* instance, const frame3f& frame); +void set_shape(gui_instance* instance, ogl_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); + +// shortcuts +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_instance* add_instance(gui_scene* scene, const frame3f& frame, + ogl_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); + +// light size +void clear_lights(gui_scene* scene); +bool has_max_lights(gui_scene* scene); + +// Draw an OpenGL scene +void draw_scene(gui_scene* scene, gui_camera* camera, const vec4i& viewport, + const gui_scene_params& params); + +} // namespace yocto + +#endif diff --git a/libs/yocto_gui/yocto_opengl.cpp b/libs/yocto_gui/yocto_opengl.cpp index 8d0ba444f..94f56cc37 100644 --- a/libs/yocto_gui/yocto_opengl.cpp +++ b/libs/yocto_gui/yocto_opengl.cpp @@ -29,6 +29,8 @@ #include "yocto_opengl.h" +#include + #include #include #include @@ -69,7 +71,26 @@ bool init_ogl(string& error) { return true; } -void assert_ogl_error() { assert(glGetError() == GL_NO_ERROR); } +GLenum _assert_ogl_error() { + auto error_code = glGetError(); + if (error_code != GL_NO_ERROR) { + auto error = ""s; + switch (error_code) { + case GL_INVALID_ENUM: error = "INVALID_ENUM"; break; + case GL_INVALID_VALUE: error = "INVALID_VALUE"; break; + case GL_INVALID_OPERATION: error = "INVALID_OPERATION"; break; + // case GL_STACK_OVERFLOW: error = "STACK_OVERFLOW"; break; + // case GL_STACK_UNDERFLOW: error = "STACK_UNDERFLOW"; break; + case GL_OUT_OF_MEMORY: error = "OUT_OF_MEMORY"; break; + case GL_INVALID_FRAMEBUFFER_OPERATION: + error = "INVALID_FRAMEBUFFER_OPERATION"; + break; + } + printf("\n OPENGL ERROR: %s\n\n", error.c_str()); + } + return error_code; +} +void assert_ogl_error() { assert(_assert_ogl_error() == GL_NO_ERROR); } bool check_ogl_error(string& error) { if (glGetError() != GL_NO_ERROR) { @@ -604,12 +625,6 @@ bool init_program(ogl_program* program, const string& vertex, program->vertex_code = vertex; program->fragment_code = fragment; - // create arrays - assert_ogl_error(); - glGenVertexArrays(1, &program->array_id); - glBindVertexArray(program->array_id); - assert_ogl_error(); - const char* ccvertex = vertex.data(); const char* ccfragment = fragment.data(); int errflags; @@ -673,11 +688,9 @@ 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->array_id) glDeleteVertexArrays(1, &program->array_id); program->program_id = 0; program->vertex_id = 0; program->fragment_id = 0; - program->array_id = 0; } bool is_initialized(const ogl_program* program) { @@ -805,75 +818,6 @@ void main() { )"; #endif -ogl_image::~ogl_image() { - if (program) delete program; - if (texcoords) delete texcoords; - if (triangles) delete triangles; -} - -bool is_initialized(const ogl_image* image) { - return is_initialized(image->program); -} - -// init image program -bool init_image(ogl_image* image) { - if (is_initialized(image)) return true; - - auto texcoords = vector{{0, 0}, {0, 1}, {1, 1}, {1, 0}}; - auto triangles = vector{{0, 1, 2}, {0, 2, 3}}; - - auto error = ""s, errorlog = ""s; - if (!init_program( - image->program, glimage_vertex, glimage_fragment, error, errorlog)) - return false; - set_arraybuffer( - image->texcoords, texcoords.size() * 2, 2, (float*)texcoords.data()); - set_elementbuffer(image->triangles, triangles.size() * 3, - ogl_element_type::triangles, (int*)triangles.data()); - return true; -} - -// clear an opengl image -void clear_image(ogl_image* image) { - clear_program(image->program); - clear_texture(image->texture); - clear_arraybuffer(image->texcoords); - clear_elementbuffer(image->triangles); -} - -// update image data -void set_image( - ogl_image* oimg, const image& img, bool linear, bool mipmap) { - set_texture(oimg->texture, img, false, linear, mipmap); -} -void set_image( - ogl_image* oimg, const image& img, bool linear, bool mipmap) { - set_texture(oimg->texture, img, false, linear, mipmap); -} - -// draw image -void draw_image(ogl_image* image, const ogl_image_params& params) { - assert_ogl_error(); - glViewport(params.framebuffer.x, params.framebuffer.y, params.framebuffer.z, - params.framebuffer.w); - glClearColor(params.background.x, params.background.y, params.background.z, - params.background.w); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glEnable(GL_DEPTH_TEST); - bind_program(image->program); - set_uniform(image->program, "txt", image->texture, 0); - set_uniform(image->program, "window_size", - vec2f{(float)params.window.x, (float)params.window.y}); - set_uniform(image->program, "image_size", - vec2f{(float)image->texture->size.x, (float)image->texture->size.y}); - set_uniform(image->program, "image_center", params.center); - set_uniform(image->program, "image_scale", params.scale); - set_attribute(image->program, "texcoord", image->texcoords); - draw_elements(image->triangles); - unbind_program(image->program); - assert_ogl_error(); -} - // set uniforms void set_uniform(ogl_program* program, int location, int value) { assert_ogl_error(); @@ -1071,32 +1015,37 @@ void draw_elements(ogl_elementbuffer* buffer) { 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) { - assert(!framebuffer->framebuffer_id); - assert(!framebuffer->renderbuffer_id); - - glGenFramebuffers(1, &framebuffer->framebuffer_id); - glGenRenderbuffers(1, &framebuffer->renderbuffer_id); - framebuffer->size = size; + if (!framebuffer->framebuffer_id) { + glGenFramebuffers(1, &framebuffer->framebuffer_id); + } - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->framebuffer_id); - glBindRenderbuffer(GL_RENDERBUFFER, framebuffer->renderbuffer_id); + if (!framebuffer->renderbuffer_id) { + glGenRenderbuffers(1, &framebuffer->renderbuffer_id); + // bind together frame buffer and render buffer + // TODO(giacomo): We put STENCIL here for the same reason... + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->framebuffer_id); + glBindRenderbuffer(GL_RENDERBUFFER, framebuffer->renderbuffer_id); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + 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); + if (size != framebuffer->size) { + // create render buffer for depth and stencil + // TODO(giacomo): Why do we need to put STENCIL8 to make things work on + // Mac?? + glBindRenderbuffer(GL_RENDERBUFFER, framebuffer->renderbuffer_id); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size.x, size.y); + framebuffer->size = size; + } - // 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); + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->framebuffer_id); assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE); + glBindFramebuffer(GL_FRAMEBUFFER, ogl_framebuffer::bound_framebuffer_id); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); assert_ogl_error(); } @@ -1145,298 +1094,28 @@ void clear_framebuffer(ogl_framebuffer* framebuffer) { *framebuffer = {}; } -} // namespace yocto - -// ----------------------------------------------------------------------------- -// HIGH-LEVEL OPENGL FUNCTIONS -// ----------------------------------------------------------------------------- -namespace yocto { - -#ifndef _WIN32 -#pragma GCC diagnostic push -#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 +void bind_shape(const ogl_shape* shape) { glBindVertexArray(shape->shape_id); } -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; - - // 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; - - // clip - gl_Position = projection * view * vec4(position,1); -} -)"; - -static const char* glscene_fragment = - R"( -#version 330 - -float pif = 3.14159265; - -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]); - } -} - -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; - } -} - -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; -} - -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 ); -} - -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_shape(ogl_shape* shape) { + glGenVertexArrays(1, &shape->shape_id); + assert_ogl_error(); } -// 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; - } - - // emission - vec3 c = brdf_ke; - - // 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); - } - } - } - - // final color correction - c = pow(c * pow(2,exposure), vec3(1/gamma)); - - // 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); - } - - // output final color by setting gl_FragColor - frag_color = vec4(c,brdf_op); +// 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); + glDeleteVertexArrays(1, &shape->shape_id); + shape->shape_id = 0; } -)"; - -#ifndef _WIN32 -#pragma GCC diagnostic pop -#endif - -// forward declaration -void clear_shape(ogl_shape* shape); ogl_shape::~ogl_shape() { clear_shape(this); @@ -1452,72 +1131,84 @@ ogl_shape::~ogl_shape() { if (edges) delete edges; } -ogl_scene::~ogl_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; +void set_vertex_attribute(int location, float value) { + glVertexAttrib1f(location, value); +} +void set_vertex_attribute(int location, const vec2f& value) { + glVertexAttrib2f(location, value.x, value.y); +} +void set_vertex_attribute(int location, const vec3f& value) { + glVertexAttrib3f(location, value.x, value.y, value.z); +} +void set_vertex_attribute(int location, const vec4f& value) { + glVertexAttrib4f(location, value.x, value.y, value.z, value.w); } -// Initialize an OpenGL scene -void init_scene(ogl_scene* scene) { - if (is_initialized(scene->program)) return; - auto error = ""s, errorlog = ""s; - init_program( - scene->program, glscene_vertex, glscene_fragment, error, errorlog); +void set_vertex_attribute(int location, const ogl_arraybuffer* buffer) { + assert_ogl_error(); + assert(buffer->buffer_id); // == 0) return; + glBindBuffer(GL_ARRAY_BUFFER, buffer->buffer_id); + glEnableVertexAttribArray(location); + glVertexAttribPointer(location, buffer->esize, GL_FLOAT, false, 0, nullptr); + assert_ogl_error(); } -bool is_initialized(ogl_scene* scene) { return is_initialized(scene->program); } -// 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); +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(); } -// Clear an OpenGL scene -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); +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(); } -// add camera -ogl_camera* add_camera(ogl_scene* scene) { - return scene->cameras.emplace_back(new ogl_camera{}); +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_frame(ogl_camera* camera, const frame3f& frame) { - camera->frame = frame; +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_lens(ogl_camera* camera, float lens, float aspect, float film) { - camera->lens = lens; - camera->aspect = aspect; - camera->film = film; +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_nearfar(ogl_camera* camera, float near, float far) { - camera->near = near; - camera->far = far; +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); } - -// add texture -ogl_texture* add_texture(ogl_scene* scene) { - return scene->textures.emplace_back(new ogl_texture{}); +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); } -// add shape -ogl_shape* add_shape(ogl_scene* scene) { - return scene->shapes.emplace_back(new ogl_shape{}); +template +void set_index_buffer( + ogl_shape* shape, ogl_elementbuffer* elements, const vector& data) { + bind_shape(shape); + set_arraybuffer(elements, data); + assert_ogl_error(); } void set_points(ogl_shape* shape, const vector& points) { @@ -1555,321 +1246,126 @@ void set_edges(ogl_shape* shape, const vector& triangles, auto edges = vector(edgemap.begin(), edgemap.end()); set_elementbuffer(shape->edges, edges); } -void set_positions(ogl_shape* shape, const vector& positions) { - set_arraybuffer(shape->positions, positions); -} -void set_normals(ogl_shape* shape, const vector& normals) { - set_arraybuffer(shape->normals, normals); -} -void set_texcoords(ogl_shape* shape, const vector& texcoords) { - set_arraybuffer(shape->texcoords, texcoords); -} -void set_colors(ogl_shape* shape, const vector& colors) { - set_arraybuffer(shape->colors, colors); -} -void set_colors(ogl_shape* shape, const vector& colors) { - set_arraybuffer(shape->colors, colors); -} -void set_tangents(ogl_shape* shape, const vector& tangents) { - set_arraybuffer(shape->tangents, tangents); -} - -// add instance -ogl_instance* add_instance(ogl_scene* scene) { - return scene->instances.emplace_back(new ogl_instance{}); -} -void set_frame(ogl_instance* instance, const frame3f& frame) { - instance->frame = frame; -} -void set_shape(ogl_instance* instance, ogl_shape* shape) { - instance->shape = shape; -} -void set_material(ogl_instance* instance, ogl_material* material) { - instance->material = material; -} -void set_hidden(ogl_instance* instance, bool hidden) { - instance->hidden = hidden; -} -void set_highlighted(ogl_instance* instance, bool highlighted) { - instance->highlighted = highlighted; -} - -// add material -ogl_material* add_material(ogl_scene* scene) { - return scene->materials.emplace_back(new ogl_material{}); -} -void set_emission( - ogl_material* material, const vec3f& emission, ogl_texture* emission_tex) { - material->emission = emission; - material->emission_tex = emission_tex; -} -void set_color( - ogl_material* material, const vec3f& color, ogl_texture* color_tex) { - material->color = color; - material->color_tex = color_tex; -} -void set_specular( - ogl_material* material, float specular, ogl_texture* specular_tex) { - material->specular = specular; - material->specular_tex = specular_tex; -} -void set_roughness( - ogl_material* material, float roughness, ogl_texture* roughness_tex) { - material->roughness = roughness; - material->roughness_tex = roughness_tex; -} -void set_opacity( - ogl_material* material, float opacity, ogl_texture* opacity_tex) { - material->opacity = opacity; -} -void set_metallic( - ogl_material* material, float metallic, ogl_texture* metallic_tex) { - material->metallic = metallic; - material->metallic_tex = metallic_tex; -} -void set_normalmap(ogl_material* material, ogl_texture* normal_tex) { - material->normal_tex = normal_tex; -} - -// shortcuts -ogl_camera* add_camera(ogl_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; -} -ogl_material* add_material(ogl_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; -} -ogl_shape* add_shape(ogl_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); - } - return shape; -} -ogl_instance* add_instance(ogl_scene* scene, const frame3f& frame, - ogl_shape* shape, ogl_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; -} - -// add light -ogl_light* add_light(ogl_scene* scene) { - return scene->lights.emplace_back(new ogl_light{}); -} -void set_light(ogl_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; -} -void clear_lights(ogl_scene* scene) { - for (auto light : scene->lights) delete light; - scene->lights.clear(); -} -bool has_max_lights(ogl_scene* scene) { return scene->lights.size() >= 16; } -void add_default_lights(ogl_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); -} - -// Draw a shape -void draw_object( - ogl_scene* scene, ogl_instance* instance, const ogl_scene_params& params) { - static auto empty_instances = vector{identity3x4f}; - - if (instance->hidden) return; - - assert_ogl_error(); - 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(scene->program, "frame", shape_xform); - set_uniform(scene->program, "frameit", shape_inv_xform); - set_uniform(scene->program, "offset", 0.0f); - if (instance->highlighted) { - set_uniform(scene->program, "highlight", vec4f{1, 1, 0, 1}); - } else { - set_uniform(scene->program, "highlight", vec4f{0, 0, 0, 0}); - } - assert_ogl_error(); - auto material = instance->material; - auto mtype = 2; - set_uniform(scene->program, "mtype", mtype); - set_uniform(scene->program, "emission", material->emission); - set_uniform(scene->program, "diffuse", material->color); - set_uniform(scene->program, "specular", - vec3f{material->metallic, material->metallic, material->metallic}); - set_uniform(scene->program, "roughness", material->roughness); - set_uniform(scene->program, "opacity", material->opacity); - set_uniform(scene->program, "double_sided", (int)params.double_sided); - set_uniform(scene->program, "emission_tex", "emission_tex_on", - material->emission_tex, 0); - set_uniform( - scene->program, "diffuse_tex", "diffuse_tex_on", material->color_tex, 1); - set_uniform(scene->program, "specular_tex", "specular_tex_on", - material->metallic_tex, 2); - set_uniform(scene->program, "roughness_tex", "roughness_tex_on", - material->roughness_tex, 3); - set_uniform(scene->program, "opacity_tex", "opacity_tex_on", - material->opacity_tex, 4); - set_uniform(scene->program, "mat_norm_tex", "mat_norm_tex_on", - material->normal_tex, 5); - assert_ogl_error(); - - auto shape = instance->shape; - set_uniform(scene->program, "faceted", !is_initialized(shape->normals)); - set_attribute(scene->program, "positions", shape->positions, vec3f{0, 0, 0}); - set_attribute(scene->program, "normals", shape->normals, vec3f{0, 0, 1}); - set_attribute(scene->program, "texcoords", shape->texcoords, vec2f{0, 0}); - set_attribute(scene->program, "colors", shape->colors, vec4f{1, 1, 1, 1}); - set_attribute(scene->program, "tangents", shape->tangents, vec4f{0, 0, 1, 1}); - assert_ogl_error(); +void draw_shape(const ogl_shape* shape) { + bind_shape(shape); if (is_initialized(shape->points)) { glPointSize(shape->points_size); - set_uniform(scene->program, "etype", 1); draw_elements(shape->points); - } - if (is_initialized(shape->lines)) { - set_uniform(scene->program, "etype", 2); + } else if (is_initialized(shape->lines)) { draw_elements(shape->lines); - } - if (is_initialized(shape->triangles)) { - set_uniform(scene->program, "etype", 3); + } else if (is_initialized(shape->triangles)) { draw_elements(shape->triangles); - } - if (is_initialized(shape->quads)) { - set_uniform(scene->program, "etype", 3); + } else if (is_initialized(shape->quads)) { draw_elements(shape->quads); } - assert_ogl_error(); +} - if (is_initialized(shape->edges) && params.edges && !params.wireframe) { - set_uniform(scene->program, "mtype", mtype); - set_uniform(scene->program, "emission", vec3f{0, 0, 0}); - set_uniform(scene->program, "diffuse", vec3f{0, 0, 0}); - set_uniform(scene->program, "specular", vec3f{0, 0, 0}); - set_uniform(scene->program, "roughness", 0); - set_uniform(scene->program, "etype", 3); - draw_elements(shape->edges); - assert_ogl_error(); +ogl_shape* cube_shape() { + static ogl_shape* cube = nullptr; + if (!cube) { + // clang-format off + static const auto cube_positions = vector{ + {1, -1, -1}, {1, -1, 1}, {-1, -1, 1}, {-1, -1, -1}, + {1, 1, -1}, {1, 1, 1}, {-1, 1, 1}, {-1, 1, -1}, + }; + static const auto cube_triangles = vector{ + {1, 3, 0}, {7, 5, 4}, {4, 1, 0}, {5, 2, 1}, + {2, 7, 3}, {0, 7, 4}, {1, 2, 3}, {7, 6, 5}, + {4, 5, 1}, {5, 6, 2}, {2, 6, 7}, {0, 3, 7} + }; + // clang-format on + cube = new ogl_shape{}; + set_shape(cube); + set_positions(cube, cube_positions); + set_triangles(cube, cube_triangles); } + return cube; +} + +ogl_shape* quad_shape() { + static ogl_shape* quad = nullptr; + if (!quad) { + // clang-format off + static const auto quad_positions = vector{ + {-1, -1, 0}, {1, -1, 0}, {1, 1, 0}, {-1, 1, 0}, + }; + static const auto quad_triangles = vector{ + {0, 1, 3}, {3, 2, 1} + }; + // clang-format on + quad = new ogl_shape{}; + set_shape(quad); + set_positions(quad, quad_positions); + set_triangles(quad, quad_triangles); + } + return quad; } -// Display a scene -void draw_scene(ogl_scene* scene, ogl_camera* camera, const vec4i& viewport, - const ogl_scene_params& params) { - static auto camera_light0 = ogl_light{normalize(vec3f{1, 1, 1}), - vec3f{pif / 2, pif / 2, pif / 2}, ogl_light_type::directional, true}; - static auto camera_light1 = ogl_light{normalize(vec3f{-1, 1, 1}), - vec3f{pif / 2, pif / 2, pif / 2}, ogl_light_type::directional, true}; - static auto camera_light2 = ogl_light{normalize(vec3f{-1, -1, 1}), - vec3f{pif / 4, pif / 4, pif / 4}, ogl_light_type::directional, true}; - static auto camera_light3 = ogl_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); - - assert_ogl_error(); - clear_ogl_framebuffer(params.background); - set_ogl_viewport(viewport); - - assert_ogl_error(); - bind_program(scene->program); - assert_ogl_error(); - set_uniform(scene->program, "eye", camera->frame.o); - set_uniform(scene->program, "view", camera_view); - set_uniform(scene->program, "projection", camera_proj); - set_uniform(scene->program, "eyelight", - params.shading == ogl_shading_type::eyelight ? 1 : 0); - set_uniform(scene->program, "exposure", params.exposure); - set_uniform(scene->program, "gamma", params.gamma); - assert_ogl_error(); - - if (params.shading == ogl_shading_type::lights || - params.shading == ogl_shading_type::camlights) { - assert_ogl_error(); - auto& lights = params.shading == ogl_shading_type::lights ? scene->lights - : camera_lights; - set_uniform(scene->program, "lamb", vec3f{0, 0, 0}); - set_uniform(scene->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(scene->program, ("lpos[" + is + "]").c_str(), position); - } else { - set_uniform( - scene->program, ("lpos[" + is + "]").c_str(), light->position); - } - set_uniform(scene->program, ("lke[" + is + "]").c_str(), light->emission); - set_uniform( - scene->program, ("ltype[" + is + "]").c_str(), (int)light->type); - lid++; - } - assert_ogl_error(); - } +ogl_image::~ogl_image() { + if (program) delete program; + if (quad) delete quad; +} - if (params.wireframe) set_ogl_wireframe(true); - for (auto instance : scene->instances) { - draw_object(scene, instance, params); - } +bool is_initialized(const ogl_image* image) { + return is_initialized(image->program); +} - unbind_program(); - if (params.wireframe) set_ogl_wireframe(false); +// init image program +bool init_image(ogl_image* image) { + if (is_initialized(image)) return true; + auto error = ""s, errorlog = ""s; + if (!init_program( + image->program, glimage_vertex, glimage_fragment, error, errorlog)) + return false; + + 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); + + return true; +} + +// clear an opengl image +void clear_image(ogl_image* image) { + clear_program(image->program); + clear_texture(image->texture); + clear_shape(image->quad); +} + +// update image data +void set_image( + ogl_image* oimg, const image& img, bool linear, bool mipmap) { + set_texture(oimg->texture, img, false, linear, mipmap); +} +void set_image( + ogl_image* oimg, const image& img, bool linear, bool mipmap) { + set_texture(oimg->texture, img, false, linear, mipmap); +} + +// draw image +void draw_image(ogl_image* image, const ogl_image_params& params) { + assert_ogl_error(); + glViewport(params.framebuffer.x, params.framebuffer.y, params.framebuffer.z, + params.framebuffer.w); + glClearColor(params.background.x, params.background.y, params.background.z, + params.background.w); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_DEPTH_TEST); + bind_program(image->program); + set_uniform(image->program, "txt", image->texture, 0); + set_uniform(image->program, "window_size", + vec2f{(float)params.window.x, (float)params.window.y}); + set_uniform(image->program, "image_size", + vec2f{(float)image->texture->size.x, (float)image->texture->size.y}); + set_uniform(image->program, "image_center", params.center); + set_uniform(image->program, "image_scale", params.scale); + draw_shape(image->quad); + unbind_program(image->program); + assert_ogl_error(); } } // namespace yocto diff --git a/libs/yocto_gui/yocto_opengl.h b/libs/yocto_gui/yocto_opengl.h index c78a2f1c5..3a2312019 100644 --- a/libs/yocto_gui/yocto_opengl.h +++ b/libs/yocto_gui/yocto_opengl.h @@ -237,7 +237,6 @@ struct ogl_program { uint program_id = 0; uint vertex_id = 0; uint fragment_id = 0; - uint array_id = 0; }; // initialize program @@ -339,51 +338,10 @@ void draw_elements(ogl_elementbuffer* buffer); } // namespace yocto // ----------------------------------------------------------------------------- -// IMAGE DRAWING +// FRAMEBUFFERS // ----------------------------------------------------------------------------- namespace yocto { -// OpenGL image data -struct ogl_image { - ogl_image() {} - ogl_image(const ogl_image&) = delete; - ogl_image& operator=(const ogl_image&) = delete; - ~ogl_image(); - - ogl_program* program = new ogl_program{}; - ogl_texture* texture = new ogl_texture{}; - ogl_arraybuffer* texcoords = new ogl_arraybuffer{}; - ogl_elementbuffer* triangles = new ogl_elementbuffer{}; -}; - -// create image drawing program -bool init_image(ogl_image* oimg); -bool is_initialized(const ogl_image* oimg); - -// clear image -void clear_image(ogl_image* oimg); - -// update image data -void set_image(ogl_image* oimg, const image& img, bool linear = false, - bool mipmap = false); -void set_image(ogl_image* oimg, const image& img, bool linear = false, - bool mipmap = false); - -// OpenGL image drawing params -struct ogl_image_params { - vec2i window = {512, 512}; - vec4i framebuffer = {0, 0, 512, 512}; - vec2f center = {0, 0}; - float scale = 1; - bool fit = true; - bool checker = true; - float border_size = 2; - vec4f background = {0.15f, 0.15f, 0.15f, 1.0f}; -}; - -// draw image -void draw_image(ogl_image* image, const ogl_image_params& params); - struct ogl_framebuffer { vec2i size = {0, 0}; uint framebuffer_id = 0; @@ -405,44 +363,12 @@ void set_framebuffer_texture(const ogl_framebuffer* framebuffer, void bind_framebuffer(const ogl_framebuffer* target); void unbind_framebuffer(); void clear_framebuffer(ogl_framebuffer* target); - } // namespace yocto // ----------------------------------------------------------------------------- -// SCENE DRAWING +// SHAPES // ----------------------------------------------------------------------------- namespace yocto { - -// Opengl caemra -struct ogl_camera { - frame3f frame = identity3x4f; - float lens = 0.050; - float aspect = 1.000; - float film = 0.036; - float near = 0.001; - float far = 10000; - float aperture = 0; - float focus = 0; -}; - -// Opengl material -struct ogl_material { - // material - vec3f emission = {0, 0, 0}; - vec3f color = {0, 0, 0}; - float metallic = 0; - 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; -}; - // Opengl shape struct ogl_shape { // vertex buffers @@ -459,116 +385,17 @@ struct ogl_shape { float points_size = 10; float line_thickness = 4; + uint shape_id = 0; + ogl_shape() {} ogl_shape(const ogl_shape&) = delete; ogl_shape& operator=(const ogl_shape&) = delete; ~ogl_shape(); }; -// Opengl instance -struct ogl_instance { - // instance properties - frame3f frame = identity3x4f; - ogl_shape* shape = nullptr; - ogl_material* material = nullptr; - bool hidden = false; - bool highlighted = false; -}; - -// Light type -enum struct ogl_light_type { point = 0, directional }; - -// Opengl light -struct ogl_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 ogl_scene { - ogl_scene() {} - ogl_scene(const ogl_scene&) = delete; - ogl_scene& operator=(const ogl_scene&) = delete; - ~ogl_scene(); - - // scene objects - vector cameras = {}; - vector instances = {}; - vector shapes = {}; - vector materials = {}; - vector textures = {}; - vector lights = {}; - - // 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 -enum struct ogl_shading_type { lights, eyelight, camlights }; - -// Shading name -const auto ogl_shading_names = vector{ - "lights", "eyelight", "camlights"}; - -// Draw options -struct ogl_scene_params { - int resolution = 1280; - bool wireframe = false; - bool edges = false; - float edge_offset = 0.01f; - ogl_shading_type shading = ogl_shading_type::camlights; - float exposure = 0; - float gamma = 2.2f; - vec3f ambient = {0, 0, 0}; - bool double_sided = true; - bool non_rigid_frames = true; - float near = 0.01f; - float far = 10000.0f; - vec4f background = vec4f{0.15f, 0.15f, 0.15f, 1.0f}; -}; - -// Initialize an OpenGL scene -void init_scene(ogl_scene* scene); -bool is_initialized(const ogl_scene* scene); - -// Clear an OpenGL scene -void clear_scene(ogl_scene* scene); - -// add scene elements -ogl_camera* add_camera(ogl_scene* scene); -ogl_texture* add_texture(ogl_scene* scene); -ogl_material* add_material(ogl_scene* scene); -ogl_shape* add_shape(ogl_scene* scene); -ogl_instance* add_instance(ogl_scene* scene); -ogl_light* add_light(ogl_scene* scene); - -// camera properties -void set_frame(ogl_camera* camera, const frame3f& frame); -void set_lens(ogl_camera* camera, float lens, float aspect, float film); -void set_nearfar(ogl_camera* camera, float near, float far); - -// material properties -void set_emission(ogl_material* material, const vec3f& emission, - ogl_texture* emission_tex = nullptr); -void set_color(ogl_material* material, const vec3f& color, - ogl_texture* color_tex = nullptr); -void set_metallic(ogl_material* material, float metallic, - ogl_texture* metallic_tex = nullptr); -void set_roughness(ogl_material* material, float roughness, - ogl_texture* roughness_tex = nullptr); -void set_specular(ogl_material* material, float specular, - ogl_texture* specular_tex = nullptr); -void set_opacity( - ogl_material* material, float opacity, ogl_texture* opacity_tex = nullptr); -void set_normalmap(ogl_material* material, ogl_texture* normal_tex); +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); @@ -580,46 +407,60 @@ void set_edges(ogl_shape* shape, const vector& triangles, 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_colors(ogl_shape* shape, const vector& colors); void set_tangents(ogl_shape* shape, const vector& tangents); -// instance properties -void set_frame(ogl_instance* instance, const frame3f& frame); -void set_shape(ogl_instance* instance, ogl_shape* shape); -void set_material(ogl_instance* instance, ogl_material* material); -void set_hidden(ogl_instance* instance, bool hidden); -void set_highlighted(ogl_instance* instance, bool highlighted); - -// shortcuts -ogl_camera* add_camera(ogl_scene* scene, const frame3f& frame, float lens, - float aspect, float film = 0.036, float near = 0.001, float far = 10000); -ogl_material* add_material(ogl_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(ogl_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); -ogl_instance* add_instance(ogl_scene* scene, const frame3f& frame, - ogl_shape* shape, ogl_material* material, bool hidden = false, - bool highlighted = false); - -// light properties -void add_default_lights(ogl_scene* scene); -void set_light(ogl_light* light, const vec3f& position, const vec3f& emission, - ogl_light_type type, bool camera); - -// light size -void clear_lights(ogl_scene* scene); -bool has_max_lights(ogl_scene* scene); - -// Draw an OpenGL scene -void draw_scene(ogl_scene* scene, ogl_camera* camera, const vec4i& viewport, - const ogl_scene_params& params); +void draw_shape(const ogl_shape* shape); + +ogl_shape* cube_shape(); +ogl_shape* quad_shape(); + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// IMAGE DRAWING +// ----------------------------------------------------------------------------- +namespace yocto { + +// OpenGL image data +struct ogl_image { + ogl_image() {} + ogl_image(const ogl_image&) = delete; + ogl_image& operator=(const ogl_image&) = delete; + ~ogl_image(); + + ogl_program* program = new ogl_program{}; + ogl_texture* texture = new ogl_texture{}; + ogl_shape* quad = new ogl_shape{}; +}; + +// create image drawing program +bool init_image(ogl_image* oimg); +bool is_initialized(const ogl_image* oimg); + +// clear image +void clear_image(ogl_image* oimg); + +// update image data +void set_image(ogl_image* oimg, const image& img, bool linear = false, + bool mipmap = false); +void set_image(ogl_image* oimg, const image& img, bool linear = false, + bool mipmap = false); + +// OpenGL image drawing params +struct ogl_image_params { + vec2i window = {512, 512}; + vec4i framebuffer = {0, 0, 512, 512}; + vec2f center = {0, 0}; + float scale = 1; + bool fit = true; + bool checker = true; + float border_size = 2; + vec4f background = {0.15f, 0.15f, 0.15f, 1.0f}; +}; + +// draw image +void draw_image(ogl_image* image, const ogl_image_params& params); } // namespace yocto