/* * ***** BEGIN GPL LICENSE BLOCK ***** * * 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. * * The Original Code is Copyright (C) 2007 Blender Foundation. * All rights reserved. * * The Original Code is: all of this file. * * Contributor(s): none yet. * * ***** END GPL LICENSE BLOCK ***** */ /** \file blender/render/intern/source/sss.c * \ingroup render */ /* Possible Improvements: * - add fresnel terms * - adapt Rd table to scale, now with small scale there are a lot of misses? * - possible interesting method: perform sss on all samples in the tree, * and then use those values interpolated somehow later. can also do this * filtering on demand for speed. since we are doing things in screen * space now there is an exact correspondence * - avoid duplicate shading (filtering points in advance, irradiance cache * like lookup?) * - lower resolution samples */ #include #include #include #include /* external modules: */ #include "MEM_guardedalloc.h" #include "BLI_math.h" #include "BLI_blenlib.h" #include "BLI_utildefines.h" #include "BLI_ghash.h" #include "BLI_memarena.h" #include "BLT_translation.h" #include "DNA_material_types.h" #include "BKE_global.h" #include "BKE_main.h" #include "BKE_scene.h" /* this module */ #include "render_types.h" #include "sss.h" /* Generic Multiple Scattering API */ /* Relevant papers: * [1] A Practical Model for Subsurface Light Transport * [2] A Rapid Hierarchical Rendering Technique for Translucent Materials * [3] Efficient Rendering of Local Subsurface Scattering * [4] Implementing a skin BSSRDF (or several...) */ /* Defines */ #define RD_TABLE_RANGE 100.0f #define RD_TABLE_RANGE_2 10000.0f #define RD_TABLE_SIZE 10000 #define MAX_OCTREE_NODE_POINTS 8 #define MAX_OCTREE_DEPTH 15 /* Struct Definitions */ struct ScatterSettings { float eta; /* index of refraction */ float sigma_a; /* absorption coefficient */ float sigma_s_; /* reduced scattering coefficient */ float sigma_t_; /* reduced extinction coefficient */ float sigma; /* effective extinction coefficient */ float Fdr; /* diffuse fresnel reflectance */ float D; /* diffusion constant */ float A; float alpha_; /* reduced albedo */ float zr; /* distance of virtual lightsource above surface */ float zv; /* distance of virtual lightsource below surface */ float ld; /* mean free path */ float ro; /* diffuse reflectance */ float color; float invsigma_t_; float frontweight; float backweight; float *tableRd; /* lookup table to avoid computing Rd */ float *tableRd2; /* lookup table to avoid computing Rd for bigger values */ }; typedef struct ScatterPoint { float co[3]; float rad[3]; float area; int back; } ScatterPoint; typedef struct ScatterNode { float co[3]; float rad[3]; float backrad[3]; float area, backarea; int totpoint; ScatterPoint *points; float split[3]; struct ScatterNode *child[8]; } ScatterNode; struct ScatterTree { MemArena *arena; ScatterSettings *ss[3]; float error, scale; ScatterNode *root; ScatterPoint *points; ScatterPoint **refpoints; ScatterPoint **tmppoints; int totpoint; float min[3], max[3]; }; typedef struct ScatterResult { float rad[3]; float backrad[3]; float rdsum[3]; float backrdsum[3]; } ScatterResult; /* Functions for BSSRDF reparametrization in to more intuitive parameters, * see [2] section 4 for more info. */ static float f_Rd(float alpha_, float A, float ro) { float sq; sq = sqrtf(3.0f * (1.0f - alpha_)); return (alpha_/2.0f)*(1.0f + expf((-4.0f/3.0f)*A*sq))*expf(-sq) - ro; } static float compute_reduced_albedo(ScatterSettings *ss) { const float tolerance= 1e-8; const int max_iteration_count= 20; float d, fsub, xn_1= 0.0f, xn= 1.0f, fxn, fxn_1; int i; /* use secant method to compute reduced albedo using Rd function inverse * with a given reflectance */ fxn= f_Rd(xn, ss->A, ss->ro); fxn_1= f_Rd(xn_1, ss->A, ss->ro); for (i= 0; i < max_iteration_count; i++) { fsub= (fxn - fxn_1); if (fabsf(fsub) < tolerance) break; d= ((xn - xn_1)/fsub)*fxn; if (fabsf(d) < tolerance) break; xn_1= xn; fxn_1= fxn; xn= xn - d; if (xn > 1.0f) xn= 1.0f; if (xn_1 > 1.0f) xn_1= 1.0f; fxn= f_Rd(xn, ss->A, ss->ro); } /* avoid division by zero later */ if (xn <= 0.0f) xn= 0.00001f; return xn; } /* Exponential falloff functions */ static float Rd_rsquare(ScatterSettings *ss, float rr) { float sr, sv, Rdr, Rdv; sr = sqrtf(rr + ss->zr * ss->zr); sv = sqrtf(rr + ss->zv * ss->zv); Rdr= ss->zr*(1.0f + ss->sigma*sr)*expf(-ss->sigma*sr)/(sr*sr*sr); Rdv= ss->zv*(1.0f + ss->sigma*sv)*expf(-ss->sigma*sv)/(sv*sv*sv); return /*ss->alpha_*/(1.0f/(4.0f*(float)M_PI))*(Rdr + Rdv); } static float Rd(ScatterSettings *ss, float r) { return Rd_rsquare(ss, r*r); } /* table lookups for Rd. this avoids expensive exp calls. we use two * separate tables as well for lower and higher numbers to improve * precision, since the number are poorly distributed because we do * a lookup with the squared distance for smaller distances, saving * another sqrt. */ static void approximate_Rd_rgb(ScatterSettings **ss, float rr, float *rd) { float indexf, t, idxf; int index; if (rr > (RD_TABLE_RANGE_2 * RD_TABLE_RANGE_2)) { /* pass */ } else if (rr > RD_TABLE_RANGE) { rr = sqrtf(rr); indexf= rr*(RD_TABLE_SIZE/RD_TABLE_RANGE_2); index= (int)indexf; idxf= (float)index; t= indexf - idxf; if (index >= 0 && index < RD_TABLE_SIZE) { rd[0]= (ss[0]->tableRd2[index]*(1-t) + ss[0]->tableRd2[index+1]*t); rd[1]= (ss[1]->tableRd2[index]*(1-t) + ss[1]->tableRd2[index+1]*t); rd[2]= (ss[2]->tableRd2[index]*(1-t) + ss[2]->tableRd2[index+1]*t); return; } } else { indexf= rr*(RD_TABLE_SIZE/RD_TABLE_RANGE); index= (int)indexf; idxf= (float)index; t= indexf - idxf; if (index >= 0 && index < RD_TABLE_SIZE) { rd[0]= (ss[0]->tableRd[index]*(1-t) + ss[0]->tableRd[index+1]*t); rd[1]= (ss[1]->tableRd[index]*(1-t) + ss[1]->tableRd[index+1]*t); rd[2]= (ss[2]->tableRd[index]*(1-t) + ss[2]->tableRd[index+1]*t); return; } } /* fallback to slow Rd computation */ rd[0]= Rd_rsquare(ss[0], rr); rd[1]= Rd_rsquare(ss[1], rr); rd[2]= Rd_rsquare(ss[2], rr); } static void build_Rd_table(ScatterSettings *ss) { float r; int i, size = RD_TABLE_SIZE+1; ss->tableRd= MEM_mallocN(sizeof(float)*size, "scatterTableRd"); ss->tableRd2= MEM_mallocN(sizeof(float)*size, "scatterTableRd"); for (i= 0; i < size; i++) { r= i*(RD_TABLE_RANGE/RD_TABLE_SIZE); #if 0 if (r < ss->invsigma_t_*ss->invsigma_t_) { r= ss->invsigma_t_*ss->invsigma_t_; } #endif ss->tableRd[i]= Rd(ss, sqrtf(r)); r= i*(RD_TABLE_RANGE_2/RD_TABLE_SIZE); #if 0 if (r < ss->invsigma_t_) { r= ss->invsigma_t_; } #endif ss->tableRd2[i]= Rd(ss, r); } } ScatterSettings *scatter_settings_new(float refl, float radius, float ior, float reflfac, float frontweight, float backweight) { ScatterSettings *ss; ss= MEM_callocN(sizeof(ScatterSettings), "ScatterSettings"); /* see [1] and [3] for these formulas */ ss->eta= ior; ss->Fdr= -1.440f/ior*ior + 0.710f/ior + 0.668f + 0.0636f*ior; ss->A= (1.0f + ss->Fdr)/(1.0f - ss->Fdr); ss->ld= radius; ss->ro= min_ff(refl, 0.99f); ss->color= ss->ro*reflfac + (1.0f-reflfac); ss->alpha_= compute_reduced_albedo(ss); ss->sigma= 1.0f/ss->ld; ss->sigma_t_= ss->sigma/sqrtf(3.0f*(1.0f - ss->alpha_)); ss->sigma_s_= ss->alpha_*ss->sigma_t_; ss->sigma_a= ss->sigma_t_ - ss->sigma_s_; ss->D= 1.0f/(3.0f*ss->sigma_t_); ss->zr= 1.0f/ss->sigma_t_; ss->zv= ss->zr + 4.0f*ss->A*ss->D; ss->invsigma_t_= 1.0f/ss->sigma_t_; ss->frontweight= frontweight; ss->backweight= backweight; /* precompute a table of Rd values for quick lookup */ build_Rd_table(ss); return ss; } void scatter_settings_free(ScatterSettings *ss) { MEM_freeN(ss->tableRd); MEM_freeN(ss->tableRd2); MEM_freeN(ss); } /* Hierarchical method as in [2]. */ /* traversal */ #define SUBNODE_INDEX(co, split) \ ((co[0]>=split[0]) + (co[1]>=split[1])*2 + (co[2]>=split[2])*4) static void add_radiance(ScatterTree *tree, float *frontrad, float *backrad, float area, float backarea, float rr, ScatterResult *result) { float rd[3], frontrd[3], backrd[3]; approximate_Rd_rgb(tree->ss, rr, rd); if (frontrad && area) { frontrd[0] = rd[0]*area; frontrd[1] = rd[1]*area; frontrd[2] = rd[2]*area; result->rad[0] += frontrad[0]*frontrd[0]; result->rad[1] += frontrad[1]*frontrd[1]; result->rad[2] += frontrad[2]*frontrd[2]; result->rdsum[0] += frontrd[0]; result->rdsum[1] += frontrd[1]; result->rdsum[2] += frontrd[2]; } if (backrad && backarea) { backrd[0] = rd[0]*backarea; backrd[1] = rd[1]*backarea; backrd[2] = rd[2]*backarea; result->backrad[0] += backrad[0]*backrd[0]; result->backrad[1] += backrad[1]*backrd[1]; result->backrad[2] += backrad[2]*backrd[2]; result->backrdsum[0] += backrd[0]; result->backrdsum[1] += backrd[1]; result->backrdsum[2] += backrd[2]; } } static void traverse_octree(ScatterTree *tree, ScatterNode *node, const float co[3], int self, ScatterResult *result) { float sub[3], dist; int i, index = 0; if (node->totpoint > 0) { /* leaf - add radiance from all samples */ for (i=0; itotpoint; i++) { ScatterPoint *p= &node->points[i]; sub_v3_v3v3(sub, co, p->co); dist= dot_v3v3(sub, sub); if (p->back) add_radiance(tree, NULL, p->rad, 0.0f, p->area, dist, result); else add_radiance(tree, p->rad, NULL, p->area, 0.0f, dist, result); } } else { /* branch */ if (self) index = SUBNODE_INDEX(co, node->split); for (i=0; i<8; i++) { if (node->child[i]) { ScatterNode *subnode= node->child[i]; if (self && index == i) { /* always traverse node containing the point */ traverse_octree(tree, subnode, co, 1, result); } else { /* decide subnode traversal based on maximum solid angle */ sub_v3_v3v3(sub, co, subnode->co); dist= dot_v3v3(sub, sub); /* actually area/dist > error, but this avoids division */ if (subnode->area+subnode->backarea>tree->error*dist) { traverse_octree(tree, subnode, co, 0, result); } else { add_radiance(tree, subnode->rad, subnode->backrad, subnode->area, subnode->backarea, dist, result); } } } } } } static void compute_radiance(ScatterTree *tree, const float co[3], float *rad) { ScatterResult result; float rdsum[3], backrad[3], backrdsum[3]; memset(&result, 0, sizeof(result)); traverse_octree(tree, tree->root, co, 1, &result); /* the original paper doesn't do this, but we normalize over the * sampled area and multiply with the reflectance. this is because * our point samples are incomplete, there are no samples on parts * of the mesh not visible from the camera. this can not only make * it darker, but also lead to ugly color shifts */ mul_v3_fl(result.rad, tree->ss[0]->frontweight); mul_v3_fl(result.backrad, tree->ss[0]->backweight); copy_v3_v3(rad, result.rad); add_v3_v3v3(backrad, result.rad, result.backrad); copy_v3_v3(rdsum, result.rdsum); add_v3_v3v3(backrdsum, result.rdsum, result.backrdsum); if (rdsum[0] > 1e-16f) rad[0]= tree->ss[0]->color*rad[0]/rdsum[0]; if (rdsum[1] > 1e-16f) rad[1]= tree->ss[1]->color*rad[1]/rdsum[1]; if (rdsum[2] > 1e-16f) rad[2]= tree->ss[2]->color*rad[2]/rdsum[2]; if (backrdsum[0] > 1e-16f) backrad[0]= tree->ss[0]->color*backrad[0]/backrdsum[0]; if (backrdsum[1] > 1e-16f) backrad[1]= tree->ss[1]->color*backrad[1]/backrdsum[1]; if (backrdsum[2] > 1e-16f) backrad[2]= tree->ss[2]->color*backrad[2]/backrdsum[2]; rad[0]= MAX2(rad[0], backrad[0]); rad[1]= MAX2(rad[1], backrad[1]); rad[2]= MAX2(rad[2], backrad[2]); } /* building */ static void sum_leaf_radiance(ScatterTree *UNUSED(tree), ScatterNode *node) { ScatterPoint *p; float rad, totrad= 0.0f, inv; int i; node->co[0]= node->co[1]= node->co[2]= 0.0; node->rad[0]= node->rad[1]= node->rad[2]= 0.0; node->backrad[0]= node->backrad[1]= node->backrad[2]= 0.0; /* compute total rad, rad weighted average position, * and total area */ for (i=0; itotpoint; i++) { p= &node->points[i]; rad= p->area*fabsf(p->rad[0] + p->rad[1] + p->rad[2]); totrad += rad; node->co[0] += rad*p->co[0]; node->co[1] += rad*p->co[1]; node->co[2] += rad*p->co[2]; if (p->back) { node->backrad[0] += p->rad[0]*p->area; node->backrad[1] += p->rad[1]*p->area; node->backrad[2] += p->rad[2]*p->area; node->backarea += p->area; } else { node->rad[0] += p->rad[0]*p->area; node->rad[1] += p->rad[1]*p->area; node->rad[2] += p->rad[2]*p->area; node->area += p->area; } } if (node->area > 1e-16f) { inv= 1.0f/node->area; node->rad[0] *= inv; node->rad[1] *= inv; node->rad[2] *= inv; } if (node->backarea > 1e-16f) { inv= 1.0f/node->backarea; node->backrad[0] *= inv; node->backrad[1] *= inv; node->backrad[2] *= inv; } if (totrad > 1e-16f) { inv= 1.0f/totrad; node->co[0] *= inv; node->co[1] *= inv; node->co[2] *= inv; } else { /* make sure that if radiance is 0.0f, we still have these points in * the tree at a good position, they count for rdsum too */ for (i=0; itotpoint; i++) { p= &node->points[i]; node->co[0] += p->co[0]; node->co[1] += p->co[1]; node->co[2] += p->co[2]; } node->co[0] /= node->totpoint; node->co[1] /= node->totpoint; node->co[2] /= node->totpoint; } } static void sum_branch_radiance(ScatterTree *UNUSED(tree), ScatterNode *node) { ScatterNode *subnode; float rad, totrad= 0.0f, inv; int i, totnode; node->co[0]= node->co[1]= node->co[2]= 0.0; node->rad[0]= node->rad[1]= node->rad[2]= 0.0; node->backrad[0]= node->backrad[1]= node->backrad[2]= 0.0; /* compute total rad, rad weighted average position, * and total area */ for (i=0; i<8; i++) { if (node->child[i] == NULL) continue; subnode= node->child[i]; rad= subnode->area*fabsf(subnode->rad[0] + subnode->rad[1] + subnode->rad[2]); rad += subnode->backarea*fabsf(subnode->backrad[0] + subnode->backrad[1] + subnode->backrad[2]); totrad += rad; node->co[0] += rad*subnode->co[0]; node->co[1] += rad*subnode->co[1]; node->co[2] += rad*subnode->co[2]; node->rad[0] += subnode->rad[0]*subnode->area; node->rad[1] += subnode->rad[1]*subnode->area; node->rad[2] += subnode->rad[2]*subnode->area; node->backrad[0] += subnode->backrad[0]*subnode->backarea; node->backrad[1] += subnode->backrad[1]*subnode->backarea; node->backrad[2] += subnode->backrad[2]*subnode->backarea; node->area += subnode->area; node->backarea += subnode->backarea; } if (node->area > 1e-16f) { inv= 1.0f/node->area; node->rad[0] *= inv; node->rad[1] *= inv; node->rad[2] *= inv; } if (node->backarea > 1e-16f) { inv= 1.0f/node->backarea; node->backrad[0] *= inv; node->backrad[1] *= inv; node->backrad[2] *= inv; } if (totrad > 1e-16f) { inv= 1.0f/totrad; node->co[0] *= inv; node->co[1] *= inv; node->co[2] *= inv; } else { /* make sure that if radiance is 0.0f, we still have these points in * the tree at a good position, they count for rdsum too */ totnode= 0; for (i=0; i<8; i++) { if (node->child[i]) { subnode= node->child[i]; node->co[0] += subnode->co[0]; node->co[1] += subnode->co[1]; node->co[2] += subnode->co[2]; totnode++; } } node->co[0] /= totnode; node->co[1] /= totnode; node->co[2] /= totnode; } } static void sum_radiance(ScatterTree *tree, ScatterNode *node) { if (node->totpoint > 0) { sum_leaf_radiance(tree, node); } else { int i; for (i=0; i<8; i++) if (node->child[i]) sum_radiance(tree, node->child[i]); sum_branch_radiance(tree, node); } } static void subnode_middle(int i, float *mid, float *subsize, float *submid) { int x= i & 1, y= i & 2, z= i & 4; submid[0]= mid[0] + ((x)? subsize[0]: -subsize[0]); submid[1]= mid[1] + ((y)? subsize[1]: -subsize[1]); submid[2]= mid[2] + ((z)? subsize[2]: -subsize[2]); } static void create_octree_node(ScatterTree *tree, ScatterNode *node, float *mid, float *size, ScatterPoint **refpoints, int depth) { ScatterNode *subnode; ScatterPoint **subrefpoints, **tmppoints= tree->tmppoints; int index, nsize[8], noffset[8], i, subco, used_nodes, usedi; float submid[3], subsize[3]; /* stopping condition */ if (node->totpoint <= MAX_OCTREE_NODE_POINTS || depth == MAX_OCTREE_DEPTH) { for (i=0; itotpoint; i++) node->points[i]= *(refpoints[i]); return; } subsize[0]= size[0]*0.5f; subsize[1]= size[1]*0.5f; subsize[2]= size[2]*0.5f; node->split[0]= mid[0]; node->split[1]= mid[1]; node->split[2]= mid[2]; memset(nsize, 0, sizeof(nsize)); memset(noffset, 0, sizeof(noffset)); /* count points in subnodes */ for (i=0; itotpoint; i++) { index= SUBNODE_INDEX(refpoints[i]->co, node->split); tmppoints[i]= refpoints[i]; nsize[index]++; } /* here we check if only one subnode is used. if this is the case, we don't * create a new node, but rather call this function again, with different * size and middle position for the same node. */ for (usedi=0, used_nodes=0, i=0; i<8; i++) { if (nsize[i]) { used_nodes++; usedi = i; } if (i != 0) noffset[i]= noffset[i-1]+nsize[i-1]; } if (used_nodes <= 1) { subnode_middle(usedi, mid, subsize, submid); create_octree_node(tree, node, submid, subsize, refpoints, depth+1); return; } /* reorder refpoints by subnode */ for (i=0; itotpoint; i++) { index= SUBNODE_INDEX(tmppoints[i]->co, node->split); refpoints[noffset[index]]= tmppoints[i]; noffset[index]++; } /* create subnodes */ for (subco=0, i=0; i<8; subco+=nsize[i], i++) { if (nsize[i] > 0) { subnode= BLI_memarena_alloc(tree->arena, sizeof(ScatterNode)); node->child[i]= subnode; subnode->points= node->points + subco; subnode->totpoint= nsize[i]; subrefpoints= refpoints + subco; subnode_middle(i, mid, subsize, submid); create_octree_node(tree, subnode, submid, subsize, subrefpoints, depth+1); } else node->child[i]= NULL; } node->points= NULL; node->totpoint= 0; } /* public functions */ ScatterTree *scatter_tree_new(ScatterSettings *ss[3], float scale, float error, float (*co)[3], float (*color)[3], float *area, int totpoint) { ScatterTree *tree; ScatterPoint *points, **refpoints; int i; /* allocate tree */ tree= MEM_callocN(sizeof(ScatterTree), "ScatterTree"); tree->scale= scale; tree->error= error; tree->totpoint= totpoint; tree->ss[0]= ss[0]; tree->ss[1]= ss[1]; tree->ss[2]= ss[2]; points = MEM_callocN(sizeof(ScatterPoint) * totpoint, "ScatterPoints"); refpoints = MEM_callocN(sizeof(ScatterPoint *) * totpoint, "ScatterRefPoints"); tree->points= points; tree->refpoints= refpoints; /* build points */ INIT_MINMAX(tree->min, tree->max); for (i=0; iscale*tree->scale); points[i].back= (area[i] < 0.0f); mul_v3_fl(points[i].co, 1.0f / tree->scale); minmax_v3v3_v3(tree->min, tree->max, points[i].co); refpoints[i]= points + i; } return tree; } void scatter_tree_build(ScatterTree *tree) { ScatterPoint *newpoints, **tmppoints; float mid[3], size[3]; int totpoint= tree->totpoint; newpoints = MEM_callocN(sizeof(ScatterPoint) * totpoint, "ScatterPoints"); tmppoints = MEM_callocN(sizeof(ScatterPoint *) * totpoint, "ScatterTmpPoints"); tree->tmppoints= tmppoints; tree->arena= BLI_memarena_new(0x8000 * sizeof(ScatterNode), "sss tree arena"); BLI_memarena_use_calloc(tree->arena); /* build tree */ tree->root= BLI_memarena_alloc(tree->arena, sizeof(ScatterNode)); tree->root->points= newpoints; tree->root->totpoint= totpoint; mid[0]= (tree->min[0]+tree->max[0])*0.5f; mid[1]= (tree->min[1]+tree->max[1])*0.5f; mid[2]= (tree->min[2]+tree->max[2])*0.5f; size[0]= (tree->max[0]-tree->min[0])*0.5f; size[1]= (tree->max[1]-tree->min[1])*0.5f; size[2]= (tree->max[2]-tree->min[2])*0.5f; create_octree_node(tree, tree->root, mid, size, tree->refpoints, 0); MEM_freeN(tree->points); MEM_freeN(tree->refpoints); MEM_freeN(tree->tmppoints); tree->refpoints= NULL; tree->tmppoints= NULL; tree->points= newpoints; /* sum radiance at nodes */ sum_radiance(tree, tree->root); } void scatter_tree_sample(ScatterTree *tree, const float co[3], float color[3]) { float sco[3]; copy_v3_v3(sco, co); mul_v3_fl(sco, 1.0f / tree->scale); compute_radiance(tree, sco, color); } void scatter_tree_free(ScatterTree *tree) { if (tree->arena) BLI_memarena_free(tree->arena); if (tree->points) MEM_freeN(tree->points); if (tree->refpoints) MEM_freeN(tree->refpoints); MEM_freeN(tree); } /* Internal Renderer API */ /* sss tree building */ typedef struct SSSData { ScatterTree *tree; ScatterSettings *ss[3]; } SSSData; typedef struct SSSPoints { struct SSSPoints *next, *prev; float (*co)[3]; float (*color)[3]; float *area; int totpoint; } SSSPoints; static void sss_create_tree_mat(Render *re, Material *mat) { SSSPoints *p; RenderResult *rr; ListBase points; float (*co)[3] = NULL, (*color)[3] = NULL, *area = NULL; int totpoint = 0, osa, osaflag, frsflag, partsdone; if (re->test_break(re->tbh)) return; points.first= points.last= NULL; /* TODO: this is getting a bit ugly, copying all those variables and * setting them back, maybe we need to create our own Render? */ /* do SSS preprocessing render */ BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); rr= re->result; osa= re->osa; osaflag= re->r.mode & R_OSA; frsflag= re->r.mode & R_EDGE_FRS; partsdone= re->i.partsdone; re->osa= 0; re->r.mode &= ~(R_OSA | R_EDGE_FRS); re->sss_points= &points; re->sss_mat= mat; re->i.partsdone = 0; if (!(re->r.scemode & (R_BUTS_PREVIEW|R_VIEWPORT_PREVIEW))) re->result= NULL; BLI_rw_mutex_unlock(&re->resultmutex); RE_TileProcessor(re); BLI_rw_mutex_lock(&re->resultmutex, THREAD_LOCK_WRITE); if (!(re->r.scemode & (R_BUTS_PREVIEW|R_VIEWPORT_PREVIEW))) { RE_FreeRenderResult(re->result); re->result= rr; } BLI_rw_mutex_unlock(&re->resultmutex); re->i.partsdone= partsdone; re->sss_mat= NULL; re->sss_points= NULL; re->osa= osa; if (osaflag) re->r.mode |= R_OSA; if (frsflag) re->r.mode |= R_EDGE_FRS; /* no points? no tree */ if (!points.first) return; /* merge points together into a single buffer */ if (!re->test_break(re->tbh)) { for (totpoint=0, p=points.first; p; p=p->next) totpoint += p->totpoint; co= MEM_mallocN(sizeof(*co)*totpoint, "SSSCo"); color= MEM_mallocN(sizeof(*color)*totpoint, "SSSColor"); area= MEM_mallocN(sizeof(*area)*totpoint, "SSSArea"); for (totpoint=0, p=points.first; p; p=p->next) { memcpy(co+totpoint, p->co, sizeof(*co)*p->totpoint); memcpy(color+totpoint, p->color, sizeof(*color)*p->totpoint); memcpy(area+totpoint, p->area, sizeof(*area)*p->totpoint); totpoint += p->totpoint; } } /* free points */ for (p=points.first; p; p=p->next) { MEM_freeN(p->co); MEM_freeN(p->color); MEM_freeN(p->area); } BLI_freelistN(&points); /* build tree */ if (!re->test_break(re->tbh)) { SSSData *sss= MEM_callocN(sizeof(*sss), "SSSData"); float ior= mat->sss_ior, cfac= mat->sss_colfac; const float *radius = mat->sss_radius; float fw= mat->sss_front, bw= mat->sss_back; float error = mat->sss_error; error= get_render_aosss_error(&re->r, error); if ((re->r.scemode & (R_BUTS_PREVIEW|R_VIEWPORT_PREVIEW)) && error < 0.5f) error= 0.5f; sss->ss[0]= scatter_settings_new(mat->sss_col[0], radius[0], ior, cfac, fw, bw); sss->ss[1]= scatter_settings_new(mat->sss_col[1], radius[1], ior, cfac, fw, bw); sss->ss[2]= scatter_settings_new(mat->sss_col[2], radius[2], ior, cfac, fw, bw); sss->tree= scatter_tree_new(sss->ss, mat->sss_scale, error, co, color, area, totpoint); MEM_freeN(co); MEM_freeN(color); MEM_freeN(area); scatter_tree_build(sss->tree); BLI_ghash_insert(re->sss_hash, mat, sss); } else { if (co) MEM_freeN(co); if (color) MEM_freeN(color); if (area) MEM_freeN(area); } } void sss_add_points(Render *re, float (*co)[3], float (*color)[3], float *area, int totpoint) { SSSPoints *p; if (totpoint > 0) { p= MEM_callocN(sizeof(SSSPoints), "SSSPoints"); p->co= co; p->color= color; p->area= area; p->totpoint= totpoint; BLI_lock_thread(LOCK_CUSTOM1); BLI_addtail(re->sss_points, p); BLI_unlock_thread(LOCK_CUSTOM1); } } static void sss_free_tree(SSSData *sss) { scatter_tree_free(sss->tree); scatter_settings_free(sss->ss[0]); scatter_settings_free(sss->ss[1]); scatter_settings_free(sss->ss[2]); MEM_freeN(sss); } /* public functions */ void make_sss_tree(Render *re) { Material *mat; bool infostr_set = false; const char *prevstr = NULL; free_sss(re); re->sss_hash= BLI_ghash_ptr_new("make_sss_tree gh"); re->stats_draw(re->sdh, &re->i); for (mat= re->main->mat.first; mat; mat= mat->id.next) { if (mat->id.us && (mat->flag & MA_IS_USED) && (mat->sss_flag & MA_DIFF_SSS)) { if (!infostr_set) { prevstr = re->i.infostr; re->i.infostr = IFACE_("SSS preprocessing"); infostr_set = true; } sss_create_tree_mat(re, mat); } } /* XXX preview exception */ /* localizing preview render data is not fun for node trees :( */ if (re->main!=G.main) { for (mat= G.main->mat.first; mat; mat= mat->id.next) { if (mat->id.us && (mat->flag & MA_IS_USED) && (mat->sss_flag & MA_DIFF_SSS)) { if (!infostr_set) { prevstr = re->i.infostr; re->i.infostr = IFACE_("SSS preprocessing"); infostr_set = true; } sss_create_tree_mat(re, mat); } } } if (infostr_set) re->i.infostr = prevstr; } void free_sss(Render *re) { if (re->sss_hash) { GHashIterator gh_iter; GHASH_ITER (gh_iter, re->sss_hash) { sss_free_tree(BLI_ghashIterator_getValue(&gh_iter)); } BLI_ghash_free(re->sss_hash, NULL, NULL); re->sss_hash= NULL; } } int sample_sss(Render *re, Material *mat, const float co[3], float color[3]) { if (re->sss_hash) { SSSData *sss= BLI_ghash_lookup(re->sss_hash, mat); if (sss) { scatter_tree_sample(sss->tree, co, color); return 1; } else { color[0]= 0.0f; color[1]= 0.0f; color[2]= 0.0f; } } return 0; } int sss_pass_done(struct Render *re, struct Material *mat) { return ((re->flag & R_BAKING) || !(re->r.mode & R_SSS) || (re->sss_hash && BLI_ghash_lookup(re->sss_hash, mat))); }