/*
  Copyright(C) 2018-2025, tetraface Inc. All rights reserved.
  
  This is a modified implementation of glTF-Sample-Viewer.
  The original code is here:
    https://github.com/KhronosGroup/glTF-Sample-Viewer
*/
/*
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/
*/
#include "world.inc"
#include "texture.inc"
#include "light.inc"
#include "model.inc"
#if OIT
#include "oit.inc"
#endif

cbuffer ConstantBufferMaterial : register( b2 )
{
	float4 MapTransform;
	float4 MapTranslate;
	float Alpha;
	float4 BaseColor;
	float Metallic;
	float Roughness;
	float4 Emissive;
	int AlphaMode;
	float AlphaCutOff;
	int SpecularGlossiness;
	float4 DiffuseFactor;
	float4 Specular;
	float GlossinessFactor;
	int Clearcoat;
	float ClearcoatFactor;
	float ClearcoatRoughnessFactor;
	int IorExt;
	float Ior;
	int Sheen;
	float4 SheenColorFactor;
	float SheenRoughnessFactor;
	int SpecularExt;
	float SpecularFactor;
	float4 SpecularColorFactor;
	int Anisotropy;
	float AnisotropyStrength;
	float AnisotropyRotation;
	int Iridescence;
	float IridescenceFactor;
	float IridescenceIor;
	float IridescenceThicknessMinimum;
	float IridescenceThicknessMaximum;
	int Transmission;
	float TransmissionFactor;
	int Volume;
	float ThicknessFactor;
	float AttenuationDistance;
	float4 AttenuationColor;
	int Dispersion;
	float DispersionFactor;
	int Unlit;
	float NormalScale;
	int HasMetalMap;
	int HasEmissiveMap;
	int HasOcclusionMap;
	float OcclusionStrength;
	int HasClearcoatMap;
	int HasClearcoatRoughnessMap;
	int HasClearcoatNormalMap;
	float ClearcoatNormalScale;
	int HasSheenColorMap;
	int HasSheenRoughnessMap;
	int HasSpecularMap;
	int HasSpecularColorMap;
	int HasAnisotropyMap;
	int HasIridescenceMap;
	int HasIridescenceThicknessMap;
	int HasTransmissionMap;
	int HasThicknessMap;
}

#if TRANSMISSION
cbuffer TransmissionBuffer : register( b6 )
{
	int TransmissionMapMipLevel;
}
#endif

#ifndef ALPHAMAP
#define ALPHAMAP TEXTURE
#endif
#ifndef USE_TANGENT
#define USE_TANGENT NORMALMAP
#endif

#if TEXTURE
Texture2D ColorMap : register( t0 );
#endif
SamplerState ColorMapSampler : register( s0 );
#if ALPHAMAP
//Texture2D AlphaMap : register( t1 );
//SamplerState AlphaMapSampler : register( s1 );
#endif
#if NORMALMAP
Texture2D NormalMap : register( t2 );
SamplerState NormalMapSampler : register( s2 );
#endif
#if SHADOW
Texture2D ShadowMap : register( t3 );
SamplerState ShadowMapSampler : register( s3 );
#endif
Texture2D MetallicRoughnessMap : register( t4 );
Texture2D EmissiveMap : register( t5 );
Texture2D OcclusionMap : register( t6 );
#if CLEARCOAT
Texture2D ClearcoatMap : register( t7 );
Texture2D ClearcoatRoughnessMap : register( t8 );
Texture2D ClearcoatNormalMap : register( t9 );
#endif
#if SHEEN
Texture2D SheenColorMap : register( t10 );
Texture2D SheenRoughnessMap : register( t11 );
#endif
#if SPECULAR
Texture2D SpecularMap : register( t12 );
Texture2D SpecularColorMap : register( t13 );
#endif
#if ANISOTROPY
Texture2D AnisotropyMap : register( t14 );
#endif
#if IRIDESCENCE
Texture2D IridescenceMap : register( t15 );
Texture2D IridescenceThicknessMap : register( t16 );
#endif
#if TRANSMISSION
Texture2D TransmissionMap : register( t17 );
#endif
#if TRANSMISSION && VOLUME
Texture2D ThicknessMap : register( t18 );
#endif
#if ENVMAP
Texture2D EnvDiffuseMap : register( t19 );
SamplerState EnvDiffuseMapSampler : register( s4 );
Texture2D EnvSpecularMap : register( t20 );
SamplerState EnvSpecularMapSampler : register( s5 );
#endif
#if ENVMAP || TRANSMISSION
Texture2D BrdfLUTMap : register( t21 );
SamplerState BrdfLUTMapSampler : register( s6 );
#endif
#if SHEEN
#if ENVMAP
Texture2D EnvSheenMap : register( t22 );
SamplerState EnvSheenMapSampler : register( s7 );
Texture2D CharlieLUTMap : register( t23 );
SamplerState CharlieLUTMapSampler : register( s8 );
#endif
Texture2D SheenELUTMap : register( t24 );
SamplerState SheenELUTMapSampler : register( s9 );
#endif
#if TRANSMISSION
Texture2D TransmissionBufferMap : register( t25 );
SamplerState TransmissionBufferMapSampler : register( s10 );
#endif

struct VS_INPUT
{
	float4 Pos : POSITION;
	float3 Normal : NORMAL;
#if 1 //always //TEXTURE || ALPHAMAP || USE_TANGENT
	float2 TexCoord : TEXCOORD0;
#if MULTIUV >= 1
	float2 TexCoord1 : TEXCOORD1;
#endif
#if MULTIUV >= 2
	float2 TexCoord2 : TEXCOORD2;
#endif
#if MULTIUV >= 3
	float2 TexCoord3 : TEXCOORD3;
#endif
#endif
#if VERTEXCOLOR
	float4 Col : COLOR0;
#endif
#if USE_TANGENT
	float4 Tangent : TANGENT;
#endif
};

struct VS_OUTPUT
{
	float4 Pos : SV_POSITION;
	float4 Col : COLOR;
	float3 Normal : NORMAL;
	float2 TexCoord : TEXCOORD0;
#if MULTIUV >= 1
	float2 TexCoord1 : TEXCOORD1;
#endif
#if MULTIUV >= 2
	float2 TexCoord2 : TEXCOORD2;
#endif
#if MULTIUV >= 3
	float2 TexCoord3 : TEXCOORD3;
#endif
#if USE_TANGENT
	float3 Tangent : TANGENT;
	float3 Binormal : BINORMAL;
#endif
	float4 WorldPos : TEXCOORD4;
#if SHADOW
	float4 SMPos : TEXCOORD5;
#endif
};

struct PS_INPUT
{
	float4 Pos : SV_POSITION;
	float4 Col : COLOR;
	float3 Normal : NORMAL;
	float2 TexCoord : TEXCOORD0;
#if MULTIUV >= 1
	float2 TexCoord1 : TEXCOORD1;
#endif
#if MULTIUV >= 2
	float2 TexCoord2 : TEXCOORD2;
#endif
#if MULTIUV >= 3
	float2 TexCoord3 : TEXCOORD3;
#endif
#if USE_TANGENT
	float3 Tangent : TANGENT;
	float3 Binormal : BINORMAL;
#endif
	float4 WorldPos : TEXCOORD4;
#if SHADOW
	float4 SMPos : TEXCOORD5;
#endif
	bool IsBack : SV_IsFrontFace; // Why inverted?
#if MSAA
    uint Coverage : SV_COVERAGE;
#endif
};

struct PS_OUTPUT
{
	float4 Color    : SV_Target0;
#if RENDER_NORMAL
	float4 Normal   : SV_Target1;
#endif
};

#include "oit_store.inc"


float2 TransformUV(float2 uv, float4 transform, float4 translate)
{
	float2x2 tm = {transform.x, transform.y, transform.z, transform.w};
	return mul(uv, tm) + float2(translate.x, translate.y);
}

