diff options
author | Brecht Van Lommel <brechtvanlommel@pandora.be> | 2007-05-04 01:37:52 +0400 |
---|---|---|
committer | Brecht Van Lommel <brechtvanlommel@pandora.be> | 2007-05-04 01:37:52 +0400 |
commit | 3a8c6c81d84f5ee6b7ae7fb8cfcba65736f08908 (patch) | |
tree | 4a2f46b430daa12522db27d465f392f79a6a2e2c /source/blender/render/intern | |
parent | 4ec6f1b4e4c5fd379559691307ff776bcda8e10f (diff) |
Subsurface scattering:
Documentation on the settings, known limitations and implementation
info can be found here:
http://www.blender.org/development/current-projects/changes-since-243/subsurface-scattering/
Diffstat (limited to 'source/blender/render/intern')
-rw-r--r-- | source/blender/render/intern/include/render_types.h | 17 | ||||
-rw-r--r-- | source/blender/render/intern/include/rendercore.h | 2 | ||||
-rw-r--r-- | source/blender/render/intern/include/sss.h | 65 | ||||
-rw-r--r-- | source/blender/render/intern/include/zbuf.h | 4 | ||||
-rw-r--r-- | source/blender/render/intern/source/convertblender.c | 8 | ||||
-rw-r--r-- | source/blender/render/intern/source/envmap.c | 2 | ||||
-rw-r--r-- | source/blender/render/intern/source/pipeline.c | 17 | ||||
-rw-r--r-- | source/blender/render/intern/source/rendercore.c | 293 | ||||
-rw-r--r-- | source/blender/render/intern/source/renderdatabase.c | 9 | ||||
-rw-r--r-- | source/blender/render/intern/source/shadeinput.c | 8 | ||||
-rw-r--r-- | source/blender/render/intern/source/shadeoutput.c | 63 | ||||
-rw-r--r-- | source/blender/render/intern/source/sss.c | 984 | ||||
-rw-r--r-- | source/blender/render/intern/source/zbuf.c | 160 |
13 files changed, 1599 insertions, 33 deletions
diff --git a/source/blender/render/intern/include/render_types.h b/source/blender/render/intern/include/render_types.h index 609c391d599..45aff8e8b60 100644 --- a/source/blender/render/intern/include/render_types.h +++ b/source/blender/render/intern/include/render_types.h @@ -68,9 +68,12 @@ typedef struct RenderPart /* result of part rendering */ RenderResult *result; - int *rectp; /* polygon index table */ + int *rectp; /* polygon index table */ int *rectz; /* zbuffer */ long *rectdaps; /* delta acum buffer for pixel structs */ + int *rectbackp; /* polygon index table for backside sss */ + int *rectbackz; /* zbuffer for backside sss */ + long *rectall; /* buffer for all faces for sss */ rcti disprect; /* part coordinates within total picture */ int rectx, recty; /* the size */ @@ -169,6 +172,10 @@ struct Render struct GHash *orco_hash; + struct GHash *sss_hash; + ListBase *sss_points; + struct Material *sss_mat; + ListBase customdata_names; /* arena for allocating data for use during render, for @@ -262,7 +269,7 @@ typedef struct VlakRen { unsigned int lay; float n[3]; struct Material *mat; - char snproj, puno; + char noflag, puno; char flag, ec; RadFace *radface; Object *ob; @@ -393,7 +400,11 @@ typedef struct LampRen { /* vertex normals are tangent or view-corrected vector, for hair strands */ #define R_TANGENT 128 - +/* vlakren->noflag (char) */ +#define R_SNPROJ_X 1 +#define R_SNPROJ_Y 2 +#define R_SNPROJ_Z 4 +#define R_FLIPPED_NO 8 diff --git a/source/blender/render/intern/include/rendercore.h b/source/blender/render/intern/include/rendercore.h index 9f22480f0cb..651fd423e5d 100644 --- a/source/blender/render/intern/include/rendercore.h +++ b/source/blender/render/intern/include/rendercore.h @@ -87,6 +87,8 @@ void zbufshadeDA(void); /* Delta Accum Pixel Struct */ void zbufshade_tile(struct RenderPart *pa); void zbufshadeDA_tile(struct RenderPart *pa); +void zbufshade_sss_tile(struct RenderPart *pa); + /* -------- ray.c ------- */ extern void freeoctree(Render *re); diff --git a/source/blender/render/intern/include/sss.h b/source/blender/render/intern/include/sss.h new file mode 100644 index 00000000000..660fb544227 --- /dev/null +++ b/source/blender/render/intern/include/sss.h @@ -0,0 +1,65 @@ +/** + * $Id$ + * + * ***** 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 ***** + */ + +#ifndef SSS_H +#define SSS_H + +/* Generic multiple scattering API */ + +struct ScatterSettings; +typedef struct ScatterSettings ScatterSettings; + +struct ScatterTree; +typedef struct ScatterTree ScatterTree; + +ScatterSettings *scatter_settings_new(float refl, float radius, float ior, + float reflfac, float frontweight, float backweight); +void scatter_settings_free(ScatterSettings *ss); + +ScatterTree *scatter_tree_new(ScatterSettings *ss[3], float scale, float error, + float (*co)[3], float (*color)[3], float *area, int totpoint); +void scatter_tree_build(ScatterTree *tree); +void scatter_tree_sample(ScatterTree *tree, float *co, float *color); +void scatter_tree_free(ScatterTree *tree); + +/* Internal renderer API */ + +struct Render; +struct Material; +struct VlakRen; + +void make_sss_tree(struct Render *re); +void sss_add_points(Render *re, float (*co)[3], float (*color)[3], float *area, int totpoint); +void free_sss(struct Render *re); + +int sample_sss(struct Render *re, struct Material *mat, float *co, float *col); +int has_sss_tree(struct Render *re, struct Material *mat); + +#endif /*SSS_H*/ + diff --git a/source/blender/render/intern/include/zbuf.h b/source/blender/render/intern/include/zbuf.h index c308ae25bce..ed3f93adfdd 100644 --- a/source/blender/render/intern/include/zbuf.h +++ b/source/blender/render/intern/include/zbuf.h @@ -51,6 +51,7 @@ void zbuffer_shadow(struct Render *re, struct LampRen *lar, int *rectz, int size void zbuffer_solid(struct RenderPart *pa, unsigned int layer, short layflag); unsigned short *zbuffer_transp_shade(struct RenderPart *pa, struct RenderLayer *rl, float *pass); void convert_zbuf_to_distbuf(struct RenderPart *pa, struct RenderLayer *rl); +void zbuffer_sss(RenderPart *pa, unsigned int lay, void *handle, void (*func)(void *, int, int, int, int)); typedef struct APixstr { unsigned short mask[4]; /* jitter mask */ @@ -85,6 +86,9 @@ typedef struct ZSpan { int polygon_offset; /* offset in Z */ float shad_alpha; /* copy from material, used by irregular shadbuf */ int mask, apsmcounter; /* in use by apixbuf */ + + void *sss_handle; /* used by sss */ + void (*sss_func)(void *, int, int, int, int); void (*zbuffunc)(struct ZSpan *, int, float *, float *, float *, float *); void (*zbuflinefunc)(struct ZSpan *, int, float *, float *); diff --git a/source/blender/render/intern/source/convertblender.c b/source/blender/render/intern/source/convertblender.c index e93a509efd1..afa9aa0b2e4 100644 --- a/source/blender/render/intern/source/convertblender.c +++ b/source/blender/render/intern/source/convertblender.c @@ -103,6 +103,7 @@ #include "shadbuf.h" #include "shading.h" #include "texture.h" +#include "sss.h" #include "zbuf.h" #ifndef DISABLE_YAFRAY /* disable yafray */ @@ -2990,6 +2991,8 @@ void RE_Database_Free(Render *re) } if(re->r.mode & R_RAYTRACE) freeoctree(re); + + free_sss(re); re->totvlak=re->totvert=re->totlamp=re->tothalo= 0; re->i.convertdone= 0; @@ -3480,6 +3483,11 @@ void RE_Database_FromScene(Render *re, Scene *scene, int use_camera_view) if(!re->test_break()) project_renderdata(re, projectverto, re->r.mode & R_PANORAMA, 0); + + /* SSS */ + if(!re->test_break()) + if (re->r.renderer==R_INTERN) + make_sss_tree(re); } if(re->test_break()) diff --git a/source/blender/render/intern/source/envmap.c b/source/blender/render/intern/source/envmap.c index c1f756894fe..ec55bc0a5e2 100644 --- a/source/blender/render/intern/source/envmap.c +++ b/source/blender/render/intern/source/envmap.c @@ -410,7 +410,7 @@ static void render_envmap(Render *re, EnvMap *env) env_set_imats(envre); if(re->test_break()==0) { - RE_TileProcessor(envre, 0); + RE_TileProcessor(envre, 0, 0); } /* rotate back */ diff --git a/source/blender/render/intern/source/pipeline.c b/source/blender/render/intern/source/pipeline.c index d93ee448f62..93c80749bca 100644 --- a/source/blender/render/intern/source/pipeline.c +++ b/source/blender/render/intern/source/pipeline.c @@ -1163,16 +1163,17 @@ static void *do_part_thread(void *pa_v) /* need to return nicely all parts on esc */ if(R.test_break()==0) { - pa->result= new_render_result(&R, &pa->disprect, pa->crop, RR_USEMEM); - - if(R.osa) + + if(R.sss_points) + zbufshade_sss_tile(pa); + else if(R.osa) zbufshadeDA_tile(pa); else zbufshade_tile(pa); /* merge too on break! */ - if(R.result->exrhandle) + if(!R.sss_points && R.result->exrhandle) save_render_result_tile(&R, pa); else merge_render_result(R.result, pa->result); @@ -1464,7 +1465,7 @@ static void threaded_tile_processor(Render *re) } /* currently only called by preview renders and envmap */ -void RE_TileProcessor(Render *re, int firsttile) +void RE_TileProcessor(Render *re, int firsttile, int threaded) { /* the partsdone variable has to be reset to firsttile, to survive esc before it was set to zero */ @@ -1472,8 +1473,10 @@ void RE_TileProcessor(Render *re, int firsttile) re->i.starttime= PIL_check_seconds_timer(); - // threaded_tile_processor(re); - render_tile_processor(re, firsttile); + if(threaded) + threaded_tile_processor(re); + else + render_tile_processor(re, firsttile); re->i.lastframetime= PIL_check_seconds_timer()- re->i.starttime; re->stats_draw(&re->i); diff --git a/source/blender/render/intern/source/rendercore.c b/source/blender/render/intern/source/rendercore.c index 10201087f2d..1c5f56362bc 100644 --- a/source/blender/render/intern/source/rendercore.c +++ b/source/blender/render/intern/source/rendercore.c @@ -64,6 +64,7 @@ #include "pixelshading.h" #include "shadbuf.h" #include "shading.h" +#include "sss.h" #include "zbuf.h" #include "PIL_time.h" @@ -1113,6 +1114,298 @@ void zbufshade_tile(RenderPart *pa) MEM_freeN(pa->clipflag); pa->clipflag= NULL; } +/* SSS preprocess tile render, fully threadable */ +typedef struct ZBufSSSHandle { + RenderPart *pa; + ListBase psmlist; + int totps; +} ZBufSSSHandle; + +static void addps_sss(void *cb_handle, int facenr, int x, int y, int z) +{ + ZBufSSSHandle *handle = cb_handle; + RenderPart *pa= handle->pa; + + if (pa->rectall) { + long *rs= pa->rectall + pa->rectx*y + x; + + addps(&handle->psmlist, rs, facenr, z, 0); + handle->totps++; + } + if (pa->rectz) { + int *rz= pa->rectz + pa->rectx*y + x; + int *rp= pa->rectp + pa->rectx*y + x; + + if (z < *rz) { + if(*rp == 0) + handle->totps++; + *rz= z; + *rp= facenr; + } + } + if (pa->rectbackz) { + int *rz= pa->rectbackz + pa->rectx*y + x; + int *rp= pa->rectbackp + pa->rectx*y + x; + + if (z >= *rz) { + if(*rp == 0) + handle->totps++; + *rz= z; + *rp= facenr; + } + } +} + +static void shade_sample_sss(ShadeSample *ssamp, Material *mat, VlakRen *vlr, int quad, int x, int y, float z, float *co, float *color, float *area) +{ + ShadeInput *shi= ssamp->shi; + ShadeResult shr; + float texfac, orthoarea, nor[3]; + + /* normal flipping must be disabled to make back scattering work, so that + backside faces actually face any lighting from the back */ + shi->puno= 0; + + /* cache for shadow */ + shi->samplenr++; + + if(quad) + shade_input_set_triangle_i(shi, vlr, 0, 2, 3); + else + shade_input_set_triangle_i(shi, vlr, 0, 1, 2); + + /* we don't want flipped normals, they screw up back scattering */ + if(vlr->noflag & R_FLIPPED_NO) { + shi->facenor[0]= -shi->facenor[0]; + shi->facenor[1]= -shi->facenor[1]; + shi->facenor[2]= -shi->facenor[2]; + } + + /* we estimate the area here using shi->dxco and shi->dyco. we need to + enabled shi->osatex these are filled. we compute two areas, one with + the normal pointed at the camera and one with the original normal, and + then clamp to avoid a too large contribution from a single pixel */ + shi->osatex= 1; + + VECCOPY(nor, shi->facenor); + calc_view_vector(shi->facenor, x, y); + Normalize(shi->facenor); + shade_input_set_viewco(shi, x, y, z); + orthoarea= VecLength(shi->dxco)*VecLength(shi->dyco); + + VECCOPY(shi->facenor, nor); + shade_input_set_viewco(shi, x, y, z); + *area= VecLength(shi->dxco)*VecLength(shi->dyco); + *area= MIN2(*area, 2.0f*orthoarea); + + shi->osatex= 0; + + shade_input_set_uv(shi); + shade_input_set_normals(shi); + + /* if nodetree, use the material that we are currently preprocessing + instead of the node material */ + if(shi->mat->nodetree && shi->mat->use_nodes) + shi->mat= mat; + + /* init material vars */ + // note, keep this synced with render_types.h + memcpy(&shi->r, &shi->mat->r, 23*sizeof(float)); + shi->har= shi->mat->har; + + /* render */ + shade_input_set_shade_texco(shi); + + shade_samples_do_AO(ssamp); + shade_material_loop(shi, &shr); + + VECCOPY(co, shi->co); + VECCOPY(color, shr.combined); + + /* texture blending */ + texfac= shi->mat->sss_texfac; + + if(texfac == 0.0f) { + if(shr.col[0]!=0.0f) color[0] /= shr.col[0]; + if(shr.col[1]!=0.0f) color[1] /= shr.col[1]; + if(shr.col[2]!=0.0f) color[2] /= shr.col[2]; + } + else if(texfac != 1.0f) { + if(shr.col[0]!=0.0f) color[0] *= pow(shr.col[0], texfac)/shr.col[0]; + if(shr.col[1]!=0.0f) color[1] *= pow(shr.col[1], texfac)/shr.col[1]; + if(shr.col[2]!=0.0f) color[2] *= pow(shr.col[2], texfac)/shr.col[2]; + } +} + +void zbufshade_sss_tile(RenderPart *pa) +{ + ShadeSample ssamp; + ZBufSSSHandle handle; + RenderResult *rr= pa->result; + RenderLayer *rl= rr->layers.first; + VlakRen *vlr; + Material *mat= R.sss_mat; + float (*co)[3], (*color)[3], *area, *fcol= rl->rectf; + int x, y, seed, quad, totpoint; +#if 0 + PixStr *ps; + long *rs; + int z; +#else + int *rz, *rp, *rbz, *rbp; +#endif + + set_part_zbuf_clipflag(pa); + + /* setup pixelstr list and buffer for zbuffering */ + handle.pa= pa; + handle.totps= 0; + +#if 0 + handle.psmlist.first= handle.psmlist.last= NULL; + addpsmain(&handle.psmlist); + + pa->rectall= MEM_callocN(sizeof(long)*pa->rectx*pa->recty+4, "rectall"); +#else + pa->rectp= MEM_mallocN(sizeof(int)*pa->rectx*pa->recty, "rectp"); + pa->rectz= MEM_mallocN(sizeof(int)*pa->rectx*pa->recty, "rectz"); + pa->rectbackp= MEM_mallocN(sizeof(int)*pa->rectx*pa->recty, "rectbackp"); + pa->rectbackz= MEM_mallocN(sizeof(int)*pa->rectx*pa->recty, "rectbackz"); +#endif + + /* create the pixelstrs to be used later */ + zbuffer_sss(pa, rl->lay, &handle, addps_sss); + + co= MEM_mallocN(sizeof(float)*3*handle.totps, "SSSCo"); + color= MEM_mallocN(sizeof(float)*3*handle.totps, "SSSColor"); + area= MEM_mallocN(sizeof(float)*handle.totps, "SSSArea"); + +#if 0 + /* create ISB (does not work currently!) */ + if(R.r.mode & R_SHADOW) + ISB_create(pa, NULL); +#endif + + /* setup shade sample with correct passes */ + shade_sample_initialize(&ssamp, pa, rl); + ssamp.shi[0].passflag= SCE_PASS_DIFFUSE|SCE_PASS_AO|SCE_PASS_RADIO; + ssamp.shi[0].passflag |= SCE_PASS_RGBA; + ssamp.shi[0].combinedflag= ~(SCE_PASS_SPEC); + + /* initialize scanline updates for main thread */ + rr->renrect.ymin= 0; + rr->renlay= rl; + + seed= pa->rectx*pa->disprect.ymin; +#if 0 + rs= pa->rectall; +#else + rz= pa->rectz; + rp= pa->rectp; + rbz= pa->rectbackz; + rbp= pa->rectbackp; +#endif + totpoint= 0; + + for(y=pa->disprect.ymin; y<pa->disprect.ymax; y++, rr->renrect.ymax++) { + for(x=pa->disprect.xmin; x<pa->disprect.xmax; x++, fcol+=4) { + /* per pixel fixed seed */ + BLI_thread_srandom(pa->thread, seed++); + +#if 0 + if(rs) { + /* for each sample in this pixel, shade it */ + for(ps=(PixStr*)*rs; ps; ps=ps->next) { + vlr= RE_findOrAddVlak(&R, (ps->facenr-1) & RE_QUAD_MASK); + quad= (ps->facenr & RE_QUAD_OFFS); + z= ps->z; + + shade_sample_sss(&ssamp, mat, vlr, quad, x, y, z, + co[totpoint], color[totpoint], &area[totpoint]); + + totpoint++; + + VECADD(fcol, fcol, color); + fcol[3]= 1.0f; + } + + rs++; + } +#else + if(rp) { + if(*rp != 0) { + /* shade front */ + vlr= RE_findOrAddVlak(&R, (*rp-1) & RE_QUAD_MASK); + quad= ((*rp) & RE_QUAD_OFFS); + + shade_sample_sss(&ssamp, mat, vlr, quad, x, y, *rz, + co[totpoint], color[totpoint], &area[totpoint]); + + VECADD(fcol, fcol, color[totpoint]); + fcol[3]= 1.0f; + totpoint++; + } + + rp++; rz++; + } + + if(rbp) { + if(*rbp != 0 && *rbp != *(rp-1)) { + /* shade back */ + vlr= RE_findOrAddVlak(&R, (*rbp-1) & RE_QUAD_MASK); + quad= ((*rbp) & RE_QUAD_OFFS); + + shade_sample_sss(&ssamp, mat, vlr, quad, x, y, *rbz, + co[totpoint], color[totpoint], &area[totpoint]); + + /* to indicate this is a back sample */ + area[totpoint]= -area[totpoint]; + + VECADD(fcol, fcol, color[totpoint]); + fcol[3]= 1.0f; + totpoint++; + } + + rbz++; rbp++; + } +#endif + } + + if(y&1) + if(R.test_break()) break; + } + + /* note: after adding we do not free these arrays, sss keeps them */ + if(totpoint > 0) { + sss_add_points(&R, co, color, area, totpoint); + } + else { + MEM_freeN(co); + MEM_freeN(color); + MEM_freeN(area); + } + +#if 0 + if(R.r.mode & R_SHADOW) + ISB_free(pa); +#endif + + /* display active layer */ + rr->renrect.ymin=rr->renrect.ymax= 0; + rr->renlay= render_get_active_layer(&R, rr); + + MEM_freeN(pa->clipflag); pa->clipflag= NULL; +#if 0 + MEM_freeN(pa->rectall); pa->rectall= NULL; + freeps(&handle.psmlist); +#else + MEM_freeN(pa->rectz); pa->rectz= NULL; + MEM_freeN(pa->rectp); pa->rectp= NULL; + MEM_freeN(pa->rectbackz); pa->rectbackz= NULL; + MEM_freeN(pa->rectbackp); pa->rectbackp= NULL; +#endif +} + /* ------------------------------------------------------------------------ */ static void renderhalo_post(RenderResult *rr, float *rectf, HaloRen *har) /* postprocess version */ diff --git a/source/blender/render/intern/source/renderdatabase.c b/source/blender/render/intern/source/renderdatabase.c index 57f0f689871..6d9f0e4eb01 100644 --- a/source/blender/render/intern/source/renderdatabase.c +++ b/source/blender/render/intern/source/renderdatabase.c @@ -894,6 +894,8 @@ void set_normalflags(Render *re) if((a1 & 255)==0) vlr= re->vlaknodes[a1>>8].vlak; else vlr++; + vlr->noflag= 0; + /* abuse of this flag... this is code that just sets face normal in direction of camera */ /* that convention we should get rid of */ if((vlr->flag & R_NOPUNOFLIP)==0) { @@ -910,6 +912,7 @@ void set_normalflags(Render *re) vlr->n[0]= -vlr->n[0]; vlr->n[1]= -vlr->n[1]; vlr->n[2]= -vlr->n[2]; + vlr->noflag |= R_FLIPPED_NO; } } @@ -924,9 +927,9 @@ void set_normalflags(Render *re) xn= fabs(vlr->n[0]); yn= fabs(vlr->n[1]); zn= fabs(vlr->n[2]); - if(zn>=xn && zn>=yn) vlr->snproj= 0; - else if(yn>=xn && yn>=zn) vlr->snproj= 1; - else vlr->snproj= 2; + if(zn>=xn && zn>=yn) vlr->noflag |= R_SNPROJ_X; + else if(yn>=xn && yn>=zn) vlr->noflag |= R_SNPROJ_Y; + else vlr->noflag |= R_SNPROJ_Z; } } diff --git a/source/blender/render/intern/source/shadeinput.c b/source/blender/render/intern/source/shadeinput.c index d9126a3cdb2..51157cb83f4 100644 --- a/source/blender/render/intern/source/shadeinput.c +++ b/source/blender/render/intern/source/shadeinput.c @@ -429,11 +429,11 @@ void shade_input_set_uv(ShadeInput *shi) /* most of this could become re-used for faces */ float detsh, t00, t10, t01, t11; - if(vlr->snproj==0) { + if(vlr->noflag & R_SNPROJ_X) { t00= v3[0]-v1[0]; t01= v3[1]-v1[1]; t10= v3[0]-v2[0]; t11= v3[1]-v2[1]; } - else if(vlr->snproj==1) { + else if(vlr->noflag & R_SNPROJ_Y) { t00= v3[0]-v1[0]; t01= v3[2]-v1[2]; t10= v3[0]-v2[0]; t11= v3[2]-v2[2]; } @@ -446,7 +446,7 @@ void shade_input_set_uv(ShadeInput *shi) t00*= detsh; t01*=detsh; t10*=detsh; t11*=detsh; - if(vlr->snproj==0) { + if(vlr->noflag & R_SNPROJ_X) { shi->u= (shi->co[0]-v3[0])*t11-(shi->co[1]-v3[1])*t10; shi->v= (shi->co[1]-v3[1])*t00-(shi->co[0]-v3[0])*t01; if(shi->osatex) { @@ -456,7 +456,7 @@ void shade_input_set_uv(ShadeInput *shi) shi->dy_v= shi->dyco[1]*t00- shi->dyco[0]*t01; } } - else if(vlr->snproj==1) { + else if(vlr->noflag & R_SNPROJ_Y) { shi->u= (shi->co[0]-v3[0])*t11-(shi->co[2]-v3[2])*t10; shi->v= (shi->co[2]-v3[2])*t00-(shi->co[0]-v3[0])*t01; if(shi->osatex) { diff --git a/source/blender/render/intern/source/shadeoutput.c b/source/blender/render/intern/source/shadeoutput.c index c2aeee6aab6..7dd7383c60c 100644 --- a/source/blender/render/intern/source/shadeoutput.c +++ b/source/blender/render/intern/source/shadeoutput.c @@ -46,6 +46,7 @@ #include "pixelblending.h" #include "rendercore.h" #include "shadbuf.h" +#include "sss.h" #include "texture.h" /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -1506,6 +1507,35 @@ void shade_lamp_loop(ShadeInput *shi, ShadeResult *shr) /* accumulates in shr->diff and shr->spec and shr->shad (diffuse with shadow!) */ shade_one_light(lar, shi, shr, passflag); } + + if(ma->sss_flag & MA_DIFF_SSS) { + float sss[3], col[3], texfac= ma->sss_texfac; + + /* this will return false in the preprocess stage */ + if(sample_sss(&R, ma, shi->co, sss)) { + if(texfac==0.0f) { + VECCOPY(col, shr->col); + } + else if(texfac==1.0f) { + col[0]= col[1]= col[2]= 1.0f; + } + else { + col[0]= pow(shr->col[0], 1.0f-texfac); + col[1]= pow(shr->col[1], 1.0f-texfac); + col[2]= pow(shr->col[2], 1.0f-texfac); + } + + shr->diff[0]= sss[0]*col[0]; + shr->diff[1]= sss[1]*col[1]; + shr->diff[2]= sss[2]*col[2]; + + if(shi->combinedflag & SCE_PASS_SHADOW) { + shr->shad[0]= sss[0]*col[0]; + shr->shad[1]= sss[1]*col[1]; + shr->shad[2]= sss[2]*col[2]; + } + } + } if(shi->combinedflag & SCE_PASS_SHADOW) VECCOPY(shr->combined, shr->shad) /* note, no ';' ! */ @@ -1544,23 +1574,26 @@ void shade_lamp_loop(ShadeInput *shi, ShadeResult *shr) shr->alpha= shi->alpha; /* from now stuff everything in shr->combined: ambient, AO, radio, ramps, exposure */ - shr->combined[0]+= shi->ambr + shi->r*shi->amb*shi->rad[0]; - shr->combined[1]+= shi->ambg + shi->g*shi->amb*shi->rad[1]; - shr->combined[2]+= shi->ambb + shi->b*shi->amb*shi->rad[2]; - - /* add AO in combined? */ - if(R.wrld.mode & WO_AMB_OCC) { - if(shi->combinedflag & SCE_PASS_AO) { - float aodiff[3]; - ambient_occlusion_to_diffuse(shi, aodiff); - - shr->combined[0] += shi->r*aodiff[0]; - shr->combined[1] += shi->g*aodiff[1]; - shr->combined[2] += shi->b*aodiff[2]; + if(!(ma->sss_flag & MA_DIFF_SSS) || !has_sss_tree(&R, ma)) { + shr->combined[0]+= shi->ambr + shi->r*shi->amb*shi->rad[0]; + shr->combined[1]+= shi->ambg + shi->g*shi->amb*shi->rad[1]; + shr->combined[2]+= shi->ambb + shi->b*shi->amb*shi->rad[2]; + + /* add AO in combined? */ + if(R.wrld.mode & WO_AMB_OCC) { + if(shi->combinedflag & SCE_PASS_AO) { + float aodiff[3]; + ambient_occlusion_to_diffuse(shi, aodiff); + + shr->combined[0] += shi->r*aodiff[0]; + shr->combined[1] += shi->g*aodiff[1]; + shr->combined[2] += shi->b*aodiff[2]; + } } + + if(ma->mode & MA_RAMP_COL) ramp_diffuse_result(shr->combined, shi); } - - if(ma->mode & MA_RAMP_COL) ramp_diffuse_result(shr->combined, shi); + if(ma->mode & MA_RAMP_SPEC) ramp_spec_result(shr->spec, shr->spec+1, shr->spec+2, shi); /* refcol is for envmap only */ diff --git a/source/blender/render/intern/source/sss.c b/source/blender/render/intern/source/sss.c new file mode 100644 index 00000000000..26833562670 --- /dev/null +++ b/source/blender/render/intern/source/sss.c @@ -0,0 +1,984 @@ +/* + * $Id$ + * + * ***** 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 ***** + */ + +/* 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 <math.h> +#include <string.h> +#include <stdio.h> +#include <string.h> + +/* external modules: */ +#include "MEM_guardedalloc.h" + +#include "BLI_arithb.h" +#include "BLI_blenlib.h" +#include "BLI_ghash.h" +#include "BLI_memarena.h" +#include "PIL_time.h" + +#include "DNA_material_types.h" + +#include "BKE_global.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_node.h" +#include "BKE_utildefines.h" + +/* this module */ +#include "render_types.h" +#include "rendercore.h" +#include "renderdatabase.h" +#include "shading.h" +#include "sss.h" +#include "zbuf.h" + +extern Render R; // meh + +/* 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 radiance[3]; + float area; + int back; +} ScatterPoint; + +typedef struct ScatterNode { + float co[3]; + float radiance[3]; + float backradiance[3]; + float area; + int back; + + 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 radiance[3]; + float backradiance[3]; + float rdsum[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= sqrt(3.0f*(1.0f - alpha_)); + return (alpha_/2.0f)*(1.0f + exp((-4.0f/3.0f)*A*sq))*exp(-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(fabs(fsub) < tolerance) + break; + d= ((xn - xn_1)/fsub)*fxn; + if(fabs(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= sqrt(rr + ss->zr*ss->zr); + sv= sqrt(rr + ss->zv*ss->zv); + + Rdr= ss->zr*(1.0f + ss->sigma*sr)*exp(-ss->sigma*sr)/(sr*sr*sr); + Rdv= ss->zv*(1.0f + ss->sigma*sv)*exp(-ss->sigma*sv)/(sv*sv*sv); + + return /*ss->alpha_*/(1.0f/(4.0f*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)) { + rd[0]= Rd_rsquare(ss[0], rr); + rd[1]= Rd_rsquare(ss[1], rr); + rd[2]= Rd_rsquare(ss[2], rr); + } + else if(rr > RD_TABLE_RANGE) { + rr= sqrt(rr); + indexf= rr*(RD_TABLE_SIZE/RD_TABLE_RANGE_2); + index= (int)indexf; + idxf= (float)index; + t= indexf - idxf; + + 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); + } + else { + indexf= rr*(RD_TABLE_SIZE/RD_TABLE_RANGE); + index= (int)indexf; + idxf= (float)index; + t= indexf - idxf; + + 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); + } +} + +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(r < ss->invsigma_t_*ss->invsigma_t_) + r= ss->invsigma_t_*ss->invsigma_t_;*/ + ss->tableRd[i]= Rd(ss, sqrt(r)); + + r= i*(RD_TABLE_RANGE_2/RD_TABLE_SIZE); + /*if(r < ss->invsigma_t_) + r= ss->invsigma_t_;*/ + 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= MIN2(refl, 0.999f); + 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/sqrt(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 rr, ScatterResult *result) +{ + float rd[3]; + +#if 0 + rd[0]= Rd_rsquare(tree->ss[0], rr); + rd[1]= Rd_rsquare(tree->ss[1], rr); + rd[2]= Rd_rsquare(tree->ss[2], rr); +#else + approximate_Rd_rgb(tree->ss, rr, rd); +#endif + + rd[0] *= area; + rd[1] *= area; + rd[2] *= area; + + if(frontrad) { + result->radiance[0] += frontrad[0]*rd[0]; + result->radiance[1] += frontrad[1]*rd[1]; + result->radiance[2] += frontrad[2]*rd[2]; + } + if(backrad) { + result->backradiance[0] += backrad[0]*rd[0]; + result->backradiance[1] += backrad[1]*rd[1]; + result->backradiance[2] += backrad[2]*rd[2]; + } + + result->rdsum[0] += rd[0]; + result->rdsum[1] += rd[1]; + result->rdsum[2] += rd[2]; +} + +static void traverse_octree(ScatterTree *tree, ScatterNode *node, float *co, 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; i<node->totpoint; i++) { + ScatterPoint *p= &node->points[i]; + + VECSUB(sub, co, p->co); + dist= INPR(sub, sub); + + if(p->back) + add_radiance(tree, NULL, p->radiance, p->area, dist, result); + else + add_radiance(tree, p->radiance, NULL, p->area, 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 */ + VECSUB(sub, co, subnode->co); + dist= INPR(sub, sub); + + /* actually area/dist > error, but this avoids division */ + if(subnode->area>tree->error*dist) { + traverse_octree(tree, subnode, co, 0, result); + } + else { + add_radiance(tree, subnode->radiance, + subnode->backradiance, subnode->area, dist, result); + } + } + } + } + } +} + +static void compute_radiance(ScatterTree *tree, float *co, float *radiance) +{ + ScatterResult result; + float rdsum[3]; + + memset(&result, 0, sizeof(result)); + + traverse_octree(tree, tree->root, co, 1, &result); + + VecMulf(result.radiance, tree->ss[0]->frontweight); + VecMulf(result.backradiance, tree->ss[0]->backweight); + + VECCOPY(radiance, result.backradiance); + VECADD(radiance, radiance, result.radiance); + + VECCOPY(rdsum, result.rdsum); + + /* 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 */ + if(rdsum[0] > 0.0f) radiance[0]= tree->ss[0]->color*radiance[0]/rdsum[0]; + if(rdsum[1] > 0.0f) radiance[1]= tree->ss[1]->color*radiance[1]/rdsum[1]; + if(rdsum[2] > 0.0f) radiance[2]= tree->ss[2]->color*radiance[2]/rdsum[2]; +} + +/* building */ + +static void sum_leaf_radiance(ScatterTree *tree, ScatterNode *node) +{ + ScatterPoint *p; + float radiance, totradiance= 0.0f, inv; + int i; + + node->co[0]= node->co[1]= node->co[2]= 0.0; + node->radiance[0]= node->radiance[1]= node->radiance[2]= 0.0; + node->backradiance[0]= node->backradiance[1]= node->backradiance[2]= 0.0; + + /* compute total radiance, radiance weighted average position, + and total area */ + for(i=0; i<node->totpoint; i++) { + p= &node->points[i]; + + radiance= p->area*(p->radiance[0] + p->radiance[1] + p->radiance[2]); + totradiance += radiance; + + node->co[0] += radiance*p->co[0]; + node->co[1] += radiance*p->co[1]; + node->co[2] += radiance*p->co[2]; + + if(p->back) { + node->backradiance[0] += p->radiance[0]*p->area; + node->backradiance[1] += p->radiance[1]*p->area; + node->backradiance[2] += p->radiance[2]*p->area; + } + else { + node->radiance[0] += p->radiance[0]*p->area; + node->radiance[1] += p->radiance[1]*p->area; + node->radiance[2] += p->radiance[2]*p->area; + } + + node->area += p->area; + } + + if(node->area > 0) { + inv= 1.0/node->area; + node->radiance[0] *= inv; + node->radiance[1] *= inv; + node->radiance[2] *= inv; + node->backradiance[0] *= inv; + node->backradiance[1] *= inv; + node->backradiance[2] *= inv; + } + + if(totradiance > 0.0f) { + inv= 1.0/totradiance; + 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; i<node->totpoint; 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 *tree, ScatterNode *node) +{ + ScatterNode *subnode; + float radiance, totradiance= 0.0f, inv; + int i, totnode; + + node->co[0]= node->co[1]= node->co[2]= 0.0; + node->radiance[0]= node->radiance[1]= node->radiance[2]= 0.0; + node->backradiance[0]= node->backradiance[1]= node->backradiance[2]= 0.0; + + /* compute total radiance, radiance weighted average position, + and total area */ + for(i=0; i<8; i++) { + if(node->child[i] == NULL) + continue; + + subnode= node->child[i]; + + radiance= subnode->area*(subnode->radiance[0] + subnode->radiance[1] + subnode->radiance[2] + subnode->backradiance[0] + subnode->backradiance[1] + subnode->backradiance[2]); + totradiance += radiance; + + node->co[0] += radiance*subnode->co[0]; + node->co[1] += radiance*subnode->co[1]; + node->co[2] += radiance*subnode->co[2]; + + node->radiance[0] += subnode->radiance[0]*subnode->area; + node->radiance[1] += subnode->radiance[1]*subnode->area; + node->radiance[2] += subnode->radiance[2]*subnode->area; + + node->backradiance[0] += subnode->backradiance[0]*subnode->area; + node->backradiance[1] += subnode->backradiance[1]*subnode->area; + node->backradiance[2] += subnode->backradiance[2]*subnode->area; + + node->area += subnode->area; + } + + if(node->area > 0) { + inv= 1.0/node->area; + node->radiance[0] *= inv; + node->radiance[1] *= inv; + node->radiance[2] *= inv; + node->backradiance[0] *= inv; + node->backradiance[1] *= inv; + node->backradiance[2] *= inv; + } + + if(totradiance > 0.0f) { + inv= 1.0/totradiance; + 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, usednodes, usedi; + float submid[3], subsize[3]; + + /* stopping condition */ + if(node->totpoint <= MAX_OCTREE_NODE_POINTS || depth == MAX_OCTREE_DEPTH) { + for(i=0; i<node->totpoint; i++) + node->points[i]= *(refpoints[i]); + + return; + } + + subsize[0]= size[0]*0.5; + subsize[1]= size[1]*0.5; + subsize[2]= size[2]*0.5; + + 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; i<node->totpoint; 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, usednodes=0, i=0; i<8; i++) { + if(nsize[i]) { + usednodes++; + usedi = i; + } + if(i != 0) + noffset[i]= noffset[i-1]+nsize[i-1]; + } + + if(usednodes<=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; i<node->totpoint; 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; i<totpoint; i++) { + VECCOPY(points[i].co, co[i]); + VECCOPY(points[i].radiance, color[i]); + points[i].area= fabs(area[i])/(tree->scale*tree->scale); + points[i].back= (area[i] < 0.0f); + + VecMulf(points[i].co, 1.0f/tree->scale); + DO_MINMAX(points[i].co, tree->min, tree->max); + + 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)); + 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.5; + mid[1]= (tree->min[1]+tree->max[1])*0.5; + mid[2]= (tree->min[2]+tree->max[2])*0.5; + + size[0]= (tree->max[0]-tree->min[0])*0.5; + size[1]= (tree->max[1]-tree->min[1])*0.5; + size[2]= (tree->max[2]-tree->min[2])*0.5; + + 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, float *co, float *color) +{ + float sco[3]; + + VECCOPY(sco, co); + VecMulf(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; + ListBase layers, points; + float (*co)[3] = NULL, (*color)[3] = NULL, *area = NULL; + int totpoint = 0, osa; + + if(re->test_break()) + return; + + points.first= points.last= NULL; + + /* do SSS preprocessing render */ + layers= re->r.layers; + osa= re->osa; + + re->r.layers.first= re->r.layers.last= NULL; + re->osa= 0; + re->sss_points= &points; + re->sss_mat= mat; + + RE_TileProcessor(re, 0, 1); + + re->sss_mat= NULL; + re->sss_points= NULL; + re->r.layers= layers; + re->osa= osa; + + /* no points? no tree */ + if(!points.first) + return; + + /* merge points together into a single buffer */ + if(!re->test_break()) { + 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()) { + SSSData *sss= MEM_callocN(sizeof(*sss), "SSSData"); + float ior= mat->sss_ior, cfac= mat->sss_colfac; + float *col= mat->sss_col, *radius= mat->sss_radius; + float fw= mat->sss_front, bw= mat->sss_back; + + sss->ss[0]= scatter_settings_new(col[0], radius[0], ior, cfac, fw, bw); + sss->ss[1]= scatter_settings_new(col[1], radius[1], ior, cfac, fw, bw); + sss->ss[2]= scatter_settings_new(col[2], radius[2], ior, cfac, fw, bw); + sss->tree= scatter_tree_new(sss->ss, mat->sss_scale, mat->sss_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; + + re->sss_hash= BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp); + + re->i.infostr= "SSS preprocessing"; + re->stats_draw(&re->i); + + for(mat= G.main->mat.first; mat; mat= mat->id.next) + if(mat->id.us && (mat->sss_flag & MA_DIFF_SSS)) + sss_create_tree_mat(re, mat); +} + +void free_sss(Render *re) +{ + if(re->sss_hash) { + GHashIterator *it= BLI_ghashIterator_new(re->sss_hash); + + while(!BLI_ghashIterator_isDone(it)) { + sss_free_tree(BLI_ghashIterator_getValue(it)); + BLI_ghashIterator_step(it); + } + + BLI_ghashIterator_free(it); + BLI_ghash_free(re->sss_hash, NULL, NULL); + re->sss_hash= NULL; + } +} + +int sample_sss(Render *re, Material *mat, float *co, float *color) +{ + 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 has_sss_tree(struct Render *re, struct Material *mat) +{ + return (re->sss_hash && BLI_ghash_lookup(re->sss_hash, mat)); +} + diff --git a/source/blender/render/intern/source/zbuf.c b/source/blender/render/intern/source/zbuf.c index ee24b15029c..f919aa11b98 100644 --- a/source/blender/render/intern/source/zbuf.c +++ b/source/blender/render/intern/source/zbuf.c @@ -51,6 +51,7 @@ #include "DNA_meshdata_types.h" #include "BKE_global.h" +#include "BKE_material.h" #include "BKE_utildefines.h" #include "radio_types.h" @@ -67,6 +68,7 @@ #include "rendercore.h" #include "shadbuf.h" #include "shading.h" +#include "sss.h" /* own includes */ #include "zbuf.h" @@ -2012,6 +2014,164 @@ void zbuffer_shadow(Render *re, LampRen *lar, int *rectz, int size, float jitx, zbuf_free_span(&zspan); } +static void zbuffill_sss(ZSpan *zspan, int zvlnr, float *v1, float *v2, float *v3, float *v4) +{ + double zxd, zyd, zy0, z; + float x0, y0, x1, y1, x2, y2, z0, z1, z2, xx1, *span1, *span2; + int x, y, sn1, sn2, rectx= zspan->rectx, my0, my2; + + /* init */ + zbuf_init_span(zspan); + + /* set spans */ + zbuf_add_to_span(zspan, v1, v2); + zbuf_add_to_span(zspan, v2, v3); + if(v4) { + zbuf_add_to_span(zspan, v3, v4); + zbuf_add_to_span(zspan, v4, v1); + } + else + zbuf_add_to_span(zspan, v3, v1); + + /* clipped */ + if(zspan->minp2==NULL || zspan->maxp2==NULL) return; + + if(zspan->miny1 < zspan->miny2) my0= zspan->miny2; else my0= zspan->miny1; + if(zspan->maxy1 > zspan->maxy2) my2= zspan->maxy2; else my2= zspan->maxy1; + + if(my2<my0) return; + + /* ZBUF DX DY, in floats still */ + x1= v1[0]- v2[0]; + x2= v2[0]- v3[0]; + y1= v1[1]- v2[1]; + y2= v2[1]- v3[1]; + z1= v1[2]- v2[2]; + z2= v2[2]- v3[2]; + + x0= y1*z2-z1*y2; + y0= z1*x2-x1*z2; + z0= x1*y2-y1*x2; + + if(z0==0.0f) return; + + xx1= (x0*v1[0] + y0*v1[1])/z0 + v1[2]; + zxd= -(double)x0/(double)z0; + zyd= -(double)y0/(double)z0; + zy0= ((double)my2)*zyd + (double)xx1; + + /* correct span */ + sn1= (my0 + my2)/2; + if(zspan->span1[sn1] < zspan->span2[sn1]) { + span1= zspan->span1+my2; + span2= zspan->span2+my2; + } + else { + span1= zspan->span2+my2; + span2= zspan->span1+my2; + } + + for(y=my2; y>=my0; y--, span1--, span2--) { + sn1= floor(*span1); + sn2= floor(*span2); + sn1++; + + if(sn2>=rectx) sn2= rectx-1; + if(sn1<0) sn1= 0; + + z= (double)sn1*zxd + zy0; + + for(x= sn1; x<=sn2; x++, z+=zxd) + zspan->sss_func(zspan->sss_handle, zvlnr, x, y, z); + + zy0 -= zyd; + } +} + +void zbuffer_sss(RenderPart *pa, unsigned int lay, void *handle, void (*func)(void *, int, int, int, int)) +{ + ZSpan zspan; + VlakRen *vlr= NULL; + VertRen *v1, *v2, *v3, *v4; + Material *ma=0, *sss_ma= R.sss_mat; + short nofill=0, env=0, wire=0; + char *clipflag= pa->clipflag; + int v, zvlnr; + + zbuf_alloc_span(&zspan, pa->rectx, pa->recty); + + zspan.sss_handle= handle; + zspan.sss_func= func; + + /* needed for transform from hoco to zbuffer co */ + zspan.zmulx= ((float)R.winx)/2.0; + zspan.zmuly= ((float)R.winy)/2.0; + + /* -0.5f to center the sample position */ + zspan.zofsx= -pa->disprect.xmin - 0.5f; + zspan.zofsy= -pa->disprect.ymin - 0.5f; + + /* filling methods */ + zspan.zbuffunc= zbuffill_sss; + + /* fill front and back zbuffer */ + if(pa->rectz) { + fillrect(pa->rectp, pa->rectx, pa->recty, 0); + fillrect(pa->rectz, pa->rectx, pa->recty, 0x7FFFFFFF); + } + if(pa->rectbackz) { + fillrect(pa->rectbackp, pa->rectx, pa->recty, 0); + fillrect(pa->rectbackz, pa->rectx, pa->recty, -0x7FFFFFFF); + } + + for(v=0; v<R.totvlak; v++) { + if((v & 255)==0) vlr= R.vlaknodes[v>>8].vlak; + else vlr++; + + if((vlr->flag & R_VISIBLE) && material_in_material(vlr->mat, sss_ma)) { + /* three cases, visible for render, only z values and nothing */ + if(vlr->lay & lay) { + if(vlr->mat!=ma) { + ma= vlr->mat; + nofill= ma->mode & MA_ONLYCAST; + env= (ma->mode & MA_ENV); + wire= (ma->mode & MA_WIRE); + } + } + else { + nofill= 1; + ma= NULL; /* otherwise nofill can hang */ + } + + if(nofill==0 && wire==0 && env==0) { + unsigned short partclip; + + v1= vlr->v1; + v2= vlr->v2; + v3= vlr->v3; + v4= vlr->v4; + + /* partclipping doesn't need viewplane clipping */ + partclip= clipflag[v1->index] & clipflag[v2->index] & clipflag[v3->index]; + if(v4) + partclip &= clipflag[v4->index]; + + + if(partclip==0) { + zvlnr= v+1; + zbufclip(&zspan, zvlnr, v1->ho, v2->ho, v3->ho, v1->clip, v2->clip, v3->clip); + if(v4) { + zvlnr+= RE_QUAD_OFFS; + zbufclip(&zspan, zvlnr, v1->ho, v3->ho, v4->ho, v1->clip, v3->clip, v4->clip); + } + } + } + } + } + + zbuf_free_span(&zspan); +} + /* ******************** VECBLUR ACCUM BUF ************************* */ typedef struct DrawBufPixel { |