Rebuild of Entagma's Thinfilm Shader for using with 3Delight

   2635   3   0
User Avatar
Member
94 posts
Joined: 7月 2019
Offline

I've rebuilt mantra shader from Entagma Mantra Thin Film Shader [entagma.com] tutorial to use it with 3Delight [www.3delight.com] render engine.
Attached archive contains Thinfilm custom osl utility node (thinfilm.oso) and hip file from Entagma with additional shader.
How to use:
1. Download and extract files from the archive (Thinfilm_Shader_3Delight.zip)
2. Copy compiled shader file - thinfilm.oso to your _3DELIGHT_USER_OSO_PATH system variable folder. See the video [vimeo.com] to learn how to setup the system environment variable if don't have one.
3. Open Houdini to check that custom node is added to mat context - tab - 3Delight - Addons - Thinfilm
4. Open Soapfilm_bubble_FLIP_Render_3delight example file
5. Complete instructions at the file.


Render time comparison:
Mantra: 1 hour 17 min.
3Delight: 1 min 29 sec.


Additional links:
3Delight's osl support [documentation.3delightcloud.com]
3Delight's osl metadata [documentation.3delightcloud.com]
3Delight's standalone tools [documentation.3delightcloud.com]
OSL Specification [github.com]
Edited by Faitel - 2021年12月24日 15:52:15

Attachments:
comparison.png (2.3 MB)
3Delight_render_time.png (1.4 MB)
3Delight_shadergraph_thinfilm.png (353.2 KB)
Mantra_render_time.png (1.2 MB)
Thinfilm_Shader_3Delight.zip (178.5 KB)
3Delight_Addons_Thinfilm.png (120.1 KB)
ThinfilmProperties.png (74.0 KB)
thinfilm_utility_node.png (17.7 KB)

User Avatar
Member
94 posts
Joined: 7月 2019
Offline
OSL shader code for reference
//Thinfilm shader cobbled together from:
//https://docs.chaosgroup.com/display/OSLShaders/Thin+Film+Shader
//and:
//https://www.shadertoy.com/view/ls2Bz1
//copied together by your's truly, Entagma - Dec. 2020
//https://entagma.com/holiday-giveaway-mantra-thinfilm-shader/
//sequentially copied and modified to use with 3delight by Faitel - Dec. 2022

#include "stdosl.h"