// Vertex shader
VS_OUTPUT VS(const VS_INPUT In)
{
	VS_OUTPUT Out;
	Out.Pos = mul(In.Pos, WorldViewProj);
#if SPECULARGLOSSINESS
	Out.Col = DiffuseFactor;
#else
	Out.Col = BaseColor;
#endif
#if VERTEXCOLOR
	Out.Col *= In.Col;
#endif
	Out.Normal = In.Normal;
	Out.TexCoord = TransformUV(In.TexCoord, MapTransform, MapTranslate);
#if MULTIUV >= 1
	Out.TexCoord1 = TransformUV(In.TexCoord1, MapTransform, MapTranslate);
#endif
#if MULTIUV >= 2
	Out.TexCoord2 = TransformUV(In.TexCoord2, MapTransform, MapTranslate);
#endif
#if MULTIUV >= 3
	Out.TexCoord3 = TransformUV(In.TexCoord3, MapTransform, MapTranslate);
#endif
#if USE_TANGENT
	if(length(In.Tangent.xyz) > 0){
		Out.Tangent = In.Tangent.xyz;
		Out.Binormal = normalize(cross(In.Tangent.xyz, In.Normal)) * In.Tangent.w;
	}else{
		if(abs(dot(In.Normal,float3(0,1,0))) < abs(dot(In.Normal,float3(0,0,1))))
			Out.Tangent = normalize(cross(float3(0,1,0),In.Normal));
		else
			Out.Tangent = normalize(cross(float3(0,0,1),In.Normal));
		Out.Binormal = normalize(cross(Out.Tangent, In.Normal));
	}
#endif
	Out.WorldPos = In.Pos;
#if SHADOW
	Out.SMPos = mul(In.Pos, ShadowMapProj);
#endif
	return Out;
}


// Geometry shader
//   Convert LineAdjacent (with 4 vertices) to TriangleStrip(with 3 or 4 vertices).
[maxvertexcount(4)]
void GSpatch(lineadj VS_OUTPUT input[4], inout TriangleStream<VS_OUTPUT> stream)
{
	VS_OUTPUT output;

	output.Pos = input[1].Pos;
	output.Col = input[1].Col;
	output.Normal = input[1].Normal;
	output.TexCoord = input[1].TexCoord;
#if MULTIUV >= 1
	output.TexCoord1 = input[1].TexCoord1;
#endif
#if MULTIUV >= 2
	output.TexCoord2 = input[1].TexCoord2;
#endif
#if MULTIUV >= 3
	output.TexCoord3 = input[1].TexCoord3;
#endif
#if USE_TANGENT
	output.Tangent = input[1].Tangent;
	output.Binormal = input[1].Binormal;
#endif
	output.WorldPos = input[1].WorldPos;
#if SHADOW
	output.SMPos = input[1].SMPos;
#endif
	stream.Append(output);

	output.Pos = input[0].Pos;
	output.Col = input[0].Col;
	output.Normal = input[0].Normal;
	output.TexCoord = input[0].TexCoord;
#if MULTIUV >= 1
	output.TexCoord1 = input[0].TexCoord1;
#endif
#if MULTIUV >= 2
	output.TexCoord2 = input[0].TexCoord2;
#endif
#if MULTIUV >= 3
	output.TexCoord3 = input[0].TexCoord3;
#endif
#if USE_TANGENT
	output.Tangent = input[0].Tangent;
	output.Binormal = input[0].Binormal;
#endif
	output.WorldPos = input[0].WorldPos;
#if SHADOW
	output.SMPos = input[0].SMPos;
#endif
	stream.Append(output);

	output.Pos = input[2].Pos;
	output.Col = input[2].Col;
	output.Normal = input[2].Normal;
	output.TexCoord = input[2].TexCoord;
#if MULTIUV >= 1
	output.TexCoord1 = input[2].TexCoord1;
#endif
#if MULTIUV >= 2
	output.TexCoord2 = input[2].TexCoord2;
#endif
#if MULTIUV >= 3
	output.TexCoord3 = input[2].TexCoord3;
#endif
#if USE_TANGENT
	output.Tangent = input[2].Tangent;
	output.Binormal = input[2].Binormal;
#endif
	output.WorldPos = input[2].WorldPos;
#if SHADOW
	output.SMPos = input[2].SMPos;
#endif
	stream.Append(output);

	if(length(input[3].Normal) > 0.0){
		output.Pos = input[3].Pos;
		output.Col = input[3].Col;
		output.Normal = input[3].Normal;
		output.TexCoord = input[3].TexCoord;
#if MULTIUV >= 1
		output.TexCoord1 = input[3].TexCoord1;
#endif
#if MULTIUV >= 2
		output.TexCoord2 = input[3].TexCoord2;
#endif
#if MULTIUV >= 3
		output.TexCoord3 = input[3].TexCoord3;
#endif
#if USE_TANGENT
		output.Tangent = input[3].Tangent;
		output.Binormal = input[3].Binormal;
#endif
		output.WorldPos = input[3].WorldPos;
#if SHADOW
		output.SMPos = input[3].SMPos;
#endif
		stream.Append(output);
	}

	stream.RestartStrip();
}

float2 GetTexCoord(PS_INPUT In, int channel)
{
#if MULTIUV >= 1
	if((channel & 0x30) == 0x10) return In.TexCoord1;
#endif
#if MULTIUV >= 2
	if((channel & 0x30) == 0x20) return In.TexCoord2;
#endif
#if MULTIUV >= 3
	if((channel & 0x30) == 0x30) return In.TexCoord3;
#endif
	return In.TexCoord;
}

static const float PI = 3.141592653589793;
static const float c_MinRoughness = 0.04;

struct BRDFInput
{
	float metallic;
	float perceptualRoughness;
	float alphaRoughness;
	float3 diffuseColor;
	float3 f0;
};

float max3(float3 v)
{
	return max(max(v.x, v.y), v.z);
}

float3 to3(float v) { return float3(v,v,v); }

float clampedDot(float3 x, float3 y)
{
	return saturate(dot(x, y));
}

float2 dirToUV(float3 dir)
{
	return float2(
		atan2(dir.z, dir.x) / (PI*2),
		acos(dir.y) / PI);
}

float2 envmapUV(float3 dir)
{
	return dirToUV(dir) + float2(EnvMapRotate, 0.0);
}

float3 F_Schlick(float3 f0, float3 f90, float VdotH)
{
	return f0 + (f90 - f0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0);
}

float F_Schlick(float f0, float f90, float VdotH)
{
	float x = clamp(1.0 - VdotH, 0.0, 1.0);
	float x2 = x * x;
	float x5 = x * x2 * x2;
	return f0 + (f90 - f0) * x5;
}

// Smith Joint GGX
// Note: Vis = G / (4 * NdotL * NdotV)
// see Eric Heitz. 2014. Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs. Journal of Computer Graphics Techniques, 3
// see Real-Time Rendering. Page 331 to 336.
// see https://google.github.io/filament/Filament.md.html#materialsystem/specularbrdf/geometricshadowing(specularg)
float V_GGX(float NdotL, float NdotV, float alphaRoughness)
{
	float alphaRoughnessSq = alphaRoughness * alphaRoughness;

	float GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - alphaRoughnessSq) + alphaRoughnessSq);
	float GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - alphaRoughnessSq) + alphaRoughnessSq);

	float GGX = GGXV + GGXL;
	if (GGX > 0.0)
		return 0.5 / GGX;
	else
		return 0.0;
}

// The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D())
// Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz
// Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3.
float D_GGX(float NdotH, float alphaRoughness)
{
	float alphaRoughnessSq = alphaRoughness * alphaRoughness;
	float f = (NdotH * NdotH) * (alphaRoughnessSq - 1.0) + 1.0;
	return alphaRoughnessSq / (PI * f * f);
}

