/* PhBRML.C: PhBRML extension to VRML'97 for physically * based materials. */ #ifndef NO_VRML #include "readvrml.H" #include "PhBRML.H" #include "cie.h" #include "texture_projection.H" #include "hit.h" #include "patch.h" using namespace xrml; // Special texture projection class for passing default RenderPark texture // coordinates to a PhBRML shader. // The 'point' argument of the project() member function (objecto coordinates // of a point to be shaded) doesn't matter: it returns the texture coordinates // of the hit point in 'hit'. The returned shading frame must be in world // coordinates. class hit_projection: public texture_projection { HITREC *hit; public: hit_projection() { hit = NULL; } inline void set_hit(HITREC *newhit) { hit = newhit; } Vec3 project(const Vec3& point, Vec3* X, Vec3 *Y, Vec3 *Z); }; Vec3 hit_projection::project(const Vec3& point, Vec3* X, Vec3 *Y, Vec3 *Z) { VECTOR texCoord; if (!hit || !HitTexCoord(hit, &texCoord)) { xrml::Warning("hit_projection::project", "Couldn't get texture coordinates"); return Vec3(0.,0.,0.); } // We cannot call HitShadingFrame() here because HitShadingFrame() on a // PhBRML shader indirectly calls this routine and infinite recursion would // result. Neither can we re-use a previously computed shading frame // in the hit record because that includes the potential effect of bump-mapping // or displacement mapping, which would taken into account twice if we would // return it here. if (Z) { DVEC2D uv; if (HitUV(hit, &uv) && (hit->flags & HIT_PATCH)) { VECTOR VX, VY, VZ; PatchInterpolatedFrameAtUV(hit->patch, uv.u, uv.v, &VX, &VY, &VZ); *Z = VECTORTOVEC3(VZ); *X = VECTORTOVEC3(VX); *Y = VECTORTOVEC3(VY); #ifdef NEVER VECTOR N = PatchShadingNormalAtUV(hit->patch, uv.u, uv.v); *Z = Vec3(N.x, N.y, N.z); if (X && Y) { //#ifdef NEVER // X = direction in which u changes for constant v. VECTOR u1, u2; PatchPoint(hit->patch, 0., hit->uv.v, &u1); PatchPoint(hit->patch, 1., hit->uv.v, &u2); *X = Vec3(u2.x-u1.x, u2.y-u1.y, u2.z-u1.z).normalize(); //#endif double zz = sqrt(1 - Z->z*Z->z); *X = (zz < EPSILON) // horizontal brush direction: X in Z=0 plane ? Vec3(1.,0.,0.) // TODO: take X along direction in which u changes at constant v : Vec3(Z->y/zz, -Z->x/zz, 0.); *Y = (*Z) ^ (*X); } #endif } else { xrml::Warning("hit_projection::project", "Couldn't determine shading frame"); if (X) *X = Vec3(1.,0.,0.); if (Y) *Y = Vec3(0.,1.,0.); if (Z) *Z = Vec3(0.,0.,1.); } } return Vec3(texCoord.x, texCoord.y, texCoord.z); } typedef struct VRML_APPEARANCE { PhBAppearance *appearance; // transforms world coordinates to object coordinates and vice versa Mat4 world2object, object2world; int refcount; // number of references: unreferenced if 0 } VRML_APPEARANCE; // last query point, normal and other data + corresponding context for // PhBAppearance queries struct context { VRML_APPEARANCE *app; HITREC hit; BSDF *inBsdf, *outBsdf; class phbcontext *ctx; class hit_projection projector; context() { inBsdf = outBsdf = NULL; ctx = NULL; } void reset(void) { inBsdf = outBsdf = NULL; if (ctx) app->appearance->endquery(ctx); ctx = NULL; } bool check(HITREC *newhit, BSDF *inb, BSDF *outb) { return (ctx && VECTOREQUAL(newhit->point, hit.point, EPSILON) && VECTOREQUAL(newhit->gnormal, hit.gnormal, EPSILON) && inBsdf == inb && outBsdf == outb); } void update(VRML_APPEARANCE *v_app, HITREC *newhit, BSDF *inb =NULL, BSDF *outb =NULL) { if (check(newhit, inb, outb)) return; // query context still adequate // copy new "state" app = v_app; hit = *newhit; inBsdf = inb; outBsdf = outb; Vec3 p(hit.point.x, hit.point.y, hit.point.z); Vec3 n(hit.normal.x, hit.normal.y, hit.normal.z); // determine index of refraction for medium outside the surface and // make sure normal always point outside the surface too. complex indexOut(1.,0.); // default is vacuum if (inBsdf && (void *)app == inBsdf->data) { // ray comes from inside medium n *= -1.; // reverse normal (vrml wants normal to always point // outside surface, RenderPark wants normal and in dir. on the same side) REFRACTIONINDEX index; BsdfIndexOfRefraction(outBsdf, &index); indexOut = complex(index.nr, index.ni); } else if (outBsdf && (void *)app == outBsdf->data) { // ray comes from outside surface // this time, inBsdf determines the medium outside the surface. REFRACTIONINDEX index; BsdfIndexOfRefraction(inBsdf, &index); indexOut = complex(index.nr, index.ni); } if (ctx) app->appearance->endquery(ctx); projector.set_hit(&hit); ctx = app->appearance->initquery(&app->world2object, &app->object2world, p, n, indexOut, &projector); } }; static struct context query; // converts PhBRML color representation to // RenderPark color representation // PhBRML uses CIE XYZ representation. static COLOR f_cvtcol(Spectrum s) { COLOR col; cvtcol(s, col); return col; } // converts VRML Vec3 to RenderPark VECTOR static VECTOR f_cvtvec(Vec3 v) { VECTOR vec; cvtvec(v, vec); return vec; } static int PhBRMLShadingFrame(VRML_APPEARANCE *app, HITREC *hit, VECTOR *X, VECTOR *Y, VECTOR *Z) { query.update(app, hit, NULL, NULL); Mat3 xf = app->appearance->get_shadingframe(query.ctx); if (X) VECTORSET(*X, xf[0][0], xf[0][1], xf[0][2]); if (Y) VECTORSET(*Y, xf[1][0], xf[1][1], xf[1][2]); if (Z) VECTORSET(*Z, xf[2][0], xf[2][1], xf[2][2]); return TRUE; } static int PhBRMLIsTextured(VRML_APPEARANCE *app) { return app->appearance->isTextured(); } static COLOR PhBEDFEmittance(VRML_APPEARANCE *app, HITREC *hit, XXDFFLAGS flags) { query.update(app, hit); // divide by WHITE_EFFICACY to convert lumen/m^2 to Watt/m^2 return f_cvtcol(app->appearance->emittance(query.ctx, (int)flags) / WHITE_EFFICACY); } static COLOR PhBEDFEval(VRML_APPEARANCE *app, HITREC *hit, VECTOR *out, XXDFFLAGS flags, double *pdf) { query.update(app, hit); float fpdf; Spectrum val = app->appearance->evaluateEDF(query.ctx, Vec3(out->x, out->y, out->z), Spectrum(1.,1.,1.), (int)flags, pdf ? &fpdf : (float *)0) / WHITE_EFFICACY; if (pdf) *pdf = fpdf; return f_cvtcol(val); } static VECTOR PhBEDFSample(VRML_APPEARANCE *app, HITREC *hit, XXDFFLAGS flags, double xi1, double xi2, COLOR *emitted_radiance, double *pdf) { query.update(app, hit); float fpdf; Spectrum val; Vec3 dir = app->appearance->sampleEDF(query.ctx, Spectrum(1.,1.,1.), xi1, xi2, (int)flags, emitted_radiance ? &val : (Spectrum *)0, pdf ? &fpdf : (float *)0); if (pdf) *pdf = fpdf; if (emitted_radiance) { val /= WHITE_EFFICACY; cvtcol(val, *emitted_radiance); } return f_cvtvec(dir); } static void PhBEDFPrint(FILE *out, VRML_APPEARANCE *app) { cerr << "VRML Surface: " << app->appearance << "\nTransform:\n" << app->world2object << "\n"; cerr << app->appearance << "\n"; } static VRML_APPEARANCE *PhBEDFDuplicate(VRML_APPEARANCE *app) { app->refcount ++; return app; } static void *PhBEDFCreateEditor(void *parent, VRML_APPEARANCE *app) { xrml::Error("PhBEDFCreateEditor", "not yet implemented"); return NULL; } static void PhBEDFDestroy(VRML_APPEARANCE *app) { query.reset(); app->refcount --; if (app->refcount <= 0) delete app; } static EDF_METHODS PhBEDFMethods = { (COLOR (*)(void *, HITREC *, XXDFFLAGS))PhBEDFEmittance, (int (*)(void *))PhBRMLIsTextured, (COLOR (*)(void *, HITREC *, VECTOR *, XXDFFLAGS, double *))PhBEDFEval, (VECTOR (*)(void *, HITREC *, XXDFFLAGS, double, double, COLOR *, double *))PhBEDFSample, (int (*)(void *, HITREC *, VECTOR *, VECTOR *, VECTOR *))PhBRMLShadingFrame, (void (*)(FILE *, void *))PhBEDFPrint, (void *(*)(void *))PhBEDFDuplicate, (void *(*)(void *, void *))PhBEDFCreateEditor, (void (*)(void *))PhBEDFDestroy }; static EDF * PhBEDFCreate(VRML_APPEARANCE *app) { if (!app->appearance->isLightSource()) { return NULL; } else { app->refcount++; return EdfCreate((void *)app, &PhBEDFMethods); } } static COLOR PhBBSDFAlbedo(VRML_APPEARANCE *app, HITREC *hit, VECTOR *in, BSDFFLAGS flags) { query.update(app, hit, NULL, NULL); Vec3 inDir(in->x, in->y, in->z); return f_cvtcol(app->appearance->albedo(query.ctx, inDir, (int)flags)); } static void PhBBSDFIndexOfRefraction(VRML_APPEARANCE *app, REFRACTIONINDEX *index) { complex n = app->appearance->get_indexOfRefraction(); index->nr = n.r; index->ni = n.i; } extern int debug_mat; /* ui_debug.c TestBsdf routines */ static COLOR PhBBSDFEval(VRML_APPEARANCE *app, HITREC *hit, BSDF *inBsdf, BSDF *outBsdf, VECTOR *in, VECTOR *out, BSDFFLAGS flags) { query.update(app, hit, inBsdf, outBsdf); return f_cvtcol(app->appearance->evaluateBSDF(query.ctx, Vec3(in->x, in->y, in->z), Vec3(out->x, out->y, out->z), Spectrum(1.,1.,1.), (int)flags, (float *)0)); } static VECTOR PhBBSDFSample(VRML_APPEARANCE *app, HITREC *hit, BSDF *inBsdf, BSDF *outBsdf, VECTOR *in, int doRussianRoulette, BSDFFLAGS flags, double xi1, double xi2, double *pdf) { query.update(app, hit, inBsdf, outBsdf); Vec3 inDir(in->x, in->y, in->z); *pdf = 0.; float albedo = 0.; if (doRussianRoulette) { albedo = (app->appearance->albedo(query.ctx, inDir, (int)flags) & Spectrum(1.,1.,1.)) / 3.; if (albedo > 1.) albedo = 1.; if (albedo < EPSILON || xi1 >= albedo) return f_cvtvec(Vec3(0.,0.,0.)); else // rescale xi1 to fill the interval [0,1] again xi1 /= albedo; } float fpdf; Vec3 outDir = app->appearance->sampleBSDF(query.ctx, inDir, Spectrum(1.,1.,1.), xi1, xi2, (int)flags, (Spectrum *)0, &fpdf); *pdf = fpdf; if (doRussianRoulette) *pdf *= albedo; return f_cvtvec(outDir); } static void PhBBSDFEvalPdf(VRML_APPEARANCE *app, HITREC *hit, BSDF *inBsdf, BSDF *outBsdf, VECTOR *in, VECTOR *out, BSDFFLAGS flags, double *pdf, double *pdfRR) { query.update(app, hit, inBsdf, outBsdf); Vec3 inDir(in->x, in->y, in->z); Vec3 outDir(out->x, out->y, out->z); float fpdf = 0.; app->appearance->evaluateBSDF(query.ctx, inDir, outDir, Spectrum(1.,1.,1.), (int)flags, (float *)&fpdf); *pdf = fpdf; float albedo = (app->appearance->albedo(query.ctx, inDir, (int)flags) & Spectrum(1.,1.,1.)) / 3.; *pdfRR = albedo; } static void PhBBSDFPrint(FILE *out, VRML_APPEARANCE *app) { cerr << "VRML Appearance: " << app->appearance << "\nTransform:\n" << app->world2object << "\n"; cerr << app->appearance << "\n"; } static VRML_APPEARANCE *PhBBSDFDuplicate(VRML_APPEARANCE *app) { app->refcount++; return app; } static void *PhBBSDFCreateEditor(void *parent, VRML_APPEARANCE *app) { xrml::Error("PhBBSDFCreateEditor", "not yet implemented"); return NULL; } static void PhBBSDFDestroy(VRML_APPEARANCE *app) { query.reset(); app->refcount --; if (app->refcount <= 0) delete app; } static BSDF_METHODS PhBBSDFMethods = { (COLOR (*)(void *, HITREC *, VECTOR *, BSDFFLAGS))PhBBSDFAlbedo, (int (*)(void *))PhBRMLIsTextured, (void (*)(void *, REFRACTIONINDEX *))PhBBSDFIndexOfRefraction, (COLOR (*)(void *, HITREC *, void *, void *, VECTOR *, VECTOR *, BSDFFLAGS))PhBBSDFEval, (VECTOR (*)(void *, HITREC *, void *, void *, VECTOR *, int, BSDFFLAGS, double, double, double *))PhBBSDFSample, (void (*)(void *, HITREC *, void *, void *, VECTOR *, VECTOR *, BSDFFLAGS, double *, double *))PhBBSDFEvalPdf, (int (*)(void *, HITREC *, VECTOR *, VECTOR *, VECTOR *))PhBRMLShadingFrame, (void (*)(FILE *, void *))PhBBSDFPrint, (void *(*)(void *))PhBBSDFDuplicate, (void *(*)(void *, void *))PhBBSDFCreateEditor, (void (*)(void *))PhBBSDFDestroy }; static BSDF * PhBBSDFCreate(VRML_APPEARANCE *app) { if (!app->appearance->isScatterer()) return NULL; else { app->refcount++; return BsdfCreate((void *)app, &PhBBSDFMethods); } } MATERIAL *PhBRMLMaterialCreate(char *name, PhBAppearance *appearance, const Mat4& world2object, const Mat4& object2world) { // TODO: DEF'ed materials: re-use previous VRML_APPEARANCE if same transforms. VRML_APPEARANCE *app = new VRML_APPEARANCE; app->appearance = appearance; app->world2object = world2object; app->object2world = object2world; app->refcount = 0; return MaterialCreate(name, PhBEDFCreate(app), PhBBSDFCreate(app), 1); } #endif /*NO_VRML*/