#ifndef lint static const char RCSid[] = "$Id: m_bsdf.c,v 2.54 2018/08/08 04:15:18 greg Exp $"; #endif /* * Shading for materials with BSDFs taken from XML data files */ #include "copyright.h" #include "ray.h" #include "otypes.h" #include "ambient.h" #include "source.h" #include "func.h" #include "bsdf.h" #include "random.h" #include "pmapmat.h" /* * Arguments to this material include optional diffuse colors. * String arguments include the BSDF and function files. * For the MAT_BSDF type, a non-zero thickness causes the useful behavior * of translating transmitted rays this distance beneath the surface * (opposite the surface normal) to bypass any intervening geometry. * Translation only affects scattered, non-source-directed samples. * A non-zero thickness has the further side-effect that an unscattered * (view) ray will pass right through our material, making the BSDF * surface invisible and showing the proxied geometry instead. Thickness * has the further effect of turning off reflection on the reverse side so * rays heading in the opposite direction pass unimpeded through the BSDF * surface. A paired surface may be placed on the opposide side of * the detail geometry, less than this thickness away, if a two-way * proxy is desired. Note that the sign of the thickness is important. * A positive thickness hides geometry behind the BSDF surface and uses * front reflectance and transmission properties. A negative thickness * hides geometry in front of the surface when rays hit from behind, * and applies only the transmission and backside reflectance properties. * Reflection is ignored on the hidden side, as those rays pass through. * For the MAT_ABSDF type, we check for a strong "through" component. * Such a component will cause direct rays to pass through unscattered. * A separate test prevents over-counting by dropping samples that are * too close to this "through" direction. BSDFs with such a through direction * will also have a view component, meaning they are somewhat see-through. * A MAT_BSDF type with zero thickness behaves the same as a MAT_ABSDF * type with no strong through component. * The "up" vector for the BSDF is given by three variables, defined * (along with the thickness) by the named function file, or '.' if none. * Together with the surface normal, this defines the local coordinate * system for the BSDF. * We do not reorient the surface, so if the BSDF has no back-side * reflectance and none is given in the real arguments, a BSDF surface * with zero thickness will appear black when viewed from behind * unless backface visibility is on, when it becomes invisible. * The diffuse arguments are added to components in the BSDF file, * not multiplied. However, patterns affect this material as a multiplier * on everything except non-diffuse reflection. * * Arguments for MAT_ABSDF are: * 5+ BSDFfile ux uy uz funcfile transform * 0 * 0|3|6|9 rdf gdf bdf * rdb gdb bdb * rdt gdt bdt * * Arguments for MAT_BSDF are: * 6+ thick BSDFfile ux uy uz funcfile transform * 0 * 0|3|6|9 rdf gdf bdf * rdb gdb bdb * rdt gdt bdt */ /* * Note that our reverse ray-tracing process means that the positions * of incoming and outgoing vectors may be reversed in our calls * to the BSDF library. This is usually fine, since the bidirectional nature * of the BSDF (that's what the 'B' stands for) means it all works out. */ typedef struct { OBJREC *mp; /* material pointer */ RAY *pr; /* intersected ray */ FVECT pnorm; /* perturbed surface normal */ FVECT vray; /* local outgoing (return) vector */ double sr_vpsa[2]; /* sqrt of BSDF projected solid angle extrema */ RREAL toloc[3][3]; /* world to local BSDF coords */ RREAL fromloc[3][3]; /* local BSDF coords to world */ double thick; /* surface thickness */ COLOR cthru; /* "through" component for MAT_ABSDF */ SDData *sd; /* loaded BSDF data */ COLOR rdiff; /* diffuse reflection */ COLOR runsamp; /* BSDF hemispherical reflection */ COLOR tdiff; /* diffuse transmission */ COLOR tunsamp; /* BSDF hemispherical transmission */ } BSDFDAT; /* BSDF material data */ #define cvt_sdcolor(cv, svp) ccy2rgb(&(svp)->spec, (svp)->cieY, cv) /* Compute "through" component color for MAT_ABSDF */ static void compute_through(BSDFDAT *ndp) { #define NDIR2CHECK 13 static const float dir2check[NDIR2CHECK][2] = { {0, 0}, {-0.8, 0}, {0, 0.8}, {0, -0.8}, {0.8, 0}, {-0.8, 0.8}, {-0.8, -0.8}, {0.8, 0.8}, {0.8, -0.8}, {-1.6, 0}, {0, 1.6}, {0, -1.6}, {1.6, 0}, }; const double peak_over = 1.5; SDSpectralDF *dfp; FVECT pdir; double tomega, srchrad; COLOR vpeak, vsum; int i; SDError ec; if (ndp->pr->rod > 0) dfp = (ndp->sd->tf != NULL) ? ndp->sd->tf : ndp->sd->tb; else dfp = (ndp->sd->tb != NULL) ? ndp->sd->tb : ndp->sd->tf; if (dfp == NULL) return; /* no specular transmission */ if (bright(ndp->pr->pcol) <= FTINY) return; /* pattern is black, here */ srchrad = sqrt(dfp->minProjSA); /* else search for peak */ setcolor(vpeak, 0, 0, 0); setcolor(vsum, 0, 0, 0); pdir[2] = 0.0; for (i = 0; i < NDIR2CHECK; i++) { FVECT tdir; SDValue sv; COLOR vcol; tdir[0] = -ndp->vray[0] + dir2check[i][0]*srchrad; tdir[1] = -ndp->vray[1] + dir2check[i][1]*srchrad; tdir[2] = -ndp->vray[2]; normalize(tdir); ec = SDevalBSDF(&sv, tdir, ndp->vray, ndp->sd); if (ec) goto baderror; cvt_sdcolor(vcol, &sv); addcolor(vsum, vcol); if (sv.cieY > bright(vpeak)) { copycolor(vpeak, vcol); VCOPY(pdir, tdir); } } if (pdir[2] == 0.0) return; /* zero neighborhood */ ec = SDsizeBSDF(&tomega, pdir, ndp->vray, SDqueryMin, ndp->sd); if (ec) goto baderror; if (tomega > 1.5*dfp->minProjSA) return; /* not really a peak? */ if ((bright(vpeak) - ndp->sd->tLamb.cieY*(1./PI))*tomega <= .001) return; /* < 0.1% transmission */ for (i = 3; i--; ) /* remove peak from average */ colval(vsum,i) -= colval(vpeak,i); if (peak_over*bright(vsum) >= (NDIR2CHECK-1)*bright(vpeak)) return; /* not peaky enough */ copycolor(ndp->cthru, vpeak); /* else use it */ scalecolor(ndp->cthru, tomega); multcolor(ndp->cthru, ndp->pr->pcol); /* modify by pattern */ return; baderror: objerror(ndp->mp, USER, transSDError(ec)); #undef NDIR2CHECK } /* Jitter ray sample according to projected solid angle and specjitter */ static void bsdf_jitter(FVECT vres, BSDFDAT *ndp, double sr_psa) { VCOPY(vres, ndp->vray); if (specjitter < 1.) sr_psa *= specjitter; if (sr_psa <= FTINY) return; vres[0] += sr_psa*(.5 - frandom()); vres[1] += sr_psa*(.5 - frandom()); normalize(vres); } /* Get BSDF specular for direct component, returning true if OK to proceed */ static int direct_specular_OK(COLOR cval, FVECT ldir, double omega, BSDFDAT *ndp) { int nsamp; double wtot = 0; FVECT vsrc, vsmp, vjit; double tomega, tomega2; double sf, tsr, sd[2]; COLOR csmp, cdiff; double diffY; SDValue sv; SDError ec; int i; /* in case we fail */ setcolor(cval, 0, 0, 0); /* transform source direction */ if (SDmapDir(vsrc, ndp->toloc, ldir) != SDEnone) return(0); /* will discount diffuse portion */ switch ((vsrc[2] > 0)<<1 | (ndp->vray[2] > 0)) { case 3: if (ndp->sd->rf == NULL) return(0); /* all diffuse */ sv = ndp->sd->rLambFront; break; case 0: if (ndp->sd->rb == NULL) return(0); /* all diffuse */ sv = ndp->sd->rLambBack; break; default: if ((ndp->sd->tf == NULL) & (ndp->sd->tb == NULL)) return(0); /* all diffuse */ sv = ndp->sd->tLamb; break; } if (sv.cieY > FTINY) { diffY = sv.cieY *= 1./PI; cvt_sdcolor(cdiff, &sv); } else { diffY = 0; setcolor(cdiff, 0, 0, 0); } /* need projected solid angle */ omega *= fabs(vsrc[2]); /* check indirect over-counting */ if ((vsrc[2] > 0) ^ (ndp->vray[2] > 0) && bright(ndp->cthru) > FTINY) { double dx = vsrc[0] + ndp->vray[0]; double dy = vsrc[1] + ndp->vray[1]; SDSpectralDF *dfp = (ndp->pr->rod > 0) ? ((ndp->sd->tf != NULL) ? ndp->sd->tf : ndp->sd->tb) : ((ndp->sd->tb != NULL) ? ndp->sd->tb : ndp->sd->tf) ; if (dx*dx + dy*dy <= (2.5*4./PI)*(omega + dfp->minProjSA + 2.*sqrt(omega*dfp->minProjSA))) return(0); } ec = SDsizeBSDF(&tomega, ndp->vray, vsrc, SDqueryMin, ndp->sd); if (ec) goto baderror; /* assign number of samples */ sf = specjitter * ndp->pr->rweight; if (tomega <= 0) nsamp = 1; else if (25.*tomega <= omega) nsamp = 100.*sf + .5; else nsamp = 4.*sf*omega/tomega + .5; nsamp += !nsamp; sf = sqrt(omega); /* sample our source area */ tsr = sqrt(tomega); for (i = nsamp; i--; ) { VCOPY(vsmp, vsrc); /* jitter query directions */ if (nsamp > 1) { multisamp(sd, 2, (i + frandom())/(double)nsamp); vsmp[0] += (sd[0] - .5)*sf; vsmp[1] += (sd[1] - .5)*sf; normalize(vsmp); } bsdf_jitter(vjit, ndp, tsr); /* compute BSDF */ ec = SDevalBSDF(&sv, vjit, vsmp, ndp->sd); if (ec) goto baderror; if (sv.cieY - diffY <= FTINY) continue; /* no specular part */ /* check for variable resolution */ ec = SDsizeBSDF(&tomega2, vjit, vsmp, SDqueryMin, ndp->sd); if (ec) goto baderror; if (tomega2 < .12*tomega) continue; /* not safe to include */ cvt_sdcolor(csmp, &sv); if (sf < 2.5*tsr) { /* weight by Y for small sources */ scalecolor(csmp, sv.cieY); wtot += sv.cieY; } else wtot += 1.; addcolor(cval, csmp); } if (wtot <= FTINY) /* no valid specular samples? */ return(0); sf = 1./wtot; /* weighted average BSDF */ scalecolor(cval, sf); /* subtract diffuse contribution */ for (i = 3*(diffY > FTINY); i--; ) if ((colval(cval,i) -= colval(cdiff,i)) < 0) colval(cval,i) = 0; return(1); baderror: objerror(ndp->mp, USER, transSDError(ec)); return(0); /* gratis return */ } /* Compute source contribution for BSDF (reflected & transmitted) */ static void dir_bsdf( COLOR cval, /* returned coefficient */ void *nnp, /* material data */ FVECT ldir, /* light source direction */ double omega /* light source size */ ) { BSDFDAT *np = (BSDFDAT *)nnp; double ldot; double dtmp; COLOR ctmp; setcolor(cval, 0, 0, 0); ldot = DOT(np->pnorm, ldir); if ((-FTINY <= ldot) & (ldot <= FTINY)) return; if (ldot > 0 && bright(np->rdiff) > FTINY) { /* * Compute diffuse reflected component */ copycolor(ctmp, np->rdiff); dtmp = ldot * omega * (1./PI); scalecolor(ctmp, dtmp); addcolor(cval, ctmp); } if (ldot < 0 && bright(np->tdiff) > FTINY) { /* * Compute diffuse transmission */ copycolor(ctmp, np->tdiff); dtmp = -ldot * omega * (1.0/PI); scalecolor(ctmp, dtmp); addcolor(cval, ctmp); } if (ambRayInPmap(np->pr)) return; /* specular already in photon map */ /* * Compute specular scattering coefficient using BSDF */ if (!direct_specular_OK(ctmp, ldir, omega, np)) return; if (ldot < 0) { /* pattern for specular transmission */ multcolor(ctmp, np->pr->pcol); dtmp = -ldot * omega; } else dtmp = ldot * omega; scalecolor(ctmp, dtmp); addcolor(cval, ctmp); } /* Compute source contribution for BSDF (reflected only) */ static void dir_brdf( COLOR cval, /* returned coefficient */ void *nnp, /* material data */ FVECT ldir, /* light source direction */ double omega /* light source size */ ) { BSDFDAT *np = (BSDFDAT *)nnp; double ldot; double dtmp; COLOR ctmp, ctmp1, ctmp2; setcolor(cval, 0, 0, 0); ldot = DOT(np->pnorm, ldir); if (ldot <= FTINY) return; if (bright(np->rdiff) > FTINY) { /* * Compute diffuse reflected component */ copycolor(ctmp, np->rdiff); dtmp = ldot * omega * (1./PI); scalecolor(ctmp, dtmp); addcolor(cval, ctmp); } if (ambRayInPmap(np->pr)) return; /* specular already in photon map */ /* * Compute specular reflection coefficient using BSDF */ if (!direct_specular_OK(ctmp, ldir, omega, np)) return; dtmp = ldot * omega; scalecolor(ctmp, dtmp); addcolor(cval, ctmp); } /* Compute source contribution for BSDF (transmitted only) */ static void dir_btdf( COLOR cval, /* returned coefficient */ void *nnp, /* material data */ FVECT ldir, /* light source direction */ double omega /* light source size */ ) { BSDFDAT *np = (BSDFDAT *)nnp; double ldot; double dtmp; COLOR ctmp; setcolor(cval, 0, 0, 0); ldot = DOT(np->pnorm, ldir); if (ldot >= -FTINY) return; if (bright(np->tdiff) > FTINY) { /* * Compute diffuse transmission */ copycolor(ctmp, np->tdiff); dtmp = -ldot * omega * (1.0/PI); scalecolor(ctmp, dtmp); addcolor(cval, ctmp); } if (ambRayInPmap(np->pr)) return; /* specular already in photon map */ /* * Compute specular scattering coefficient using BSDF */ if (!direct_specular_OK(ctmp, ldir, omega, np)) return; /* full pattern on transmission */ multcolor(ctmp, np->pr->pcol); dtmp = -ldot * omega; scalecolor(ctmp, dtmp); addcolor(cval, ctmp); } /* Sample separate BSDF component */ static int sample_sdcomp(BSDFDAT *ndp, SDComponent *dcp, int xmit) { const int hasthru = (xmit && !(ndp->pr->crtype & (SPECULAR|AMBIENT)) && bright(ndp->cthru) > FTINY); int nstarget = 1; int nsent = 0; int n; SDError ec; SDValue bsv; double xrand; FVECT vsmp, vinc; RAY sr; /* multiple samples? */ if (specjitter > 1.5) { nstarget = specjitter*ndp->pr->rweight + .5; nstarget += !nstarget; } /* run through our samples */ for (n = 0; n < nstarget; n++) { if (nstarget == 1) { /* stratify random variable */ xrand = urand(ilhash(dimlist,ndims)+samplendx); if (specjitter < 1.) xrand = .5 + specjitter*(xrand-.5); } else { xrand = (n + frandom())/(double)nstarget; } SDerrorDetail[0] = '\0'; /* sample direction & coef. */ bsdf_jitter(vsmp, ndp, ndp->sr_vpsa[0]); VCOPY(vinc, vsmp); /* to compare after */ ec = SDsampComponent(&bsv, vsmp, xrand, dcp); if (ec) objerror(ndp->mp, USER, transSDError(ec)); if (bsv.cieY <= FTINY) /* zero component? */ break; if (hasthru) { /* check for view ray */ double dx = vinc[0] + vsmp[0]; double dy = vinc[1] + vsmp[1]; if (dx*dx + dy*dy <= ndp->sr_vpsa[0]*ndp->sr_vpsa[0]) continue; /* exclude view sample */ } /* map non-view sample->world */ if (SDmapDir(sr.rdir, ndp->fromloc, vsmp) != SDEnone) break; /* spawn a specular ray */ if (nstarget > 1) bsv.cieY /= (double)nstarget; cvt_sdcolor(sr.rcoef, &bsv); /* use sample color */ if (xmit) /* apply pattern on transmit */ multcolor(sr.rcoef, ndp->pr->pcol); if (rayorigin(&sr, SPECULAR, ndp->pr, sr.rcoef) < 0) { if (!n & (nstarget > 1)) { n = nstarget; /* avoid infinitue loop */ nstarget = nstarget*sr.rweight/minweight; if (n == nstarget) break; n = -1; /* moved target */ } continue; /* try again */ } if (xmit && ndp->thick != 0) /* need to offset origin? */ VSUM(sr.rorg, sr.rorg, ndp->pr->ron, -ndp->thick); rayvalue(&sr); /* send & evaluate sample */ multcolor(sr.rcol, sr.rcoef); addcolor(ndp->pr->rcol, sr.rcol); ++nsent; } return(nsent); } /* Sample non-diffuse components of BSDF */ static int sample_sdf(BSDFDAT *ndp, int sflags) { int hasthru = (sflags == SDsampSpT && !(ndp->pr->crtype & (SPECULAR|AMBIENT)) && bright(ndp->cthru) > FTINY); int n, ntotal = 0; double b = 0; SDSpectralDF *dfp; COLORV *unsc; if (sflags == SDsampSpT) { unsc = ndp->tunsamp; if (ndp->pr->rod > 0) dfp = (ndp->sd->tf != NULL) ? ndp->sd->tf : ndp->sd->tb; else dfp = (ndp->sd->tb != NULL) ? ndp->sd->tb : ndp->sd->tf; } else /* sflags == SDsampSpR */ { unsc = ndp->runsamp; if (ndp->pr->rod > 0) dfp = ndp->sd->rf; else dfp = ndp->sd->rb; } setcolor(unsc, 0, 0, 0); if (dfp == NULL) /* no specular component? */ return(0); if (hasthru) { /* separate view sample? */ RAY tr; if (rayorigin(&tr, TRANS, ndp->pr, ndp->cthru) == 0) { VCOPY(tr.rdir, ndp->pr->rdir); rayvalue(&tr); multcolor(tr.rcol, tr.rcoef); addcolor(ndp->pr->rcol, tr.rcol); ++ntotal; b = bright(ndp->cthru); } else hasthru = 0; } if (dfp->maxHemi - b <= FTINY) { /* have specular to sample? */ b = 0; } else { FVECT vjit; bsdf_jitter(vjit, ndp, ndp->sr_vpsa[1]); b = SDdirectHemi(vjit, sflags, ndp->sd) - b; if (b < 0) b = 0; } if (b <= specthresh+FTINY) { /* below sampling threshold? */ if (b > FTINY) { /* XXX no color from BSDF */ if (sflags == SDsampSpT) { copycolor(unsc, ndp->pr->pcol); scalecolor(unsc, b); } else /* no pattern on reflection */ setcolor(unsc, b, b, b); } return(ntotal); } dimlist[ndims] = (int)(size_t)ndp->mp; /* else sample specular */ ndims += 2; for (n = dfp->ncomp; n--; ) { /* loop over components */ dimlist[ndims-1] = n + 9438; ntotal += sample_sdcomp(ndp, &dfp->comp[n], sflags==SDsampSpT); } ndims -= 2; return(ntotal); } /* Color a ray that hit a BSDF material */ int m_bsdf(OBJREC *m, RAY *r) { int hasthick = (m->otype == MAT_BSDF); int hitfront; COLOR ctmp; SDError ec; FVECT upvec, vtmp; MFUNC *mf; BSDFDAT nd; /* check arguments */ if ((m->oargs.nsargs < hasthick+5) | (m->oargs.nfargs > 9) | (m->oargs.nfargs % 3)) objerror(m, USER, "bad # arguments"); /* record surface struck */ hitfront = (r->rod > 0); /* load cal file */ mf = hasthick ? getfunc(m, 5, 0x1d, 1) : getfunc(m, 4, 0xe, 1) ; setfunc(m, r); nd.thick = 0; /* set thickness */ if (hasthick) { nd.thick = evalue(mf->ep[0]); if ((-FTINY <= nd.thick) & (nd.thick <= FTINY)) nd.thick = 0; } /* check backface visibility */ if (!hitfront & !backvis) { raytrans(r); return(1); } /* check other rays to pass */ if (nd.thick != 0 && (r->crtype & SHADOW || !(r->crtype & (SPECULAR|AMBIENT)) || (nd.thick > 0) ^ hitfront)) { raytrans(r); /* hide our proxy */ return(1); } if (hasthick && r->crtype & SHADOW) /* early shadow check #1 */ return(1); nd.mp = m; nd.pr = r; /* get BSDF data */ nd.sd = loadBSDF(m->oargs.sarg[hasthick]); /* early shadow check #2 */ if (r->crtype & SHADOW && (nd.sd->tf == NULL) & (nd.sd->tb == NULL)) return(1); /* diffuse reflectance */ if (hitfront) { cvt_sdcolor(nd.rdiff, &nd.sd->rLambFront); if (m->oargs.nfargs >= 3) { setcolor(ctmp, m->oargs.farg[0], m->oargs.farg[1], m->oargs.farg[2]); addcolor(nd.rdiff, ctmp); } } else { cvt_sdcolor(nd.rdiff, &nd.sd->rLambBack); if (m->oargs.nfargs >= 6) { setcolor(ctmp, m->oargs.farg[3], m->oargs.farg[4], m->oargs.farg[5]); addcolor(nd.rdiff, ctmp); } } /* diffuse transmittance */ cvt_sdcolor(nd.tdiff, &nd.sd->tLamb); if (m->oargs.nfargs >= 9) { setcolor(ctmp, m->oargs.farg[6], m->oargs.farg[7], m->oargs.farg[8]); addcolor(nd.tdiff, ctmp); } /* get modifiers */ raytexture(r, m->omod); /* modify diffuse values */ multcolor(nd.rdiff, r->pcol); multcolor(nd.tdiff, r->pcol); /* get up vector */ upvec[0] = evalue(mf->ep[hasthick+0]); upvec[1] = evalue(mf->ep[hasthick+1]); upvec[2] = evalue(mf->ep[hasthick+2]); /* return to world coords */ if (mf->fxp != &unitxf) { multv3(upvec, upvec, mf->fxp->xfm); nd.thick *= mf->fxp->sca; } if (r->rox != NULL) { multv3(upvec, upvec, r->rox->f.xfm); nd.thick *= r->rox->f.sca; } raynormal(nd.pnorm, r); /* compute local BSDF xform */ ec = SDcompXform(nd.toloc, nd.pnorm, upvec); if (!ec) { nd.vray[0] = -r->rdir[0]; nd.vray[1] = -r->rdir[1]; nd.vray[2] = -r->rdir[2]; ec = SDmapDir(nd.vray, nd.toloc, nd.vray); } if (ec) { objerror(m, WARNING, "Illegal orientation vector"); return(1); } setcolor(nd.cthru, 0, 0, 0); /* consider through component */ if (m->otype == MAT_ABSDF) { compute_through(&nd); if (r->crtype & SHADOW) { RAY tr; /* attempt to pass shadow ray */ if (rayorigin(&tr, TRANS, r, nd.cthru) < 0) return(1); /* no through component */ VCOPY(tr.rdir, r->rdir); rayvalue(&tr); /* transmit with scaling */ multcolor(tr.rcol, tr.rcoef); copycolor(r->rcol, tr.rcol); return(1); /* we're done */ } } ec = SDinvXform(nd.fromloc, nd.toloc); if (!ec) /* determine BSDF resolution */ ec = SDsizeBSDF(nd.sr_vpsa, nd.vray, NULL, SDqueryMin+SDqueryMax, nd.sd); if (ec) objerror(m, USER, transSDError(ec)); nd.sr_vpsa[0] = sqrt(nd.sr_vpsa[0]); nd.sr_vpsa[1] = sqrt(nd.sr_vpsa[1]); if (!hitfront) { /* perturb normal towards hit */ nd.pnorm[0] = -nd.pnorm[0]; nd.pnorm[1] = -nd.pnorm[1]; nd.pnorm[2] = -nd.pnorm[2]; } /* sample reflection */ sample_sdf(&nd, SDsampSpR); /* sample transmission */ sample_sdf(&nd, SDsampSpT); /* compute indirect diffuse */ copycolor(ctmp, nd.rdiff); addcolor(ctmp, nd.runsamp); if (bright(ctmp) > FTINY) { /* ambient from reflection */ if (!hitfront) flipsurface(r); multambient(ctmp, r, nd.pnorm); addcolor(r->rcol, ctmp); if (!hitfront) flipsurface(r); } copycolor(ctmp, nd.tdiff); addcolor(ctmp, nd.tunsamp); if (bright(ctmp) > FTINY) { /* ambient from other side */ FVECT bnorm; if (hitfront) flipsurface(r); bnorm[0] = -nd.pnorm[0]; bnorm[1] = -nd.pnorm[1]; bnorm[2] = -nd.pnorm[2]; if (nd.thick != 0) { /* proxy with offset? */ VCOPY(vtmp, r->rop); VSUM(r->rop, vtmp, r->ron, nd.thick); multambient(ctmp, r, bnorm); VCOPY(r->rop, vtmp); } else multambient(ctmp, r, bnorm); addcolor(r->rcol, ctmp); if (hitfront) flipsurface(r); } /* add direct component */ if ((bright(nd.tdiff) <= FTINY) & (nd.sd->tf == NULL) & (nd.sd->tb == NULL)) { direct(r, dir_brdf, &nd); /* reflection only */ } else if (nd.thick == 0) { direct(r, dir_bsdf, &nd); /* thin surface scattering */ } else { direct(r, dir_brdf, &nd); /* reflection first */ VCOPY(vtmp, r->rop); /* offset for transmitted */ VSUM(r->rop, vtmp, r->ron, -nd.thick); direct(r, dir_btdf, &nd); /* separate transmission */ VCOPY(r->rop, vtmp); } /* clean up */ SDfreeCache(nd.sd); return(1); }