//  https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB
float3 BRDF_specularGGX(float3 f0, float3 f90, float alphaRoughness, float specularWeight, float VdotH, float NdotL, float NdotV, float NdotH)
{
	float3 F = F_Schlick(f0, f90, VdotH);
	float Vis = V_GGX(NdotL, NdotV, alphaRoughness);
	float D = D_GGX(NdotH, alphaRoughness);

	return specularWeight * F * Vis * D;
}

#if IRIDESCENCE
float3 BRDF_specularGGXIridescence(float3 f0, float3 f90, float3 iridescenceFresnel, float alphaRoughness, float iridescenceFactor, float specularWeight, float VdotH, float NdotL, float NdotV, float NdotH)
{
	float3 F = lerp(F_Schlick(f0, f90, VdotH), iridescenceFresnel, iridescenceFactor);
	float Vis = V_GGX(NdotL, NdotV, alphaRoughness);
	float D = D_GGX(NdotH, alphaRoughness);

	return specularWeight * F * Vis * D;
}
#endif

#if ANISOTROPY
// GGX Distribution Anisotropic (Same as Babylon.js)
// https://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf Addenda
float D_GGX_anisotropic(float NdotH, float TdotH, float BdotH, float anisotropy, float at, float ab)
{
    float a2 = at * ab;
    float3 f = float3(ab * TdotH, at * BdotH, a2 * NdotH);
    float w2 = a2 / dot(f, f);
    return a2 * w2 * w2 / PI;
}

// GGX Mask/Shadowing Anisotropic (Same as Babylon.js - smithVisibility_GGXCorrelated_Anisotropic)
// Heitz http://jcgt.org/published/0003/02/03/paper.pdf
float V_GGX_anisotropic(float NdotL, float NdotV, float BdotV, float TdotV, float TdotL, float BdotL, float at, float ab)
{
    float GGXV = NdotL * length(float3(at * TdotV, ab * BdotV, NdotV));
    float GGXL = NdotV * length(float3(at * TdotL, ab * BdotL, NdotL));
    float v = 0.5 / (GGXV + GGXL);
    return clamp(v, 0.0, 1.0);
}

float3 BRDF_specularGGXAnisotropy(float3 f0, float3 f90, float alphaRoughness, float anisotropy, float3 n, float3 v, float3 l, float3 h, float3 t, float3 b)
{
    // Roughness along the anisotropy bitangent is the material roughness, while the tangent roughness increases with anisotropy.
    float at = lerp(alphaRoughness, 1.0, anisotropy * anisotropy);
    float ab = clamp(alphaRoughness, 0.001, 1.0);

    float NdotL = clamp(dot(n, l), 0.0, 1.0);
    float NdotH = clamp(dot(n, h), 0.001, 1.0);
    float NdotV = dot(n, v);
    float VdotH = clamp(dot(v, h), 0.0, 1.0);

    float V = V_GGX_anisotropic(NdotL, NdotV, dot(b, v), dot(t, v), dot(t, l), dot(b, l), at, ab);
    float D = D_GGX_anisotropic(NdotH, dot(t, h), dot(b, h), anisotropy, at, ab);

    float3 F = F_Schlick(f0, f90, VdotH);
    return F * V * D;
}
#endif

//https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB
float3 BRDF_lambertian(float3 f0, float3 f90, float3 diffuseColor, float specularWeight, float VdotH)
{
	// see https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
	return (1.0 - specularWeight * F_Schlick(f0, f90, VdotH)) * (diffuseColor / PI);
}

#if IRIDESCENCE
//https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB
float3 BRDF_lambertianIridescence(float3 f0, float3 f90, float3 iridescenceFresnel, float iridescenceFactor, float3 diffuseColor, float specularWeight, float VdotH)
{
	// Use the maximum component of the iridescence Fresnel color
	// Maximum is used instead of the RGB value to not get inverse colors for the diffuse BRDF
	float3 iridescenceFresnelMax = to3(max(max(iridescenceFresnel.r, iridescenceFresnel.g), iridescenceFresnel.b));

	float3 schlickFresnel = F_Schlick(f0, f90, VdotH);

	// Blend default specular Fresnel with iridescence Fresnel
	float3 F = lerp(schlickFresnel, iridescenceFresnelMax, iridescenceFactor);

	// see https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
	return (1.0 - specularWeight * F) * (diffuseColor / PI);
}
#endif

#if ENVMAP
float3 getIBLRadianceGGX(float3 N, float3 V, float roughness, float3 F0, float specularWeight)
{
    float NdotV = clampedDot(N, V);
    float lod = roughness * float(EnvSpecularMapMipLevel-1);
    float3 reflection = normalize(reflect(-V, N));

    float2 brdfSamplePoint = saturate(float2(NdotV, 1.0 - roughness));
    float2 f_ab = BrdfLUTMap.Sample(BrdfLUTMapSampler, brdfSamplePoint).xy;
    float3 specularLight = EnvSpecularMap.SampleLevel(EnvSpecularMapSampler, envmapUV(reflection), lod).xyz;

    // see https://bruop.github.io/ibl/#single_scattering_results at Single Scattering Results
    // Roughness dependent fresnel, from Fdez-Aguera
    float3 Fr = max(to3(1.0 - roughness), F0) - F0;
    float3 k_S = F0 + Fr * pow(1.0 - NdotV, 5.0);
    float3 FssEss = k_S * f_ab.x + f_ab.y;

    return specularWeight * specularLight * FssEss;
}

#if IRIDESCENCE
float3 getIBLRadianceGGXIridescence(float3 N, float3 V, float roughness, float3 F0, float3 iridescenceFresnel, float iridescenceFactor, float specularWeight)
{
	float NdotV = clampedDot(N, V);
	float lod = roughness * float(EnvSpecularMapMipLevel-1);
	float3 reflection = normalize(reflect(-V, N));

	float2 brdfSamplePoint = saturate(float2(NdotV, 1.0 - roughness));
	float2 f_ab = BrdfLUTMap.Sample(BrdfLUTMapSampler, brdfSamplePoint).xy;
	float3 specularLight = EnvSpecularMap.SampleLevel(EnvSpecularMapSampler, envmapUV(reflection), lod).xyz;

	// see https://bruop.github.io/ibl/#single_scattering_results at Single Scattering Results
	// Roughness dependent fresnel, from Fdez-Aguera
	float3 Fr = max(to3(1.0 - roughness), F0) - F0;
	float3 k_S = lerp(F0 + Fr * pow(1.0 - NdotV, 5.0), iridescenceFresnel, iridescenceFactor);
	float3 FssEss = k_S * f_ab.x + f_ab.y;

	return specularWeight * specularLight * FssEss;
}
#endif

#if ANISOTROPY
float3 getIBLRadianceAnisotropy(float3 n, float3 v, float roughness, float anisotropy, float3 anisotropyDirection, float3 F0, float specularWeight)
{
    float NdotV = clampedDot(n, v);

    float tangentRoughness = lerp(roughness, 1.0, anisotropy * anisotropy);
    float3 anisotropicTangent = cross(anisotropyDirection, v);
    float3 anisotropicNormal  = cross(anisotropicTangent, anisotropyDirection);
    float bendFactor          = 1.0 - anisotropy * (1.0 - roughness);
    float bendFactorPow4      = bendFactor * bendFactor * bendFactor * bendFactor;
    float3 bentNormal         = normalize(lerp(anisotropicNormal, n, bendFactorPow4));

	float lod = roughness * float(EnvSpecularMapMipLevel-1);
    float3 reflection = normalize(reflect(-v, bentNormal));

    float2 brdfSamplePoint = saturate(float2(NdotV, roughness));
	float2 f_ab = BrdfLUTMap.Sample(BrdfLUTMapSampler, brdfSamplePoint).xy;
	float3 specularLight = EnvSpecularMap.SampleLevel(EnvSpecularMapSampler, envmapUV(reflection), lod).xyz;

    // see https://bruop.github.io/ibl/#single_scattering_results at Single Scattering Results
    // Roughness dependent fresnel, from Fdez-Aguera
    float3 Fr = max(to3(1.0 - roughness), F0) - F0;
    float3 k_S = F0 + Fr * pow(1.0 - NdotV, 5.0);
    float3 FssEss = k_S * f_ab.x + f_ab.y;

    return specularWeight * specularLight * FssEss;
}
#endif

