Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/draw/engines/clay')
-rw-r--r--source/blender/draw/engines/clay/clay.c722
-rw-r--r--source/blender/draw/engines/clay/clay.h36
-rw-r--r--source/blender/draw/engines/clay/shaders/clay_frag.glsl207
-rw-r--r--source/blender/draw/engines/clay/shaders/clay_vert.glsl20
-rw-r--r--source/blender/draw/engines/clay/shaders/ssao_alchemy.glsl73
-rw-r--r--source/blender/draw/engines/clay/shaders/ssao_groundtruth.glsl120
6 files changed, 1178 insertions, 0 deletions
diff --git a/source/blender/draw/engines/clay/clay.c b/source/blender/draw/engines/clay/clay.c
new file mode 100644
index 00000000000..f443606f11d
--- /dev/null
+++ b/source/blender/draw/engines/clay/clay.c
@@ -0,0 +1,722 @@
+/*
+ * Copyright 2016, Blender Foundation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor(s): Blender Institute
+ *
+ */
+
+#include "DRW_render.h"
+
+#include "BKE_icons.h"
+#include "BKE_main.h"
+
+#include "BLI_dynstr.h"
+#include "BLI_rand.h"
+
+#include "IMB_imbuf.h"
+#include "IMB_imbuf_types.h"
+
+#include "UI_resources.h"
+#include "UI_interface_icons.h"
+
+#include "clay.h"
+#ifdef WITH_CLAY_ENGINE
+/* Shaders */
+
+extern char datatoc_clay_frag_glsl[];
+extern char datatoc_clay_vert_glsl[];
+extern char datatoc_ssao_alchemy_glsl[];
+extern char datatoc_ssao_groundtruth_glsl[];
+
+/* Storage */
+
+/* UBOs data needs to be 16 byte aligned (size of vec4) */
+/* Reminder : float, int, bool are 4 bytes */
+typedef struct CLAY_UBO_Material {
+ float ssao_params_var[4];
+ /* - 16 -*/
+ float matcap_hsv[3];
+ float matcap_id; /* even float encoding have enough precision */
+ /* - 16 -*/
+ float matcap_rot[2];
+ float pad[2]; /* ensure 16 bytes alignement */
+} CLAY_UBO_Material; /* 48 bytes */
+
+typedef struct CLAY_UBO_Storage {
+ CLAY_UBO_Material materials[512]; /* 512 = 9 bit material id */
+} CLAY_UBO_Storage;
+
+static struct CLAY_data {
+ /* Depth Pre Pass */
+ struct GPUShader *depth_sh;
+ /* Shading Pass */
+ struct GPUShader *clay_sh;
+
+ /* Materials Parameter UBO */
+ struct GPUUniformBuffer *mat_ubo;
+ CLAY_UBO_Storage mat_storage;
+ short ubo_flag;
+
+ /* Matcap textures */
+ struct GPUTexture *matcap_array;
+ float matcap_colors[24][3];
+
+ /* Ssao */
+ float winmat[4][4];
+ float viewvecs[3][4];
+ float ssao_params[4];
+ struct GPUTexture *jitter_tx;
+ struct GPUTexture *sampling_tx;
+} data = {NULL};
+
+/* CLAY_data.ubo_flag */
+enum {
+ CLAY_UBO_CLEAR = (1 << 0),
+ CLAY_UBO_REFRESH = (1 << 1),
+};
+
+/* keep it under MAX_BUFFERS */
+typedef struct CLAY_FramebufferList{
+ /* default */
+ struct GPUFrameBuffer *default_fb;
+ /* engine specific */
+ struct GPUFrameBuffer *downsample_depth;
+} CLAY_FramebufferList;
+
+/* keep it under MAX_TEXTURES */
+typedef struct CLAY_TextureList{
+ /* default */
+ struct GPUTexture *color;
+ struct GPUTexture *depth;
+ /* engine specific */
+ struct GPUTexture *depth_low;
+} CLAY_TextureList;
+
+/* for clarity follow the same layout as CLAY_TextureList */
+enum {
+ SCENE_COLOR,
+ SCENE_DEPTH,
+ SCENE_DEPTH_LOW,
+};
+
+/* keep it under MAX_PASSES */
+typedef struct CLAY_PassList{
+ /* default */
+ struct DRWPass *non_meshes_pass;
+ struct DRWPass *ob_center_pass;
+ /* engine specific */
+ struct DRWPass *depth_pass;
+ struct DRWPass *clay_pass;
+ struct DRWPass *wire_overlay_pass;
+ struct DRWPass *wire_outline_pass;
+} CLAY_PassList;
+
+//#define GTAO
+
+/* Functions */
+
+static void add_icon_to_rect(PreviewImage *prv, float *final_rect, int layer)
+{
+ int image_size = prv->w[0] * prv->h[0];
+ float *new_rect = &final_rect[image_size * 4 * layer];
+
+ IMB_buffer_float_from_byte(new_rect, (unsigned char *)prv->rect[0], IB_PROFILE_SRGB, IB_PROFILE_SRGB,
+ false, prv->w[0], prv->h[0], prv->w[0], prv->w[0]);
+
+ /* Find overall color */
+ for (int y = 0; y < 4; ++y) {
+ for (int x = 0; x < 4; ++x) {
+ data.matcap_colors[layer][0] += new_rect[y * 512 * 128 * 4 + x * 128 * 4 + 0];
+ data.matcap_colors[layer][1] += new_rect[y * 512 * 128 * 4 + x * 128 * 4 + 1];
+ data.matcap_colors[layer][2] += new_rect[y * 512 * 128 * 4 + x * 128 * 4 + 2];
+ }
+ }
+
+ data.matcap_colors[layer][0] /= 16.0f * 2.0f; /* the * 2 is to darken for shadows */
+ data.matcap_colors[layer][1] /= 16.0f * 2.0f;
+ data.matcap_colors[layer][2] /= 16.0f * 2.0f;
+}
+
+static struct GPUTexture *load_matcaps(PreviewImage *prv[24], int nbr)
+{
+ struct GPUTexture *tex;
+ int w = prv[0]->w[0];
+ int h = prv[0]->h[0];
+ float *final_rect = MEM_callocN(sizeof(float) * 4 * w * h * nbr, "Clay Matcap array rect");
+
+ for (int i = 0; i < nbr; ++i) {
+ add_icon_to_rect(prv[i], final_rect, i);
+ BKE_previewimg_free(&prv[i]);
+ }
+
+ tex = DRW_texture_create_2D_array(w, h, nbr, DRW_TEX_RGBA_8, DRW_TEX_FILTER, final_rect);
+ MEM_freeN(final_rect);
+
+ return tex;
+}
+
+static int matcap_to_index(int matcap)
+{
+ if (matcap == ICON_MATCAP_02) return 1;
+ else if (matcap == ICON_MATCAP_03) return 2;
+ else if (matcap == ICON_MATCAP_04) return 3;
+ else if (matcap == ICON_MATCAP_05) return 4;
+ else if (matcap == ICON_MATCAP_06) return 5;
+ else if (matcap == ICON_MATCAP_07) return 6;
+ else if (matcap == ICON_MATCAP_08) return 7;
+ else if (matcap == ICON_MATCAP_09) return 8;
+ else if (matcap == ICON_MATCAP_10) return 9;
+ else if (matcap == ICON_MATCAP_11) return 10;
+ else if (matcap == ICON_MATCAP_12) return 11;
+ else if (matcap == ICON_MATCAP_13) return 12;
+ else if (matcap == ICON_MATCAP_14) return 13;
+ else if (matcap == ICON_MATCAP_15) return 14;
+ else if (matcap == ICON_MATCAP_16) return 15;
+ else if (matcap == ICON_MATCAP_17) return 16;
+ else if (matcap == ICON_MATCAP_18) return 17;
+ else if (matcap == ICON_MATCAP_19) return 18;
+ else if (matcap == ICON_MATCAP_20) return 19;
+ else if (matcap == ICON_MATCAP_21) return 20;
+ else if (matcap == ICON_MATCAP_22) return 21;
+ else if (matcap == ICON_MATCAP_23) return 22;
+ else if (matcap == ICON_MATCAP_24) return 23;
+ return 0;
+}
+
+static struct GPUTexture *create_spiral_sample_texture(int numsaples)
+{
+ struct GPUTexture *tex;
+ float (*texels)[2] = MEM_mallocN(sizeof(float[2]) * numsaples, "concentric_tex");
+ const float numsaples_inv = 1.0f / numsaples;
+ int i;
+ /* arbitrary number to ensure we don't get conciding samples every circle */
+ const float spirals = 7.357;
+
+ for (i = 0; i < numsaples; i++) {
+ float r = (i + 0.5f) * numsaples_inv;
+ float phi = r * spirals * (float)(2.0 * M_PI);
+ texels[i][0] = r * cosf(phi);
+ texels[i][1] = r * sinf(phi);
+ }
+
+ tex = DRW_texture_create_1D(numsaples, DRW_TEX_RG_16, 0, (float *)texels);
+
+ MEM_freeN(texels);
+ return tex;
+}
+
+static struct GPUTexture *create_jitter_texture(void)
+{
+ float jitter[64 * 64][2];
+ int i;
+
+ /* TODO replace by something more evenly distributed like blue noise */
+ for (i = 0; i < 64 * 64; i++) {
+#ifdef GTAO
+ jitter[i][0] = BLI_frand();
+ jitter[i][1] = BLI_frand();
+#else
+ jitter[i][0] = 2.0f * BLI_frand() - 1.0f;
+ jitter[i][1] = 2.0f * BLI_frand() - 1.0f;
+ normalize_v2(jitter[i]);
+#endif
+ }
+
+ return DRW_texture_create_2D(64, 64, DRW_TEX_RG_16, DRW_TEX_FILTER | DRW_TEX_WRAP, &jitter[0][0]);
+}
+
+static void clay_material_settings_init(MaterialEngineSettingsClay *ma)
+{
+ ma->matcap_icon = ICON_MATCAP_01;
+ ma->matcap_rot = 0.0f;
+ ma->matcap_hue = 0.5f;
+ ma->matcap_sat = 0.5f;
+ ma->matcap_val = 0.5f;
+ ma->ssao_distance = 0.2;
+ ma->ssao_attenuation = 1.0f;
+ ma->ssao_factor_cavity = 1.0f;
+ ma->ssao_factor_edge = 1.0f;
+}
+
+RenderEngineSettings *CLAY_render_settings_create(void)
+{
+ RenderEngineSettingsClay *settings = MEM_callocN(sizeof(RenderEngineSettingsClay), "RenderEngineSettingsClay");
+
+ clay_material_settings_init((MaterialEngineSettingsClay *)settings);
+
+ settings->ssao_samples = 32;
+
+ return (RenderEngineSettings *)settings;
+}
+
+MaterialEngineSettings *CLAY_material_settings_create(void)
+{
+ MaterialEngineSettingsClay *settings = MEM_callocN(sizeof(MaterialEngineSettingsClay), "MaterialEngineSettingsClay");
+
+ clay_material_settings_init(settings);
+
+ return (MaterialEngineSettings *)settings;
+}
+
+static void CLAY_engine_init(const bContext *C)
+{
+ Main *bmain = CTX_data_main(C);
+
+ /* Create Texture Array */
+ if (!data.matcap_array) {
+ PreviewImage *prv[24]; /* For now use all of the 24 internal matcaps */
+
+ /* TODO only load used matcaps */
+ prv[0] = UI_icon_to_preview(ICON_MATCAP_01);
+ prv[1] = UI_icon_to_preview(ICON_MATCAP_02);
+ prv[2] = UI_icon_to_preview(ICON_MATCAP_03);
+ prv[3] = UI_icon_to_preview(ICON_MATCAP_04);
+ prv[4] = UI_icon_to_preview(ICON_MATCAP_05);
+ prv[5] = UI_icon_to_preview(ICON_MATCAP_06);
+ prv[6] = UI_icon_to_preview(ICON_MATCAP_07);
+ prv[7] = UI_icon_to_preview(ICON_MATCAP_08);
+ prv[8] = UI_icon_to_preview(ICON_MATCAP_09);
+ prv[9] = UI_icon_to_preview(ICON_MATCAP_10);
+ prv[10] = UI_icon_to_preview(ICON_MATCAP_11);
+ prv[11] = UI_icon_to_preview(ICON_MATCAP_12);
+ prv[12] = UI_icon_to_preview(ICON_MATCAP_13);
+ prv[13] = UI_icon_to_preview(ICON_MATCAP_14);
+ prv[14] = UI_icon_to_preview(ICON_MATCAP_15);
+ prv[15] = UI_icon_to_preview(ICON_MATCAP_16);
+ prv[16] = UI_icon_to_preview(ICON_MATCAP_17);
+ prv[17] = UI_icon_to_preview(ICON_MATCAP_18);
+ prv[18] = UI_icon_to_preview(ICON_MATCAP_19);
+ prv[19] = UI_icon_to_preview(ICON_MATCAP_20);
+ prv[20] = UI_icon_to_preview(ICON_MATCAP_21);
+ prv[21] = UI_icon_to_preview(ICON_MATCAP_22);
+ prv[22] = UI_icon_to_preview(ICON_MATCAP_23);
+ prv[23] = UI_icon_to_preview(ICON_MATCAP_24);
+
+ data.matcap_array = load_matcaps(prv, 24);
+ }
+
+ /* AO Jitter */
+ if (!data.jitter_tx) {
+ data.jitter_tx = create_jitter_texture();
+ }
+
+ /* AO Samples */
+ /* TODO use hammersley sequence */
+ if (!data.sampling_tx) {
+ data.sampling_tx = create_spiral_sample_texture(500);
+ }
+
+ /* Depth prepass */
+ if (!data.depth_sh) {
+ data.depth_sh = DRW_shader_create_3D_depth_only();
+ }
+
+ if (!data.mat_ubo) {
+ data.mat_ubo = DRW_uniformbuffer_create(sizeof(CLAY_UBO_Storage), NULL);
+ }
+
+ /* Shading pass */
+ if (!data.clay_sh) {
+ DynStr *ds = BLI_dynstr_new();
+ const char *max_mat =
+ "#define MAX_MATERIAL 512\n"
+ "#define USE_ROTATION\n"
+ "#define USE_AO\n"
+ "#define USE_HSV\n";
+ char *matcap_with_ao;
+
+ BLI_dynstr_append(ds, datatoc_clay_frag_glsl);
+#ifdef GTAO
+ BLI_dynstr_append(ds, datatoc_ssao_groundtruth_glsl);
+#else
+ BLI_dynstr_append(ds, datatoc_ssao_alchemy_glsl);
+#endif
+
+ matcap_with_ao = BLI_dynstr_get_cstring(ds);
+
+ data.clay_sh = DRW_shader_create(datatoc_clay_vert_glsl, NULL, matcap_with_ao, max_mat);
+
+ BLI_dynstr_free(ds);
+ MEM_freeN(matcap_with_ao);
+ }
+
+ /* Cleanup all runtime data loaded from file */
+ for (Scene *sce = bmain->scene.first; sce; sce = sce->id.next) {
+ /* Using render settings as material settings */
+ MaterialEngineSettingsClay *res = DRW_render_settings_get(sce, RE_engine_id_BLENDER_CLAY);
+ res->flag = CLAY_OUTDATED;
+ res->ubo_index = -1;
+
+ /* Update Collections Materials */
+ for (SceneLayer *sl = sce->render_layers.first; sl; sl = sl->next) {
+ for (LayerCollection *lc = sl->layer_collections.first; lc; lc = lc->next) {
+ CollectionEngineSettings *ces;
+ ces = BKE_layer_collection_engine_get(lc, RE_engine_id_BLENDER_CLAY);
+ if (ces) { /* May not exists */
+ BKE_collection_engine_property_value_set_int(ces, "flag", CLAY_OUTDATED);
+ BKE_collection_engine_property_value_set_int(ces, "ubo_index", -1);
+ }
+ }
+ }
+ }
+
+ data.ubo_flag |= CLAY_UBO_REFRESH;
+}
+
+static void CLAY_ssao_setup(void)
+{
+ float invproj[4][4];
+ float dfdyfacs[2];
+ bool is_persp = DRW_viewport_is_persp_get();
+ /* view vectors for the corners of the view frustum. Can be used to recreate the world space position easily */
+ float viewvecs[3][4] = {
+ {-1.0f, -1.0f, -1.0f, 1.0f},
+ {1.0f, -1.0f, -1.0f, 1.0f},
+ {-1.0f, 1.0f, -1.0f, 1.0f}
+ };
+ int i;
+ float *size = DRW_viewport_size_get();
+ RenderEngineSettingsClay *settings = DRW_render_settings_get(NULL, RE_engine_id_BLENDER_CLAY);
+
+ DRW_get_dfdy_factors(dfdyfacs);
+
+ data.ssao_params[0] = settings->ssao_samples;
+ data.ssao_params[1] = size[0] / 64.0;
+ data.ssao_params[2] = size[1] / 64.0;
+ data.ssao_params[3] = dfdyfacs[1]; /* dfdy sign for offscreen */
+
+ /* invert the view matrix */
+ DRW_viewport_matrix_get(data.winmat, DRW_MAT_WIN);
+ invert_m4_m4(invproj, data.winmat);
+
+ /* convert the view vectors to view space */
+ for (i = 0; i < 3; i++) {
+ mul_m4_v4(invproj, viewvecs[i]);
+ /* normalized trick see http://www.derschmale.com/2014/01/26/reconstructing-positions-from-the-depth-buffer */
+ mul_v3_fl(viewvecs[i], 1.0f / viewvecs[i][3]);
+ if (is_persp)
+ mul_v3_fl(viewvecs[i], 1.0f / viewvecs[i][2]);
+ viewvecs[i][3] = 1.0;
+
+ copy_v4_v4(data.viewvecs[i], viewvecs[i]);
+ }
+
+ /* we need to store the differences */
+ data.viewvecs[1][0] -= data.viewvecs[0][0];
+ data.viewvecs[1][1] = data.viewvecs[2][1] - data.viewvecs[0][1];
+
+ /* calculate a depth offset as well */
+ if (!is_persp) {
+ float vec_far[] = {-1.0f, -1.0f, 1.0f, 1.0f};
+ mul_m4_v4(invproj, vec_far);
+ mul_v3_fl(vec_far, 1.0f / vec_far[3]);
+ data.viewvecs[1][2] = vec_far[2] - data.viewvecs[0][2];
+ }
+}
+
+static DRWShadingGroup *CLAY_shgroup_create(DRWPass *pass, int *UNUSED(material_id))
+{
+ const int depthloc = 0, matcaploc = 1, jitterloc = 2, sampleloc = 3;
+
+ //CLAY_UBO_Material *mat = &data.mat_storage.materials[0];
+ DRWShadingGroup *grp = DRW_shgroup_create(data.clay_sh, pass);
+
+ DRW_shgroup_uniform_vec2(grp, "screenres", DRW_viewport_size_get(), 1);
+ DRW_shgroup_uniform_buffer(grp, "depthtex", SCENE_DEPTH, depthloc);
+ DRW_shgroup_uniform_texture(grp, "matcaps", data.matcap_array, matcaploc);
+ DRW_shgroup_uniform_mat4(grp, "WinMatrix", (float *)data.winmat);
+ DRW_shgroup_uniform_vec4(grp, "viewvecs", (float *)data.viewvecs, 3);
+ DRW_shgroup_uniform_vec4(grp, "ssao_params", data.ssao_params, 1);
+ DRW_shgroup_uniform_vec3(grp, "matcaps_color", (float *)data.matcap_colors, 24);
+
+ //DRW_shgroup_uniform_int(grp, "material_id", material_id, 1);
+
+#ifndef GTAO
+ DRW_shgroup_uniform_texture(grp, "ssao_jitter", data.jitter_tx, jitterloc);
+ DRW_shgroup_uniform_texture(grp, "ssao_samples", data.sampling_tx, sampleloc);
+#endif
+
+ return grp;
+}
+
+static void update_ubo_storage(float matcap_rot, float matcap_hue, float matcap_sat, float matcap_val,
+ float ssao_distance, float ssao_factor_cavity, float ssao_factor_edge,
+ float ssao_attenuation, int matcap_icon, unsigned int current_id)
+{
+ CLAY_UBO_Material *ubo = &data.mat_storage.materials[current_id];
+
+ ubo->matcap_rot[0] = cosf(matcap_rot * 3.14159f * 2.0f);
+ ubo->matcap_rot[1] = sinf(matcap_rot * 3.14159f * 2.0f);
+
+ ubo->matcap_hsv[0] = matcap_hue + 0.5f;
+ ubo->matcap_hsv[1] = matcap_sat * 2.0f;
+ ubo->matcap_hsv[2] = matcap_val * 2.0f;
+
+ ubo->ssao_params_var[0] = ssao_distance;
+ ubo->ssao_params_var[1] = ssao_factor_cavity;
+ ubo->ssao_params_var[2] = ssao_factor_edge;
+ ubo->ssao_params_var[3] = ssao_attenuation;
+
+
+ ubo->matcap_id = matcap_to_index(matcap_icon);
+}
+
+static void CLAY_update_material_ubo(const struct bContext *C)
+{
+ Main *bmain = CTX_data_main(C);
+
+ /* Update Default materials */
+ for (Scene *sce = bmain->scene.first; sce; sce = sce->id.next) {
+ /* Using render settings as material settings */
+ MaterialEngineSettingsClay *res = DRW_render_settings_get(sce, RE_engine_id_BLENDER_CLAY);
+
+ if (res->flag & CLAY_OUTDATED)
+ data.ubo_flag |= CLAY_UBO_REFRESH;
+
+ if (res->matcap_icon < ICON_MATCAP_01 ||
+ res->matcap_icon > ICON_MATCAP_24)
+ {
+ res->matcap_icon = ICON_MATCAP_01;
+ }
+
+ res->flag &= ~CLAY_OUTDATED;
+
+
+ /* Update Collections Materials */
+ for (SceneLayer *sl = sce->render_layers.first; sl; sl = sl->next) {
+ for (LayerCollection *lc = sl->layer_collections.first; lc; lc = lc->next) {
+ CollectionEngineSettings *ces;
+ ces = BKE_layer_collection_engine_get(lc, RE_engine_id_BLENDER_CLAY);
+
+ BKE_collection_engine_property_value_set_int(ces, "flag", 0);
+ BKE_collection_engine_property_value_set_int(ces, "ubo_index", 0);
+ }
+ }
+ }
+
+ if (data.ubo_flag & CLAY_UBO_REFRESH) {
+ int current_id = 0;
+
+
+ /* Default materials */
+ for (Scene *sce = bmain->scene.first; sce; sce = sce->id.next) {
+ MaterialEngineSettingsClay *res = DRW_render_settings_get(sce, RE_engine_id_BLENDER_CLAY);
+
+ update_ubo_storage(res->matcap_rot, res->matcap_hue, res->matcap_sat, res->matcap_val,
+ res->ssao_distance, res->ssao_factor_cavity, res->ssao_factor_edge,
+ res->ssao_attenuation, res->matcap_icon, current_id);
+ current_id++;
+
+ for (SceneLayer *sl = sce->render_layers.first; sl; sl = sl->next) {
+ for (LayerCollection *lc = sl->layer_collections.first; lc; lc = lc->next) {
+ /* TODO */
+ current_id++;
+ }
+ }
+ current_id++;
+ }
+
+
+ DRW_uniformbuffer_update(data.mat_ubo, &data.mat_storage);
+ }
+
+ data.ubo_flag = 0;
+}
+
+static void CLAY_create_cache(CLAY_PassList *passes, const struct bContext *C)
+{
+ SceneLayer *sl = CTX_data_scene_layer(C);
+ DRWShadingGroup *default_shgrp, *depthbatch;
+
+ /* Depth Pass */
+ {
+ passes->depth_pass = DRW_pass_create("Depth Pass", DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS);
+
+ depthbatch = DRW_shgroup_create(data.depth_sh, passes->depth_pass);
+ }
+
+ /* Clay Pass */
+ {
+ MaterialEngineSettingsClay *settings = DRW_render_settings_get(NULL, RE_engine_id_BLENDER_CLAY);
+
+ passes->clay_pass = DRW_pass_create("Clay Pass", DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS);
+
+ default_shgrp = CLAY_shgroup_create(passes->clay_pass, &settings->ubo_index);
+ DRW_shgroup_uniform_block(default_shgrp, "material_block", data.mat_ubo, 0);
+ }
+
+ /* Object Mode */
+ {
+ DRW_pass_setup_common(&passes->wire_overlay_pass,
+ &passes->wire_outline_pass,
+ &passes->non_meshes_pass,
+ &passes->ob_center_pass);
+ }
+
+ /* TODO Create hash table of batch based on material id*/
+ Object *ob;
+ DEG_OBJECT_ITER(sl, ob)
+ {
+ if ((ob->base_flag & BASE_VISIBLED) == 0) {
+ continue;
+ }
+
+ struct Batch *geom;
+ //bool do_outlines;
+
+ switch (ob->type) {
+ case OB_MESH:
+ geom = DRW_cache_surface_get(ob);
+
+ /* Add everything for now */
+ DRW_shgroup_call_add(depthbatch, geom, ob->obmat);
+ DRW_shgroup_call_add(default_shgrp, geom, ob->obmat);
+
+ //DRW_shgroup_wire_overlay(passes->wire_overlay_pass, ob);
+
+ //do_outlines = ((ob->base_flag & BASE_SELECTED) != 0);
+ //DRW_shgroup_wire_outline(passes->wire_outline_pass, ob, false, false, do_outlines);
+
+ /* When encountering a new material :
+ * - Create new Batch
+ * - Initialize Batch
+ * - Push it to the hash table
+ * - The pass takes care of inserting it
+ * next to the same shader calls */
+
+ /* Free hash table */
+ break;
+ case OB_LAMP:
+ case OB_CAMERA:
+ case OB_EMPTY:
+ default:
+ DRW_shgroup_non_meshes(passes->non_meshes_pass, ob);
+ break;
+ }
+
+ DRW_shgroup_object_center(passes->ob_center_pass, ob);
+ DRW_shgroup_relationship_lines(passes->non_meshes_pass, ob);
+ }
+ DEG_OBJECT_ITER_END
+}
+
+static void CLAY_view_draw(RenderEngine *UNUSED(engine), const bContext *context)
+{
+ /* This function may run for multiple viewports
+ * so get the current viewport buffers */
+ CLAY_FramebufferList *buffers = NULL;
+ CLAY_TextureList *textures = NULL;
+ CLAY_PassList *passes = NULL;
+
+ DRW_viewport_init(context, (void **)&buffers, (void **)&textures, (void **)&passes);
+
+ CLAY_engine_init(context);
+
+ CLAY_update_material_ubo(context);
+
+ /* TODO : tag to refresh by the deps graph */
+ /* ideally only refresh when objects are added/removed */
+ /* or render properties / materials change */
+#ifdef WITH_VIEWPORT_CACHE_TEST
+ static bool once = false;
+#endif
+ if (DRW_viewport_cache_is_dirty()
+#ifdef WITH_VIEWPORT_CACHE_TEST
+ && !once
+#endif
+ ) {
+#ifdef WITH_VIEWPORT_CACHE_TEST
+ once = true;
+#endif
+ CLAY_create_cache(passes, context);
+ }
+
+ /* Start Drawing */
+ DRW_draw_background();
+
+ /* Pass 1 : Depth pre-pass */
+ DRW_draw_pass(passes->depth_pass);
+
+ /* Pass 2 (Optionnal) : Separated Downsampled AO */
+ DRW_framebuffer_texture_detach(textures->depth);
+ /* TODO */
+
+ /* Pass 3 : Shading */
+ CLAY_ssao_setup();
+ DRW_draw_pass(passes->clay_pass);
+
+ /* Pass 4 : Overlays */
+ DRW_framebuffer_texture_attach(buffers->default_fb, textures->depth, 0);
+ //DRW_draw_pass(passes->wire_overlay_pass);
+ //DRW_draw_pass(passes->wire_outline_pass);
+ DRW_draw_pass(passes->non_meshes_pass);
+ DRW_draw_pass(passes->ob_center_pass);
+
+ /* Always finish by this */
+ DRW_state_reset();
+}
+
+static void CLAY_collection_settings_create(RenderEngine *UNUSED(engine), CollectionEngineSettings *ces)
+{
+ BLI_assert(ces);
+ BKE_collection_engine_property_add_int(ces, "matcap_icon", ICON_MATCAP_01);
+ BKE_collection_engine_property_add_int(ces, "type", CLAY_MATCAP_NONE);
+ BKE_collection_engine_property_add_float(ces, "matcap_rotation", 0.0f);
+ BKE_collection_engine_property_add_float(ces, "matcap_hue", 0.5f);
+ BKE_collection_engine_property_add_float(ces, "matcap_saturation", 0.5f);
+ BKE_collection_engine_property_add_float(ces, "matcap_value", 0.5f);
+ BKE_collection_engine_property_add_float(ces, "ssao_distance", 0.2f);
+ BKE_collection_engine_property_add_float(ces, "ssao_attenuation", 1.0f);
+ BKE_collection_engine_property_add_float(ces, "ssao_factor_cavity", 1.0f);
+ BKE_collection_engine_property_add_float(ces, "ssao_factor_edge", 1.0f);
+
+ /* Runtime data (not display in settings) */
+ BKE_collection_engine_property_add_int(ces, "ubo_index", -1);
+ BKE_collection_engine_property_add_int(ces, "flag", CLAY_OUTDATED);
+}
+
+void clay_engine_free(void)
+{
+ /* data.depth_sh Is builtin so it's automaticaly freed */
+ if (data.clay_sh) {
+ DRW_shader_free(data.clay_sh);
+ }
+
+ if (data.matcap_array) {
+ DRW_texture_free(data.matcap_array);
+ }
+
+ if (data.jitter_tx) {
+ DRW_texture_free(data.jitter_tx);
+ }
+
+ if (data.sampling_tx) {
+ DRW_texture_free(data.sampling_tx);
+ }
+
+ if (data.mat_ubo) {
+ DRW_uniformbuffer_free(data.mat_ubo);
+ }
+}
+
+RenderEngineType viewport_clay_type = {
+ NULL, NULL,
+ "BLENDER_CLAY", N_("Clay"), RE_INTERNAL | RE_USE_OGL_PIPELINE,
+ NULL, NULL, NULL, NULL, &CLAY_view_draw, NULL, &CLAY_collection_settings_create,
+ {NULL, NULL, NULL}
+};
+#endif \ No newline at end of file
diff --git a/source/blender/draw/engines/clay/clay.h b/source/blender/draw/engines/clay/clay.h
new file mode 100644
index 00000000000..404924be2a1
--- /dev/null
+++ b/source/blender/draw/engines/clay/clay.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016, Blender Foundation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor(s): Blender Institute
+ *
+ */
+
+/** \file clay.h
+ * \ingroup DNA
+ */
+
+#ifndef __ENGINE_CLAY_H__
+#define __ENGINE_CLAY_H__
+
+extern RenderEngineType viewport_clay_type;
+
+struct RenderEngineSettings *CLAY_render_settings_create(void);
+struct MaterialEngineSettings *CLAY_material_settings_create(void);
+
+void clay_engine_free(void);
+
+#endif /* __ENGINE_CLAY_H__ */ \ No newline at end of file
diff --git a/source/blender/draw/engines/clay/shaders/clay_frag.glsl b/source/blender/draw/engines/clay/shaders/clay_frag.glsl
new file mode 100644
index 00000000000..d9b372b652a
--- /dev/null
+++ b/source/blender/draw/engines/clay/shaders/clay_frag.glsl
@@ -0,0 +1,207 @@
+uniform vec2 screenres;
+uniform sampler2D depthtex;
+uniform mat4 WinMatrix;
+
+/* Matcap */
+uniform sampler2DArray matcaps;
+uniform vec3 matcaps_color[24];
+
+/* Screen Space Occlusion */
+/* store the view space vectors for the corners of the view frustum here.
+ * It helps to quickly reconstruct view space vectors by using uv coordinates,
+ * see http://www.derschmale.com/2014/01/26/reconstructing-positions-from-the-depth-buffer */
+uniform vec4 viewvecs[3];
+uniform vec4 ssao_params;
+
+uniform sampler2D ssao_jitter;
+uniform sampler1D ssao_samples;
+
+/* Material Parameters packed in an UBO */
+struct Material {
+ vec4 ssao_params_var;
+ vec4 matcap_hsv_id;
+ vec4 matcap_rot; /* vec4 to ensure 16 bytes alignement (don't trust compiler) */
+};
+
+layout(std140) uniform material_block {
+ Material matcaps_param[MAX_MATERIAL];
+};
+
+int mat_id;
+
+/* Aliases */
+#define ssao_samples_num ssao_params.x
+#define jitter_tilling ssao_params.yz
+#define dfdy_sign ssao_params.w
+
+#define matcap_hsv matcaps_param[mat_id].matcap_hsv_id.xyz
+#define matcap_index matcaps_param[mat_id].matcap_hsv_id.w
+#define matcap_rotation matcaps_param[mat_id].matcap_rot.xy
+
+in vec3 normal;
+out vec4 fragColor;
+
+/* TODO Move this to SSAO modules */
+/* simple depth reconstruction, see http://www.derschmale.com/2014/01/26/reconstructing-positions-from-the-depth-buffer
+ * we change the factors from the article to fit the OpennGL model. */
+vec3 get_view_space_from_depth(in vec2 uvcoords, in float depth)
+{
+ if (WinMatrix[3][3] == 0.0) {
+ /* Perspective */
+ float d = 2.0 * depth - 1.0;
+
+ float zview = -WinMatrix[3][2] / (d + WinMatrix[2][2]);
+
+ return zview * (viewvecs[0].xyz + vec3(uvcoords, 0.0) * viewvecs[1].xyz);
+ }
+ else {
+ /* Orthographic */
+ vec3 offset = vec3(uvcoords, depth);
+
+ return viewvecs[0].xyz + offset * viewvecs[1].xyz;
+ }
+}
+
+/* TODO remove this when switching to geometric normals */
+vec3 calculate_view_space_normal(in vec3 viewposition)
+{
+ vec3 normal = cross(normalize(dFdx(viewposition)), dfdy_sign * normalize(dFdy(viewposition)));
+ return normalize(normal);
+}
+
+#ifdef USE_HSV
+void rgb_to_hsv(vec3 rgb, out vec3 outcol)
+{
+ float cmax, cmin, h, s, v, cdelta;
+ vec3 c;
+
+ cmax = max(rgb[0], max(rgb[1], rgb[2]));
+ cmin = min(rgb[0], min(rgb[1], rgb[2]));
+ cdelta = cmax - cmin;
+
+ v = cmax;
+ if (cmax != 0.0)
+ s = cdelta / cmax;
+ else {
+ s = 0.0;
+ h = 0.0;
+ }
+
+ if (s == 0.0) {
+ h = 0.0;
+ }
+ else {
+ c = (vec3(cmax, cmax, cmax) - rgb.xyz) / cdelta;
+
+ if (rgb.x == cmax) h = c[2] - c[1];
+ else if (rgb.y == cmax) h = 2.0 + c[0] - c[2];
+ else h = 4.0 + c[1] - c[0];
+
+ h /= 6.0;
+
+ if (h < 0.0)
+ h += 1.0;
+ }
+
+ outcol = vec3(h, s, v);
+}
+
+void hsv_to_rgb(vec3 hsv, out vec3 outcol)
+{
+ float i, f, p, q, t, h, s, v;
+ vec3 rgb;
+
+ h = hsv[0];
+ s = hsv[1];
+ v = hsv[2];
+
+ if (s == 0.0) {
+ rgb = vec3(v, v, v);
+ }
+ else {
+ if (h == 1.0)
+ h = 0.0;
+
+ h *= 6.0;
+ i = floor(h);
+ f = h - i;
+ rgb = vec3(f, f, f);
+ p = v * (1.0 - s);
+ q = v * (1.0 - (s * f));
+ t = v * (1.0 - (s * (1.0 - f)));
+
+ if (i == 0.0) rgb = vec3(v, t, p);
+ else if (i == 1.0) rgb = vec3(q, v, p);
+ else if (i == 2.0) rgb = vec3(p, v, t);
+ else if (i == 3.0) rgb = vec3(p, q, v);
+ else if (i == 4.0) rgb = vec3(t, p, v);
+ else rgb = vec3(v, p, q);
+ }
+
+ outcol = rgb;
+}
+
+void hue_sat(float hue, float sat, float value, inout vec3 col)
+{
+ vec3 hsv;
+
+ rgb_to_hsv(col, hsv);
+
+ hsv.x += hue;
+ hsv.x -= floor(hsv.x);
+ hsv.y *= sat;
+ hsv.y = clamp(hsv.y, 0.0, 1.0);
+ hsv.z *= value;
+ hsv.z = clamp(hsv.z, 0.0, 1.0);
+
+ hsv_to_rgb(hsv, col);
+}
+#endif
+
+#ifdef USE_AO
+/* Prototype */
+void ssao_factors(in float depth, in vec3 normal, in vec3 position, in vec2 screenco, out float cavities, out float edges);
+#endif
+
+void main() {
+ vec2 screenco = vec2(gl_FragCoord.xy) / screenres;
+ float depth = texture(depthtex, screenco).r;
+
+ vec3 position = get_view_space_from_depth(screenco, depth);
+ vec3 normal = calculate_view_space_normal(position);
+
+ //mat_id = int(screenco.x*3.0);
+
+ /* Manual Depth test */
+ /* Doing this test earlier gives problem with dfdx calculations
+ * TODO move this before when we have proper geometric normals */
+ if (gl_FragCoord.z > depth + 1e-5)
+ discard;
+
+#ifdef USE_ROTATION
+ /* Rotate texture coordinates */
+ vec2 rotY = vec2(-matcap_rotation.y, matcap_rotation.x);
+ vec2 texco = abs(vec2(dot(normal.xy, matcap_rotation), dot(normal.xy, rotY)) * .49 + 0.5);
+#else
+ vec2 texco = abs(normal.xy * .49 + 0.5);
+#endif
+ vec3 col = texture(matcaps, vec3(texco, matcap_index)).rgb;
+
+#ifdef USE_AO
+ float cavity, edges;
+ ssao_factors(depth, normal, position, screenco, cavity, edges);
+
+ col *= mix(vec3(1.0), matcaps_color[int(matcap_index)], cavity);
+#endif
+
+#ifdef USE_HSV
+ hue_sat(matcap_hsv.x, matcap_hsv.y, matcap_hsv.z, col);
+#endif
+
+#ifdef USE_AO
+ /* Apply highlights after hue shift */
+ col *= edges + 1.0;
+#endif
+
+ fragColor = vec4(col, 1.0);
+}
diff --git a/source/blender/draw/engines/clay/shaders/clay_vert.glsl b/source/blender/draw/engines/clay/shaders/clay_vert.glsl
new file mode 100644
index 00000000000..0b598ea0291
--- /dev/null
+++ b/source/blender/draw/engines/clay/shaders/clay_vert.glsl
@@ -0,0 +1,20 @@
+uniform mat4 ModelViewProjectionMatrix;
+uniform mat3 NormalMatrix;
+
+#if __VERSION__ == 120
+attribute vec3 pos;
+attribute vec3 nor;
+varying vec3 normal;
+#else
+in vec3 pos;
+in vec3 nor;
+out vec3 normal;
+#endif
+
+
+void main()
+{
+ normal = normalize(NormalMatrix * nor);
+ gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
+}
+
diff --git a/source/blender/draw/engines/clay/shaders/ssao_alchemy.glsl b/source/blender/draw/engines/clay/shaders/ssao_alchemy.glsl
new file mode 100644
index 00000000000..d032fb91c01
--- /dev/null
+++ b/source/blender/draw/engines/clay/shaders/ssao_alchemy.glsl
@@ -0,0 +1,73 @@
+#define ssao_distance matcaps_param[mat_id].ssao_params_var.x
+#define ssao_factor_cavity matcaps_param[mat_id].ssao_params_var.y
+#define ssao_factor_edge matcaps_param[mat_id].ssao_params_var.z
+#define ssao_attenuation matcaps_param[mat_id].ssao_params_var.w
+
+/* from The Alchemy screen-space ambient obscurance algorithm
+ * http://graphics.cs.williams.edu/papers/AlchemyHPG11/VV11AlchemyAO.pdf */
+
+void ssao_factors(in float depth, in vec3 normal, in vec3 position, in vec2 screenco, out float cavities, out float edges)
+{
+ /* take the normalized ray direction here */
+ vec2 rotX = texture2D(ssao_jitter, screenco.xy * jitter_tilling).rg;
+ vec2 rotY = vec2(-rotX.y, rotX.x);
+
+ /* find the offset in screen space by multiplying a point
+ * in camera space at the depth of the point by the projection matrix. */
+ vec2 offset;
+ float homcoord = WinMatrix[2][3] * position.z + WinMatrix[3][3];
+ offset.x = WinMatrix[0][0] * ssao_distance / homcoord;
+ offset.y = WinMatrix[1][1] * ssao_distance / homcoord;
+ /* convert from -1.0...1.0 range to 0.0..1.0 for easy use with texture coordinates */
+ offset *= 0.5;
+
+ cavities = edges = 0.0;
+ int x;
+ int num_samples = int(ssao_samples_num);
+
+ for (x = 0; x < num_samples; x++) {
+ /* TODO : optimisation replace by constant */
+ vec2 dir_sample = texture1D(ssao_samples, (float(x) + 0.5) / ssao_samples_num).rg;
+
+ /* rotate with random direction to get jittered result */
+ vec2 dir_jittered = vec2(dot(dir_sample, rotX), dot(dir_sample, rotY));
+
+ vec2 uvcoords = screenco.xy + dir_jittered * offset;
+
+ if (uvcoords.x > 1.0 || uvcoords.x < 0.0 || uvcoords.y > 1.0 || uvcoords.y < 0.0)
+ continue;
+
+ float depth_new = texture2D(depthtex, uvcoords).r;
+
+ /* Handle Background case */
+ bool is_background = (depth_new == 1.0);
+
+ /* This trick provide good edge effect even if no neighboor is found. */
+ vec3 pos_new = get_view_space_from_depth(uvcoords, (is_background) ? depth : depth_new);
+
+ if (is_background)
+ pos_new.z -= ssao_distance;
+
+ vec3 dir = pos_new - position;
+ float len = length(dir);
+ float f_cavities = dot(dir, normal);
+ float f_edge = -f_cavities;
+ float f_bias = 0.05 * len + 0.0001;
+
+ float attenuation = 1.0 / (len * (1.0 + len * len * ssao_attenuation));
+
+ /* use minor bias here to avoid self shadowing */
+ if (f_cavities > -f_bias)
+ cavities += f_cavities * attenuation;
+
+ if (f_edge > f_bias)
+ edges += f_edge * attenuation;
+ }
+
+ cavities /= ssao_samples_num;
+ edges /= ssao_samples_num;
+
+ /* don't let cavity wash out the surface appearance */
+ cavities = clamp(cavities * ssao_factor_cavity, 0.0, 1.0);
+ edges = edges * ssao_factor_edge;
+}
diff --git a/source/blender/draw/engines/clay/shaders/ssao_groundtruth.glsl b/source/blender/draw/engines/clay/shaders/ssao_groundtruth.glsl
new file mode 100644
index 00000000000..2f29624824e
--- /dev/null
+++ b/source/blender/draw/engines/clay/shaders/ssao_groundtruth.glsl
@@ -0,0 +1,120 @@
+#define ssao_distance matcaps_param[mat_id].ssao_params_var.x
+#define ssao_factor_cavity matcaps_param[mat_id].ssao_params_var.y
+#define ssao_factor_edge matcaps_param[mat_id].ssao_params_var.z
+#define ssao_attenuation matcaps_param[mat_id].ssao_params_var.w
+
+/* Based on Practical Realtime Strategies for Accurate Indirect Occlusion
+ * http://blog.selfshadow.com/publications/s2016-shading-course/activision/s2016_pbs_activision_occlusion.pdf
+ * http://blog.selfshadow.com/publications/s2016-shading-course/activision/s2016_pbs_activision_occlusion.pptx */
+
+#define COSINE_WEIGHTING
+
+float integrate_arc(in float h1, in float h2, in float gamma, in float n_proj_len)
+{
+ float a = 0.0;
+#ifdef COSINE_WEIGHTING
+ float cos_gamma = cos(gamma);
+ float sin_gamma_2 = 2.0 * sin(gamma);
+ a += -cos(2.0 * h1 - gamma) + cos_gamma + h1 * sin_gamma_2;
+ a += -cos(2.0 * h2 - gamma) + cos_gamma + h2 * sin_gamma_2;
+ a *= 0.25; /* 1/4 */
+ a *= n_proj_len;
+#else
+ /* Uniform weighting (slide 59) */
+ a += 1 - cos(h1);
+ a += 1 - cos(h2);
+#endif
+ return a;
+}
+
+float get_max_horizon(in vec2 co, in vec3 x, in vec3 omega_o, in float h)
+{
+ if (co.x > 1.0 || co.x < 0.0 || co.y > 1.0 || co.y < 0.0)
+ return h;
+
+ float depth = texture2D(depthtex, co).r;
+
+ /* Background case */
+ if (depth == 1.0)
+ return h;
+
+ vec3 s = get_view_space_from_depth(co, depth); /* s View coordinate */
+ vec3 omega_s = s - x;
+ float len = length(omega_s);
+
+ if (len < ssao_distance) {
+ omega_s /= len;
+ h = max(h, dot(omega_s, omega_o));
+ }
+ return h;
+}
+
+void ssao_factors(in float depth, in vec3 normal, in vec3 position, in vec2 screenco, out float cavities, out float edges)
+{
+ /* Renaming */
+ vec3 omega_o = -normalize(position); /* viewvec */
+ vec2 x_ = screenco; /* x^ Screen coordinate */
+ vec3 x = position; /* x view space coordinate */
+
+#ifdef SPATIAL_DENOISE
+ float noise_dir = (1.0 / 16.0) * float(((int(gl_FragCoord.x + gl_FragCoord.y) & 0x3) << 2) + (int(gl_FragCoord.x) & 0x3));
+ float noise_offset = (1.0 / 4.0) * float(int(gl_FragCoord.y - gl_FragCoord.x) & 0x3);
+#else
+ float noise_dir = (1.0 / 16.0) * float(((int(gl_FragCoord.x + gl_FragCoord.y) & 0x3) << 2) + (int(gl_FragCoord.x) & 0x3));
+ float noise_offset = (0.5 / 16.0) + (1.0 / 16.0) * float(((int(gl_FragCoord.x - gl_FragCoord.y) & 0x3) << 2) + (int(gl_FragCoord.x) & 0x3));
+#endif
+
+ const float phi_step = 16.0;
+ const float theta_step = 16.0;
+ const float m_pi = 3.14159265358979323846;
+ vec2 pixel_ratio = vec2(screenres.y / screenres.x, 1.0);
+ vec2 pixel_size = vec2(1.0) / screenres.xy;
+ float min_stride = length(pixel_size);
+ float homcco = WinMatrix[2][3] * position.z + WinMatrix[3][3];
+ float n = max(min_stride * theta_step, ssao_distance / homcco); /* Search distance */
+
+ /* Integral over PI */
+ float A = 0.0;
+ for (float i = 0.0; i < phi_step; i++) {
+ float phi = m_pi * ((noise_dir + i) / phi_step);
+
+ vec2 t_phi = vec2(cos(phi), sin(phi)); /* Screen space direction */
+
+ /* Search maximum horizon angles Theta1 and Theta2 */
+ float theta1 = -1.0, theta2 = -1.0; /* init at cos(pi) */
+ for (float j = 0.0; j < theta_step; j++) {
+ vec2 s_ = t_phi * pixel_ratio * n * ((j + noise_offset)/ theta_step); /* s^ Screen coordinate */
+ vec2 co;
+
+ co = x_ + s_;
+ theta1 = get_max_horizon(co, x, omega_o, theta1);
+
+ co = x_ - s_;
+ theta2 = get_max_horizon(co, x, omega_o, theta2);
+ }
+
+ /* (Slide 54) */
+ theta1 = -acos(theta1);
+ theta2 = acos(theta2);
+
+ /* Projecting Normal to Plane P defined by t_phi and omega_o */
+ vec3 h = normalize(cross(vec3(t_phi, 0.0), omega_o)); /* Normal vector to Integration plane */
+ vec3 t = cross(h, omega_o); /* Normal vector to plane */
+ vec3 n_proj = normal - h * dot(normal, h);
+ float n_proj_len = length(n_proj);
+ vec3 n_proj_norm = normalize(n_proj);
+
+ /* Clamping thetas (slide 58) */
+ float gamma = sign(dot(n_proj_norm, t)) * acos(dot(normal, omega_o)); /* Angle between view vec and normal */
+ theta1 = gamma + max(theta1 - gamma, -m_pi * 0.5);
+ theta2 = gamma + min(theta2 - gamma, m_pi * 0.5);
+
+ /* Solving inner integral */
+ A += integrate_arc(theta1, theta2, gamma, n_proj_len);
+ }
+
+ A /= phi_step;
+
+ cavities = 1.0 - A;
+ edges = 0.0;
+} \ No newline at end of file