Substance Painter VRayMtl shader
Channels needed for metal/rough workflow are bound here.
Copied to your clipboard//: param auto channel_basecoloruniform SamplerSparse basecolor_tex;//: param auto channel_roughnessuniform SamplerSparse roughness_tex;//: param auto channel_metallicuniform SamplerSparse metallic_tex;//: param auto channel_transmissiveuniform SamplerSparse transmissive_tex;//: param auto channel_emissiveuniform SamplerSparse emissive_tex;//: param auto channel_anisotropyangleuniform SamplerSparse anisotropyangle_tex;//: param auto channel_anisotropyleveluniform SamplerSparse anisotropylevel_tex;import lib-env.glslimport lib-random.glslimport lib-sampler.glslimport lib-utils.glslimport lib-vectors.glsl//: state cull_face off
Parameters from Substance
Copied to your clipboard//: param auto main_lightuniform vec4 uniform_main_light;// params from lib-pbr.// We don't want to import the entire lib-pbr because it brings lib-emissive// and that messes with our self-illumination params//: param auto environment_max_loduniform float maxLod;//: param custom {//: "default": 16,//: "label": "Quality",//: "widget": "combobox",//: "values": {//: "Very low (4 spp)": 4,//: "Low (16 spp)": 16,//: "Medium (32 spp)": 32,//: "High (64 spp)": 64,//: "Very high (128 spp)": 128,//: "Ultra (256 spp)": 256//: },//: "group": "Common Parameters"//: }uniform int nbSamples;
VRayMtl specific options
Copied to your clipboard// Group basic options//: param custom { "default": 1, "label": "Base color amount", "min": 0, "max": 1, "group": "Base color" }uniform float uniform_diffuse_amount;// Group reflection//: param custom { "default": 1, "label": "Reflection amount", "min": 0, "max": 1, "group": "Reflection" }uniform float uniform_reflection_amount;//: param custom { "default": true, "label": "Use fresnel", "group": "Reflection", "description": "Make reflection strength dependent on the viewing angle (e.g. glass materials). Depends on IOR." }uniform bool uniform_use_fresnel;//: param custom { "default": true, "label": "Lock fresnel IOR to refraction IOR", "group": "Reflection", "description": "Use the same IOR as refraction for reflection fresnel." }uniform bool uniform_lock_fresnel_ior;//: param custom { "default": 1.6, "label": "Fresnel IOR", "group": "Reflection", "description": "Separate fresnel reflection IOR, when not locked to refraction IOR." }uniform float uniform_fresnel_ior;// Group refraction//: param custom {//: "default": 1, "label": "Refraction amount", "min": 0, "max": 1, "widget": "color", "group": "Refraction"//: }uniform float uniform_refraction_amount;//: param custom {//: "default": 1.6, "label": "IOR", "group": "Refraction",//: "description": "Index of refraction for refraction effect and fresnel reflections (unless disabled in Reflection options)"//: }uniform float uniform_refraction_ior;//: param custom { "default": 1, "label": "Refraction glossiness", "min": 0, "max": 1, "group": "Refraction", "description": "Separate refraction glossiness" }uniform float uniform_refraction_glossiness;// Group BRDF//: param custom {//: "default": 3,//: "label": "BRDF type",//: "widget": "combobox",//: "values": {//: "Phong": 0,//: "Blinn": 1,//: "Ward": 2,//: "GGX": 3//: },//: "group": "BRDF"//: }uniform int uniform_brdf_type;//: param custom { "default": 2, "label": "GGX tail falloff", "min": 1, "max": 10, "group": "BRDF" }uniform float uniform_gtr_gamma;//: param custom {//: "default": 2,//: "label": "Anisotropy axis",//: "widget": "combobox",//: "values": {//: "X": 0,//: "Y": 1,//: "Z": 2//: },//: "group": "BRDF",//: "description": "Base local axis for anisotropic highlight"//: }uniform int uniform_anisotropy_axis;// Group options//: param custom {//: "default": true, "label": "Trace reflections", "group": "Options",//: "description": "When disabled, reflections are not traced, resulting in only highlights. Also the diffuse color is not dimmed by the reflection color, as would happen normally"//: }uniform bool uniform_trace_reflections;//: param custom {//: "default": true, "label": "Trace refractions", "group": "Options",//: "description": "When disabled, refractions are not traced"//: }uniform bool uniform_trace_refractions;//: param custom {//: "default": false, "label": "Double sided", "group": "Options",//: "description": "When enabled, V-Ray also shades the back-facing surfaces. Otherwise, the lighting for the outer side is always computed. Can be used to achieve a fake translucent effect for thin objects like paper."//: }uniform bool uniform_double_sided;float vraySqr(float x) {return x * x;}// Functions from lib-pbr we need to sample the environment properly {{{/// Compute the inverse of the solid angle of the differential pixel in the/// cube map pointed at by Wn/// @param Wn World-space directionfloat distortion(vec3 Wn) {float sinT = sqrt(1.0-Wn.y*Wn.y);return sinT;}/// Get the LOD for sampling the environment/// @param Wn World-space normal/// @param p Probability of this direction (from sampleBRDF)/// @param numSamples Total number of samplesfloat computeLOD(vec3 Wn, float p, int numSamples) {if (numSamples < 2) {return 0.0;} else {return max(0.0, maxLod - 1.5 - 0.5 * log2(1.0 + float(numSamples) * p * distortion(Wn)));}}// }}} End functions from lib-pbrstruct VRayMtlInitParams {vec3 Vw;vec3 geomNormal;vec3 diffuseColor;float diffuseAmount;float roughness;vec3 selfIllum;vec3 reflColor;float reflAmount;float reflGloss;bool traceReflections;float metalness;float aniso;float anisoRotation;int anisoAxis;vec3 opacity;vec3 refractionColor;float refractionAmount;float refrGloss;bool traceRefractions;float refractionIOR;bool useFresnel;float fresnelIOR;bool lockFresnelIOR;bool doubleSided;bool useRoughness;float gtrGamma;int brdfType;bool approxEnv;};struct VRayMtlContext {vec3 geomNormal;float gloss1;float gloss2;float reflGloss;vec3 e;vec3 diff;float fresnel;vec3 reflNoFresnel;vec3 refl;vec3 refr;vec3 illum;vec3 opacity;float rtermA;float rtermB;float gtrGamma;float blueNoise; // blue noise value based on fragmentmat3 nm;mat3 inm;};float intensity(vec3 v);vec3 vraySampleBRDF(VRayMtlInitParams params, VRayMtlContext ctx, int sampleIdx, out float brdfContrib);vec3 vraySampleRefractBRDF(VRayMtlInitParams params, VRayMtlContext ctx, int sampleIdx, out bool totalInternalReflection);VRayMtlContext initVRayMtlContext(VRayMtlInitParams initParams);vec3 vrayComputeDirectDiffuseContribution(VRayMtlInitParams params, VRayMtlContext ctx, vec3 lightDir);vec3 vrayComputeDirectReflectionContribution(VRayMtlInitParams params, VRayMtlContext ctx, vec3 lightDir);vec3 vrayComputeIndirectDiffuseContribution(VRayMtlInitParams params, VRayMtlContext ctx);vec3 vrayComputeIndirectReflectionContribution(VRayMtlInitParams params, VRayMtlContext ctx);vec3 vrayComputeIndirectRefractionContribution(VRayMtlInitParams params, VRayMtlContext ctx, float alpha, vec3 alphaDir, vec3 diffuseIndirect);int debugOutput = 0;// Output v as colour, suppress normal shadingvoid vrayDebug(vec3 v) {debugOutput = 1;albedoOutput(v);diffuseShadingOutput(vec3(1.0));}// Output f as grey colour, suppress normal shadingvoid vrayDebug(float f) {vrayDebug(vec3(f));}#define PI 3.1415926535897932384626433832795#define INV_PI (1.0/PI)vec3 vrayWhiteComplement(vec3 x) {return clamp(1.0 - x, 0.0, 1.0);}void vrayComputeTangentVectors(vec3 n, out vec3 u, out vec3 v) {// It doesn't matter what these vectors are, the result vectors just need to be perpendicular to the normal and to each otheru = cross(n, vec3(0.643782, 0.98432, 0.324632));if (length(u) < 1e-6)u = cross(n, vec3(0.432902, 0.43223, 0.908953));u = normalize(u);v = normalize(cross(n, u));}void vrayMakeNormalMatrix(in vec3 n, out mat3 m) {vrayComputeTangentVectors(n, m[0], m[1]);m[2] = n;}float vrayGetFresnelCoeff(float fresnelIOR, vec3 e, vec3 n, vec3 refractDir) {if (abs(fresnelIOR - 1.0) < 1e-6)return 0.0;float cosIn = -dot(e, n);float cosR = -dot(refractDir, n);if (cosIn > 1.0 - 1e-12 || cosR > 1.0 - 1e-12) { // View direction is perpendicular to the surfacefloat f = (fresnelIOR - 1.0) / (fresnelIOR + 1.0);return f * f;}float ks = (cosR / cosIn) * fresnelIOR;float fs2 = (ks - 1.0) / (ks + 1.0);float Fs = fs2 * fs2;float kp = (cosIn / cosR) * fresnelIOR;float fp2 = (kp - 1.0) / (kp + 1.0);float Fp = fp2 * fp2;return 0.5 * (Fs + Fp);}vec3 vrayGetSpecularDir(float u, float v, float k) {float thetaSin = clamp(pow(u, 1.0 / (k + 1.0)), 0.0, 1.0);float thetaCos = sqrt(1.0 - thetaSin * thetaSin);float phi = 2.0 * PI * v;return vec3(cos(phi) * thetaCos, sin(phi) * thetaCos, thetaSin);}vec3 vrayGetPhongDir(float uc, float vc, float glossiness, vec3 view, mat3 nm) {vec3 reflectDir = reflect(-view, nm[2]);vec3 s = cross(vec3(0, 1, 0), reflectDir);vec3 s1 = cross(reflectDir, s);mat3 m;m[0] = normalize(s);m[1] = normalize(s1);m[2] = normalize(reflectDir);vec3 sampleDir = vrayGetSpecularDir(uc, vc, glossiness);return m * sampleDir;}vec3 vrayGetBlinnDir(float uc, float vc, float glossiness, vec3 view, mat3 nm) {vec3 nn = vrayGetSpecularDir(uc, vc, glossiness);vec3 h = normalize(nm * nn);float cs = 2.0 * dot(h, view);vec3 dir = normalize(-view + cs * h);return dir;}vec3 vrayGetSphereDir(float u, float v) {float thetaSin = u;float thetaCos = sqrt(1.0 - thetaSin * thetaSin);float phi = 2.0 * PI * v;return vec3(cos(phi) * thetaCos, sin(phi) * thetaCos, thetaSin);}vec3 vrayGetWardDir(float u, float v, float glossiness, vec3 view, mat3 nm) {if (u >= 1.0)u -= 1.0;float k = -log(1.0 - u);if (k < 0.0)k = 0.0;float thetaCos = sqrt(1.0 / (glossiness * k + 1.0));vec3 hn = vrayGetSphereDir(thetaCos, v);vec3 hw = normalize(nm * hn);vec3 dir = reflect(-view, hw);return dir;}vec3 vrayGetGTR1MicroNormal(float uc, float vc, float sharpness) {float sharpness2 = min(sharpness * sharpness, 0.999);float thetaCosSqr = (1.0 - pow(sharpness2, 1.0 - uc)) / (1.0 - sharpness2);float thetaCos = sqrt(thetaCosSqr);float thetaSin = sqrt(max(1.0 - thetaCosSqr, 0.0));float phi = 2.0 * PI * vc;return vec3(cos(phi) * thetaSin, sin(phi) * thetaSin, thetaCos);}// Specific implementation when gamma == 2. See section B.2 Physically-Based Shading at Disney from SIGGRAPH 2012vec3 vrayGetGTR2MicroNormal(float uc, float vc, float sharpness) {//vrayDebug(sharpness);float thetaCosSqr = (1.0 - uc) / (1.0 + (sharpness * sharpness - 1.0) * uc);float thetaCos = sqrt(thetaCosSqr);float thetaSin = sqrt(max(1.0 - thetaCosSqr, 0.0));float phi = 2.0 * PI * vc;return vec3(cos(phi) * thetaSin, sin(phi) * thetaSin, thetaCos);}// // General implementation when gamma != 1 and != 2. See section B.2 Physically-Based Shading at Disney from SIGGRAPH 2012vec3 vrayGetGTRMicroNormal(float uc, float vc, float sharpness, float gtrGamma) {float sharpness2 = min(sharpness * sharpness, 0.999);float thetaCosSqr = (1.0 - pow(pow(sharpness2, 1.0 - gtrGamma) * (1.0 - uc) + uc, 1.0 / (1.0 - gtrGamma))) / (1.0 - sharpness2);float thetaCos = sqrt(thetaCosSqr);float thetaSin = sqrt(max(1.0 - thetaCosSqr, 0.0));float phi = 2.0 * PI * vc;return vec3(cos(phi) * thetaSin, sin(phi) * thetaSin, thetaCos);}vec3 vrayGetGGXMicroNormal(float uc, float vc, float sharpness, float gtrGamma) {if (abs(gtrGamma - 1.0) < 1e-3)return vrayGetGTR1MicroNormal(uc, vc, sharpness);else if (abs(gtrGamma - 2.0) < 1e-3)return vrayGetGTR2MicroNormal(uc, vc, sharpness);else // if (gtrLowerLimit <= gtrGamma && gtrGamma <= gtrUpperLimit)return vrayGetGTRMicroNormal(uc, vc, sharpness, gtrGamma);}float vrayGetGTR1MicrofacetDistribution(float mz, float sharpness) {float cosThetaM = mz; // dotf(microNormal, normal);if (cosThetaM <= 1e-3)return 0.0;float cosThetaM2 = vraySqr(cosThetaM);float tanThetaM2 = (1.0 / cosThetaM2) - 1.0;float sharpness2 = vraySqr(sharpness);float div = PI * log(sharpness2) * cosThetaM2 * (sharpness2 + tanThetaM2);// when div<(sharpness2-1.0f)*1e-6f no division by zero will occur (the dividend and the divisor are always negative);// div can get 0 in rare situation when the sharpness read from texture mapped in reflection glossines is 0// and cosThetaM is 1 (and consequently tanThetaM2 is 0);float res = (div < (sharpness2 - 1.0) * 1e-6) ? (sharpness2 - 1.0) / div : 0.0;return res;}float vrayGetGTR2MicrofacetDistribution(float mz, float sharpness) {float cosThetaM = mz; // dotf(microNormal, normal);if (cosThetaM <= 1e-3f)return 0.0f;float cosThetaM2 = vraySqr(cosThetaM);float tanThetaM2 = (1.0 / cosThetaM2) - 1.0;float sharpness2 = vraySqr(sharpness);float div = PI * vraySqr(cosThetaM2 * (sharpness2 + tanThetaM2));// when div>sharpness2*1e-6f no division by zero will occur (the dividend and the divisor are always positive);// div canget0 in rare situation when the sharpness read from texture mapped in reflection glossines is 0// and cosThetaM is 1 (and consequently tanThetaM2 is 0);float res = (div > sharpness2 * 1e-6) ? sharpness2 / div : 0.0;return res;}float vrayGetGTRMicrofacetDistribution(float mz, float sharpness, float gtrGamma) {float cosThetaM = mz; // dotf(microNormal, normal);if (cosThetaM <= 1e-3)return 0.0;float cosThetaM2 = vraySqr(cosThetaM);float tanThetaM2 = (1.0 / cosThetaM2) - 1.0;float sharpness2 = vraySqr(sharpness);float divisor = PI * (1.0 - pow(sharpness2, 1.0 - gtrGamma)) * pow(cosThetaM2 * (sharpness2 + tanThetaM2), gtrGamma);float dividend = (gtrGamma - 1.0) * (sharpness2 - 1.0);// when fabsf(divisor)>fabsf(dividend)*1e-6f no division by zero will occur// (the dividend and the divisor are always either both positive or both negative);// divisor canget0 in rare situation when the sharpness read from texture mapped in reflection glossines is 0// and cosThetaM is 1 (and consequently tanThetaM2 is 0);float res = (abs(divisor) > abs(dividend) * 1e-6) ? dividend / divisor : 0.0;return res;}float vrayGetGGXMicrofacetDistribution(float cosNH, float sharpness, float gtrGamma) {if (abs(gtrGamma - 1.0) < 1e-3)return vrayGetGTR1MicrofacetDistribution(cosNH, sharpness);else if (abs(gtrGamma - 2.0) < 1e-3)return vrayGetGTR2MicrofacetDistribution(cosNH, sharpness);else // if (gtrLowerLimit <= gtrGamma && gtrGamma <= gtrUpperLimit)return vrayGetGTRMicrofacetDistribution(cosNH, sharpness, gtrGamma);}float vrayGetGTRMonodirectionalShadowing0(float cotThetaV) {return 2.0 / (1.0 + sqrt(1.0 + 1.0 / (cotThetaV * cotThetaV)));}float vrayGetGTRMonodirectionalShadowing1(float sharpness, float cotThetaV) {float cotThetaV2 = vraySqr(cotThetaV);float sharpness2 = min(0.999, vraySqr(sharpness));float a = sqrt(cotThetaV2 + sharpness2);float b = sqrt(cotThetaV2 + 1.0);return cotThetaV * log(sharpness2) / (a - b + cotThetaV * log(sharpness2 * (cotThetaV + b) / (cotThetaV + a)));}float vrayGetGTRMonodirectionalShadowing2(float sharpness, float cotThetaV) {return 2.0 / (1.0 + sqrt(1.0 + vraySqr(sharpness / cotThetaV)));}float vrayGetGTRMonodirectionalShadowing3(float sharpness, float cotThetaV) {float cotThetaV2 = vraySqr(cotThetaV);float sharpness2 = min(0.999, vraySqr(sharpness));float a = sqrt(cotThetaV2 + sharpness2);float b = sharpness2 + 1.0;return 4.0 * cotThetaV * a * b / (2.0 * cotThetaV * b * (cotThetaV + a) + sharpness2 * (3.0 * sharpness2 + 1.0));}float vrayGetGTRMonodirectionalShadowing4(float sharpness, float cotThetaV) {float cotThetaV2 = cotThetaV * cotThetaV;float sharpness2 = min(0.999, vraySqr(sharpness));float sharpness4 = sharpness2 * sharpness2;float a = 8.0 * (sharpness4 + sharpness2 + 1.0);float b = sqrt(cotThetaV2 + sharpness2);float b3 = b * (cotThetaV2 + sharpness2);return 2.0 * cotThetaV * a * b3 / (a * cotThetaV * (b3 + cotThetaV * cotThetaV2) + 3.0 * sharpness2 * (4.0 * cotThetaV2 * (2.0 * sharpness4 + sharpness2 + 1.0) + sharpness2 * (5.0 * sharpness4 + 2.0 * sharpness2 + 1.0)));}float vrayGetGTRMonodirectionalShadowingSpline(float gtrGamma, float sharpness, float cotThetaV) {const int numKnots = 5;float knots[numKnots];knots[0] = vrayGetGTRMonodirectionalShadowing0(cotThetaV);knots[1] = vrayGetGTRMonodirectionalShadowing1(sharpness, cotThetaV);knots[2] = vrayGetGTRMonodirectionalShadowing2(sharpness, cotThetaV);knots[3] = vrayGetGTRMonodirectionalShadowing3(sharpness, cotThetaV);knots[4] = vrayGetGTRMonodirectionalShadowing4(sharpness, cotThetaV);float m[numKnots];float c[numKnots];for (int i = 1; i < numKnots - 1; i++) {m[i] = 4.0;c[i - 1] = 6.0 * (knots[i + 1] - 2.0 * knots[i] + knots[i - 1]);}// solve tridiagonalfor (int i = 1; i < numKnots - 2; i++) {float x = 1.0 / m[i];m[i + 1] -= x;c[i] -= x * c[i - 1];}m[numKnots - 2] = c[numKnots - 3] / m[numKnots - 2];for (int i = numKnots - 4; i >= 0; i--) {m[i + 1] = (c[i] - m[i + 2]) / m[i + 1];}m[0] = 0.0;m[numKnots - 1] = 0.0;// contstruct polynomialsvec4 polys[numKnots - 1];for (int i = 0; i < numKnots - 1; i++) {polys[i].x = (m[i + 1] - m[i]) / 6.0;polys[i].y = 0.5 * m[i];polys[i].z = (knots[i + 1] - knots[i]) - (2.0 * m[i] + m[i + 1]) / 6.0;polys[i].w = knots[i];}// evalfloat gamma = clamp(gtrGamma, 0.0, 4.0);int idx = int(floor(gtrGamma));float x = gtrGamma - float(idx);float v = ((polys[idx].x * x + polys[idx].y) * x + polys[idx].z) * x + polys[idx].w;return v;}float vrayGetGGXMonodirectionalShadowing(vec3 dir, vec3 hw, vec3 normal, float sharpness, float gtrGamma) {float cosThetaV = dot(dir, normal);if (cosThetaV <= 1e-3)return 0.0;if (dot(dir, hw) * cosThetaV <= 0.0) // Note: technically this is a division, but since we are only interested in the sign, we can do multiplicationreturn 0.0;// when direction is collinear to the normal there is no shadowing// moreover if this case is not handled a division by zero will happen on the next lineif (cosThetaV >= 1.0 - 1e-6)return 1.0;float cotThetaV = cosThetaV / sqrt(1.0 - vraySqr(cosThetaV));float res = 0.0;// when gamma is any of the integer values 0, 1, 2, 3, 4 apply analytical solutionif (gtrGamma <= 0.01)res = vrayGetGTRMonodirectionalShadowing0(cotThetaV);else if (abs(gtrGamma - 1.0) <= 1e-2)res = vrayGetGTRMonodirectionalShadowing1(sharpness, cotThetaV);else if (abs(gtrGamma - 2.0) <= 1e-2)res = vrayGetGTRMonodirectionalShadowing2(sharpness, cotThetaV);else if (abs(gtrGamma - 3.0) <= 1e-2)res = vrayGetGTRMonodirectionalShadowing3(sharpness, cotThetaV);else if (gtrGamma >= 4.0 - 1e-2)res = vrayGetGTRMonodirectionalShadowing4(sharpness, cotThetaV);else {// gamma is not an integer. interpolateres = vrayGetGTRMonodirectionalShadowingSpline(gtrGamma, sharpness, cotThetaV);}return clamp(res, 0.0, 1.0);}float vrayGetGGXBidirectionalShadowingMasking(vec3 view, vec3 dir, vec3 hw, vec3 normal, float sharpness, float gtrGamma) {return vrayGetGGXMonodirectionalShadowing(view, hw, normal, sharpness, gtrGamma) * vrayGetGGXMonodirectionalShadowing(dir, hw, normal, sharpness, gtrGamma);}float vrayGetGGXContribution(vec3 view, vec3 dir, vec3 hw, vec3 hl, float sharpness, float gtrGamma, vec3 normal, out float partialProb, out float D) {float cosIN = abs(dot(view, normal));float cosON = abs(dot(dir, normal));if (cosIN <= 1e-6 || cosON <= 1e-6)return 0.0;float partialBrdf = 0.0;float hn = hl.z;D = vrayGetGGXMicrofacetDistribution(hn, sharpness, gtrGamma);partialBrdf = 0.25 * vrayGetGGXBidirectionalShadowingMasking(view, dir, hw, normal, sharpness, gtrGamma) / cosIN; // division by cosON is omitted because we would have to multiply by the same below;if (hn > 0.0) {partialProb = hn;float ho = dot(hw, dir);partialProb *= ho > 0.0 ? 0.25 / ho : 0.0;}// reduce some multiplications in the final version// partialBrdf *= cosON; - omittedreturn partialBrdf;}vec3 vrayGetGGXDir(float u, float v, float sharpness, float gtrGamma, vec3 view, mat3 nm, out float prob, out float brdfDivByProb) {vec3 microNormalLocal = vrayGetGGXMicroNormal(u, v, sharpness, gtrGamma);//vrayDebug(microNormalLocal * 0.5 + vec3(0.5));if (microNormalLocal.z < 0.0)return nm[2];vec3 microNormal = nm * microNormalLocal;// Compute and keep the length of the half-vector in local space; needed for anisotropy correctionfloat L2 = dot(microNormal, microNormal);float L = sqrt(L2);microNormal /= L;vec3 dir = reflect(-view, microNormal);float Dval = 0.0;float partialProb = 0.0;float partialBrdf = vrayGetGGXContribution(view, dir, microNormal, microNormalLocal, sharpness, gtrGamma, nm[2], partialProb, Dval);partialProb *= L * L2; // take anisotropy in considerationprob = (Dval >= 1e-6) ? partialProb * Dval : 1e18; // compute full probability// note: in the full VRayMtl prob is multiplied by 2PI, but in this shader// it's used exclusively to sample tne environment map, and we would have// to divide by 2PI in that computation.brdfDivByProb = (partialProb >= 1e-6) ? partialBrdf / partialProb : 0.0;return dir;}float vrayRand(vec2 co) {return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);}/// Generate a random vec2, u in (0, 1), v in (B, B+1) where B is a fragment-dependent random blue noise value./// The returned value is suitable to be used for sampling a specular BRDF. V is/// offset using blue noise, so it can be above 1, but that should be OK because/// it is expected to be used as the argument to a trigonometric function.vec2 uvRand(VRayMtlContext ctx, int sampleIdx) {// plastic constant// gives slightly better result than golden ratiofloat plast = 1.324717957244746;float invPlast = 1.0/plast;return vec2(fract(float(sampleIdx + 1) * invPlast),float(sampleIdx) / float(nbSamples) + ctx.blueNoise);}float intensity(vec3 v) {return (v.x + v.y + v.z) / 3.0;}vec3 vraySampleBRDF(VRayMtlInitParams params, VRayMtlContext ctx, int sampleIdx, out float rayProb, out float brdfContrib) {vec3 geomNormal = params.geomNormal;int brdfType = params.brdfType;vec2 uv = uvRand(ctx, sampleIdx);float u = uv.x;float v = uv.y;vec3 dir = vec3(0.0);rayProb = 1.0;brdfContrib = 1.0;if (brdfType==0) { // Phongdir = vrayGetPhongDir(u, v, ctx.gloss1, -ctx.e, ctx.nm);}else if (brdfType==1) { // Blinndir = vrayGetBlinnDir(u, v, ctx.gloss1, -ctx.e, ctx.nm);}else if (brdfType==2) { // Warddir = vrayGetWardDir(u, v, ctx.gloss2, -ctx.e, ctx.nm);}else {dir = vrayGetGGXDir(u, v, ctx.gloss2, ctx.gtrGamma, -ctx.e, ctx.nm, rayProb, brdfContrib);}if (dot(dir, geomNormal) < 0.0f) {brdfContrib = 0.0;}return dir;}vec3 vraySampleRefractBRDF(VRayMtlInitParams params, VRayMtlContext ctx, int sampleIdx, out bool totalInternalReflection) {vec3 geomNormal = params.geomNormal;vec3 refractDir = refract(ctx.e, geomNormal, 1.0 / params.refractionIOR);totalInternalReflection = false;if (refractDir == vec3(0.0)) {refractDir = reflect(ctx.e, geomNormal);totalInternalReflection = true;}vec3 s = cross(vec3(0, 1, 0), refractDir);vec3 s1 = cross(refractDir, s);mat3 m;m[0] = normalize(s);m[1] = normalize(s1);m[2] = normalize(refractDir);vec2 uv = uvRand(ctx, sampleIdx);float u = uv.x;float v = uv.y;float gloss = 1.0 / pow(max(1.0 - params.refrGloss, 1e-4), 3.5) - 1.0;vec3 sampleDir = vrayGetSpecularDir(u, v, gloss);return m * sampleDir;}float pow35(float x) {return x * x * x * sqrt(x);}VRayMtlContext initVRayMtlContext(VRayMtlInitParams initParams) {float reflGloss = initParams.reflGloss;vec3 Vw = initParams.Vw;vec3 geomNormal = initParams.geomNormal;vec3 selfIllum = initParams.selfIllum;vec3 diffuseColor = initParams.diffuseColor;float diffuseAmount = initParams.diffuseAmount;vec3 reflColor = initParams.reflColor;float reflAmount = initParams.reflAmount;bool traceReflections = initParams.traceReflections;float metalness = initParams.metalness;float aniso = initParams.aniso;float anisoRotation = initParams.anisoRotation;int anisoAxis = initParams.anisoAxis;vec3 opacity = initParams.opacity;float roughness = initParams.roughness;vec3 refractionColor = initParams.refractionColor;float refractionAmount = initParams.refractionAmount;bool traceRefractions = initParams.traceRefractions;float refractionIOR = initParams.refractionIOR;bool useFresnel = initParams.useFresnel;float fresnelIOR = initParams.fresnelIOR;bool lockFresnelIOR = initParams.lockFresnelIOR;bool doubleSided = initParams.doubleSided;bool useRoughness = initParams.useRoughness;float gtrGamma = initParams.gtrGamma;int brdfType = initParams.brdfType;VRayMtlContext result;if (initParams.lockFresnelIOR)fresnelIOR = initParams.refractionIOR;result.e = -normalize(Vw);if (useRoughness)reflGloss = 1.0 - reflGloss; // Invert glossiness (turn it into roughness)result.reflGloss = reflGloss;result.opacity = opacity;result.diff = diffuseColor * diffuseAmount * result.opacity;result.illum = selfIllum * result.opacity;// roughnessfloat sqrRough = roughness * roughness;result.rtermA = 1.0 - 0.5 * (sqrRough / (sqrRough + 0.33));result.rtermB = 0.45 * (sqrRough / (sqrRough + 0.09));if (doubleSided && dot(geomNormal, result.e) > 0.0)geomNormal = -geomNormal;vec3 reflectDir = reflect(result.e, geomNormal);result.geomNormal = geomNormal;// check for internal reflectionbool internalReflection;vec3 refractDir;bool outToIn = (dot(geomNormal, result.e) < 0.0);float ior = (outToIn ? 1.0 / refractionIOR : refractionIOR);vec3 normal = (outToIn ? geomNormal : -geomNormal);float cost = -dot(result.e, normal);float sintSqr = 1.0 - ior * ior * (1.0 - cost * cost);if (sintSqr > 1e-6) {internalReflection = false;refractDir = ior * result.e + (ior * cost - sqrt(sintSqr)) * normal;} else {internalReflection = true;refractDir = reflectDir;}result.fresnel = 1.0;if (useFresnel && !internalReflection)result.fresnel = clamp(vrayGetFresnelCoeff(fresnelIOR, result.e, normal, refractDir), 0.0, 1.0);//vrayDebug(result.fresnel);result.reflNoFresnel = reflColor * reflAmount * result.opacity;result.refl = result.reflNoFresnel * result.fresnel;// Reflection calculation including metalness. Taken from VRayMtl's original implementation.vec3 metalColor = result.diff * result.fresnel * metalness;vec3 dielectricReflectionTransparency = traceReflections ? (1.0 - result.refl) : vec3(1.0);vec3 reflectionTransparency = (1.0 - metalness) * dielectricReflectionTransparency;if (traceRefractions) {result.refr = refractionColor * refractionAmount * result.opacity * reflectionTransparency;} else {result.refr = vec3(0.0);}result.diff *= reflectionTransparency - result.refr;result.refl = mix(metalColor, result.refl, result.fresnel);if (result.fresnel > 1e-6) {result.refl /= result.fresnel;}result.gloss1 = max(0.0, 1.0 / pow35(max(1.0 - reflGloss, 1e-4)) - 1.0); // [0, 1] -> [0, inf)result.gloss2 = max(1.0 - reflGloss, 1e-4);result.gloss2 *= result.gloss2;result.gtrGamma = gtrGamma;result.blueNoise = getBlueNoiseThresholdTemporal();// Set up the normal/inverse normal matrices for BRDFs that support anisotropyvec3 anisoDirection = vec3(0.0, 0.0, 1.0);if (anisoAxis == 0)anisoDirection = vec3(1.0, 0.0, 0.0);else if (anisoAxis == 1)anisoDirection = vec3(0.0, 1.0, 0.0);float anisoAbs = abs(aniso);if (anisoAbs < 1e-12 || anisoAbs >= 1.0 - 1e-6) {vrayMakeNormalMatrix(geomNormal, result.nm);result.inm = transpose(result.nm); // inverse = transpose for orthogonal matrix} else if (!internalReflection) {vec3 base0, base1;base0 = normalize(cross(geomNormal, anisoDirection));base1 = normalize(cross(base0, geomNormal));float anisor = anisoRotation * 6.2831853;if (abs(anisor) > 1e-6) {float cs = cos(anisor);float sn = sin(anisor);vec3 nu = base0 * cs - base1 * sn;vec3 nv = base0 * sn + base1 * cs;base0 = nu;base1 = nv;}if (length(cross(base0, base1)) < 1e-6)vrayComputeTangentVectors(geomNormal, base0, base1);if (aniso > 0.0) {float a = 1.0 / (1.0 - aniso);base0 *= a;base1 /= a;} else {float a = 1.0 / (1.0 + aniso);base0 /= a;base1 *= a;}result.nm[0] = base0;result.nm[1] = base1;result.nm[2] = geomNormal;result.inm = inverse(result.nm);}return result;}vec3 vrayMtlDiffuse(vec3 lightDir, vec3 normal) {return vec3(max(0.0, dot(lightDir, normal)));}vec3 vrayMtlDiffuseRoughness(vec3 lightDir, VRayMtlContext ctx) {float lightNdotL = max(0.0, dot(lightDir, ctx.geomNormal));float rmult = 1.0;vec3 vecV = -ctx.e;float NV = clamp(dot(ctx.geomNormal, vecV), 0.0, 1.0);float theta_i = acos(lightNdotL);float theta_r = acos(NV);float alpha = max(theta_i, theta_r);if (alpha > 1.571) { // 1.571==pi/2rmult = 0.0;} else {float beta = min(theta_i, theta_r);vec3 vecVtan = vecV - ctx.geomNormal * NV;vec3 vecLtan = lightDir - ctx.geomNormal * lightNdotL;float fMult = length(vecVtan) * length(vecLtan);float cosDeltaPhi = fMult < 0.000001 ? 1.0 : dot(vecVtan, vecLtan) / fMult;rmult = (ctx.rtermA + ctx.rtermB * sin(alpha) * tan(beta) * max(0.0, cosDeltaPhi));}return vec3(lightNdotL * rmult);}vec3 vrayMtlBlinn(vec3 lightDir, VRayMtlContext ctx) {float k = max(0.0, ctx.gloss1);vec3 hw = lightDir - ctx.e;vec3 hn = normalize(ctx.inm * hw);float cs1 = hn.z;if (cs1 > 1e-6) {float lightNdotL = dot(ctx.geomNormal, lightDir);if (cs1 > 1.0)cs1 = 1.0;float cs = -dot(normalize(hw), ctx.e);k = cs < 1e-6 ? 0.0 : pow(cs1, k) * (k + 1.0) * 0.125 / cs;k *= lightNdotL;if (k > 0.0)return vec3(k);}return vec3(0.0);}vec3 vrayMtlPhong(vec3 lightDir, VRayMtlContext ctx) {vec3 reflectDir = reflect(ctx.e, ctx.geomNormal);float cs1 = dot(lightDir, reflectDir);if (cs1 > 0.0) {float lightNdotL = dot(ctx.geomNormal, lightDir);if (cs1 > 1.0)cs1 = 1.0;float k = pow(cs1, ctx.gloss1) * (ctx.gloss1 + 1.0) * 0.5; // phong kk *= lightNdotL;if (k > 0.0)return vec3(k);}return vec3(0.0);}vec3 vrayMtlWard(vec3 lightDir, VRayMtlContext ctx) {float cs1 = -dot(ctx.e, ctx.geomNormal);float lightNdotL = dot(ctx.geomNormal, lightDir);if (lightNdotL > 1e-6 && cs1 > 1e-6) {vec3 hw = lightDir - ctx.e;vec3 hn = normalize(ctx.inm * hw);if (hn.z > 1e-3) {float tanhSqr = (1.0 / (hn.z * hn.z) - 1.0);float divd = cs1 * ctx.gloss2;float k = exp(-tanhSqr / ctx.gloss2) / divd;k *= lightNdotL;if (k > 0.0)return vec3(k);}}return vec3(0.0);}vec3 vrayMtlGGX(vec3 lightDir, VRayMtlContext ctx) {float cs1 = -dot(ctx.e, ctx.geomNormal);float lightNdotL = dot(ctx.geomNormal, lightDir);if (lightNdotL > 1e-6 && cs1 > 1e-6) {vec3 hw = normalize(lightDir - ctx.e);vec3 hn = normalize(ctx.inm * hw);if (hn.z > 1e-3) {float D = vrayGetGGXMicrofacetDistribution(hn.z, ctx.gloss2, ctx.gtrGamma);float G = vrayGetGGXBidirectionalShadowingMasking(-ctx.e, lightDir, hw, ctx.geomNormal, ctx.gloss2, ctx.gtrGamma);float cs2 = max(dot(hw, lightDir), 0.0001);vec3 micron = ctx.nm * hn;float L2 = dot(micron, micron);float anisotropyCorrection = L2 * sqrt(L2);float k = 0.25 * D * G * anisotropyCorrection / cs1; // anisotropy correctionif (k > 0.0)return vec3(k);}}return vec3(0.0);}vec3 vrayComputeDirectDiffuseContribution(VRayMtlInitParams params, VRayMtlContext ctx, vec3 lightDir) {vec3 res = vec3(0.0);if (params.roughness < 1e-6f) {res = vrayMtlDiffuse(lightDir, ctx.geomNormal);} else {res = vrayMtlDiffuseRoughness(lightDir, ctx);}return res;}vec3 vrayComputeDirectReflectionContribution(VRayMtlInitParams params, VRayMtlContext ctx, vec3 lightDir) {vec3 res = vec3(0.0);if (params.brdfType==0) {res = vrayMtlPhong(lightDir, ctx);}else if (params.brdfType==1) {res = vrayMtlBlinn(lightDir, ctx);}else if (params.brdfType==2) {res = vrayMtlWard(lightDir, ctx);}else if (params.brdfType==3) {res = vrayMtlGGX(lightDir, ctx);}return res;}vec3 vrayComputeIndirectDiffuseContribution(VRayMtlInitParams params, VRayMtlContext ctx) {vec3 res = vec3(0.0);res = envIrradiance(params.geomNormal);return res;}vec3 vrayComputeIndirectReflectionContribution(VRayMtlInitParams params, VRayMtlContext ctx) {vec3 res = vec3(0.0);if (!params.traceReflections)return res;float invNumSamples = 1.0f / float(nbSamples);vec3 envSum = vec3(0.0);for (int i = 0; i < nbSamples; ++i) {float brdfContrib = 0.0f;float rayProb = 0.0f;vec3 dir = vraySampleBRDF(params, ctx, i, rayProb, brdfContrib);if (brdfContrib < 1e-6f)continue;float lod = computeLOD(dir, rayProb, nbSamples);envSum += envSampleLOD(dir, lod) * brdfContrib;}res += envSum * invNumSamples;return res;}vec3 vrayComputeIndirectRefractionContribution(VRayMtlInitParams params, VRayMtlContext ctx, float alpha, vec3 alphaDir, vec3 diffuseIndirect) {vec3 res = vec3(0.0);if (!params.traceRefractions)return res;float invNumSamples = 1.0f / float(nbSamples);vec3 view = -params.Vw;if (alpha <= 0.999) {res += envSampleLOD(alphaDir, 0.0);} else {vec3 envSum = vec3(0.0);for (int i = 0; i < nbSamples; ++i) {bool totalInternalReflection;vec3 dir = vraySampleRefractBRDF(params, ctx, i, totalInternalReflection);if (totalInternalReflection) {envSum += envSampleLOD(dir, 0.0);} else {envSum += envSampleLOD(dir, 0.0);}}res += envSum * invNumSamples;}return res;}/// Sample texture and blend with default color where no data exists.vec3 textureWithDefault(SamplerSparse samplerSparse, SparseCoord coord, vec3 defaultColor) {vec4 sampledColor = textureSparse(samplerSparse, coord);return sampledColor.rgb + (1.0 - sampledColor.a) * defaultColor;}/// Sample texture and blend with default value, treating green as alpha.float textureWithDefault(SamplerSparse samplerSparse, SparseCoord coord, float defaultValue) {vec4 sampledValue = textureSparse(samplerSparse, coord);return sampledValue.r + (1.0 - sampledValue.g) * defaultValue;}/// Implemented in specular/metallic/// initParams will be set up with geometry data prior to this callvoid setupInitParams(inout VRayMtlInitParams initParams, SparseCoord texCoord);
Shader entry point.
Copied to your clipboardvoid shade(V2F inputs){// Normal with bump/normal map appliedvec3 bumpNormal = computeWSNormal(inputs.sparse_coord, inputs.tangent, inputs.bitangent, inputs.normal);// Init VRayMtlVRayMtlInitParams initParams;// setup geometric data// for 2D put view vector along normal (see lib-vectors)initParams.Vw = is2DView ? bumpNormal : getEyeVec(inputs.position);initParams.geomNormal = bumpNormal;initParams.approxEnv = false;// setup common parametersinitParams.diffuseAmount = uniform_diffuse_amount;initParams.reflAmount = uniform_reflection_amount;initParams.traceReflections = uniform_trace_reflections;initParams.aniso = 0.0;initParams.anisoRotation = 0.0;initParams.anisoAxis = uniform_anisotropy_axis;initParams.refractionAmount = uniform_refraction_amount;initParams.refractionIOR = uniform_refraction_ior;initParams.refrGloss = uniform_refraction_glossiness;initParams.traceRefractions = uniform_trace_refractions;initParams.useFresnel = uniform_use_fresnel;initParams.fresnelIOR = uniform_fresnel_ior;initParams.lockFresnelIOR = uniform_lock_fresnel_ior;initParams.doubleSided = uniform_double_sided;initParams.useRoughness = false;initParams.gtrGamma = uniform_gtr_gamma;initParams.brdfType = uniform_brdf_type;initParams.opacity = vec3(1.0);// flavour-specific setupsetupInitParams(initParams, inputs.sparse_coord);// Get detail (ambient occlusion) and global (shadow) occlusion factorsfloat occlusion = getAO(inputs.sparse_coord) * getShadowFactor();float specOcclusion = specularOcclusionCorrection(occlusion, initParams.metalness, initParams.useRoughness ? 1.0 - initParams.reflGloss : initParams.reflGloss);// Init context and sample materialVRayMtlContext ctx = initVRayMtlContext(initParams);vec3 lightDir = uniform_main_light.xyz - inputs.position * uniform_main_light.w;vec3 diffuseDirect = vrayComputeDirectDiffuseContribution(initParams, ctx, lightDir);vec3 diffuseIndirect = vrayComputeIndirectDiffuseContribution(initParams, ctx);vec3 diffuse = diffuseDirect + diffuseIndirect;vec3 reflection =vrayComputeDirectReflectionContribution(initParams, ctx, lightDir) +vrayComputeIndirectReflectionContribution(initParams, ctx);float alpha = intensity(ctx.opacity);vec3 refraction =vrayComputeIndirectRefractionContribution(initParams, ctx, alpha, -initParams.Vw, diffuseIndirect);// Output valuesif (debugOutput == 0) {albedoOutput(ctx.diff);diffuseShadingOutput(occlusion * diffuse);specularShadingOutput(specOcclusion * reflection * ctx.refl);// output refraction with emissive as it can't really go elsewhereemissiveColorOutput(ctx.illum + refraction * ctx.refr);}}void setupInitParams(inout VRayMtlInitParams initParams, SparseCoord texCoord) {// Fetch material parametersvec3 baseColor = textureWithDefault(basecolor_tex, texCoord, vec3(0.5));float roughness = textureWithDefault(roughness_tex, texCoord, 0.0);float metallic = textureWithDefault(metallic_tex, texCoord, 0.0);vec3 refractionColor = textureWithDefault(transmissive_tex, texCoord, vec3(0.0));vec3 selfIllumColor = textureWithDefault(emissive_tex, texCoord, vec3(0.0));float anisotropy = textureWithDefault(anisotropylevel_tex, texCoord, 0.0);float anisotropyAngle = textureWithDefault(anisotropyangle_tex, texCoord, 0.0);initParams.diffuseColor = baseColor;initParams.roughness = roughness;initParams.selfIllum = selfIllumColor;initParams.reflColor = vec3(1.0);initParams.reflGloss = roughness;initParams.refractionColor = refractionColor;initParams.metalness = metallic;initParams.aniso = anisotropy;initParams.anisoRotation = anisotropyAngle;initParams.useRoughness = true;}