// specularWeight is introduced with KHR_materials_specular
float3 getIBLRadianceLambertian(float3 n, float3 v, float roughness, float3 diffuseColor, float3 F0, float specularWeight)
{
	float NdotV = clampedDot(n, v);
	float2 brdfSamplePoint = clamp(float2(NdotV, 1.0 - roughness), 0, 1);
	float2 f_ab = BrdfLUTMap.Sample(BrdfLUTMapSampler, brdfSamplePoint).xy;

	float3 irradiance = EnvDiffuseMap.Sample(EnvDiffuseMapSampler, envmapUV(n)).xyz;

	// see https://bruop.github.io/ibl/#single_scattering_results at Single Scattering Results
	// Roughness dependent fresnel, from Fdez-Aguera

	float3 Fr = max(to3(1.0 - roughness), F0) - F0;
	float3 k_S = F0 + Fr * pow(1.0 - NdotV, 5.0);
	float3 FssEss = specularWeight * k_S * f_ab.x + f_ab.y; // <--- GGX / specular light contribution (scale it down if the specularWeight is low)

	// Multiple scattering, from Fdez-Aguera
	float Ems = (1.0 - (f_ab.x + f_ab.y));
	float3 F_avg = specularWeight * (F0 + (1.0 - F0) / 21.0);
	float3 FmsEms = Ems * FssEss * F_avg / (1.0 - F_avg * Ems);
	float3 k_D = diffuseColor * (1.0 - FssEss + FmsEms); // we use +FmsEms as indicated by the formula in the blog post (might be a typo in the implementation)

	return (FmsEms + k_D) * irradiance;
}

#if IRIDESCENCE
// specularWeight is introduced with KHR_materials_specular
float3 getIBLRadianceLambertianIridescence(float3 n, float3 v, float roughness, float3 diffuseColor, float3 F0, float3 iridescenceF0, float iridescenceFactor, float specularWeight)
{
	float NdotV = clampedDot(n, v);
	float2 brdfSamplePoint = clamp(float2(NdotV, 1.0 - roughness), 0, 1);
	float2 f_ab = BrdfLUTMap.Sample(BrdfLUTMapSampler, brdfSamplePoint).xy;

	float3 irradiance = EnvDiffuseMap.Sample(EnvDiffuseMapSampler, envmapUV(n)).xyz;

	// Use the maximum component of the iridescence Fresnel color
	// Maximum is used instead of the RGB value to not get inverse colors for the diffuse BRDF
	float3 iridescenceF0Max = to3(max(max(iridescenceF0.r, iridescenceF0.g), iridescenceF0.b));

	// Blend between base F0 and iridescence F0
	float3 mixedF0 = lerp(F0, iridescenceF0Max, iridescenceFactor);

	// see https://bruop.github.io/ibl/#single_scattering_results at Single Scattering Results
	// Roughness dependent fresnel, from Fdez-Aguera

	float3 Fr = max(to3(1.0 - roughness), mixedF0) - mixedF0;
	float3 k_S = mixedF0 + Fr * pow(1.0 - NdotV, 5.0);
	float3 FssEss = specularWeight * k_S * f_ab.x + f_ab.y; // <--- GGX / specular light contribution (scale it down if the specularWeight is low)

	// Multiple scattering, from Fdez-Aguera
	float Ems = (1.0 - (f_ab.x + f_ab.y));
	float3 F_avg = specularWeight * (mixedF0 + (1.0 - mixedF0) / 21.0);
	float3 FmsEms = Ems * FssEss * F_avg / (1.0 - F_avg * Ems);
	float3 k_D = diffuseColor * (1.0 - FssEss + FmsEms); // we use +FmsEms as indicated by the formula in the blog post (might be a typo in the implementation)

	return (FmsEms + k_D) * irradiance;
}
#endif
#endif

#if CLEARCOAT
float3 getPunctualRadianceClearCoat(float3 clearcoatNormal, float3 v, float3 l, float3 h, float VdotH, float f0, float f90, float roughness)
{
	float NdotL = clampedDot(clearcoatNormal, l);
	float NdotV = clampedDot(clearcoatNormal, v);
	float NdotH = clampedDot(clearcoatNormal, h);
	return NdotL * BRDF_specularGGX(to3(f0), to3(f90), roughness * roughness, 1.0, VdotH, NdotL, NdotV, NdotH);
}
#endif

#if SHEEN
float lambdaSheenNumericHelper(float x, float alphaG)
{
	float oneMinusAlphaSq = (1.0 - alphaG) * (1.0 - alphaG);
	float a = lerp(21.5473, 25.3245, oneMinusAlphaSq);
	float b = lerp(3.82987, 3.32435, oneMinusAlphaSq);
	float c = lerp(0.19823, 0.16801, oneMinusAlphaSq);
	float d = lerp(-1.97760, -1.27393, oneMinusAlphaSq);
	float e = lerp(-4.32054, -4.85967, oneMinusAlphaSq);
	return a / (1.0 + b * pow(x, c)) + d * x + e;
}

float lambdaSheen(float cosTheta, float alphaG)
{
	if (abs(cosTheta) < 0.5)
	{
		return exp(lambdaSheenNumericHelper(cosTheta, alphaG));
	}
	else
	{
		return exp(2.0 * lambdaSheenNumericHelper(0.5, alphaG) - lambdaSheenNumericHelper(1.0 - cosTheta, alphaG));
	}
}

float V_Sheen(float NdotL, float NdotV, float sheenRoughness)
{
	sheenRoughness = max(sheenRoughness, 0.000001); //clamp (0,1]
	float alphaG = sheenRoughness * sheenRoughness;

	return clamp(1.0 / ((1.0 + lambdaSheen(NdotV, alphaG) + lambdaSheen(NdotL, alphaG)) *
		(4.0 * NdotV * NdotL)), 0.0, 1.0);
}

// Estevez and Kulla http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf
float D_Charlie(float sheenRoughness, float NdotH)
{
	sheenRoughness = max(sheenRoughness, 0.000001); //clamp (0,1]
	float alphaG = sheenRoughness * sheenRoughness;
	float invR = 1.0 / alphaG;
	float cos2h = NdotH * NdotH;
	float sin2h = 1.0 - cos2h;
	return (2.0 + invR) * pow(sin2h, invR * 0.5) / (2.0 * PI);
}

float3 BRDF_specularSheen(float3 sheenColor, float sheenRoughness, float NdotL, float NdotV, float NdotH)
{
	float sheenDistribution = D_Charlie(sheenRoughness, NdotH);
	float sheenVisibility = V_Sheen(NdotL, NdotV, sheenRoughness);
	return sheenColor * sheenDistribution * sheenVisibility;
}

float3 getPunctualRadianceSheen(float3 sheenColor, float sheenRoughness, float NdotL, float NdotV, float NdotH)
{
	return NdotL * BRDF_specularSheen(sheenColor, sheenRoughness, NdotL, NdotV, NdotH);
}

#if ENVMAP
float3 getIBLRadianceCharlie(float3 n, float3 v, float sheenRoughness, float3 sheenColor)
{
	float NdotV = clampedDot(n, v);
	float lod = sheenRoughness * float(EnvSheenMapMipLevel - 1);
	float3 reflection = normalize(reflect(-v, n));

	float2 brdfSamplePoint = clamp(float2(NdotV, sheenRoughness), 0.0, 1.0);
	float brdf = CharlieLUTMap.Sample(CharlieLUTMapSampler, brdfSamplePoint).r;
	float3 sheenLight = EnvSheenMap.SampleLevel(EnvSheenMapSampler, envmapUV(reflection), lod).xyz;

	return sheenLight * sheenColor * brdf;
}
#endif

