You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
479 lines
12 KiB
Plaintext
479 lines
12 KiB
Plaintext
|
4 years ago
|
// Copyright(c) 2016, Michal Skalsky
|
||
|
|
// All rights reserved.
|
||
|
|
//
|
||
|
|
// Redistribution and use in source and binary forms, with or without modification,
|
||
|
|
// are permitted provided that the following conditions are met:
|
||
|
|
//
|
||
|
|
// 1. Redistributions of source code must retain the above copyright notice,
|
||
|
|
// this list of conditions and the following disclaimer.
|
||
|
|
//
|
||
|
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||
|
|
// this list of conditions and the following disclaimer in the documentation
|
||
|
|
// and/or other materials provided with the distribution.
|
||
|
|
//
|
||
|
|
// 3. Neither the name of the copyright holder nor the names of its contributors
|
||
|
|
// may be used to endorse or promote products derived from this software without
|
||
|
|
// specific prior written permission.
|
||
|
|
//
|
||
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||
|
|
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT
|
||
|
|
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||
|
|
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||
|
|
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||
|
|
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||
|
|
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
|
||
|
|
//Modified for use with Enviro - Sky and Weather
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
Shader "Enviro/Standard/VolumeLight"
|
||
|
|
{
|
||
|
|
Properties
|
||
|
|
{ _MainTex("Texture", any) = "" {}
|
||
|
|
[HideInInspector]_ZTest ("ZTest", Float) = 0
|
||
|
|
[HideInInspector]_LightColor("_LightColor", Color) = (1,1,1,1)
|
||
|
|
}
|
||
|
|
SubShader
|
||
|
|
{
|
||
|
|
Tags { "RenderType"="Opaque" }
|
||
|
|
LOD 100
|
||
|
|
|
||
|
|
CGINCLUDE
|
||
|
|
#if defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE)
|
||
|
|
#define SHADOWS_NATIVE
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#include "UnityCG.cginc"
|
||
|
|
#include "UnityDeferredLibrary.cginc"
|
||
|
|
#include "../Core/EnviroVolumeLightCore.cginc"
|
||
|
|
|
||
|
|
struct appdata
|
||
|
|
{
|
||
|
|
float4 vertex : POSITION;
|
||
|
|
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||
|
|
};
|
||
|
|
|
||
|
|
struct v2f
|
||
|
|
{
|
||
|
|
float4 pos : SV_POSITION;
|
||
|
|
float4 uv : TEXCOORD0;
|
||
|
|
float3 wpos : TEXCOORD1;
|
||
|
|
float4 interpolatedRay : TEXCOORD2;
|
||
|
|
UNITY_VERTEX_OUTPUT_STEREO
|
||
|
|
};
|
||
|
|
|
||
|
|
v2f vert(appdata v)
|
||
|
|
{
|
||
|
|
v2f o;
|
||
|
|
UNITY_SETUP_INSTANCE_ID(v); //Insert
|
||
|
|
UNITY_INITIALIZE_OUTPUT(v2f, o); //Insert
|
||
|
|
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); //Ins
|
||
|
|
if (unity_StereoEyeIndex == 0)
|
||
|
|
o.pos = mul(_WorldViewProj, v.vertex);
|
||
|
|
else
|
||
|
|
o.pos = mul(_WorldViewProj_SP, v.vertex);
|
||
|
|
|
||
|
|
o.uv = ComputeScreenPos(o.pos);
|
||
|
|
o.wpos = mul(unity_ObjectToWorld, v.vertex);
|
||
|
|
|
||
|
|
return o;
|
||
|
|
}
|
||
|
|
|
||
|
|
ENDCG
|
||
|
|
// pass 0 - point light, camera inside
|
||
|
|
Pass
|
||
|
|
{
|
||
|
|
ZTest Off
|
||
|
|
Cull Front
|
||
|
|
ZWrite Off
|
||
|
|
Blend One One
|
||
|
|
|
||
|
|
CGPROGRAM
|
||
|
|
#pragma vertex vert
|
||
|
|
#pragma fragment fragPointInside
|
||
|
|
#pragma target 3.5
|
||
|
|
#pragma exclude_renderers d3d9 gles
|
||
|
|
|
||
|
|
#define UNITY_HDR_ON
|
||
|
|
|
||
|
|
#pragma shader_feature HEIGHT_FOG
|
||
|
|
#pragma shader_feature NOISE
|
||
|
|
#pragma shader_feature SHADOWS_CUBE
|
||
|
|
#pragma shader_feature POINT_COOKIE
|
||
|
|
#pragma shader_feature POINT
|
||
|
|
|
||
|
|
#ifdef SHADOWS_DEPTH
|
||
|
|
#define SHADOWS_NATIVE
|
||
|
|
#endif
|
||
|
|
|
||
|
|
|
||
|
|
fixed4 fragPointInside(v2f i) : SV_Target
|
||
|
|
{
|
||
|
|
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
|
||
|
|
float2 uv = i.uv.xy / i.uv.w;
|
||
|
|
|
||
|
|
#if defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)
|
||
|
|
if(unity_StereoEyeIndex != _Eye)
|
||
|
|
return float4(0,0,0,0);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// read depth and reconstruct world position
|
||
|
|
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UnityStereoScreenSpaceUVAdjust(uv,_CameraDepthTexture_ST));
|
||
|
|
|
||
|
|
float3 rayStart = _WorldSpaceCameraPos;
|
||
|
|
float3 rayEnd = i.wpos;
|
||
|
|
|
||
|
|
float3 rayDir = (rayEnd - rayStart);
|
||
|
|
float rayLength = length(rayDir);
|
||
|
|
|
||
|
|
rayDir /= rayLength;
|
||
|
|
|
||
|
|
float linearDepth = LinearEyeDepth(depth);
|
||
|
|
float projectedDepth = linearDepth / dot(_CameraForward, rayDir);
|
||
|
|
rayLength = min(rayLength, projectedDepth);
|
||
|
|
|
||
|
|
return RayMarch(i.pos.xy, rayStart, rayDir, rayLength);
|
||
|
|
}
|
||
|
|
ENDCG
|
||
|
|
}
|
||
|
|
|
||
|
|
// pass 1 - spot light, camera inside
|
||
|
|
Pass
|
||
|
|
{
|
||
|
|
ZTest Off
|
||
|
|
Cull Front
|
||
|
|
ZWrite Off
|
||
|
|
Blend One One
|
||
|
|
|
||
|
|
CGPROGRAM
|
||
|
|
#pragma vertex vert
|
||
|
|
#pragma fragment fragSpotInside
|
||
|
|
#pragma target 3.5
|
||
|
|
#pragma exclude_renderers d3d9 gles
|
||
|
|
|
||
|
|
#define UNITY_HDR_ON
|
||
|
|
|
||
|
|
#pragma shader_feature HEIGHT_FOG
|
||
|
|
#pragma shader_feature NOISE
|
||
|
|
#pragma shader_feature SHADOWS_DEPTH
|
||
|
|
#pragma shader_feature SPOT
|
||
|
|
|
||
|
|
#ifdef SHADOWS_DEPTH
|
||
|
|
#define SHADOWS_NATIVE
|
||
|
|
#endif
|
||
|
|
|
||
|
|
fixed4 fragSpotInside(v2f i) : SV_Target
|
||
|
|
{
|
||
|
|
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
|
||
|
|
float2 uv = i.uv.xy / i.uv.w;
|
||
|
|
|
||
|
|
#if defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)
|
||
|
|
if(unity_StereoEyeIndex != _Eye)
|
||
|
|
return float4(0,0,0,0);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// read depth and reconstruct world position
|
||
|
|
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UnityStereoScreenSpaceUVAdjust(uv,_CameraDepthTexture_ST));
|
||
|
|
|
||
|
|
float3 rayStart = _WorldSpaceCameraPos;
|
||
|
|
float3 rayEnd = i.wpos;
|
||
|
|
|
||
|
|
float3 rayDir = (rayEnd - rayStart);
|
||
|
|
float rayLength = length(rayDir);
|
||
|
|
|
||
|
|
rayDir /= rayLength;
|
||
|
|
|
||
|
|
float linearDepth = LinearEyeDepth(depth);
|
||
|
|
float projectedDepth = linearDepth / dot(_CameraForward, rayDir);
|
||
|
|
rayLength = min(rayLength, projectedDepth);
|
||
|
|
|
||
|
|
return RayMarch(i.pos.xy, rayStart, rayDir, rayLength);
|
||
|
|
}
|
||
|
|
ENDCG
|
||
|
|
}
|
||
|
|
|
||
|
|
// pass 2 - point light, camera outside
|
||
|
|
Pass
|
||
|
|
{
|
||
|
|
//ZTest Off
|
||
|
|
ZTest [_ZTest]
|
||
|
|
Cull Back
|
||
|
|
ZWrite Off
|
||
|
|
Blend One One
|
||
|
|
|
||
|
|
CGPROGRAM
|
||
|
|
#pragma vertex vert
|
||
|
|
#pragma fragment fragPointOutside
|
||
|
|
#pragma target 3.5
|
||
|
|
#pragma exclude_renderers d3d9 gles
|
||
|
|
|
||
|
|
#define UNITY_HDR_ON
|
||
|
|
|
||
|
|
#pragma shader_feature HEIGHT_FOG
|
||
|
|
#pragma shader_feature SHADOWS_CUBE
|
||
|
|
#pragma shader_feature NOISE
|
||
|
|
#pragma shader_feature POINT_COOKIE
|
||
|
|
#pragma shader_feature POINT
|
||
|
|
|
||
|
|
fixed4 fragPointOutside(v2f i) : SV_Target
|
||
|
|
{
|
||
|
|
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
|
||
|
|
float2 uv = i.uv.xy / i.uv.w;
|
||
|
|
|
||
|
|
#if defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)
|
||
|
|
if(unity_StereoEyeIndex != _Eye)
|
||
|
|
return float4(0,0,0,0);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// read depth and reconstruct world position
|
||
|
|
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UnityStereoScreenSpaceUVAdjust(uv,_CameraDepthTexture_ST));
|
||
|
|
|
||
|
|
float3 rayStart = _WorldSpaceCameraPos;
|
||
|
|
float3 rayEnd = i.wpos;
|
||
|
|
|
||
|
|
float3 rayDir = (rayEnd - rayStart);
|
||
|
|
float rayLength = length(rayDir);
|
||
|
|
|
||
|
|
rayDir /= rayLength;
|
||
|
|
|
||
|
|
float3 lightToCamera = _WorldSpaceCameraPos - _LightPos;
|
||
|
|
|
||
|
|
float b = dot(rayDir, lightToCamera);
|
||
|
|
float c = dot(lightToCamera, lightToCamera) - (_VolumetricLight.z * _VolumetricLight.z);
|
||
|
|
|
||
|
|
float d = sqrt((b*b) - c);
|
||
|
|
float start = -b - d;
|
||
|
|
float end = -b + d;
|
||
|
|
|
||
|
|
float linearDepth = LinearEyeDepth(depth);
|
||
|
|
float projectedDepth = linearDepth / dot(_CameraForward, rayDir);
|
||
|
|
end = min(end, projectedDepth);
|
||
|
|
|
||
|
|
rayStart = rayStart + rayDir * start;
|
||
|
|
rayLength = end - start;
|
||
|
|
|
||
|
|
return RayMarch(i.pos.xy, rayStart, rayDir, rayLength);
|
||
|
|
}
|
||
|
|
ENDCG
|
||
|
|
}
|
||
|
|
|
||
|
|
// pass 3 - spot light, camera outside
|
||
|
|
Pass
|
||
|
|
{
|
||
|
|
//ZTest Off
|
||
|
|
ZTest[_ZTest]
|
||
|
|
Cull Back
|
||
|
|
ZWrite Off
|
||
|
|
Blend One One
|
||
|
|
|
||
|
|
CGPROGRAM
|
||
|
|
#pragma vertex vert
|
||
|
|
#pragma fragment fragSpotOutside
|
||
|
|
#pragma target 3.5
|
||
|
|
#pragma exclude_renderers d3d9 gles
|
||
|
|
|
||
|
|
#define UNITY_HDR_ON
|
||
|
|
|
||
|
|
#pragma shader_feature HEIGHT_FOG
|
||
|
|
#pragma shader_feature SHADOWS_DEPTH
|
||
|
|
#pragma shader_feature NOISE
|
||
|
|
#pragma shader_feature SPOT
|
||
|
|
|
||
|
|
#ifdef SHADOWS_DEPTH
|
||
|
|
#define SHADOWS_NATIVE
|
||
|
|
#endif
|
||
|
|
|
||
|
|
float _CosAngle;
|
||
|
|
float4 _ConeAxis;
|
||
|
|
float4 _ConeApex;
|
||
|
|
float _PlaneD;
|
||
|
|
|
||
|
|
fixed4 fragSpotOutside(v2f i) : SV_Target
|
||
|
|
{
|
||
|
|
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
|
||
|
|
float2 uv = i.uv.xy / i.uv.w;
|
||
|
|
|
||
|
|
#if defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)
|
||
|
|
if(unity_StereoEyeIndex != _Eye)
|
||
|
|
return float4(0,0,0,0);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// read depth and reconstruct world position
|
||
|
|
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, UnityStereoScreenSpaceUVAdjust(uv,_CameraDepthTexture_ST));
|
||
|
|
|
||
|
|
float3 rayStart = _WorldSpaceCameraPos;
|
||
|
|
float3 rayEnd = i.wpos;
|
||
|
|
|
||
|
|
float3 rayDir = (rayEnd - rayStart);
|
||
|
|
float rayLength = length(rayDir);
|
||
|
|
|
||
|
|
rayDir /= rayLength;
|
||
|
|
|
||
|
|
|
||
|
|
// inside cone
|
||
|
|
float3 r1 = rayEnd + rayDir * 0.001;
|
||
|
|
|
||
|
|
// plane intersection
|
||
|
|
float planeCoord = RayPlaneIntersect(_ConeAxis, _PlaneD, r1, rayDir);
|
||
|
|
// ray cone intersection
|
||
|
|
float2 lineCoords = RayConeIntersect(_ConeApex, _ConeAxis, _CosAngle, r1, rayDir);
|
||
|
|
|
||
|
|
float linearDepth = LinearEyeDepth(depth);
|
||
|
|
float projectedDepth = linearDepth / dot(_CameraForward, rayDir);
|
||
|
|
|
||
|
|
float z = (projectedDepth - rayLength);
|
||
|
|
rayLength = min(planeCoord, min(lineCoords.x, lineCoords.y));
|
||
|
|
rayLength = min(rayLength, z);
|
||
|
|
|
||
|
|
return RayMarch(i.pos.xy, rayEnd, rayDir, rayLength);
|
||
|
|
}
|
||
|
|
ENDCG
|
||
|
|
}
|
||
|
|
|
||
|
|
// pass 4 - directional light
|
||
|
|
Pass
|
||
|
|
{
|
||
|
|
ZTest Off
|
||
|
|
Cull Off
|
||
|
|
ZWrite Off
|
||
|
|
Blend One One, One Zero
|
||
|
|
|
||
|
|
CGPROGRAM
|
||
|
|
|
||
|
|
#pragma vertex vertDir
|
||
|
|
#pragma fragment fragDir
|
||
|
|
#pragma target 3.5
|
||
|
|
#pragma exclude_renderers d3d9 gles
|
||
|
|
|
||
|
|
#define UNITY_HDR_ON
|
||
|
|
|
||
|
|
#pragma shader_feature HEIGHT_FOG
|
||
|
|
#pragma shader_feature NOISE
|
||
|
|
#pragma shader_feature SHADOWS_DEPTH
|
||
|
|
#pragma shader_feature DIRECTIONAL_COOKIE
|
||
|
|
#pragma shader_feature DIRECTIONAL
|
||
|
|
|
||
|
|
#ifdef SHADOWS_DEPTH
|
||
|
|
#define SHADOWS_NATIVE
|
||
|
|
#endif
|
||
|
|
|
||
|
|
v2f vertDir(appdata_img v)
|
||
|
|
{
|
||
|
|
v2f o;
|
||
|
|
UNITY_SETUP_INSTANCE_ID(v);
|
||
|
|
UNITY_INITIALIZE_OUTPUT(v2f, o);
|
||
|
|
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
|
||
|
|
o.pos = UnityObjectToClipPos(v.vertex);
|
||
|
|
//o.pos = v.vertex * float4(2,2,1,1) + float4(-1,-1,0,0);
|
||
|
|
o.uv.xy = v.texcoord.xy;
|
||
|
|
#if UNITY_UV_STARTS_AT_TOP
|
||
|
|
//o.uv.y = 1.0f - o.uv.y; //blit flips the uv for some reason
|
||
|
|
#endif
|
||
|
|
return o;
|
||
|
|
}
|
||
|
|
|
||
|
|
fixed4 fragDir(v2f i) : SV_Target
|
||
|
|
{
|
||
|
|
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
|
||
|
|
float2 uv = i.uv.xy;
|
||
|
|
|
||
|
|
//return lerp(float4(1,0,0,1),float4(0,0,1,1),unity_StereoEyeIndex);
|
||
|
|
|
||
|
|
|
||
|
|
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv);
|
||
|
|
|
||
|
|
float4x4 proj, eyeToWorld;
|
||
|
|
|
||
|
|
if (unity_StereoEyeIndex == 0)
|
||
|
|
{
|
||
|
|
proj = _LeftViewFromScreen;
|
||
|
|
eyeToWorld = _LeftWorldFromView;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
proj = _RightViewFromScreen;
|
||
|
|
eyeToWorld = _RightWorldFromView;
|
||
|
|
}
|
||
|
|
|
||
|
|
//bit of matrix math to take the screen space coord (u,v,depth) and transform to world space
|
||
|
|
float2 uvClip = i.uv * 2.0 - 1.0;
|
||
|
|
float clipDepth = depth; // Fix for OpenGl Core thanks to Lars Bertram
|
||
|
|
clipDepth = (UNITY_NEAR_CLIP_VALUE < 0) ? clipDepth * 2 - 1 : clipDepth;
|
||
|
|
float4 clipPos = float4(uvClip, clipDepth, 1.0);
|
||
|
|
float4 viewPos = mul(proj, clipPos); // inverse projection by clip position
|
||
|
|
viewPos /= viewPos.w; // perspective division
|
||
|
|
float3 wpos = mul(eyeToWorld, viewPos).xyz;
|
||
|
|
|
||
|
|
|
||
|
|
//float3 wpos = i.interpolatedRay + _WorldSpaceCameraPos;
|
||
|
|
|
||
|
|
float linearDepth = Linear01Depth(depth);
|
||
|
|
|
||
|
|
float3 rayStart = _WorldSpaceCameraPos;
|
||
|
|
float3 rayDir = wpos - _WorldSpaceCameraPos;
|
||
|
|
|
||
|
|
//rayDir *= linearDepth;
|
||
|
|
|
||
|
|
float rayLength = length(rayDir);
|
||
|
|
rayDir /= rayLength;
|
||
|
|
|
||
|
|
rayLength = min(rayLength, _MaxRayLength);
|
||
|
|
|
||
|
|
float4 color = RayMarch(i.pos.xy, rayStart, rayDir, rayLength);
|
||
|
|
|
||
|
|
|
||
|
|
if (linearDepth > 0.999999)
|
||
|
|
{
|
||
|
|
color.w = lerp(color.w, 1, _VolumetricLight.w);
|
||
|
|
}
|
||
|
|
|
||
|
|
return color;
|
||
|
|
}
|
||
|
|
ENDCG
|
||
|
|
}
|
||
|
|
|
||
|
|
// pass 5 - black
|
||
|
|
Pass
|
||
|
|
{
|
||
|
|
ZTest Off
|
||
|
|
Cull Off
|
||
|
|
ZWrite Off
|
||
|
|
Blend One One, One Zero
|
||
|
|
|
||
|
|
CGPROGRAM
|
||
|
|
|
||
|
|
#pragma vertex vertW
|
||
|
|
#pragma fragment fragW
|
||
|
|
#pragma target 3.5
|
||
|
|
#pragma exclude_renderers d3d9 gles
|
||
|
|
|
||
|
|
#ifdef SHADOWS_DEPTH
|
||
|
|
#define SHADOWS_NATIVE
|
||
|
|
#endif
|
||
|
|
|
||
|
|
|
||
|
|
v2f vertW(appdata_img i)
|
||
|
|
{
|
||
|
|
v2f o;
|
||
|
|
UNITY_SETUP_INSTANCE_ID(i); //Insert
|
||
|
|
UNITY_INITIALIZE_OUTPUT(v2f, o); //Insert
|
||
|
|
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); //Ins
|
||
|
|
half index = i.vertex.z;
|
||
|
|
i.vertex.z = 0.1;
|
||
|
|
o.pos = UnityObjectToClipPos(i.vertex);
|
||
|
|
o.uv.xy = i.texcoord.xy;
|
||
|
|
return o;
|
||
|
|
}
|
||
|
|
|
||
|
|
fixed4 fragW(v2f i) : SV_Target
|
||
|
|
{
|
||
|
|
return float4(0,0,0,0);
|
||
|
|
}
|
||
|
|
ENDCG
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|