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/
This commit is contained in:
Brecht Van Lommel 2007-05-03 21:37:52 +00:00
parent 4ec6f1b4e4
commit 3a8c6c81d8
20 changed files with 1794 additions and 37 deletions

@ -62,6 +62,8 @@ void init_render_materials(int, float *);
void end_render_material(struct Material *);
void end_render_materials(void);
int material_in_material(struct Material *parmat, struct Material *mat);
void automatname(struct Material *);
void delete_material_index(void);

@ -144,6 +144,20 @@ void init_material(Material *ma)
ma->pr_lamp= 3; /* two lamps, is bits */
ma->pr_type= MA_SPHERE;
ma->sss_radius[0]= 1.0f;
ma->sss_radius[1]= 1.0f;
ma->sss_radius[2]= 1.0f;
ma->sss_col[0]= 0.8f;
ma->sss_col[1]= 0.8f;
ma->sss_col[2]= 0.8f;
ma->sss_error= 0.05f;
ma->sss_scale= 0.1f;
ma->sss_ior= 1.3f;
ma->sss_colfac= 1.0f;
ma->sss_texfac= 0.0f;
ma->sss_front= 1.0f;
ma->sss_back= 1.0f;
ma->mode= MA_TRACEBLE|MA_SHADBUF|MA_SHADOW|MA_RADIO|MA_RAYBIAS|MA_TANGENT_STR;
}
@ -702,6 +716,33 @@ void end_render_materials(void)
end_render_material(ma);
}
static int material_in_nodetree(bNodeTree *ntree, Material *mat)
{
bNode *node;
for(node=ntree->nodes.first; node; node= node->next) {
if(node->id && GS(node->id->name)==ID_MA) {
if(node->id==(ID*)mat)
return 1;
}
else if(node->type==NODE_GROUP)
if(material_in_nodetree((bNodeTree*)node->id, mat))
return 1;
}
return 0;
}
int material_in_material(Material *parmat, Material *mat)
{
if(parmat==mat)
return 1;
else if(parmat->nodetree && parmat->use_nodes)
return material_in_nodetree(parmat->nodetree, mat);
else
return 0;
}
/* ****************** */
char colname_array[125][20]= {

@ -6387,10 +6387,36 @@ static void do_versions(FileData *fd, Library *lib, Main *main)
if(main->versionfile <= 243) {
Object *ob= main->object.first;
Camera *cam = main->camera.first;
Material *ma;
for(; cam; cam= cam->id.next) {
cam->angle= 360.0f * atan(16.0f/cam->lens) / M_PI;
}
for(ma=main->mat.first; ma; ma= ma->id.next) {
if(ma->sss_scale==0.0f) {
ma->sss_radius[0]= 1.0f;
ma->sss_radius[1]= 1.0f;
ma->sss_radius[2]= 1.0f;
ma->sss_col[0]= 0.8f;
ma->sss_col[1]= 0.8f;
ma->sss_col[2]= 0.8f;
ma->sss_error= 0.05f;
ma->sss_scale= 0.1f;
ma->sss_ior= 1.3f;
ma->sss_colfac= 1.0f;
ma->sss_texfac= 0.0f;
}
if(ma->sss_front==0 && ma->sss_back==0) {
ma->sss_front= 1.0f;
ma->sss_back= 1.0f;
}
if(ma->sss_col[0]==0 && ma->sss_col[1]==0 && ma->sss_col[2]==0) {
ma->sss_col[0]= ma->r;
ma->sss_col[1]= ma->g;
ma->sss_col[2]= ma->b;
}
}
for(; ob; ob= ob->id.next) {
bDeformGroup *curdef;

@ -111,6 +111,12 @@ typedef struct Material {
float fhdist, xyfrict;
short dynamode, pad2;
float sss_radius[3], sss_col[3];
float sss_error, sss_scale, sss_ior;
float sss_colfac, sss_texfac;
float sss_front, sss_back;
short sss_flag, sss_preset;
/* yafray: absorption color, dispersion parameters and material preset menu */
float YF_ar, YF_ag, YF_ab, YF_dscale, YF_dpwr;
int YF_dsmp, YF_preset, YF_djit;
@ -268,5 +274,8 @@ typedef struct Material {
/* pr_back */
#define MA_DARK 1
/* sss_flag */
#define MA_DIFF_SSS 1
#endif

@ -173,7 +173,7 @@ void RE_DataBase_ApplyWindow(struct Render *re);
void RE_set_max_threads(int threads);
/* the main processor, assumes all was set OK! */
void RE_TileProcessor(struct Render *re, int firsttile);
void RE_TileProcessor(struct Render *re, int firsttile, int threaded);
/* only RE_NewRender() needed, main Blender render calls */
void RE_BlenderFrame(struct Render *re, struct Scene *scene, int frame);

@ -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

@ -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);

@ -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*/

@ -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 *);

@ -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())

@ -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 */

@ -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);

@ -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 */

@ -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;
}
}