float albedoSheenScalingLUT(float NdotV, float sheenRoughnessFactor)
{
	return SheenELUTMap.Sample(SheenELUTMapSampler, float2(NdotV, sheenRoughnessFactor)).r;
}
#endif

#if IRIDESCENCE
// XYZ to sRGB color space
static const float3x3 XYZ_TO_REC709 = float3x3(
	 3.2404542, -0.9692660,  0.0556434,
	-1.5371385,  1.8760108, -0.2040259,
	-0.4985314,  0.0415560,  1.0572252
);

float sq(float t)
{
	return t * t;
}

float3 sq(float3 t)
{
	return t * t;
}

float3 Schlick_to_F0(float3 f, float3 f90, float VdotH) {
    float x = clamp(1.0 - VdotH, 0.0, 1.0);
    float x2 = x * x;
    float x5 = clamp(x * x2 * x2, 0.0, 0.9999);

    return (f - f90 * x5) / (1.0 - x5);
}

// Assume air interface for top
// Note: We don't handle the case fresnel0 == 1
float3 Fresnel0ToIor(float3 fresnel0) {
	float3 sqrtF0 = sqrt(fresnel0);
	return (float3(1.0,1.0,1.0) + sqrtF0) / (float3(1.0,1.0,1.0) - sqrtF0);
}

// Conversion FO/IOR
float3 IorToFresnel0(float3 transmittedIor, float incidentIor) {
	return sq((transmittedIor - to3(incidentIor)) / (transmittedIor + to3(incidentIor)));
}

// ior is a value between 1.0 and 3.0. 1.0 is air interface
float IorToFresnel0(float transmittedIor, float incidentIor) {
	return sq((transmittedIor - incidentIor) / (transmittedIor + incidentIor));
}

// Fresnel equations for dielectric/dielectric interfaces.
// Ref: https://belcour.github.io/blog/research/2017/05/01/brdf-thin-film.html
// Evaluation XYZ sensitivity curves in Fourier space
float3 evalSensitivity(float OPD, float3 shift) {
	float phase = 2.0 * PI * OPD * 1.0e-9;
	float3 val = float3(5.4856e-13, 4.4201e-13, 5.2481e-13);
	float3 pos = float3(1.6810e+06, 1.7953e+06, 2.2084e+06);
	float3 var = float3(4.3278e+09, 9.3046e+09, 6.6121e+09);

	float3 xyz = val * sqrt(2.0 * PI * var) * cos(pos * phase + shift) * exp(-sq(phase) * var);
	xyz.x += 9.7470e-14 * sqrt(2.0 * PI * 4.5282e+09) * cos(2.2399e+06 * phase + shift[0]) * exp(-4.5282e+09 * sq(phase));
	xyz /= 1.0685e-7;

	float3 srgb = mul(xyz, XYZ_TO_REC709);
	return srgb;
}

float3 evalIridescence(float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, float3 baseF0) {
	float3 I;

	// Force iridescenceIor -> outsideIOR when thinFilmThickness -> 0.0
	float iridescenceIor = lerp(outsideIOR, eta2, smoothstep(0.0, 0.03, thinFilmThickness));
	// Evaluate the cosTheta on the base layer (Snell law)
	float sinTheta2Sq = sq(outsideIOR / iridescenceIor) * (1.0 - sq(cosTheta1));

	// Handle TIR:
	float cosTheta2Sq = 1.0 - sinTheta2Sq;
	if (cosTheta2Sq < 0.0) {
		return to3(1.0);
	}

	float cosTheta2 = sqrt(cosTheta2Sq);

	// First interface
	float R0 = IorToFresnel0(iridescenceIor, outsideIOR);
	float R12 = F_Schlick(R0, 1.0, cosTheta1);
	float R21 = R12;
	float T121 = 1.0 - R12;
	float phi12 = 0.0;
	if (iridescenceIor < outsideIOR) phi12 = PI;
	float phi21 = PI - phi12;

	// Second interface
	float3 baseIOR = Fresnel0ToIor(clamp(baseF0, 0.0, 0.9999)); // guard against 1.0
	float3 R1 = IorToFresnel0(baseIOR, iridescenceIor);
	float3 R23 = F_Schlick(R1, to3(1.0), cosTheta2);
	float3 phi23 = to3(0.0);
	if (baseIOR[0] < iridescenceIor) phi23[0] = PI;
	if (baseIOR[1] < iridescenceIor) phi23[1] = PI;
	if (baseIOR[2] < iridescenceIor) phi23[2] = PI;

	// Phase shift
	float OPD = 2.0 * iridescenceIor * thinFilmThickness * cosTheta2;
	float3 phi = to3(phi21) + phi23;

	// Compound terms
	float3 R123 = clamp(R12 * R23, 1e-5, 0.9999);
	float3 r123 = sqrt(R123);
	float3 Rs = sq(T121) * R23 / (to3(1.0) - R123);

	// Reflectance term for m = 0 (DC term amplitude)
	float3 C0 = R12 + Rs;
	I = C0;

	// Reflectance term for m > 0 (pairs of diracs)
	float3 Cm = Rs - T121;
	for (int m = 1; m <= 2; ++m)
	{
		Cm *= r123;
		float3 Sm = 2.0 * evalSensitivity(float(m) * OPD, float(m) * phi);
		I += Cm * Sm;
	}

	// Since out of gamut colors might be produced, negative color values are clamped to 0.
	return max(I, to3(0.0));
}
#endif

#if TRANSMISSION
// Compute attenuated light as it travels through a volume.
float3 applyVolumeAttenuation(float3 radiance, float transmissionDistance, float3 attenuationColor, float attenuationDistance)
{
	if (attenuationDistance == 0.0)
	{
		// Attenuation distance is +inf (which we indicate by zero), i.e. the transmitted color is not attenuated at all.
		return radiance;
	}
	else
	{
		// Compute light attenuation using Beer's law.
		float3 attenuationCoefficient = -log(attenuationColor) / max(0.0001,attenuationDistance);
		float3 transmittance = exp(-attenuationCoefficient * transmissionDistance); // Beer's law
		return transmittance * radiance;
	}
}

float3 getVolumeTransmissionRay(float3 n, float3 v, float thickness, float ior, matrix modelMatrix)
{
	// Direction of refracted light.
	float3 refractionVector = refract(-v, normalize(n), 1.0 / ior);

	// Compute rotation-independant scaling of the model matrix.
	float3 modelScale;
	modelScale.x = length(float3(modelMatrix[0].xyz));
	modelScale.y = length(float3(modelMatrix[1].xyz));
	modelScale.z = length(float3(modelMatrix[2].xyz));

	// The thickness is specified in local space.
	return normalize(refractionVector) * thickness * modelScale;
}

float applyIorToRoughness(float roughness, float ior)
{
	// Scale roughness with IOR so that an IOR of 1.0 results in no microfacet refraction and
	// an IOR of 1.5 results in the default amount of microfacet refraction.
	return roughness * clamp(ior * 2.0 - 2.0, 0.0, 1.0);
}

float3 getTransmissionSample(float2 fragCoord, float roughness, float ior)
{
	float framebufferLod = float(TransmissionMapMipLevel-1) * applyIorToRoughness(roughness, ior);
	float4 transmittedLight = TransmissionBufferMap.SampleLevel(TransmissionBufferMapSampler, fragCoord, framebufferLod);
	return SRGBtoLINEAR(transmittedLight).xyz;
}

