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 <iostream>
#include <vector>
#include <cmath>

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<float> r = {0.7, 0.55, 0.075};
  std::vector<float> g = {0.8, 0.65, 0.35};
  std::cout << "r: " << r[0] << " " << r[1] << " " << r[2] << std::endl;
  std::cout << "g: " << g[0] << " " << g[1] << " " << g[2] << std::endl;


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


  std::vector<float> r_check(3);
  std::vector<float> g_check(3);
  for (int i = 0; i<3; i++) {
    r_check[i] = get_r(n[i], k[i]);
    g_check[i] = get_g(n[i], k[i]);
  }
  std::cout << "r_check: " << r_check[0] << " " << r_check[1] << " " << r_check[2] << std::endl;
  std::cout << "g_check: " << g_check[0] << " " << g_check[1] << " " << g_check[2] << std::endl;
}
up vote 4 down vote accepted

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) ));
    }
  • 2
    Big up for the paper's author to register and provide an answer! – zeno Dec 4 at 22:01

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.