solstice-solver

Solver library of the solstice app
git clone git://git.meso-star.com/solstice-solver.git
Log | Files | Refs | README | LICENSE

ssol_material.c (22636B)


      1 /* Copyright (C) 2018-2026 |Meso|Star> (contact@meso-star.com)
      2  * Copyright (C) 2016, 2018 CNRS
      3  *
      4  * This program is free software: you can redistribute it and/or modify
      5  * it under the terms of the GNU General Public License as published by
      6  * the Free Software Foundation, either version 3 of the License, or
      7  * (at your option) any later version.
      8  *
      9  * This program is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     12  * GNU General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU General Public License
     15  * along with this program. If not, see <http://www.gnu.org/licenses/>. */
     16 
     17 #include "ssol.h"
     18 #include "ssol_c.h"
     19 #include "ssol_device_c.h"
     20 #include "ssol_material_c.h"
     21 #include "ssol_spectrum_c.h"
     22 
     23 #include <rsys/double2.h>
     24 #include <rsys/double3.h>
     25 #include <rsys/double33.h>
     26 #include <rsys/float2.h>
     27 #include <rsys/float3.h>
     28 #include <rsys/float33.h>
     29 #include <rsys/ref_count.h>
     30 #include <rsys/rsys.h>
     31 #include <rsys/mem_allocator.h>
     32 
     33 #include <star/ssf.h>
     34 
     35 #include <math.h>
     36 #include <omp.h>
     37 
     38 /*******************************************************************************
     39  * Helper functions
     40  ******************************************************************************/
     41 /* Define if the submitted ssol_data are *certainly* equals or not. Note that it
     42  * does not check explicitly the spectrum data since it would be too expensive;
     43  * it compares their checksum and that's why one cannot certify that the data
     44  * are strictly equals. Anyway, since this function is used to detect medium
     45  * inconsistencies, it is actually really sufficient to use this strategy. */
     46 static INLINE int
     47 ssol_data_ceq(const struct ssol_data* a, const struct ssol_data* b)
     48 {
     49   int i;
     50   ASSERT(a && b);
     51 
     52   if(a->type != b->type) {
     53     i = 0;
     54   } else {
     55     switch(a->type) {
     56       case SSOL_DATA_REAL:
     57         i = a->value.real == b->value.real;
     58         break;
     59       case SSOL_DATA_SPECTRUM:
     60         i =  a->value.spectrum->checksum[0] == b->value.spectrum->checksum[0]
     61           && a->value.spectrum->checksum[1] == b->value.spectrum->checksum[1];
     62         break;
     63       default: FATAL("Unreachable code\n"); break;
     64     }
     65   }
     66   return i;
     67 }
     68 
     69 static void
     70 shade_normal_default
     71   (struct ssol_device* dev,
     72    struct ssol_param_buffer* buf,
     73    const double wlen,
     74    const struct ssol_surface_fragment* frag,
     75    double* val) /* Returned value */
     76 {
     77   ASSERT(frag && val);
     78   (void)dev, (void)buf, (void)wlen;
     79   d3_set(val, frag->Ns);
     80 }
     81 
     82 static res_T
     83 create_dielectric_bsdf
     84   (const struct ssol_material* mtl,
     85    const struct ssol_surface_fragment* fragment,
     86    const double wavelength, /* In nanometer */
     87    const struct ssol_medium* medium,
     88    struct ssf_bsdf** bsdf)
     89 {
     90   double eta_i, eta_t;
     91   const int ithread = omp_get_thread_num();
     92   res_T res = RES_OK;
     93   ASSERT(mtl && fragment && mtl->type == SSOL_MATERIAL_DIELECTRIC);
     94   ASSERT(medium && bsdf);
     95   (void)wavelength, (void)fragment;
     96 
     97   if(!media_ceq(medium, &mtl->out_medium)) {
     98     log_error(mtl->dev, "Inconsistent medium description.\n");
     99     res = RES_BAD_OP;
    100     goto error;
    101   }
    102 
    103   eta_i = ssol_data_get_value(&mtl->out_medium.refractive_index, wavelength);
    104   eta_t = ssol_data_get_value(&mtl->in_medium.refractive_index, wavelength);
    105 
    106   #define CALL(Func) { res = Func; if(res != RES_OK) goto error; } (void)0
    107   CALL(ssf_bsdf_create(&mtl->dev->bsdf_allocators[ithread],
    108     &ssf_specular_dielectric_dielectric_interface, bsdf));
    109   CALL(ssf_specular_dielectric_dielectric_interface_setup(*bsdf, eta_i, eta_t));
    110   #undef CALL
    111 
    112 exit:
    113   return res;
    114 error:
    115   if(*bsdf) SSF(bsdf_ref_put(*bsdf)), bsdf = NULL;
    116   goto exit;
    117 }
    118 
    119 static res_T
    120 create_matte_bsdf
    121   (const struct ssol_material* mtl,
    122    const struct ssol_surface_fragment* fragment,
    123    const double wavelength, /* In nanometer */
    124    struct ssf_bsdf** bsdf)
    125 {
    126   double reflectivity;
    127   const int ithread = omp_get_thread_num();
    128   res_T res;
    129   ASSERT(mtl && fragment && mtl->type == SSOL_MATERIAL_MATTE);
    130   ASSERT(bsdf);
    131 
    132   /* Fetch material attribs */
    133   mtl->data.matte.reflectivity
    134     (mtl->dev, mtl->buf, wavelength, fragment, &reflectivity);
    135 
    136   /* Setup the BRDF */
    137   res = ssf_bsdf_create
    138     (&mtl->dev->bsdf_allocators[ithread], &ssf_lambertian_reflection, bsdf);
    139   if(res != RES_OK) goto error;
    140   res = ssf_lambertian_reflection_setup(*bsdf, reflectivity);
    141   if(res != RES_OK) goto error;
    142 
    143 exit:
    144   return res;
    145 error:
    146   if(*bsdf) SSF(bsdf_ref_put(*bsdf)), bsdf = NULL;
    147   goto exit;
    148 }
    149 
    150 static res_T
    151 create_mirror_bsdf
    152   (const struct ssol_material* mtl,
    153    const struct ssol_surface_fragment* fragment,
    154    const double wavelength, /* In nanometer */
    155    const int rendering,
    156    struct ssf_bsdf** bsdf)
    157 {
    158   struct ssf_fresnel* fresnel = NULL;
    159   struct ssf_microfacet_distribution* distrib = NULL;
    160   double roughness;
    161   double reflectivity;
    162   const int ithread = omp_get_thread_num();
    163   res_T res;
    164   ASSERT(mtl && fragment && mtl->type == SSOL_MATERIAL_MIRROR);
    165   ASSERT(bsdf);
    166 
    167   /* Fetch material attribs */
    168   mtl->data.mirror.reflectivity
    169     (mtl->dev, mtl->buf, wavelength, fragment, &reflectivity);
    170   mtl->data.mirror.roughness
    171     (mtl->dev, mtl->buf, wavelength, fragment, &roughness);
    172 
    173   /* Setup the fresnel term */
    174   res = ssf_fresnel_create(mtl->dev->allocator, &ssf_fresnel_constant, &fresnel);
    175   if(res != RES_OK) goto error;
    176   res = ssf_fresnel_constant_setup(fresnel, reflectivity);
    177   if(res != RES_OK) goto error;
    178 
    179   /* Setup the BRDF */
    180   if(roughness == 0) { /* Purely specular reflection */
    181     res = ssf_bsdf_create
    182       (&mtl->dev->bsdf_allocators[ithread], &ssf_specular_reflection, bsdf);
    183     if(res != RES_OK) goto error;
    184     res = ssf_specular_reflection_setup(*bsdf, fresnel);
    185     if(res != RES_OK) goto error;
    186   } else { /* Glossy reflection */
    187     switch(mtl->data.mirror.distrib) {
    188       /* Setup the microfacet distribution */
    189       case SSOL_MICROFACET_BECKMANN:
    190         res = ssf_microfacet_distribution_create
    191           (mtl->dev->allocator, &ssf_beckmann_distribution, &distrib);
    192         if(res != RES_OK) goto error;
    193         res = ssf_beckmann_distribution_setup(distrib, roughness);
    194         if(res != RES_OK) goto error;
    195         break;
    196       case SSOL_MICROFACET_PILLBOX:
    197         res = ssf_microfacet_distribution_create
    198           (mtl->dev->allocator, &ssf_pillbox_distribution, &distrib);
    199         if(res != RES_OK) goto error;
    200         res = ssf_pillbox_distribution_setup(distrib, roughness);
    201         if(res != RES_OK) goto error;
    202         break;
    203       default: FATAL("Unreachable code.\n"); break;
    204     }
    205 
    206     /* Microfacet2 is not well suited for rendering since it cannot be
    207      * evaluated and consequently it returns an invalid result for direct
    208      * lighting. */
    209     if(rendering) {
    210       res = ssf_bsdf_create
    211         (&mtl->dev->bsdf_allocators[ithread], &ssf_microfacet_reflection, bsdf);
    212     } else {
    213       res = ssf_bsdf_create
    214         (&mtl->dev->bsdf_allocators[ithread], &ssf_microfacet2_reflection, bsdf);
    215     }
    216     if(res != RES_OK) goto error;
    217     res = ssf_microfacet_reflection_setup(*bsdf, fresnel, distrib);
    218     if(res != RES_OK) goto error;
    219   }
    220 
    221 exit:
    222   if(fresnel) SSF(fresnel_ref_put(fresnel));
    223   if(distrib) SSF(microfacet_distribution_ref_put(distrib));
    224   return res;
    225 error:
    226   if(*bsdf) SSF(bsdf_ref_put(*bsdf)), bsdf = NULL;
    227   goto exit;
    228 }
    229 
    230 static res_T
    231 create_thin_dielectric_bsdf
    232   (const struct ssol_material* mtl,
    233    const struct ssol_surface_fragment* fragment,
    234    const double wavelength, /* In nanometer */
    235    struct ssf_bsdf** bsdf)
    236 {
    237   double thickness;
    238   double absorption;
    239   double eta_i;
    240   double eta_t;
    241   const int ithread = omp_get_thread_num();
    242   res_T res = RES_OK;
    243   ASSERT(mtl && fragment && mtl->type == SSOL_MATERIAL_THIN_DIELECTRIC);
    244   ASSERT(bsdf);
    245   (void)wavelength, (void)fragment;
    246 
    247   eta_i = ssol_data_get_value(&mtl->out_medium.refractive_index, wavelength);
    248   eta_t = ssol_data_get_value
    249     (&mtl->data.thin_dielectric.slab_medium.refractive_index, wavelength);
    250   /* Here extinction is absorption only */
    251   absorption = ssol_data_get_value
    252     (&mtl->data.thin_dielectric.slab_medium.extinction, wavelength);
    253   thickness = mtl->data.thin_dielectric.thickness;
    254 
    255   /* Setup the BxDF */
    256   res = ssf_bsdf_create
    257     (&mtl->dev->bsdf_allocators[ithread], &ssf_thin_specular_dielectric, bsdf);
    258   if(res != RES_OK) goto error;
    259   res = ssf_thin_specular_dielectric_setup
    260     (*bsdf, absorption, eta_i, eta_t, thickness);
    261   if(res != RES_OK) goto error;
    262 
    263 exit:
    264   return res;
    265 error:
    266   if(*bsdf) SSF(bsdf_ref_put(*bsdf)), bsdf = NULL;
    267   goto exit;
    268 }
    269 
    270 static INLINE int
    271 check_shader_dielectric(const struct ssol_dielectric_shader* shader)
    272 {
    273   return shader && shader->normal;
    274 }
    275 
    276 static INLINE int
    277 check_shader_mirror(const struct ssol_mirror_shader* shader)
    278 {
    279   return shader
    280       && shader->normal
    281       && shader->reflectivity
    282       && shader->roughness;
    283 }
    284 
    285 static INLINE int
    286 check_shader_matte(const struct ssol_matte_shader* shader)
    287 {
    288   return shader
    289       && shader->normal
    290       && shader->reflectivity;
    291 }
    292 
    293 static INLINE int
    294 check_shader_thin_differential(const struct ssol_thin_dielectric_shader* shader)
    295 {
    296   return shader && shader->normal;
    297 }
    298 
    299 static INLINE int
    300 check_medium(const struct ssol_medium* medium)
    301 {
    302   if(!medium) return 0;
    303 
    304   /* Check extinction in [0, INF) */
    305   switch(medium->extinction.type) {
    306     case SSOL_DATA_REAL:
    307       if(medium->extinction.value.real < 0)
    308         return 0;
    309       break;
    310     case SSOL_DATA_SPECTRUM:
    311       if(!medium->extinction.value.spectrum
    312       || !spectrum_check_data(medium->extinction.value.spectrum, 0, DBL_MAX))
    313         return 0;
    314       break;
    315     default: FATAL("Unreachable code\n"); break;
    316   }
    317 
    318   /* Check refractive index in ]0, INF) */
    319   switch(medium->refractive_index.type) {
    320     case SSOL_DATA_REAL:
    321       if(medium->refractive_index.value.real <= 0)
    322         return 0;
    323       break;
    324     case SSOL_DATA_SPECTRUM:
    325       if(!medium->refractive_index.value.spectrum
    326       || !spectrum_check_data
    327          (medium->refractive_index.value.spectrum, DBL_EPSILON, DBL_MAX))
    328         return 0;
    329       break;
    330     default: FATAL("Unreachable code\n"); break;
    331   }
    332 
    333   return 1;
    334 }
    335 
    336 static void
    337 material_release(ref_T* ref)
    338 {
    339   struct ssol_device* dev;
    340   struct ssol_material* material = CONTAINER_OF(ref, struct ssol_material, ref);
    341   ASSERT(ref);
    342   dev = material->dev;
    343   if(material->buf) SSOL(param_buffer_ref_put(material->buf));
    344   if(material->type == SSOL_MATERIAL_THIN_DIELECTRIC) {
    345     ssol_medium_clear(&material->data.thin_dielectric.slab_medium);
    346   }
    347   ssol_medium_clear(&material->in_medium);
    348   ssol_medium_clear(&material->out_medium);
    349   ASSERT(dev && dev->allocator);
    350   MEM_RM(dev->allocator, material);
    351   SSOL(device_ref_put(dev));
    352 }
    353 
    354 /*******************************************************************************
    355  * Local functions
    356  ******************************************************************************/
    357 static res_T
    358 ssol_material_create
    359   (struct ssol_device* dev,
    360    struct ssol_material** out_material,
    361    enum ssol_material_type type)
    362 {
    363   struct ssol_material* material = NULL;
    364   res_T res = RES_OK;
    365   if(!dev
    366   || !out_material
    367   || type >= SSOL_MATERIAL_TYPES_COUNT__) {
    368     return RES_BAD_ARG;
    369   }
    370 
    371   material = (struct ssol_material*)MEM_CALLOC
    372     (dev->allocator, 1, sizeof(struct ssol_material));
    373   if (!material) {
    374     res = RES_MEM_ERR;
    375     goto error;
    376   }
    377 
    378   SSOL(device_ref_get(dev));
    379   material->dev = dev;
    380   ref_init(&material->ref);
    381   material->type = type;
    382   material->in_medium = SSOL_MEDIUM_VACUUM;
    383   material->out_medium = SSOL_MEDIUM_VACUUM;
    384   material->normal = shade_normal_default;
    385 
    386 exit:
    387   if (out_material) *out_material = material;
    388   return res;
    389 error:
    390   if (material) {
    391     SSOL(material_ref_put(material));
    392     material = NULL;
    393   }
    394   goto exit;
    395 }
    396 
    397 /*******************************************************************************
    398  * Exported ssol_material functions
    399  ******************************************************************************/
    400 res_T
    401 ssol_material_ref_get(struct ssol_material* material)
    402 {
    403   if (!material)
    404     return RES_BAD_ARG;
    405   ASSERT(material->type < SSOL_MATERIAL_TYPES_COUNT__);
    406   ref_get(&material->ref);
    407   return RES_OK;
    408 }
    409 
    410 res_T
    411 ssol_material_ref_put(struct ssol_material* material)
    412 {
    413   if (!material)
    414     return RES_BAD_ARG;
    415   ASSERT(material->type < SSOL_MATERIAL_TYPES_COUNT__);
    416   ref_put(&material->ref, material_release);
    417   return RES_OK;
    418 }
    419 
    420 res_T
    421 ssol_material_get_type
    422   (const struct ssol_material* mtl, enum ssol_material_type* type)
    423 {
    424   if(!mtl || !type) return RES_BAD_ARG;
    425   *type = mtl->type;
    426   return RES_OK;
    427 }
    428 
    429 res_T
    430 ssol_material_set_param_buffer
    431   (struct ssol_material* mtl, struct ssol_param_buffer* buf)
    432 {
    433   if(!mtl || !buf) return RES_BAD_ARG;
    434   SSOL(param_buffer_ref_get(buf));
    435   mtl->buf = buf;
    436   return RES_OK;
    437 }
    438 
    439 res_T
    440 ssol_material_create_dielectric
    441   (struct ssol_device* dev, struct ssol_material** out_material)
    442 {
    443   return ssol_material_create(dev, out_material, SSOL_MATERIAL_DIELECTRIC);
    444 }
    445 
    446 res_T
    447 ssol_material_create_mirror
    448   (struct ssol_device* dev, struct ssol_material** out_material)
    449 {
    450   return ssol_material_create(dev, out_material, SSOL_MATERIAL_MIRROR);
    451 }
    452 
    453 res_T
    454 ssol_material_create_matte
    455   (struct ssol_device* dev, struct ssol_material** out_material)
    456 {
    457   return ssol_material_create(dev, out_material, SSOL_MATERIAL_MATTE);
    458 }
    459 
    460 res_T
    461 ssol_material_create_thin_dielectric
    462   (struct ssol_device* dev, struct ssol_material** out_material)
    463 {
    464   return ssol_material_create(dev, out_material, SSOL_MATERIAL_THIN_DIELECTRIC);
    465 }
    466 
    467 res_T
    468 ssol_dielectric_setup
    469   (struct ssol_material* material,
    470    const struct ssol_dielectric_shader* shader,
    471    const struct ssol_medium* outside_medium,
    472    const struct ssol_medium* inside_medium)
    473 {
    474   if(!material
    475   || material->type != SSOL_MATERIAL_DIELECTRIC
    476   || !check_shader_dielectric(shader)
    477   || !check_medium(outside_medium)
    478   || !check_medium(inside_medium))
    479     return RES_BAD_ARG;
    480   material->data.dielectric.dummy = 1;
    481   ssol_medium_copy(&material->out_medium, outside_medium);
    482   ssol_medium_copy(&material->in_medium, inside_medium);
    483   material->normal = shader->normal;
    484   return RES_OK;
    485 }
    486 
    487 res_T
    488 ssol_mirror_setup
    489   (struct ssol_material* material,
    490    const struct ssol_mirror_shader* shader,
    491    const enum ssol_microfacet_distribution distrib)
    492 {
    493   if(!material
    494   || material->type != SSOL_MATERIAL_MIRROR
    495   || !check_shader_mirror(shader)
    496   || (unsigned)distrib >= SSOL_MICROFACET_DISTRIBUTIONS_COUNT__)
    497     return RES_BAD_ARG;
    498   material->normal = shader->normal;
    499   material->data.mirror.reflectivity = shader->reflectivity;
    500   material->data.mirror.roughness = shader->roughness;
    501   material->data.mirror.distrib = distrib;
    502   return RES_OK;
    503 }
    504 
    505 res_T
    506 ssol_matte_setup
    507   (struct ssol_material* material, const struct ssol_matte_shader* shader)
    508 {
    509   if(!material
    510   || material->type != SSOL_MATERIAL_MATTE
    511   || !check_shader_matte(shader))
    512     return RES_BAD_ARG;
    513   material->normal = shader->normal;
    514   material->data.matte.reflectivity = shader->reflectivity;
    515   return RES_OK;
    516 }
    517 
    518 res_T
    519 ssol_thin_dielectric_setup
    520   (struct ssol_material* material,
    521    const struct ssol_thin_dielectric_shader* shader,
    522    const struct ssol_medium* outside_medium,
    523    const struct ssol_medium* slab_medium,
    524    const double thickness)
    525 {
    526   if(!material
    527   || material->type != SSOL_MATERIAL_THIN_DIELECTRIC
    528   || !check_shader_thin_differential(shader)
    529   || !check_medium(outside_medium)
    530   || !check_medium(slab_medium)
    531   || thickness < 0)
    532     return RES_BAD_ARG;
    533   ssol_medium_copy(&material->data.thin_dielectric.slab_medium, slab_medium);
    534   material->data.thin_dielectric.thickness = thickness;
    535   ssol_medium_copy(&material->out_medium, outside_medium);
    536   ssol_medium_copy(&material->in_medium, outside_medium);
    537   material->normal = shader->normal;
    538   return RES_OK;
    539 }
    540 
    541 res_T
    542 ssol_material_create_virtual
    543   (struct ssol_device* dev, struct ssol_material** out_material)
    544 {
    545   return ssol_material_create(dev, out_material, SSOL_MATERIAL_VIRTUAL);
    546 }
    547 
    548 /*******************************************************************************
    549  * Local functions
    550  ******************************************************************************/
    551 void
    552 surface_fragment_setup
    553   (struct ssol_surface_fragment* fragment,
    554    const double pos[3],
    555    const double dir[3],
    556    const double normal[3],
    557    const struct s3d_primitive* primitive,
    558    const float uv[2])
    559 {
    560   struct s3d_attrib attr;
    561   char has_texcoord, has_normal;
    562   struct s3d_attrib uvs[3];
    563   struct s3d_attrib P[3];
    564   double duv1[2], duv2[2];
    565   double dP1[3], dP2[3];
    566   double det;
    567   ASSERT(fragment && pos && dir && primitive && uv);
    568 
    569   /* Assume that the submitted normal look forward the incoming dir */
    570   ASSERT(d3_dot(normal, dir) <= 0);
    571 
    572   d3_set(fragment->dir, dir); /* Setup the incoming direction */
    573   d3_set(fragment->P, pos); /* Setup the surface position */
    574   d3_normalize(fragment->Ng, normal); /* Normalize the geometry normal */
    575 
    576   /* Retrieve the position of the triangle vertices */
    577   S3D(triangle_get_vertex_attrib(primitive, 0, S3D_POSITION, &P[0]));
    578   S3D(triangle_get_vertex_attrib(primitive, 1, S3D_POSITION, &P[1]));
    579   S3D(triangle_get_vertex_attrib(primitive, 2, S3D_POSITION, &P[2]));
    580 
    581   /* Retrieve the tex coord */
    582   S3D(primitive_has_attrib(primitive, SSOL_TO_S3D_TEXCOORD, &has_texcoord));
    583   if (!has_texcoord) {
    584     d2_set_f2(fragment->uv, uv);
    585     uvs[0].type = uvs[1].type = uvs[2].type = S3D_FLOAT2;
    586     uvs[0].usage = uvs[1].usage = uvs[2].usage = SSOL_TO_S3D_TEXCOORD;
    587     f2(uvs[0].value, 1, 0);
    588     f2(uvs[1].value, 0, 1);
    589     f2(uvs[2].value, 0, 0);
    590   } else {
    591     S3D(primitive_get_attrib(primitive, SSOL_TO_S3D_TEXCOORD, uv, &attr));
    592     S3D(triangle_get_vertex_attrib(primitive, 0, SSOL_TO_S3D_TEXCOORD, &uvs[0]));
    593     S3D(triangle_get_vertex_attrib(primitive, 1, SSOL_TO_S3D_TEXCOORD, &uvs[1]));
    594     S3D(triangle_get_vertex_attrib(primitive, 2, SSOL_TO_S3D_TEXCOORD, &uvs[2]));
    595     ASSERT(attr.type == S3D_FLOAT2);
    596     d2_set_f2(fragment->uv, attr.value);
    597   }
    598 
    599   /* Compute the partial derivatives. */
    600   duv1[0] = uvs[1].value[0] - uvs[0].value[0];
    601   duv1[1] = uvs[1].value[1] - uvs[0].value[1];
    602   duv2[0] = uvs[2].value[0] - uvs[0].value[0];
    603   duv2[1] = uvs[2].value[1] - uvs[0].value[1];
    604   dP1[0] = P[1].value[0] - P[0].value[0];
    605   dP1[1] = P[1].value[1] - P[0].value[1];
    606   dP1[2] = P[1].value[2] - P[0].value[2];
    607   dP2[0] = P[2].value[0] - P[0].value[0];
    608   dP2[1] = P[2].value[1] - P[0].value[1];
    609   dP2[2] = P[2].value[2] - P[0].value[2];
    610   det = duv1[0]*duv2[1] - duv1[1]*duv2[0];
    611   if(det == 0) { /* Handle zero determinant */
    612     double basis[9];
    613     d33_basis(basis, fragment->Ng);
    614     d3_set(fragment->dPdu, basis + 0);
    615     d3_set(fragment->dPdv, basis + 3);
    616   } else {
    617     double a[3], b[3];
    618     d3_sub(fragment->dPdu, d3_muld(a, dP1, duv2[1]), d3_muld(b, dP2, duv1[1]));
    619     d3_sub(fragment->dPdv, d3_muld(a, dP2, duv1[0]), d3_muld(b, dP1, duv2[0]));
    620     d3_divd(fragment->dPdu, fragment->dPdu, det);
    621     d3_divd(fragment->dPdv, fragment->dPdv, det);
    622   }
    623 
    624   /* Retrieve and normalize the shading normal in world space */
    625   S3D(primitive_has_attrib(primitive, SSOL_TO_S3D_NORMAL, &has_normal));
    626   if (!has_normal) {
    627     d3_set(fragment->Ns, fragment->Ng);
    628   } else {
    629     float transform[12];
    630     float vec[3];
    631 
    632     S3D(primitive_get_attrib(primitive, SSOL_TO_S3D_NORMAL, uv, &attr));
    633     ASSERT(attr.type == S3D_FLOAT3);
    634 
    635     S3D(primitive_get_transform(primitive, transform));
    636     /* Check that transform is not "identity" */
    637     if(!f3_eq(transform + 0, f3(vec, 1.f, 0.f, 0.f))
    638     && !f3_eq(transform + 3, f3(vec, 0.f, 1.f, 0.f))
    639     && !f3_eq(transform + 6, f3(vec, 0.f, 0.f, 1.f))) {
    640       /* Transform the normal in world space, i.e. multiply it by the inverse
    641        * transpose of the "object to world" primitive matrix. Since the affine
    642        * part of the 3x4 transformation matrix does not influence the normal
    643        * transformation, use the linear part only. */
    644       f33_invtrans(transform, transform);
    645       f33_mulf3(attr.value, transform, attr.value);
    646     }
    647     d3_set_f3(fragment->Ns, attr.value);
    648     d3_normalize(fragment->Ns, fragment->Ns);
    649 
    650     /* Ensure that the fetched shading normal look forward the incoming dir */
    651     if(d3_dot(dir, fragment->Ns) > 0) {
    652       d3_minus(fragment->Ns, fragment->Ns);
    653     }
    654   }
    655 }
    656 
    657 void
    658 material_shade_normal
    659   (const struct ssol_material* mtl,
    660    const struct ssol_surface_fragment* frag,
    661    const double wavelength,
    662    double N[3])
    663 {
    664   ASSERT(mtl && frag && N);
    665   mtl->normal(mtl->dev, mtl->buf, wavelength, frag, N);
    666 }
    667 
    668 res_T
    669 material_create_bsdf
    670   (const struct ssol_material* mtl,
    671    const struct ssol_surface_fragment* fragment,
    672    const double wavelength, /* In nanometer */
    673    const struct ssol_medium* medium,
    674    const int rendering, /* Is BSDF used for rendering */
    675    struct ssf_bsdf** bsdf)
    676 {
    677   res_T res = RES_OK;
    678   ASSERT(mtl);
    679 
    680   switch(mtl->type) {
    681     case SSOL_MATERIAL_DIELECTRIC:
    682       res = create_dielectric_bsdf
    683         (mtl, fragment, wavelength, medium, bsdf);
    684       break;
    685     case SSOL_MATERIAL_MATTE:
    686       res = create_matte_bsdf(mtl, fragment, wavelength, bsdf);
    687       break;
    688     case SSOL_MATERIAL_MIRROR:
    689       res = create_mirror_bsdf(mtl, fragment, wavelength, rendering, bsdf);
    690       break;
    691     case SSOL_MATERIAL_THIN_DIELECTRIC:
    692       res = create_thin_dielectric_bsdf(mtl, fragment, wavelength, bsdf);
    693       break;
    694     case SSOL_MATERIAL_VIRTUAL: /* Nothing to shade */ break;
    695     default: FATAL("Unreachable code\n"); break;
    696   }
    697   return res;
    698 }
    699 
    700 res_T
    701 material_get_next_medium
    702   (const struct ssol_material* mtl,
    703    const struct ssol_medium* medium,
    704    struct ssol_medium* next_medium)
    705 {
    706   ASSERT(mtl && medium && next_medium);
    707   switch(mtl->type) {
    708     /* The material is an interface between 2 media */
    709     case SSOL_MATERIAL_DIELECTRIC:
    710       if(media_ceq(&mtl->out_medium, medium)) {
    711         ssol_medium_copy(next_medium, &mtl->in_medium);
    712       } else {
    713         ASSERT(media_ceq(&mtl->in_medium, medium));
    714         ssol_medium_copy(next_medium, &mtl->out_medium);
    715       }
    716       break;
    717     /* The material is not an interface between 2 media */
    718     case SSOL_MATERIAL_MATTE:
    719     case SSOL_MATERIAL_MIRROR:
    720     case SSOL_MATERIAL_THIN_DIELECTRIC:
    721       ssol_medium_copy(next_medium, medium);
    722       break;
    723     default: FATAL("Unreachable code\n"); break;
    724   }
    725   return RES_OK;
    726 }
    727 
    728 /* Define if the submitted media are *certainly* equals. Refer to the
    729 * check_ssol_data for more details. */
    730 int
    731 media_ceq(const struct ssol_medium* a, const struct ssol_medium* b)
    732 {
    733   ASSERT(a && b);
    734   return ssol_data_ceq(&a->refractive_index, &b->refractive_index)
    735     && ssol_data_ceq(&a->extinction, &b->extinction);
    736 }