float3 getIBLVolumeRefraction(float3 n, float3 v, float perceptualRoughness, float3 baseColor, float3 f0, float3 f90,
	float3 position, matrix modelMatrix, float ior, float thickness, float3 attenuationColor, float attenuationDistance)
{
#if DISPERSION
	// Dispersion will spread out the ior values for each r,g,b channel
	float halfSpread = (ior - 1.0) * 0.025 * DispersionFactor;
	float3 iors = float3(ior - halfSpread, ior, ior + halfSpread);

	float3 transmittedLight;
	float transmissionRayLength;
	for (int i = 0; i < 3; i++)
	{
		float3 transmissionRay = getVolumeTransmissionRay(n, v, thickness, iors[i], modelMatrix);
		// TODO: taking length of blue ray, ideally we would take the length of the green ray. For now overwriting seems ok
		transmissionRayLength = length(transmissionRay);
		float3 refractedRayExit = position + transmissionRay;

		// Project refracted vector on the framebuffer, while mapping to normalized device coordinates.
		float4 ndcPos = mul(float4(refractedRayExit, 1.0), WorldViewProj);
		float2 refractionCoords = ndcPos.xy / ndcPos.w;
		refractionCoords.y = -refractionCoords.y;
		refractionCoords += 1.0;
		refractionCoords /= 2.0;

		// Sample framebuffer to get pixel the refracted ray hits for this color channel.
		float3 sample = getTransmissionSample(refractionCoords, perceptualRoughness, iors[i]);
		switch(i){
		case 0: transmittedLight.x = sample.x; break;
		case 1: transmittedLight.y = sample.y; break;
		case 2: transmittedLight.z = sample.z; break;
		}
	}
#else
	float3 transmissionRay = getVolumeTransmissionRay(n, v, thickness, ior, modelMatrix);
	float transmissionRayLength = length(transmissionRay);
	float3 refractedRayExit = position + transmissionRay;

	// Project refracted vector on the framebuffer, while mapping to normalized device coordinates.
	float4 ndcPos = mul(float4(refractedRayExit, 1.0), WorldViewProj);
	float2 refractionCoords = ndcPos.xy / ndcPos.w;
	refractionCoords.y = -refractionCoords.y;
	refractionCoords += 1.0;
	refractionCoords /= 2.0;

	// Sample framebuffer to get pixel the refracted ray hits.
	float3 transmittedLight = getTransmissionSample(refractionCoords, perceptualRoughness, ior);
#endif

	float3 attenuatedColor = applyVolumeAttenuation(transmittedLight, transmissionRayLength, attenuationColor, attenuationDistance);

	// Sample GGX LUT to get the specular component.
	float NdotV = clampedDot(n, v);
	float2 brdfSamplePoint = saturate(float2(NdotV, 1.0 - perceptualRoughness));
	float2 brdf = BrdfLUTMap.Sample(BrdfLUTMapSampler, brdfSamplePoint).xy;
	float3 specularColor = f0 * brdf.x + f90 * brdf.y;

	return (1.0 - specularColor) * attenuatedColor * baseColor;
}

float3 getPunctualRadianceTransmission(float3 normal, float3 view, float3 pointToLight, float alphaRoughness,
    float3 f0, float3 f90, float3 baseColor, float ior)
{
	float transmissionRougness = applyIorToRoughness(alphaRoughness, ior);

	float3 n = normalize(normal);           // Outward direction of surface point
	float3 v = normalize(view);             // Direction from surface point to view
	float3 l = normalize(pointToLight);
	float3 l_mirror = normalize(l + 2.0*n*dot(-l, n));     // Mirror light reflection vector on surface
	float3 h = normalize(l_mirror + v);            // Halfway vector between transmission light vector and v

	float D = D_GGX(clamp(dot(n, h), 0.0, 1.0), transmissionRougness);
	float3 F = F_Schlick(f0, f90, clamp(dot(v, h), 0.0, 1.0));
	float Vis = V_GGX(clamp(dot(n, l_mirror), 0.0, 1.0), clamp(dot(n, v), 0.0, 1.0), transmissionRougness);

	// Transmission BTDF
	return (1.0 - F) * baseColor * D * Vis;
}
#endif

// The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D())
// Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz
// Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3.
float microfacetDistribution(float NdotH, float alphaRoughness)
{
    float roughnessSq = alphaRoughness * alphaRoughness;
    float f = (NdotH * roughnessSq - NdotH) * NdotH + 1.0;
    return roughnessSq / (PI * f * f);
}

BRDFInput BRDFSetup(PS_INPUT In, float3 baseColor)
{
	BRDFInput brdfInput;
#if SPECULARGLOSSINESS
	brdfInput.metallic = 0;
	brdfInput.f0 = Specular.xyz;
	brdfInput.perceptualRoughness = GlossinessFactor;

	/*vec4 sgSample = texture(u_SpecularGlossinessSampler, getSpecularGlossinessUV());
	brdfInput.perceptualRoughness *= sgSample.a ; // glossiness to roughness
	brdfInput.f0 *= sgSample.rgb; // specular*/

	brdfInput.perceptualRoughness = 1.0 - brdfInput.perceptualRoughness; // 1 - glossiness
	brdfInput.alphaRoughness = brdfInput.perceptualRoughness * brdfInput.perceptualRoughness;
	brdfInput.diffuseColor = baseColor * (1.0 - max(max(brdfInput.f0.r, brdfInput.f0.g), brdfInput.f0.b));
#else
    // Metallic and Roughness material properties are packed together
    // In glTF, these factors can be specified by fixed scalar values
    // or from a metallic-roughness map
	brdfInput.metallic = Metallic;
    brdfInput.perceptualRoughness = Roughness;
	if(HasMetalMap){
		// Roughness is stored in the 'g' channel, metallic is stored in the 'b' channel.
		// This layout intentionally reserves the 'r' channel for (optional) occlusion map data
		float4 mrSample = MetallicRoughnessMap.Sample(ColorMapSampler, GetTexCoord(In,HasMetalMap));
		brdfInput.perceptualRoughness *= mrSample.g;
		brdfInput.metallic *= mrSample.b;
	}
    brdfInput.perceptualRoughness = clamp(brdfInput.perceptualRoughness, 0.0, 1.0);
    brdfInput.metallic = clamp(brdfInput.metallic, 0.0, 1.0);
    // Roughness is authored as perceptual roughness; as is convention,
    // convert to material roughness by squaring the perceptual roughness [2].
    brdfInput.alphaRoughness = clamp(brdfInput.perceptualRoughness * brdfInput.perceptualRoughness, c_MinRoughness * c_MinRoughness, 1.0);

	brdfInput.diffuseColor = baseColor * (1.0 - brdfInput.metallic);
#if IOR
	brdfInput.f0 = to3(pow((Ior - 1.0) /  (Ior + 1.0), 2.0));
#else
	brdfInput.f0 = float3(0.04,0.04,0.04);
#endif

	// Achromatic f0 based on IOR.
	brdfInput.f0 = lerp(brdfInput.f0, baseColor, brdfInput.metallic);
#endif

	return brdfInput;
}

float GetShadow(PS_INPUT In)
{
#if SHADOW
	float2 smt = float2((In.SMPos.x+1)*0.5, 1.0-(In.SMPos.y+1)*0.5);
	float smz = ShadowMap.Sample(ShadowMapSampler, smt.xy).r;
	float sm_coef = (In.SMPos.z < smz+0.005) ? 1.0 : 0.5;
	return sm_coef;
#else
	return 1.0;
#endif
}

float4 GetBaseColor(PS_INPUT In)
{
#if TEXTURE
	// Replace the base color
	float4 col = In.Col * SRGBtoLINEAR(ColorMap.Sample(ColorMapSampler, GetTexCoord(In,HasTextureMap)), (HasTextureMap&3)==1);
#else
	float4 col = In.Col;
#endif
#if ALPHAMAP
	//float4 alpha_col = AlphaMap.Sample(AlphaMapSampler, In.TexCoord);
	//col.a *= alpha_col.a;
#endif
	//col.a *= Alpha;

	// Cancel to write if the color is almost transparent.
	if(AlphaMode < 2)
		col.a = 1;
	else if(AlphaMode == 2){
		clip(col.a - AlphaCutOff);
		col.a = 1;
	}else
		clip(col.a - 1.0/255.0);
	return col;
}

