"Artist Friendly Metallic Fresnel" reversible? While implementing the "Artist Friendly Metallic Fresnel" paper by Ole Gulbrandsen (http://jcgt.org/published/0003/04/03/paper.pdf), I read that it is fully reversible:


  (section 2.3.2): for all r,g ∈ (0,1), the map from (n, k) to (r,g) is a bijection


However, this doesn't seem to be the case? When converting (r, g) to (n, k) and back to (r, g), I do not get a matching output to my initial parameters. Is this expected?

r: 0.7 0.55 0.075
g: 0.8 0.65 0.35
n: 2.39006 2.5479 1.44145
k: 4.98834 3.604 0.537046
r_check: 0.737183 0.601524 0.0773389
g_check: 0.827686 0.699977 0.35995


Here is the implementation to verify, note that I only implemented get_k(), the other functions were already supplied by the paper. get_k() is derived from the following formula in the paper:

$$k(r, n(r,g)) = \sqrt{ \frac {1}{1-r}(r(n(r,g)+1)^2 - (n(r,g) -1)^2)}$$

#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;cmath&gt;

float n_min(float r) {
  return (1.0-r)/(1.0+r);
}

float n_max(float r) {
  return (1.0 + std::sqrt(r))/(1.0-std::sqrt(r));
}

float get_n(float r, float g) {
  return g*n_min(r) + (1.0-g)*n_max(r);
}

float get_k(float r, float n) {
  return std::sqrt( (1.0/(1.0-r)) * (r * std::pow(n+1.0, 2)) - std::pow(n-1.0, 2) );
}

//float get_k2(float r, float n) {
//  float nr = (n+1.0)*(n+1.0)*r-(n-1.0)*(n-1.0);
//  return nr/(1.0-r);
//}

float get_r(float n, float k) {
  return (std::pow(n-1.0, 2)+std::pow(k, 2)) / (std::pow(n+1.0, 2)+std::pow(k, 2));
}

float get_g(float n, float k) {
  float r = get_r(n, k);
  return (n_max(r)-n)/(n_max(r)-n_min(r));
}



int main() {
  std::vector&lt;float&gt; r = {0.7, 0.55, 0.075};
  std::vector&lt;float&gt; g = {0.8, 0.65, 0.35};
  std::cout &lt;&lt; "r: " &lt;&lt; r[0] &lt;&lt; " " &lt;&lt; r[1] &lt;&lt; " " &lt;&lt; r[2] &lt;&lt; std::endl;
  std::cout &lt;&lt; "g: " &lt;&lt; g[0] &lt;&lt; " " &lt;&lt; g[1] &lt;&lt; " " &lt;&lt; g[2] &lt;&lt; std::endl;


  std::vector&lt;float&gt; n(3);
  std::vector&lt;float&gt; k(3);
  for (int i = 0; i&lt;3; i++) {
    n[i] = get_n(r[i], g[i]);
    k[i] = get_k(r[i], n[i]);
  }
  std::cout &lt;&lt; "n: " &lt;&lt; n[0] &lt;&lt; " " &lt;&lt; n[1] &lt;&lt; " " &lt;&lt; n[2] &lt;&lt; std::endl;
  std::cout &lt;&lt; "k: " &lt;&lt; k[0] &lt;&lt; " " &lt;&lt; k[1] &lt;&lt; " " &lt;&lt; k[2] &lt;&lt; std::endl;


  std::vector&lt;float&gt; r_check(3);
  std::vector&lt;float&gt; g_check(3);
  for (int i = 0; i&lt;3; i++) {
    r_check[i] = get_r(n[i], k[i]);
    g_check[i] = get_g(n[i], k[i]);
  }
  std::cout &lt;&lt; "r_check: " &lt;&lt; r_check[0] &lt;&lt; " " &lt;&lt; r_check[1] &lt;&lt; " " &lt;&lt; r_check[2] &lt;&lt; std::endl;
  std::cout &lt;&lt; "g_check: " &lt;&lt; g_check[0] &lt;&lt; " " &lt;&lt; g_check[1] &lt;&lt; " " &lt;&lt; g_check[2] &lt;&lt; std::endl;
}
I wasn't really expecting that, no. The formula in the paper is not the most elegant - there's quite a few parentheses in there. In this case I think it's just a matter of shuffling the parentheses around a little in the get_k implementation - both terms should be divided by (1-r):

    float get_k(float r, float n) {
        return std::sqrt( 1.0/(1.0-r) * ( r*std::pow(n+1.0, 2) - std::pow(n-1.0, 2) ));
    }