/*
  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;
	int Type;
	float4 BaseColor;
	//float Alpha;
	float4 Emissive;
	//float Metallic;
	float Roughness;
	float Ior;
	float4 Scattering;
	float Scanisotropy;
	float TRDepth;
	float4 AttenuationColor;
	int HasRoughnessMap;
	int HasEmissiveMap;
}

cbuffer TransmissionBuffer : register( b6 )
{
	int TransmissionMapMipLevel;
}

static const float Alpha = 1;
static const float Metallic = 0;

#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 RoughnessMap : register( t4 );
Texture2D EmissiveMap : register( t5 );
#if ENVMAP
Texture2D EnvDiffuseMap : register( t6 );
SamplerState EnvDiffuseMapSampler : register( s4 );
Texture2D EnvSpecularMap : register( t7 );
SamplerState EnvSpecularMapSampler : register( s5 );
#endif
Texture2D BrdfLUTMap : register( t8 );
SamplerState BrdfLUTMapSampler : register( s6 );
Texture2D TransmissionBufferMap : register( t9 );
SamplerState TransmissionBufferMapSampler : register( s7 );

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 VERTEXCOLOR
	Out.Col.xyz = In.Col.xyz;	//RGB
	Out.Col.w = BaseColor.w * In.Col.w;
#else
	Out.Col = BaseColor;
#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);
}

// 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;
}

//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 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;
}

// 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;
}
#endif

// 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)
{
	float3 transmissionRay = getVolumeTransmissionRay(n, v, thickness, ior, modelMatrix);
	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);

	float3 attenuatedColor = applyVolumeAttenuation(transmittedLight, length(transmissionRay), 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;
}

// 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;
    // 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(HasRoughnessMap){
		// 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 = RoughnessMap.Sample(ColorMapSampler, GetTexCoord(In,HasRoughnessMap));
		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);
	brdfInput.f0 = to3(pow((Ior - 1.0) /  (Ior + 1.0), 2.0));

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

	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.
	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(In.IsBack){
		N = -N;
		base_color.xyz *= BackFaceColor.xyz;
	}
	
	BRDFInput brdfInput = BRDFSetup(In, base_color.xyz);

	float ior = Ior;

	float3 transmission = float3(0,0,0);
	float transmissionFactor = (AlphaBlendMode != 0) ? 1.0 : 0.0;
	float thickness = 0;
	float3 attenuationColor = float3(0,0,0);
	float attenuationDistance = 0;
	if(Type == 1){
		//thickness = ThicknessFactor;
		attenuationColor = AttenuationColor.xyz;
		attenuationDistance = TRDepth;
		//if(HasThicknessMap)
		//	thickness *= ThicknessMap.Sample(ColorMapSampler, GetTexCoord(In,HasThicknessMap)).g;
	}
	float3 f90 = float3(1,1,1);
	float specularWeight = 1;

	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 NdotV = clampedDot(N, V);
		float NdotH = clampedDot(N, h);
		float VdotH = clampedDot(V, h);
		if (NdotL > 0.0 || NdotV > 0.0)
		{
			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);
		}

		// 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(Type == 1)
			transmittedLight = applyVolumeAttenuation(transmittedLight, length(transmissionRay), attenuationColor, attenuationDistance);
		transmission += transmissionFactor * transmittedLight;

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

#if ENVMAP
	b_specular += getIBLRadianceGGX(N, V, brdfInput.perceptualRoughness, brdfInput.f0, specularWeight) * EnvMapFactor;
	b_diffuse += getIBLRadianceLambertian(N, V, brdfInput.perceptualRoughness, brdfInput.diffuseColor, brdfInput.f0, specularWeight) * EnvMapFactor;
#else // ENVMAP
	b_diffuse += base_color.xyz * GlobalAmbient.xyz;
#endif

	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);

	float3 color = b_diffuse + b_specular;
	if(HasEmissiveMap){
		float4 emissive = SRGBtoLINEAR(EmissiveMap.Sample(ColorMapSampler, GetTexCoord(In,HasEmissiveMap)), (HasEmissiveMap&3)==1);
		color += emissive.xyz * Emissive.xyz;
	}else{
		color += Emissive.xyz;
	}
	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;
}