// Pixel shader
#if OIT
[earlydepthstencil]
#endif
PS_OUTPUT PS(PS_INPUT In)
{
	PS_OUTPUT output;
	
	if(IsArbClipped(In.WorldPos.xyz))
		discard;

	float3 V = normalize(CameraPos - In.WorldPos.xyz);
	float4 base_color = GetBaseColor(In);
	float shadow = GetShadow(In);
	
#if NORMALMAP
	float3 normal_col = NormalMap.Sample(NormalMapSampler, GetTexCoord(In,HasBumpMap)).xyz * 2.0 - 1.0;
	normal_col *= float3(NormalScale, NormalScale, 1.0);
	normal_col *= NormalMapFlip;

	float3x3 mtxn = {In.Tangent, In.Binormal, In.Normal};
	float3 N = normalize(mul(normal_col, mtxn));
#else
	float3 N = normalize(In.Normal);
#endif
#if ANISOTROPY
	float3 NB, NT;
#if NORMALMAP
	NB = In.Binormal;
	NT = In.Tangent;
#else
	{
		float2 uv_dx = ddx(In.TexCoord);
		float2 uv_dy = ddy(In.TexCoord);
		if (length(uv_dx) <= 1e-2) {
		  uv_dx = float2(1.0, 0.0);
		}
		if (length(uv_dy) <= 1e-2) {
		  uv_dy = float2(0.0, 1.0);
		}
		float3 fragCoord = In.Pos.xyz / float3((float)Resolution.x,(float)Resolution.y,1.0) - float3(0.5,0.5,0);
		float3 t_ = (uv_dy.y * ddx(fragCoord) - uv_dx.y * ddy(fragCoord)) /
                    (uv_dx.x * uv_dy.y - uv_dy.x * uv_dx.y);
		NT = normalize(t_ - N * dot(N, t_));
		NB = cross(N, NT);
	}
#endif
#endif
	if(In.IsBack){
		N = -N;
#if ANISOTROPY
		NB = -NB;
		NT = -NT;
#endif
		base_color.xyz *= BackFaceColor.xyz;
	}
	float NdotV = clampedDot(N, V);
	
	BRDFInput brdfInput = BRDFSetup(In, base_color.xyz);

#if IOR
	float ior = Ior;
#else
	float ior = 1.5;
#endif
#if CLEARCOAT
	float3 clearcoat = float3(0,0,0);
	float clearcoatFactor = ClearcoatFactor;
	float clearcoatRoughness = ClearcoatRoughnessFactor;
	float clearcoatF0 = pow((ior - 1.0) / (ior + 1.0), 2.0);
	float clearcoatF90 = 1.0;
	float3 clearcoatNormal = normalize(In.Normal);
	if(HasClearcoatMap)
		clearcoatFactor *= ClearcoatMap.Sample(ColorMapSampler, GetTexCoord(In,HasClearcoatMap)).r;
	if(HasClearcoatRoughnessMap)
		clearcoatRoughness *= ClearcoatRoughnessMap.Sample(ColorMapSampler, GetTexCoord(In,HasClearcoatRoughnessMap)).g;
	if(HasClearcoatNormalMap){
#if USE_TANGENT
		float3 n = ClearcoatNormalMap.Sample(ColorMapSampler, GetTexCoord(In,HasClearcoatNormalMap)).xyz * 2.0 - 1.0;
		n *= float3(ClearcoatNormalScale, ClearcoatNormalScale, 1.0);
		n *= NormalMapFlip;
		float3x3 mtxn = {In.Tangent, In.Binormal, clearcoatNormal};
		clearcoatNormal = normalize(mul(n, mtxn));
#endif
	}
#endif
#if SHEEN
	float3 sheen = float3(0,0,0);
	float3 sheenColorFactor = SheenColorFactor.xyz;
	float sheenRoughnessFactor = SheenRoughnessFactor;
	float albedoSheenScaling = 1.0;
	if(HasSheenColorMap)
		sheenColorFactor *= SheenColorMap.Sample(ColorMapSampler, GetTexCoord(In,HasSheenColorMap)).rgb;
	if(HasSheenRoughnessMap)
		sheenRoughnessFactor *= SheenRoughnessMap.Sample(ColorMapSampler, GetTexCoord(In,HasSheenRoughnessMap)).a;
#endif
#if TRANSMISSION
	float3 transmission = float3(0,0,0);
	float transmissionFactor = (AlphaBlendMode != 0) ? TransmissionFactor : 0.0;
	float thickness = 0;
	float3 attenuationColor = float3(0,0,0);
	float attenuationDistance = 0;
	if(HasTransmissionMap)
		transmissionFactor *= TransmissionMap.Sample(ColorMapSampler, GetTexCoord(In,HasTransmissionMap)).r;
#if VOLUME
	thickness = ThicknessFactor;
	attenuationColor = AttenuationColor.xyz;
	attenuationDistance = AttenuationDistance;
	if(HasThicknessMap)
		thickness *= ThicknessMap.Sample(ColorMapSampler, GetTexCoord(In,HasThicknessMap)).g;
#endif
#endif
	float3 f90 = float3(1,1,1);
	float specularWeight = 1;
#if SPECULAR
	float3 specularColorFactor = SpecularColorFactor.xyz;
	if(HasSpecularColorMap)
		specularColorFactor *= SpecularColorMap.Sample(ColorMapSampler, GetTexCoord(In,HasSpecularColorMap)).rgb;
	float3 dielectricSpecularF0 = min(brdfInput.f0 * specularColorFactor, float3(1,1,1));
	brdfInput.f0 = lerp(dielectricSpecularF0, base_color.rgb, brdfInput.metallic);
	specularWeight = SpecularFactor;
	if(HasSpecularMap)
		specularWeight *= SpecularMap.Sample(ColorMapSampler, GetTexCoord(In,HasSpecularMap)).a;
#endif
#if ANISOTROPY
    float3 anisotropicT, anisotropicB;
    float anisotropyStrength;
	{
		float2 direction = float2(1.0, 0.0);
		float strengthFactor = 1.0;
		if(HasAnisotropyMap){
			float3 anisotropySample = AnisotropyMap.Sample(ColorMapSampler, GetTexCoord(In,HasAnisotropyMap)).xyz;
			direction = anisotropySample.xy * 2.0 - float2(1.0, 1.0);
			strengthFactor = anisotropySample.z;
		}
		float2 directionRotation = float2(cos(AnisotropyRotation/180.0*PI), sin(AnisotropyRotation/180.0*PI));
		float2x2 rotationMatrix = float2x2(directionRotation.x, directionRotation.y, -directionRotation.y, directionRotation.x);
		direction = mul(direction.xy, rotationMatrix);

		anisotropicT = mul(normalize(float3(direction, 0.0)), float3x3(NT, NB, N));
		anisotropicB = cross(N, anisotropicT);
		anisotropyStrength = clamp(AnisotropyStrength * strengthFactor, 0.0, 1.0);
	}
#endif
#if IRIDESCENCE
	float iridescenceThickness = IridescenceThicknessMaximum;
	float iridescenceFactor = IridescenceFactor;
	float3 iridescenceFresnel = brdfInput.f0;
	float3 iridescenceF0 = brdfInput.f0;
	if(HasIridescenceMap){
		iridescenceFactor *= IridescenceMap.Sample(ColorMapSampler, GetTexCoord(In,HasIridescenceMap)).x;
	}
	if(HasIridescenceThicknessMap){
		float thicknessSampled = IridescenceThicknessMap.Sample(ColorMapSampler, GetTexCoord(In,HasIridescenceThicknessMap)).y;
		iridescenceThickness = lerp(IridescenceThicknessMinimum, iridescenceThickness, thicknessSampled);
	}
	if(iridescenceThickness == 0.0){
		iridescenceFactor = 0.0;
	}
	if(iridescenceFactor > 0.0){
		iridescenceFresnel = evalIridescence(1.0, IridescenceIor, NdotV, iridescenceThickness, brdfInput.f0);
		iridescenceF0 = Schlick_to_F0(iridescenceFresnel, to3(1.0), NdotV);
	}
#endif

	float3 b_diffuse = float3(0,0,0);
	float3 b_specular = float3(0,0,0);
#if MULTILIGHT
	for(int i=0; i<LIGHT_MAX && i<LightNum; i++)
#endif
	{
#if MULTILIGHT
		float3 L;
		if(LightPos[i].w == 0){
			L = normalize(LightPos[i].xyz - In.WorldPos.xyz); // point light
		}else{
			L = LightPos[i].xyz; // directional light
		}
		float3 litcol = LightCol[i];
#else
		float3 L = LightDir;
		float3 litcol = float3(1,1,1);
#endif
		float3 diffuse = float3(0,0,0);
		float3 specular = float3(0,0,0);

		float3 h = normalize(L + V);          // Direction of the vector between l and v, called halfway vector
		float NdotL = clampedDot(N, L);
		float NdotH = clampedDot(N, h);
		float VdotH = clampedDot(V, h);
		if (NdotL > 0.0 || NdotV > 0.0)
		{
#if IRIDESCENCE
			diffuse = litcol * NdotL * BRDF_lambertianIridescence(brdfInput.f0, f90, iridescenceFresnel, IridescenceFactor, brdfInput.diffuseColor, specularWeight, VdotH) * shadow;
			specular = litcol * NdotL * BRDF_specularGGXIridescence(brdfInput.f0, f90, iridescenceFresnel, brdfInput.alphaRoughness, IridescenceFactor, specularWeight, VdotH, NdotL, NdotV, NdotH);
#elif ANISOTROPY
            diffuse = litcol * NdotL * BRDF_lambertian(brdfInput.f0, f90, brdfInput.diffuseColor, specularWeight, VdotH) * shadow;
            specular = litcol * NdotL * BRDF_specularGGXAnisotropy(brdfInput.f0, f90, brdfInput.alphaRoughness, anisotropyStrength, N, V, L, h, anisotropicT, anisotropicB) * specularWeight;
#else
			diffuse = litcol * NdotL * BRDF_lambertian(brdfInput.f0, f90, brdfInput.diffuseColor, specularWeight, VdotH) * shadow;
			specular = litcol * NdotL * BRDF_specularGGX(brdfInput.f0, f90, brdfInput.alphaRoughness, specularWeight, VdotH, NdotL, NdotV, NdotH);
#endif
#if SHEEN
			sheen += litcol * getPunctualRadianceSheen(sheenColorFactor, sheenRoughnessFactor, NdotL, NdotV, NdotH);
			float albedoScaling = min(
				1.0 - max3(sheenColorFactor) * albedoSheenScalingLUT(NdotV, sheenRoughnessFactor),
				1.0 - max3(sheenColorFactor) * albedoSheenScalingLUT(NdotL, sheenRoughnessFactor));
			diffuse *= albedoScaling;
			specular *= albedoScaling;
#endif
#if CLEARCOAT
			clearcoat += litcol * getPunctualRadianceClearCoat(clearcoatNormal, V, L, h, VdotH, 
				clearcoatF0, clearcoatF90, clearcoatRoughness);
#endif
		}

#if TRANSMISSION
		// If the light ray travels through the geometry, use the point it exits the geometry again.
		// That will change the angle to the light source, if the material refracts the light ray.
		float3 transmissionRay = getVolumeTransmissionRay(N, V, thickness, ior, ModelMatrix);
		float3 tL = normalize(L - transmissionRay);
		float3 transmittedLight = litcol * getPunctualRadianceTransmission(N, V, tL, brdfInput.alphaRoughness, brdfInput.f0, f90, brdfInput.diffuseColor, ior);
#if VOLUME
		transmittedLight = applyVolumeAttenuation(transmittedLight, length(transmissionRay), attenuationColor, attenuationDistance);
#endif
		transmission += transmissionFactor * transmittedLight;
#endif

		b_diffuse += diffuse;
		b_specular += specular;
	}
	b_diffuse *= PI;
	b_specular *= PI;

#if ENVMAP
#if IRIDESCENCE
	b_specular += getIBLRadianceGGXIridescence(N, V, brdfInput.perceptualRoughness, brdfInput.f0, iridescenceFresnel, iridescenceFactor, specularWeight) * EnvMapFactor;
	b_diffuse += getIBLRadianceLambertianIridescence(N, V, brdfInput.perceptualRoughness, brdfInput.diffuseColor, brdfInput.f0, iridescenceF0, iridescenceFactor, specularWeight) * EnvMapFactor;
#elif ANISOTROPY
    b_specular += getIBLRadianceAnisotropy(N, V, brdfInput.perceptualRoughness, anisotropyStrength, anisotropicB, brdfInput.f0, specularWeight) * EnvMapFactor;
    b_diffuse += getIBLRadianceLambertian(N, V, brdfInput.perceptualRoughness, brdfInput.diffuseColor, brdfInput.f0, specularWeight) * EnvMapFactor;
#else
	b_specular += getIBLRadianceGGX(N, V, brdfInput.perceptualRoughness, brdfInput.f0, specularWeight) * EnvMapFactor;
	b_diffuse += getIBLRadianceLambertian(N, V, brdfInput.perceptualRoughness, brdfInput.diffuseColor, brdfInput.f0, specularWeight) * EnvMapFactor;
#endif
#if CLEARCOAT
	clearcoat += getIBLRadianceGGX(clearcoatNormal, V, clearcoatRoughness, to3(clearcoatF0), 1.0) * EnvMapFactor;
#endif
#if SHEEN
	sheen += getIBLRadianceCharlie(N, V, sheenRoughnessFactor, sheenColorFactor) * EnvMapFactor;
#endif
#else // ENVMAP
	b_diffuse += base_color.xyz * GlobalAmbient.xyz;
#endif

#if TRANSMISSION
	transmission += transmissionFactor * getIBLVolumeRefraction(
		N, V,
		brdfInput.perceptualRoughness,
		brdfInput.diffuseColor, brdfInput.f0, f90,
		In.WorldPos.xyz, ModelMatrix,
		ior, thickness, attenuationColor, attenuationDistance);
	b_diffuse = lerp(b_diffuse, transmission, transmissionFactor);
#endif

#if UNLIT
	float3 color = base_color.xyz;
#else
	float3 color = b_diffuse + b_specular;
	if(HasOcclusionMap){
		float ao = OcclusionMap.Sample(ColorMapSampler, GetTexCoord(In,HasOcclusionMap)).x;
		color = lerp(color, color * ao, OcclusionStrength);
#if CLEARCOAT
		clearcoat = lerp(clearcoat, clearcoat * ao, OcclusionStrength);
#endif
	}
	if(HasEmissiveMap){
		float4 emissive = SRGBtoLINEAR(EmissiveMap.Sample(ColorMapSampler, GetTexCoord(In,HasEmissiveMap)), (HasEmissiveMap&3)==1);
		color += emissive.xyz * Emissive.xyz;
	}else
		color += Emissive.xyz;
#if SHEEN
	color = sheen + color * albedoSheenScaling;
#endif
#if CLEARCOAT
	float3 clearcoatFresnel = F_Schlick(to3(clearcoatF0), to3(clearcoatF90), clampedDot(clearcoatNormal, V));
	clearcoat = clearcoat * clearcoatFactor;
	color = color * (1.0 - clearcoatFactor * clearcoatFresnel) + clearcoat;
#endif
#endif // UNLIT
	output.Color.xyz = LINEARtoSRGB(saturate(color));
	output.Color.w = base_color.w;

#if OIT
    StoreOIT(In, output.Color);
    output.Color = float4(0,0,0,0);	// This does not affect anything because RenderTargetWriteMask is 0.
#endif

#if RENDER_NORMAL
	output.Normal = float4(mul(In.Normal.xyz, (float3x3)WorldView), 1);
#endif

	return output;
}

