403 lines
17 KiB
HLSL
403 lines
17 KiB
HLSL
#ifndef TOD_BASE_INCLUDED
|
|
#define TOD_BASE_INCLUDED
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
|
|
|
|
|
|
TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);
|
|
TEXTURE2D(_CloudMainTex); SAMPLER(sampler_CloudMainTex);
|
|
TEXTURE2D(TOD_BayerTexture); SAMPLER(samplerTOD_BayerTexture);
|
|
TEXTURE2D(TOD_CloudTexture); SAMPLER(samplerTOD_CloudTexture);
|
|
TEXTURE2D(TOD_CloudAlphaTexture); SAMPLER(samplerTOD_CloudAlphaTexture);
|
|
TEXTURECUBE(_CubeTex); SAMPLER(sampler_CubeTex);
|
|
|
|
CBUFFER_START(UnityPerMaterial_TOD)
|
|
float _CubeBrightness;
|
|
half4 _MainTex_ST; half4 _TintColor;
|
|
half4 _CloudMainTex_ST; half4 _CloudTintColor; half _CloudBrightness;
|
|
half4x4 TOD_World2Sky, TOD_Sky2World, TOD_World2CloudCameraProjMatrix;
|
|
half3 TOD_SunLightColor, TOD_MoonLightColor, TOD_SunSkyColor, TOD_MoonSkyColor;
|
|
half3 TOD_SunMeshColor, TOD_MoonMeshColor, TOD_SunCloudColor, TOD_MoonCloudColor;
|
|
half3 TOD_FogColor, TOD_GroundColor, TOD_AmbientColor;
|
|
half3 TOD_SunDirection, TOD_MoonDirection, TOD_LightDirection;
|
|
half3 TOD_LocalSunDirection, TOD_LocalMoonDirection, TOD_LocalLightDirection;
|
|
half TOD_Contrast, TOD_Brightness, TOD_Fogginess;
|
|
half TOD_MoonHaloPower; half3 TOD_MoonHaloColor;
|
|
half TOD_CloudOpacity, TOD_CloudCoverage, TOD_CloudSharpness, TOD_CloudDensity, TOD_CloudColoring,
|
|
TOD_CloudAttenuation, TOD_CloudSaturation, TOD_CloudScattering, TOD_CloudBrightness;
|
|
half3 TOD_CloudOffset, TOD_CloudWind, TOD_CloudSize;
|
|
half TOD_CloudShadowCutoff, TOD_CloudShadowFade, TOD_CloudShadowIntensity;
|
|
half TOD_StarSize, TOD_StarBrightness, TOD_StarVisibility;
|
|
half TOD_SunMeshContrast, TOD_SunMeshBrightness;
|
|
half TOD_MoonMeshContrast, TOD_MoonMeshBrightness;
|
|
|
|
half3 TOD_ScatterDensity, TOD_kBetaMie;
|
|
half4 TOD_kSun, TOD_k4PI, TOD_kRadius, TOD_kScale;
|
|
half3 TOD_SunWorldPos, TOD_HeroWorldPos;
|
|
half TOD_IsDay;
|
|
half4 _SG_VirtualScatterLightColor; half _SG_AtmosphereFogDensityFactor;
|
|
CBUFFER_END
|
|
|
|
// Vertex transform used by the entire sky dome
|
|
#if UNITY_VERSION >= 540
|
|
#define TOD_TRANSFORM_VERT(vert) UnityObjectToClipPos(vert)
|
|
#else
|
|
#define TOD_TRANSFORM_VERT(vert) UnityObjectToClipPos(vert)
|
|
#endif
|
|
|
|
#define TOD_ROTATION_UV(angle) half2x2(cos(angle), -sin(angle), sin(angle), cos(angle)) // UV rotation matrix constructor
|
|
#define TOD_HDR2LDR(color) (1.0 - exp2(-TOD_Brightness * color)) // Fast and simple tonemapping
|
|
#define TOD_GAMMA2LINEAR(color) (color * color) // Approximates gamma by 2.0 instead of 2.2
|
|
#define TOD_LINEAR2GAMMA(color) sqrt(color)
|
|
#if UNITY_VERSION >= 540 // Matrices
|
|
#define TOD_Object2World unity_ObjectToWorld
|
|
#define TOD_World2Object unity_WorldToObject
|
|
#else
|
|
#define TOD_Object2World unity_ObjectToWorld
|
|
#define TOD_World2Object unity_WorldToObject
|
|
#endif
|
|
#if UNITY_VERSION >= 540 // Screen space adjust
|
|
#define TOD_UV(x, y) UnityStereoScreenSpaceUVAdjust(x, y)
|
|
#else
|
|
#define TOD_UV(x, y) x
|
|
#endif
|
|
#if UNITY_VERSION >= 540 // Stereo output
|
|
#define TOD_VERTEX_OUTPUT_STEREO UNITY_VERTEX_OUTPUT_STEREO
|
|
#define TOD_INITIALIZE_VERTEX_OUTPUT_STEREO(o) UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o)
|
|
#else
|
|
#define TOD_VERTEX_OUTPUT_STEREO
|
|
#define TOD_INITIALIZE_VERTEX_OUTPUT_STEREO(o)
|
|
#endif
|
|
#if UNITY_VERSION >= 540 // Instancing
|
|
#define TOD_INSTANCE_ID UNITY_VERTEX_INPUT_INSTANCE_ID
|
|
#define TOD_SETUP_INSTANCE_ID(o) UNITY_SETUP_INSTANCE_ID(o)
|
|
#else
|
|
#define TOD_INSTANCE_ID
|
|
#define TOD_SETUP_INSTANCE_ID(o)
|
|
#endif
|
|
|
|
#ifndef TOD_SCATTERING_MIE
|
|
#define TOD_SCATTERING_MIE 1
|
|
#endif
|
|
#ifndef TOD_SCATTERING_RAYLEIGH
|
|
#define TOD_SCATTERING_RAYLEIGH 1
|
|
#endif
|
|
#ifndef TOD_BAYER_DIM
|
|
#define TOD_BAYER_DIM 8.0
|
|
#endif
|
|
|
|
half4 Adjust(half4 color)
|
|
{
|
|
#if !TOD_OUTPUT_HDR
|
|
color = TOD_HDR2LDR(color);
|
|
#endif
|
|
#if !TOD_OUTPUT_LINEAR
|
|
color = TOD_LINEAR2GAMMA(color);
|
|
#endif
|
|
return color;
|
|
}
|
|
|
|
//////////////////////////////////////////CLOUD CALCULATION//////////////////////////////////////////////////
|
|
inline half3 CloudPosition(half3 viewDir, half3 offset)
|
|
{
|
|
half mult = 1.0 / lerp(0.1, 1.0, viewDir.y);
|
|
return (half3(viewDir.x * mult + offset.x, 0, viewDir.z * mult + offset.z)) / TOD_CloudSize;
|
|
}
|
|
inline half4 CloudUV(half3 viewDir, half3 offset)
|
|
{
|
|
half3 cloudPos = CloudPosition(viewDir, offset);
|
|
half2 uv1 = cloudPos.xz + TOD_CloudOffset.xz + TOD_CloudWind.xz;
|
|
half2 uv2 = mul(TOD_ROTATION_UV(radians(10.0)), cloudPos.xz) + TOD_CloudOffset.xz + TOD_CloudWind.xz;
|
|
return half4(uv1.x, uv1.y, uv2.x, uv2.y);
|
|
}
|
|
inline half4 CloudUV(half3 viewDir)
|
|
{
|
|
return CloudUV(viewDir, 0);
|
|
}
|
|
inline half3 CloudColor(half3 viewDir, half3 lightDir)
|
|
{
|
|
half lerpValue = saturate(1 + 4 * lightDir.y) * saturate(dot(viewDir, lightDir) + 1.25);
|
|
half3 cloudColor = lerp(TOD_MoonCloudColor, TOD_SunCloudColor, lerpValue);
|
|
half3 fogColor = TOD_FogColor;
|
|
return TOD_Brightness * lerp(cloudColor, fogColor, TOD_Fogginess);
|
|
}
|
|
inline half3 CloudLayerDensity(half4 uv, half3 viewDir)
|
|
{
|
|
half3 density = 0;
|
|
#if TOD_CLOUDS_DENSITY
|
|
const half thickness = 0.1;
|
|
const half4 stepoffset = half4(0.0, 1.0, 2.0, 3.0) * thickness;
|
|
const half4 sumy = half4(0.5000, 0.2500, 0.1250, 0.0625) / half4(1, 2, 3, 4);
|
|
const half4 sumz = half4(0.5000, 0.2500, 0.1250, 0.0625);
|
|
half2 uv1 = uv.xy + viewDir.xz * stepoffset.x;
|
|
half2 uv2 = uv.zw + viewDir.xz * stepoffset.y;
|
|
half2 uv3 = uv.xy + viewDir.xz * stepoffset.z;
|
|
half2 uv4 = uv.zw + viewDir.xz * stepoffset.w;
|
|
|
|
half4 n1 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv1);
|
|
half4 n2 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv2);
|
|
half4 n3 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv3);
|
|
half4 n4 = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv4);
|
|
|
|
half4 ny = half4(n1.r, n1.g + n2.g, n1.b + n2.b + n3.b, n1.a + n2.a + n3.a + n4.a); // Noise when marching in up direction
|
|
half4 nz = half4(n1.r, n2.g, n3.b, n4.a); // Noise when marching in view direction
|
|
density.y += dot(ny, sumy); // Density when marching in up direction
|
|
density.z += dot(nz, sumz); // Density when marching in view direction
|
|
half2 stepA = TOD_CloudCoverage; // Coverage
|
|
half2 stepB = TOD_CloudCoverage + TOD_CloudSharpness;
|
|
half2 stepC = TOD_CloudDensity;
|
|
density.yz = smoothstep(stepA, stepB, density.yz) + saturate(density.yz - stepB) * stepC;
|
|
density.x = saturate(density.z); // Opacity
|
|
density.yz *= half2(TOD_CloudAttenuation, TOD_CloudSaturation); // Shading
|
|
density.yz = 1.0 - exp2(-density.yz); // Remap
|
|
#else
|
|
half4 n = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv.xy);
|
|
density.y += n.r; // Density when marching in up direction
|
|
density.z += n.r; // Density when marching in view direction
|
|
density.yz = (density.yz - TOD_CloudCoverage) * half2(TOD_CloudAttenuation, TOD_CloudDensity); // Coverage
|
|
density.x = saturate(density.z); // Opacity
|
|
#endif
|
|
return density;
|
|
}
|
|
inline half4 CloudLayerColor(half4 uv, half4 color, half3 viewDir, half3 lightDir, half3 lightCol)
|
|
{
|
|
half3 density = CloudLayerDensity(uv, viewDir);
|
|
half4 res = 0;
|
|
res.a = density.x;
|
|
res.rgb = 1.0 - density.y;
|
|
res *= color;
|
|
#if TOD_CLOUDS_BUMPED
|
|
res.rgb += saturate((1.0 - density.z) * (1.0 - TOD_Fogginess)) * lightCol;
|
|
#endif
|
|
return res;
|
|
}
|
|
inline half CloudShadow(half4 uv)
|
|
{
|
|
return CloudLayerDensity(uv, half3(0, 1, 0)).x;
|
|
}
|
|
inline half CloudShadow(half3 cameraToWorldPos)
|
|
{
|
|
half3 worldPos = _WorldSpaceCameraPos + cameraToWorldPos;
|
|
half3 skyPos = mul((half3x3)TOD_World2Sky, worldPos);
|
|
half4 cloudUV = CloudUV(TOD_LocalLightDirection, skyPos);
|
|
return TOD_CloudOpacity * CloudShadow(cloudUV);
|
|
}
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//////////////////////////////////////////SCATTERING CALCULATION////////////////////////////////////////////
|
|
inline half3 ApplayVirtualScatterLight(half3 curColor, float3 worldPos)
|
|
{
|
|
float3 worldViewDir = _WorldSpaceCameraPos.xyz - worldPos.xyz;
|
|
half3 worldLightDir = normalize(GetMainLight().direction);
|
|
half angleFactor = 1.0 - saturate(dot(normalize(worldViewDir), worldLightDir) * 0.5 + 0.8);
|
|
half3 scatterLightColor = _SG_VirtualScatterLightColor.rgb * angleFactor;
|
|
half3 finalColor = curColor + scatterLightColor;
|
|
return finalColor;
|
|
}
|
|
inline half Scale(half inCos)
|
|
{
|
|
half x = 1.0 - inCos;
|
|
return 0.25 * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));
|
|
}
|
|
inline half MiePhase(half eyeCos, half eyeCos2)
|
|
{
|
|
return TOD_kBetaMie.x * (1.0 + eyeCos2) / pow(TOD_kBetaMie.y + TOD_kBetaMie.z * eyeCos, 1.5);
|
|
}
|
|
inline half RayleighPhase(half eyeCos2)
|
|
{
|
|
return 0.75 + 0.75 * eyeCos2;
|
|
}
|
|
inline half RayleighPhase(half eyeCos2, half3 dir)
|
|
{
|
|
half groundPercent = 1-saturate(dir.y);
|
|
half base = lerp(1.5, 0.2, groundPercent);
|
|
half phase = base + 0.75 * eyeCos2;
|
|
half isBottom = dir.y < -0.5;
|
|
phase *= lerp(1, 0.2, isBottom);
|
|
return phase;
|
|
}
|
|
inline half CloudPhase(half eyeCos, half eyeCos2)
|
|
{
|
|
const half g = 0.3;
|
|
const half g2 = g * g;
|
|
return TOD_CloudScattering * (1.5 * (1.0 - g2) / (2.0 + g2) * (1.0 + eyeCos2) / (1.0 + g2 - 2.0 * g * eyeCos) + g * eyeCos);
|
|
}
|
|
inline half3 NightPhase(half3 dir)
|
|
{
|
|
dir.y = abs(dir.y);
|
|
return TOD_MoonSkyColor * (1.0 - 0.75 * dir.y);
|
|
}
|
|
inline half3 MoonPhase(half3 dir)
|
|
{
|
|
return TOD_MoonHaloColor * pow(max(0, dot(dir, TOD_LocalMoonDirection)), TOD_MoonHaloPower);
|
|
}
|
|
inline half3 PostProcess(half3 col, half3 dir)
|
|
{
|
|
col = pow(col * TOD_Brightness, TOD_Contrast);
|
|
return col;
|
|
}
|
|
|
|
#if TOD_SCATTERING_RAYLEIGH && TOD_SCATTERING_MIE
|
|
inline void ScatteringCoefficients(half3 dir, out half3 inscatter, out half3 outscatter)
|
|
#elif TOD_SCATTERING_RAYLEIGH
|
|
inline void ScatteringCoefficients(half3 dir, out half3 inscatter)
|
|
#else
|
|
inline void ScatteringCoefficients(half3 dir, out half3 outscatter)
|
|
#endif
|
|
{
|
|
dir = normalize(half3(dir.x, max(0, dir.y), dir.z));
|
|
half kInnerRadius = TOD_kRadius.x;
|
|
half kInnerRadius2 = TOD_kRadius.y;
|
|
half kOuterRadius2 = TOD_kRadius.w;
|
|
half kScale = TOD_kScale.x;
|
|
half kScaleOverScaleDepth = TOD_kScale.z;
|
|
half kCameraHeight = TOD_kScale.w;
|
|
half3 kKr4PI = TOD_k4PI.xyz;
|
|
half kKm4PI = TOD_k4PI.w;
|
|
half3 kKrESun = TOD_kSun.xyz;
|
|
half kKmESun = TOD_kSun.w;
|
|
half3 cameraPos = half3(0, kInnerRadius + kCameraHeight, 0); // Current camera position
|
|
half far = sqrt(kOuterRadius2 + kInnerRadius2 * dir.y * dir.y - kInnerRadius2) - kInnerRadius * dir.y; // Length of the atmosphere
|
|
half startDepth = exp(kScaleOverScaleDepth * (-kCameraHeight)); // Ray starting position and its scattering offset
|
|
half startAngle = dot(dir, cameraPos) / (kInnerRadius + kCameraHeight);
|
|
half startOffset = startDepth * Scale(startAngle);
|
|
half sampleLength = far / 2; // Scattering loop variables
|
|
half scaledLength = sampleLength * kScale;
|
|
half3 sampleRay = dir * sampleLength;
|
|
half3 samplePoint = cameraPos + sampleRay * 0.5;
|
|
half3 sunColor = half3(0.0, 0.0, 0.0);
|
|
half height = max(1, length(samplePoint));
|
|
half invHeight = 1.0 / height;
|
|
half depth = exp(kScaleOverScaleDepth * (kInnerRadius - height));
|
|
half atten = depth * scaledLength;
|
|
half cameraAngle = dot(dir, samplePoint) * invHeight;
|
|
half sunAngle = dot(TOD_LocalSunDirection, samplePoint) * invHeight;
|
|
half sunScatter = startOffset + depth * (Scale(sunAngle) - Scale(cameraAngle));
|
|
half3 sunAtten = exp(-sunScatter * (kKr4PI + kKm4PI));
|
|
sunColor += sunAtten * atten;
|
|
samplePoint += sampleRay;
|
|
height = max(1, length(samplePoint));
|
|
invHeight = 1.0 / height;
|
|
depth = exp(kScaleOverScaleDepth * (kInnerRadius - height));
|
|
atten = depth * scaledLength;
|
|
cameraAngle = dot(dir, samplePoint) * invHeight;
|
|
sunAngle = dot(TOD_LocalSunDirection, samplePoint) * invHeight;
|
|
sunScatter = startOffset + depth * (Scale(sunAngle) - Scale(cameraAngle));
|
|
sunAtten = exp(-sunScatter * (kKr4PI + kKm4PI));
|
|
sunColor += sunAtten * atten;
|
|
samplePoint += sampleRay;
|
|
#if TOD_SCATTERING_RAYLEIGH // Sun scattering
|
|
inscatter = TOD_SunSkyColor * sunColor * kKrESun;
|
|
#endif
|
|
#if TOD_SCATTERING_MIE
|
|
outscatter = TOD_SunSkyColor * sunColor * kKmESun;
|
|
#endif
|
|
}
|
|
|
|
#if TOD_SCATTERING_RAYLEIGH && TOD_SCATTERING_MIE
|
|
inline half4 ScatteringColor(half3 dir, half3 inscatter, half3 outscatter)
|
|
#elif TOD_SCATTERING_RAYLEIGH
|
|
inline half4 ScatteringColor(half3 dir, half3 inscatter)
|
|
#else
|
|
inline half4 ScatteringColor(half3 dir, half3 outscatter)
|
|
#endif
|
|
{
|
|
half3 resultColor = half3(0.05, 0.05, 0.1);
|
|
half sunCos = dot(TOD_LocalSunDirection, dir);
|
|
half sunCos2 = sunCos * sunCos;
|
|
#if TOD_SCATTERING_RAYLEIGH
|
|
resultColor += NightPhase(dir);
|
|
#endif
|
|
#if TOD_SCATTERING_MIE
|
|
resultColor += MoonPhase(dir);
|
|
#endif
|
|
#if TOD_SCATTERING_RAYLEIGH
|
|
resultColor += RayleighPhase(sunCos2, dir) * inscatter;
|
|
#endif
|
|
#if TOD_SCATTERING_MIE
|
|
resultColor += MiePhase(sunCos, sunCos2) * outscatter;
|
|
#endif
|
|
return half4(PostProcess(resultColor, dir), 1.0);
|
|
}
|
|
inline half4 ScatteringColor(half3 dir)
|
|
{
|
|
#if TOD_SCATTERING_RAYLEIGH && TOD_SCATTERING_MIE
|
|
half3 inscatter, outscatter;
|
|
ScatteringCoefficients(dir, inscatter, outscatter);
|
|
return ScatteringColor(dir, inscatter, outscatter);
|
|
#elif TOD_SCATTERING_RAYLEIGH
|
|
half3 inscatter;
|
|
ScatteringCoefficients(dir, inscatter);
|
|
return ScatteringColor(dir, inscatter);
|
|
#else
|
|
half3 outscatter;
|
|
ScatteringCoefficients(dir, outscatter);
|
|
return ScatteringColor(dir, outscatter);
|
|
#endif
|
|
}
|
|
inline half FogDensity(half3 worldPos)
|
|
{
|
|
half camDist = length(worldPos - _WorldSpaceCameraPos);
|
|
half globalDensity = TOD_ScatterDensity.z;
|
|
half heightDensity = TOD_ScatterDensity.x * (worldPos.y - TOD_ScatterDensity.y);
|
|
half heightDensityExpInv = exp(-heightDensity);
|
|
half fogIntensity = camDist * heightDensityExpInv; // Get base fog intensity at pixel
|
|
half clampRes = step(abs(heightDensity), 0.01); // Apply height falloff
|
|
fogIntensity *= ((1.0 - heightDensityExpInv) / heightDensity) * step(abs(heightDensity), 0.01) + step(0.01, abs(heightDensity));
|
|
fogIntensity = min(10, globalDensity * fogIntensity); // Clamp intensity
|
|
return 1.0 - exp(-fogIntensity);
|
|
}
|
|
inline half4 AtmosphericScattering(half3 cameraRay, half3 worldPos, half depth, half mask)
|
|
{
|
|
half3 dir = normalize(mul((half3x3)TOD_World2Sky, cameraRay));
|
|
#if TOD_SCATTERING_RAYLEIGH && TOD_SCATTERING_MIE
|
|
half3 inscatter, outscatter;
|
|
ScatteringCoefficients(dir, inscatter, outscatter);
|
|
#elif TOD_SCATTERING_RAYLEIGH
|
|
half3 inscatter;
|
|
ScatteringCoefficients(dir, inscatter);
|
|
#else
|
|
half3 outscatter;
|
|
ScatteringCoefficients(dir, outscatter);
|
|
#endif
|
|
depth = FogDensity(worldPos);
|
|
#if TOD_SCATTERING_MIE
|
|
outscatter = outscatter * depth * mask;
|
|
#endif
|
|
#if TOD_SCATTERING_RAYLEIGH
|
|
inscatter = inscatter * depth;
|
|
#endif
|
|
#if TOD_SCATTERING_RAYLEIGH && TOD_SCATTERING_MIE
|
|
half3 color = ScatteringColor(dir, inscatter, outscatter).rgb;
|
|
#elif TOD_SCATTERING_RAYLEIGH
|
|
half3 color = ScatteringColor(dir, inscatter).rgb;
|
|
#else
|
|
half3 color = ScatteringColor(dir, outscatter).rgb;
|
|
#endif
|
|
return half4(color, depth);
|
|
}
|
|
inline half4 AtmosphericScattering(half3 cameraRay, half3 worldPos, half depth)
|
|
{
|
|
const half mask = 1; // This should be sampled from the scattering occlusion mask
|
|
|
|
return AtmosphericScattering(cameraRay, worldPos, depth, mask);
|
|
}
|
|
inline half4 AtmosphericScattering(half3 cameraRay, half3 worldPos)
|
|
{
|
|
half3 dir = normalize(mul((half3x3)TOD_World2Sky, cameraRay));
|
|
half depth = FogDensity(worldPos);
|
|
half3 color = ScatteringColor(dir).rgb;
|
|
return half4(color, depth);
|
|
}
|
|
inline half2 DitheringCoords(half2 screenPos)
|
|
{
|
|
return screenPos * _ScreenParams.xy * (1.0 / TOD_BAYER_DIM);
|
|
}
|
|
inline half DitheringColor(half2 uv)
|
|
{
|
|
return SAMPLE_TEXTURE2D(TOD_BayerTexture, samplerTOD_BayerTexture, uv).a * (1.0 / (TOD_BAYER_DIM * TOD_BAYER_DIM + 1.0));
|
|
}
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#endif |