surface thinfilm
(
  color col = color(0)
  [[  string help = "Constant color",
      string label = "Color",
      string widget = "number"]],
  float fintensity = 0
  [[  string help = "Fresnel reduction multiplier",
      string label = "Intensity",
      string page = "Fresnel reduction",
      string widget = "number",
      float min = 0
  ]],
    float fior = 1.4
  [[  string help = "Index of reflection for fresnel reduction",
      string label = "Ior",
      string page = "Fresnel reduction",
      string widget = "number",
      float min = 0
  ]],
  int sbiter = 8
  [[  string help = "Number of spectral bands. 8 seems fine, 32 is excessive.",
      string label = "Iterations",
      string page = "Spectral Bands",
      string widget = "number",
  ]],
  int spfunc = 0
  [[
      string label = "Function",
      string page = "Spectral Bands",
      string widget = "mapper",
      string options =
        "Gems:0|"
        "Zucconi:1|"
        "Bruton:2|"
  ]],
  float thickness = 1
  [[  string help = "Thin Film Thickness",
      string label = "Thickness",
      string page = "Thin Film",
      string widget = "number",
      float min = 0,
      float max = 1
  ]],
  float thicknessMin = 100
  [[  string help = "Minimal Thin Film Thickness",
      string label = "Thickness Min",
      string page = "Thin Film",
      string widget = "number",
      float min = 0,
      float max = 1000
  ]],
  float thicknessMax = 900
  [[  string help = "Maximal Thin Film Thickness",
      string label = "Thickness Max",
      string page = "Thin Film",
      string widget = "number",
      float min = 0,
      float max = 1000
  ]],
  float nmedium = 1
  [[  string help = "approximate refractive index of air",
      string label = "nmedium",
      string page = "Thin Film",
      string widget = "number",
      float min = 0,
      float max = 10
  ]],
  float nfilm = 1.4
  [[  string help = "approximate refractive index of water",
      string label = "nfilm",
      string page = "Thin Film",
      string widget = "number",
      float min = 0,
      float max = 10
  ]],
  float ninternal = 1
  [[  string help = "approximate refractive index of the lower material", 
      string label = "ninternal",
      string page = "Thin Film",
      string widget = "number",
      float min = 0,
      float max = 10
  ]],

  output color outColor = 0
)
{

float saturate(float x)
{
    return min(1.0, max(0.0,x));
}
vector saturate (vector x)
{
    return min(color(1.0,1.0,1.0), max(color(0.0,0.0,0.0),x));
}
// --- Spectral Zucconi --------------------------------------------
// By Alan Zucconi
// Based on GPU Gems: https://developer.nvidia.com/sites/all/modules/custom/gpugems/books/GPUGems/gpugems_ch08.html
// But with values optimised to match as close as possible the visible spectrum
// Fits this: https://commons.wikimedia.org/wiki/File:Linear_visible_spectrum.svg
// With weighter MSE (RGB weights: 0.3, 0.59, 0.11)
vector bump3y(vector x, vector yoffset)
{
	vector y = color(1.0,1.0,1.0) - x * x;
	y = saturate(y-yoffset);
	return y;
}
vector spectral_zucconi(float w)
{
    // w: [400, 700]
	// x: [0,   1]
	float x = saturate((w - 400.0)/ 300.0);

	vector cs = color(3.54541723, 2.86670055, 2.29421995);
	vector xs = color(0.69548916, 0.49416934, 0.28269708);
	vector ys = color(0.02320775, 0.15936245, 0.53520021);

	return bump3y (	cs * (x - xs), ys);
}

// --- GPU Gems -------------------------------------------------------
// https://developer.nvidia.com/sites/all/modules/custom/gpugems/books/GPUGems/gpugems_ch08.html
vector bump3(vector x) {
        vector y = color(1.0,1.0,1.0) - x * x;
        y = max(y, {0.,0.,0.});
        return y;
}

vector spectral_gems (float w) {
    // w: [400, 700]
    // x: [0,   1]
    float x = clamp((w - 400.0)/ 300.0, 0., 1.);
    float r = 4. * (x - 0.75);
    float g = 4. * (x - 0.5);
    float b = 4. * (x - 0.25);
    vector rgb = {r,g,b};
    return bump3(rgb);
}

// --- Approximate RGB values for Visible Wavelengths ------------------
// by Dan Bruton
// http://www.physics.sfasu.edu/astro/color/spectra.html
// https://stackoverflow.com/questions/3407942/rgb-values-of-visible-spectrum
vector spectral_bruton(float w)
{
	vector c;

	if (w >= 380. && w < 440.)
		c = color
		(
			-(w - 440.) / (440. - 380.),
			0.0,
			1.0
		);
	else if (w >= 440. && w < 490.)
		c = color
		(
			0.0,
			(w - 440.) / (490. - 440.),
			1.0
		);
	else if (w >= 490. && w < 510.)
		c = color
		(	0.0,
			1.0,
			-(w - 510.) / (510. - 490.)
		);
	else if (w >= 510. && w < 580.)
		c = color
		(
			(w - 510.) / (580. - 510.),
			1.0,
			0.0
		);
	else if (w >= 580. && w < 645.)
		c = color
		(
			1.0,
			-(w - 645.) / (645. - 580.),
			0.0
		);
	else if (w >= 645. && w <= 780.)
		c = color
		(	1.0,
			0.0,
			0.0
		);
	else
		c = color
		(	0.0,
			0.0,
			0.0
		);

	return saturate(c);
}

//--- OSL Thinfilm Shader ---
/* Amplitude reflection coefficient (s-polarized) */
float rs(float n1, float n2, float cosI, float cosT) {
    return (n1 * cosI - n2 * cosT) / (n1 * cosI + n2 * cosT);
}

/* Amplitude reflection coefficient (p-polarized) */
float rp(float n1, float n2, float cosI, float cosT) {
    return (n2 * cosI - n1 * cosT) / (n1 * cosT + n2 * cosI);
}

/* Amplitude transmission coefficient (s-polarized) */
float ts(float n1, float n2, float cosI, float cosT) {
    return 2 * n1 * cosI / (n1 * cosI + n2 * cosT);
}

/* Amplitude transmission coefficient (p-polarized) */
float tp(float n1, float n2, float cosI, float cosT) {
    return 2 * n1 * cosI / (n1 * cosT + n2 * cosI);
}

// cosI is the cosine of the incident angle, that is, cos0 = dot(view angle, normal)
// lambda is the wavelength of the incident light (e.g. lambda = 510 for green)
// From http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/thin-film-interference-for-computer-graphics-r2962
float thinFilmReflectance(float cos0, float lambda, float thickness, float n0, float n1, float n2) {

    float PI=M_PI; //Pi osl constant

    // compute the phase change term (constant)
    float d10 = (n1 > n0) ? 0 : PI;
    float d12 = (n1 > n2) ? 0 : PI;
    float delta = d10 + d12;

    // now, compute cos1, the cosine of the reflected angle
    float sin1 = pow(n0 / n1, 2) * (1 - pow(cos0, 2));
    if (sin1 > 1) return 1.0; // total internal reflection
    float cos1 = sqrt(1 - sin1);

    // compute cos2, the cosine of the final transmitted angle, i.e. cos(theta_2)
    // we need this angle for the Fresnel terms at the bottom interface
    float sin2 = pow(n0 / n2, 2) * (1 - pow(cos0, 2));
    if (sin2 > 1) return 1.0; // total internal reflection
    float cos2 = sqrt(1 - sin2);

    // get the reflection transmission amplitude Fresnel coefficients
    float alpha_s = rs(n1, n0, cos1, cos0) * rs(n1, n2, cos1, cos2); // rho_10 * rho_12 (s-polarized)
    float alpha_p = rp(n1, n0, cos1, cos0) * rp(n1, n2, cos1, cos2); // rho_10 * rho_12 (p-polarized)

    float beta_s = ts(n0, n1, cos0, cos1) * ts(n1, n2, cos1, cos2); // tau_01 * tau_12 (s-polarized)
    float beta_p = tp(n0, n1, cos0, cos1) * tp(n1, n2, cos1, cos2); // tau_01 * tau_12 (p-polarized)

    // compute the phase term (phi)
    float phi = (2 * PI / lambda) * (2 * n1 * thickness * cos1) + delta;

    // finally, evaluate the transmitted intensity for the two possible polarizations
    float ts = pow(beta_s, 2) / (pow(alpha_s, 2) - 2 * alpha_s * cos(phi) + 1);
    float tp = pow(beta_p, 2) / (pow(alpha_p, 2) - 2 * alpha_p * cos(phi) + 1);

    // we need to take into account conservation of energy for transmission
    float beamRatio = (n2 * cos2) / (n0 * cos0);

    // calculate the average transmitted intensity (if you know the polarization distribution of your
    // light source, you should specify it here. if you don't, a 50%/50% average is generally used)
    float myt = beamRatio * (ts + tp) / 2;

    // and finally, derive the reflected intensity
    return 1 - myt;
}

float cos0 = abs(dot(I , N));

float myt = clamp(thickness, 0., 1.);
float thick=thicknessMin*(1.0-myt)+thicknessMax*myt;

color outcol = col; //get color;
float fint = fintensity; //get fresnel multiplier
float fnfilm = fior; //get fresnel ior

int maxiter = sbiter; //number of spectral bands. 8 seems fine, 32 is excessive.
for(int i = 0; i < maxiter; i++)
{
    float lambda = 400. + (300. * (float(i)/float(maxiter))); //Spectral bands ranging from 400nm to 700nm
    float ref = thinFilmReflectance(cos0, lambda, thick, nmedium, nfilm, ninternal);

    //we don't need no fresnel, so we need to divide it out again
    //I know - being cheap, but atm just not in the mood for algebra
    float kr, kt;
    fresnel(-I, N, fnfilm, kr, kt);
    kr = kr;
    if (fint == 0 ) ref = ref;
    if (fint > 0 ) ref = ref / (kr * fint);

    vector rgb;
    if (spfunc == 0) rgb = spectral_gems(lambda);
    if (spfunc == 1) rgb = spectral_zucconi(lambda);
    if (spfunc == 2) rgb = spectral_bruton(lambda);

    rgb = rgb * ref;
    outcol = outcol + rgb;
}

outColor = outcol / float(maxiter);
}
Edited by Faitel - 2021年12月24日 15:35:59

Attachments:
thinfilm.osl (9.5 KB)

User Avatar
Member
236 posts
Joined: 3月 2013
Offline
Fantastic work mate. Really nice, and the time comparison is not surprising at all. I see anywhere
from 10x to 200x speed differences depending on what I'm working on.

Thank you for posting code and all.


Lewis
I'm not lying, I'm writing fiction with my mouth.
User Avatar
Member
833 posts
Joined: 1月 2018
Offline
"Render time comparison:
Mantra: 1 hour 17 min.
3Delight: 1 min 29 sec."


LOL!
>>Kays
For my Houdini tutorials and more visit:
https://www.youtube.com/c/RightBrainedTutorials [www.youtube.com]
  • Quick Links