@ -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) {

@ -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 */

@ -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));
}

@ -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 {

@ -3269,6 +3269,118 @@ static void material_panel_tramir_yafray(Material *ma)
}
static void material_sss_preset_cb(void *material_v, void *unused_v)
{
static const float presets[11][7] = {
{0.909578, 0.905931, 0.665691, 6.961082, 6.400181, 1.895899, 1.300000},
{0.429632, 0.210025, 0.167767, 11.605177, 3.883766, 1.754386, 1.300000},
{0.439300, 0.216000, 0.141027, 9.435642, 3.347647, 1.790287, 1.300000},
{0.986552, 0.942637, 0.827285, 15.027623, 4.663968, 2.541380, 1.300000},
{0.221636, 0.007505, 0.002154, 4.761743, 0.574827, 0.394116, 1.300000},
{0.925008, 0.905025, 0.884275, 8.509412, 5.566180, 3.951266, 1.500000},
{0.855344, 0.740311, 0.291994, 14.266395, 7.227615, 2.036157, 1.300000},
{0.889319, 0.888034, 0.795811, 18.424364, 10.443473, 3.501882, 1.300000},
{0.573652, 0.312750, 0.174289, 3.673294, 1.366534, 0.682693, 1.300000},
{0.748679, 0.570766, 0.467133, 4.821475, 1.693699, 1.089971, 1.300000},
{0.947235, 0.931028, 0.851872, 10.898815, 6.575351, 2.508417, 1.300000}};
Material *ma= (Material*)material_v;
if(ma->sss_preset==0) return;
ma->sss_col[0]= presets[ma->sss_preset][0];
ma->sss_col[1]= presets[ma->sss_preset][1];
ma->sss_col[2]= presets[ma->sss_preset][2];
ma->sss_radius[0]= presets[ma->sss_preset][3];
ma->sss_radius[1]= presets[ma->sss_preset][4];
ma->sss_radius[2]= presets[ma->sss_preset][5];
ma->sss_ior= presets[ma->sss_preset][6];
}
static void material_sss_custom_set_cb(void *material_v, void *unused_v)
{
Material *ma= (Material*)material_v;
ma->sss_preset= 0;
allqueue(REDRAWNODE, 0);
}
static void material_panel_sss(Material *ma)
{
uiBlock *block;
uiBut *bt;
block= uiNewBlock(&curarea->uiblocks, "material_panel_sss", UI_EMBOSS, UI_HELV, curarea->win);
uiNewPanelTabbed("Shaders", "Material");
if(uiNewPanel(curarea, block, "SSS", "Material", 640, 0, 318, 204)==0) return;
uiSetButLock(ma->id.lib!=NULL, ERROR_LIBDATA_MESSAGE);
uiDefButBitS(block, TOG, MA_DIFF_SSS, B_MATPRV,"Subsurface Scattering",10,180,180,20, &(ma->sss_flag), 0, 0, 0, 0, "Enables diffuse subsurface scattering");
bt=uiDefButS(block, MENU, B_MATPRV, "Apple %x1|Chicken %x2|Cream %x3|Ketchup %x4|Marble %x5|Potato %x6|Skim Milk %x7|Skin 1 %x8|Skin 2 %x9|Whole Milk %x10|Custom %x0",
200,180,110,20, &ma->sss_preset, 0, 0, 0, 0, "");
uiButSetFunc(bt, material_sss_preset_cb, ma, NULL);
uiBlockBeginAlign(block);
uiDefButF(block, NUM, B_MATPRV, "Scale:", 10,150,145,20,
&ma->sss_scale, 0.001, 1000, 1, 3, "Object scale");
bt=uiDefButF(block, NUM, B_MATPRV, "Radius R", 10,130,145,20,
&ma->sss_radius[0], 0.00001, 10000, 0, 0,
"Mean red scattering path length");
uiButSetFunc(bt, material_sss_custom_set_cb, ma, NULL);
bt=uiDefButF(block, NUM, B_MATPRV, "Radius G", 10,110,145,20,
&ma->sss_radius[1], 0.00001, 10000, 0, 0,
"Mean green scattering path length");
uiButSetFunc(bt, material_sss_custom_set_cb, ma, NULL);
bt=uiDefButF(block, NUM, B_MATPRV, "Radius B", 10,90,145,20,
&ma->sss_radius[2], 0.00001, 10000, 0, 0,
"Mean blue scattering path length");
uiButSetFunc(bt, material_sss_custom_set_cb, ma, NULL);
uiBlockEndAlign(block);
bt=uiDefButF(block, NUM, B_MATPRV, "IOR:", 10,60,145,20,
&ma->sss_ior, 0.1, 2, 1, 3, "Index of refraction");
uiButSetFunc(bt, material_sss_custom_set_cb, ma, NULL);
uiBlockBeginAlign(block);
uiDefButF(block, NUM, B_MATPRV, "Error:", 10,30,145,20,
&ma->sss_error, 0.0001, 10, 1, 3, "Error");
uiBlockEndAlign(block);
uiBlockBeginAlign(block);
bt=uiDefButF(block, NUMSLI, B_MATPRV, "R ", 165,150,145,20,
&ma->sss_col[0], 0.0, 1.0, 0, 0,
"Red scattering color");
uiButSetFunc(bt, material_sss_custom_set_cb, ma, NULL);
bt=uiDefButF(block, NUMSLI, B_MATPRV, "G ", 165,130,145,20,
&ma->sss_col[1], 0.0, 1.0, 0, 0,
"Green scattering color");
uiButSetFunc(bt, material_sss_custom_set_cb, ma, NULL);
bt=uiDefButF(block, NUMSLI, B_MATPRV, "B ", 165,110,145,20,
&ma->sss_col[2], 0.0, 1.0, 0, 0,
"Blue scattering color");
uiButSetFunc(bt, material_sss_custom_set_cb, ma, NULL);
uiBlockEndAlign(block);
uiBlockBeginAlign(block);
uiDefButF(block, NUMSLI, B_MATPRV, "Col ", 165,80,145,20,
&ma->sss_colfac, 0.0, 1.0, 0, 0,
"Blend factor for SSS colors");
uiDefButF(block, NUMSLI, B_MATPRV, "Tex ", 165,60,145,20,
&ma->sss_texfac, 0.0, 1.0, 0, 0,
"Texture scattering factor");
uiBlockEndAlign(block);
uiBlockBeginAlign(block);
uiDefButF(block, NUMSLI, B_MATPRV, "Front ", 165,30,145,20,
&ma->sss_front, 0.0, 2.0, 0, 0,
"Front scattering weight");
uiDefButF(block, NUMSLI, B_MATPRV, "Back ", 165,10,145,20,
&ma->sss_back, 0.0, 10.0, 0, 0,
"Back scattering weight");
uiBlockEndAlign(block);
}
static void material_panel_shading(Material *ma)
{
@ -3731,7 +3843,8 @@ void material_panels()
}
material_panel_tramir_yafray(ma);
}
material_panel_sss(ma);
material_panel_texture(ma);
mtex= ma->mtex[ ma->texact ];

@ -467,7 +467,7 @@ void BIF_previewrender(struct ID *id, struct RenderInfo *ri, struct ScrArea *are
/* entire cycle for render engine */
RE_SetCamera(re, sce->camera);
RE_Database_FromScene(re, sce, 1);
RE_TileProcessor(re, ri->curtile); // actual render engine
RE_TileProcessor(re, ri->curtile, 0); // actual render engine
RE_Database_Free(re);
/* handle results */
@ -842,7 +842,7 @@ void BIF_view3d_previewrender(ScrArea *sa)
/* OK, can we enter render code? */
if(ri->status==(PR_DISPRECT|PR_DBASE|PR_PROJECTED|PR_ROTATED)) {
//printf("curtile %d tottile %d\n", ri->curtile, ri->tottile);
RE_TileProcessor(ri->re, ri->curtile);
RE_TileProcessor(ri->re, ri->curtile, 0);
if(ri->rect==NULL)
ri->rect= MEM_mallocN(sizeof(int)*ri->pr_rectx*ri->pr_recty, "preview view3d rect");