Files
QuizzinMk5.1/Packages/com.z3y.vrcmarkerpro/Runtime/Shaders/Line.shader
2025-12-03 01:44:40 +00:00

833 lines
22 KiB
GLSL

Shader "Hidden/VRCMarker Internal/Line Renderer"
{
Properties
{
[HideInInspector]_Color ("Color", Color) = (1,1,1,1)
[HideInInspector]_Scale ("Width", Range(0, 1)) = 0.5
_GradientPeriod ("Gradient Period", Float) = 256
//[NoScaleOffset] _MainTex("Albedo (RGB)", 2D) = "white" {}
////[NoScaleOffset] _MetallicGlossMap("Mask Map", 2D) = "white" {}
//[NoScaleOffset] _ColorMask("Color Mask Map", 2D) = "white" {}
//_Glossiness ("Smoothness", Range(0,1)) = 0.5
//_GradientLength ("Gradient Length", Float) = 2
[HideInInspector] _StencilRef ("", Float) = 0
_PositionData ("Data", 2D) = "white" {}
_SubdivideDistance ("Subdivide Distance", Float) = 3
[Toggle(_VRC_LIGHTVOLUMES)] _EnableLightVolumes ("VRC Light Volumes", Float) = 0
_LightVolumesInfluence ("VRC Light Volume Influence", Range(0,1)) = 0.9
}
CGINCLUDE
#pragma vertex vert
#pragma fragment frag
#ifdef SHADER_API_D3D11
#define USE_GEOM
#endif
#pragma multi_compile_instancing
#include "Line.cginc"
#include "UnityCG.cginc"
#pragma shader_feature_local_fragment _VRC_LIGHTVOLUMES
#ifdef _VRC_LIGHTVOLUMES
#include "Packages/red.sim.lightvolumes/Shaders/LightVolumes.cginc"
// fallback to 1.0
void LV_SampleLightProbe_1(out float3 L0, out float3 L1r, out float3 L1g, out float3 L1b) {
L0 = 1;
L1r = 1.0/3.0;
L1g = 1.0/3.0;
L1b = 1.0/3.0;
}
#define LV_SampleLightProbe LV_SampleLightProbe_1
#endif
half _LightVolumesInfluence;
half3 _Color;
half _OutlineValue;
half _OutlineHue;
half _Scale;
half _GradientPeriod;
half4 _Gradient[8];
uint _GradientLength;
uint _VertexCount;
float3 _LastPosition;
float _DummyZero;
float _OutlineWidth;
float _EraseDistance;
float3 _EraserPosition;
float _SubdivideDistance;
Texture2D<float4> _PositionData;
struct appdata
{
float4 vertex : POSITION; // not needed lol
uint vertexID : SV_VertexID;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
#ifdef USE_GEOM
struct v2g
{
float4 vertex : SV_POSITION;
uint vertexID : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct g2f
#else
struct v2f
#endif
{
float4 vertex : SV_POSITION;
float2 uv0 : TEXCOORD0;
nointerpolation bool isLine : TEXCOORD1;
half3 gradient : TEXCOORD2;
float3 position : TEXCOORD3;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
float3 centerEyePos()
{
// trail tube looks more convincing in vr without this
// #if defined(UNITY_STEREO_MULTIVIEW_ENABLED) || defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_SINGLE_PASS_STEREO)
// return 0.5 * (unity_StereoWorldSpaceCameraPos[0] + unity_StereoWorldSpaceCameraPos[1]);
// #else
return _WorldSpaceCameraPos;
// #endif
}
// thanks error.mdl for help
float3 billboardTriangle(float3 vertex, float3 triCenter)
{
vertex -= triCenter;
float3 head = centerEyePos();
float3 center2Head = head - triCenter;
float c2hLen = length(center2Head);
float c2hXZLen = length(center2Head.xz);
float sin1 = center2Head.y / c2hLen;
float cos1 = c2hXZLen / c2hLen;
float2x2 rot1 = float2x2(cos1, -sin1, -sin1, cos1);
vertex.zy = mul(rot1, vertex.zy);
float sin2 = center2Head.x / c2hXZLen;
float cos2 = center2Head.z / c2hXZLen;
float2x2 rot2 = float2x2(cos2, sin2, -sin2, cos2);
vertex.xz = mul(rot2, vertex.xz);
vertex += triCenter;
return vertex;
}
// vertices always set in this order, i hope
static const float2 offsets[7] =
{
float2(0, 0),
float2(0, 1),
float2(1, 1),
float2(1, 0),
float2(-0.077350269189625764509148780501f, 0),
float2(0.5f, 1),
float2(1.077350269189625764509148780501f, 0)
};
struct Gradient
{
int type;
int colorsLength;
half4 colors[8];
};
Gradient NewGradient(int type, int colorsLength, half4 colors[8])
{
Gradient output =
{
type, colorsLength, colors
};
return output;
}
half3 EvaluateGradient(Gradient gradient, half time)
{
half3 color = gradient.colors[0].rgb;
[unroll(6)]
for (int c = 1; c < gradient.colorsLength; c++)
{
half colorPos = saturate((time - gradient.colors[c - 1].w) / (gradient.colors[c].w - gradient.colors[c - 1].w)) * step(c, gradient.colorsLength - 1);
color = lerp(color, gradient.colors[c].rgb, lerp(colorPos, step(0.01, colorPos), gradient.type));
}
return color;
}
half evaluateTime(uint id, half frequency)
{
half t = (sin((float)id / frequency) + 1.0) / 2.0;
return t;
}
#ifdef USE_GEOM
v2g vert (appdata v)
{
v2g o;
UNITY_INITIALIZE_OUTPUT(v2g, o);
UNITY_SETUP_INSTANCE_ID(v);
UNITY_TRANSFER_INSTANCE_ID(v, o);
o.vertex = v.vertex;
o.vertexID = v.vertexID;
return o;
}
#else
v2f vert(appdata v)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v); //Insert
UNITY_INITIALIZE_OUTPUT(v2f, o); //Insert
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); //Insert
uint index = v.vertexID / 7;
uint type = v.vertexID % 7;
bool isLine = type <= 3;
// float3 originalPos = v.vertex.xyz;
//v.vertex.xyz = _PositionData[uint2(0, index)].xyz;
float4 data0 = _PositionData[IndexToCoords(index)];
float4 data1 = _PositionData[IndexToCoords(index - 1)];
float3 start = data0.xyz;
float3 end = data1.xyz;
float timestamp0 = data0.a;
float timestamp1 = data1.a;
float time = _Time.y;
bool shouldDiscard = false;
if (all(start.xyz == end.xyz))
{
start = 0;
shouldDiscard = true;
}
if (timestamp0 > 0)
{
float increment = 0.02f;
float timeScale = 1.0f / increment;
float nextTime = _PositionData[IndexToCoords(index + 1)].a;
if (nextTime == 0) nextTime = timestamp1;
float timeDistance = abs(timestamp0 - nextTime);
// if (timeDistance == 0) timeDistance = 0.04;
// if (timeDistance > 0.49) timeDistance = 0.02;
float timeOffset = _Time.y - timestamp0;
float lerpTime = saturate(timeOffset / timeDistance);
if (all(end == 0)) end = start;
start = lerp(end, start, lerpTime);
if (timeOffset <= 0 || (_Time.y - timestamp1) <= 0)
{
shouldDiscard = true;
}
}
UNITY_BRANCH
if (shouldDiscard|| all(start.xyz == 0))
{
o.vertex = 0.0 / 0.0;
// o.vertex = asfloat(-1);
return o;
}
else
{
uint gradientTime = type <= 1 ? data1.a : data0.a;
float3 vertexPos = start;
float3 otherPos = end;
o.position = vertexPos;
if (isLine && type <= 1)
{
otherPos = start;
if (all(end != 0 && start == 0) || all(end == 0 && start != 0))
{
vertexPos = start;
}
else
{
vertexPos = end;
}
}
float2 offset = offsets[type];
#ifdef OUTLINE_PASS
_Scale += _OutlineWidth;
#endif
UNITY_BRANCH
if (isLine)
{
gradientTime = data0.a;
float3 v1 = otherPos - vertexPos;
float3 v2 = centerEyePos() - vertexPos;
float3 scaleDir = normalize(cross(v1, v2));
scaleDir *= _Scale;
if (offset.x > 0) scaleDir = -scaleDir;
// scaleDir *= offset.x * 2 - 1;
vertexPos += scaleDir * offset.y;
vertexPos -= scaleDir * (1 - offset.y);
}
else
{
float scaleFactor = 1.5;
float2 triangleOffset = offset * 2 - 1;
triangleOffset *= _Scale * scaleFactor;
triangleOffset.y += _Scale * scaleFactor * UNITY_PI * 0.106;
float3 vertexOffset = float3(triangleOffset.x, triangleOffset.y, 0);
float3 triangleVertex = float3(vertexPos.xy + triangleOffset, vertexPos.z);
vertexPos = billboardTriangle(triangleVertex, vertexPos);
}
o.vertex = mul(UNITY_MATRIX_VP, float4(vertexPos, 1.0));
// o.vertex = UnityObjectToClipPos(float4(originalPos, 1));
o.uv0 = offset;
o.isLine = isLine;
UNITY_BRANCH
if (_GradientLength > 0)
{
// half t = evaluateTime((gradientTime * 7) + type , _GradientPeriod);
half t = evaluateTime(v.vertexID, 256);
Gradient g = NewGradient(0, _GradientLength, _Gradient);
o.gradient = EvaluateGradient(g,t);
}
return o;
}
}
#endif
float3 CatmullRom(float3 p0, float3 p1, float3 p2, float3 p3, float t) {
float t2 = t * t;
float t3 = t2 * t;
float3 p = 0.5 * (
(2.0 * p1) +
(-p0 + p2) * t +
(2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 +
(-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3
);
return p;
}
#ifdef USE_GEOM
[maxvertexcount(6)]
void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream)
{
g2f o1 = (g2f)0;
g2f o2[3] = { (g2f)0,(g2f)0,(g2f)0 };
#ifdef OUTLINE_PASS
_Scale += _OutlineWidth;
#endif
bool shouldSubvidide = false;
for(int i = 0; i < 3; i++)
{
UNITY_SETUP_INSTANCE_ID(IN[i]);
uint vertexID = IN[i].vertexID;
uint index = vertexID / 7;
uint type = vertexID % 7;
bool isLine = type <= 3;
// float3 originalPos = v.vertex.xyz;
//v.vertex.xyz = _PositionData[uint2(0, index)].xyz;
float4 data0 = _PositionData[IndexToCoords(index)];
float4 data1 = _PositionData[IndexToCoords(index - 1)];
float3 start = data0.xyz;
float3 end = data1.xyz;
// end = lerp(start.xyz, end.xyz, 0.5);
float timestamp0 = data0.a;
float timestamp1 = data1.a;
float time = _Time.y;
bool shouldDiscard = false;
if (all(start.xyz == end.xyz))
{
start = 0;
shouldDiscard = true;
}
if (timestamp0 > 0)
{
float increment = 0.02f;
float timeScale = 1.0f / increment;
float nextTime = _PositionData[IndexToCoords(index + 1)].a;
if (nextTime == 0) nextTime = timestamp1;
float timeDistance = abs(timestamp0 - nextTime);
// if (timeDistance == 0) timeDistance = 0.04;
// if (timeDistance > 0.49) timeDistance = 0.02;
float timeOffset = _Time.y - timestamp0;
float lerpTime = saturate(timeOffset / timeDistance);
if (all(end == 0)) end = start;
start = lerp(end, start, lerpTime);
if (timeOffset <= 0 || (_Time.y - timestamp1) <= 0)
{
shouldDiscard = true;
}
}
UNITY_BRANCH
if (shouldDiscard|| all(start.xyz == 0))
{
// o.vertex = 0.0 / 0.0;
// o.vertex = asfloat(-1);
// triStream.RestartStrip();
return;
}
else
{
float3 subdivide = 0;
if (!all(end.xyz == 0))
{
float distanceToCam = distance(end, centerEyePos());
UNITY_BRANCH
if (distanceToCam < _SubdivideDistance)
{
float3 previousPoint = _PositionData[IndexToCoords(index-2)];
float3 nextPoint = _PositionData[IndexToCoords(index+1)];
if (all(previousPoint == 0) || all(nextPoint.xyz == 0))
{
previousPoint = start;
nextPoint = end;
}
float tt = mad(sin(_Time.y), 0.5, 0.5);
float d1 = distance(previousPoint, start);
float d2 = distance(start, end);
float maxDistance = distance(start, end) * 3.0;
if (distance(previousPoint, start) > maxDistance)
{
previousPoint = start;
}
if (distance(nextPoint, end) > maxDistance)
{
nextPoint = end;
}
subdivide = CatmullRom(previousPoint, start, end, nextPoint, 0.5);
subdivide = lerp((start + end) / 2.0, subdivide, saturate((_SubdivideDistance - distanceToCam) * 3.0));
shouldSubvidide = true;
}
}
uint gradientTime = type <= 1 ? data1.a : data0.a;
UNITY_BRANCH
if (_GradientLength > 0)
{
// half t = evaluateTime((gradientTime * 7) + type , _GradientPeriod);
half t = evaluateTime(vertexID, 256);
Gradient g = NewGradient(0, _GradientLength, _Gradient);
o1.gradient = EvaluateGradient(g, t);
o2[i].gradient = o1.gradient;
}
float3 start1 = start;
float3 end1 = end;
// o1
{
if (shouldSubvidide)
{
start = subdivide;
}
float3 vertexPos = start;
float3 otherPos = end;
if (isLine && type <= 1)
{
otherPos = start;
if (all(end != 0 && start == 0) || all(end == 0 && start != 0))
{
vertexPos = start;
}
else
{
vertexPos = end;
}
}
float2 offset = offsets[type];
UNITY_BRANCH
if (isLine)
{
gradientTime = data0.a;
float3 v1 = otherPos - vertexPos;
float3 v2 = centerEyePos() - vertexPos;
float3 scaleDir = normalize(cross(v1, v2));
scaleDir *= _Scale;
if (offset.x > 0) scaleDir = -scaleDir;
// scaleDir *= offset.x * 2 - 1;
vertexPos += scaleDir * offset.y;
vertexPos -= scaleDir * (1 - offset.y);
}
else
{
float scaleFactor = 1.5;
float2 triangleOffset = offset * 2 - 1;
triangleOffset *= _Scale * scaleFactor;
triangleOffset.y += _Scale * scaleFactor * UNITY_PI * 0.106;
float3 vertexOffset = float3(triangleOffset.x, triangleOffset.y, 0);
float3 triangleVertex = float3(vertexPos.xy + triangleOffset, vertexPos.z);
vertexPos = billboardTriangle(triangleVertex, vertexPos);
}
o1.vertex = mul(UNITY_MATRIX_VP, float4(vertexPos, 1.0));
o1.position = start;
o1.uv0 = offset;
o1.isLine = isLine;
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o1);
triStream.Append(o1);
}
// o2
UNITY_BRANCH
if (shouldSubvidide)
{
start = start1;
end = subdivide;
float3 vertexPos = start;
float3 otherPos = end;
if (isLine && type <= 1)
{
otherPos = start;
if (all(end != 0 && start == 0) || all(end == 0 && start != 0))
{
vertexPos = start;
}
else
{
vertexPos = end;
}
}
float2 offset = offsets[type];
UNITY_BRANCH
if (isLine)
{
gradientTime = data0.a;
float3 v1 = otherPos - vertexPos;
float3 v2 = centerEyePos() - vertexPos;
float3 scaleDir = normalize(cross(v1, v2));
scaleDir *= _Scale;
if (offset.x > 0) scaleDir = -scaleDir;
// scaleDir *= offset.x * 2 - 1;
vertexPos += scaleDir * offset.y;
vertexPos -= scaleDir * (1 - offset.y);
}
else
{
float scaleFactor = 1.5;
float2 triangleOffset = offset * 2 - 1;
triangleOffset *= _Scale * scaleFactor;
triangleOffset.y += _Scale * scaleFactor * UNITY_PI * 0.106;
float3 vertexOffset = float3(triangleOffset.x, triangleOffset.y, 0);
float3 triangleVertex = float3(vertexPos.xy + triangleOffset, vertexPos.z);
vertexPos = billboardTriangle(triangleVertex, vertexPos);
}
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o2[i]);
o2[i].vertex = mul(UNITY_MATRIX_VP, float4(vertexPos, 1.0));
o2[i].uv0 = offset;
o2[i].isLine = isLine;
}
}
}
o2[0].isLine = o1.isLine;
o2[1].isLine = o1.isLine;
o2[2].isLine = o1.isLine;
o2[0].position = o1.position;
o2[1].position = o1.position;
o2[2].position = o1.position;
if (shouldSubvidide)
{
triStream.RestartStrip();
triStream.Append(o2[0]);
triStream.Append(o2[1]);
triStream.Append(o2[2]);
}
triStream.RestartStrip();
}
#endif
half3 HueShift(float3 In, float Offset)
{
float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
float4 P = lerp(float4(In.bg, K.wz), float4(In.gb, K.xy), step(In.b, In.g));
float4 Q = lerp(float4(P.xyw, In.r), float4(In.r, P.yzx), step(P.x, In.r));
float D = Q.x - min(Q.w, Q.y);
float E = 1e-10;
float3 hsv = float3(abs(Q.z + (Q.w - Q.y)/(6.0 * D + E)), D / (Q.x + E), Q.x);
float hue = hsv.x + Offset / 360;
hsv.x = (hue < 0)
? hue + 1
: (hue > 1)
? hue - 1
: hue;
float4 K2 = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
float3 P2 = abs(frac(hsv.xxx + K2.xyz) * 6.0 - K2.www);
return hsv.z * lerp(K2.xxx, saturate(P2 - K2.xxx), hsv.y);
}
float TriangleInsideQuad(float2 uv, float2 p0, float2 p1, float2 p2)
{
float e0 = (p1.x - p0.x) * (uv.y - p0.y) - (p1.y - p0.y) * (uv.x - p0.x);
float e1 = (p2.x - p1.x) * (uv.y - p1.y) - (p2.y - p1.y) * (uv.x - p1.x);
float e2 = (p0.x - p2.x) * (uv.y - p2.y) - (p0.y - p2.y) * (uv.x - p2.x);
return (e0 >= 0 && e1 >= 0 && e2 >= 0) ? 1.0 : 0.0;
}
#ifdef USE_GEOM
half4 frag(g2f i) : SV_Target
#else
half4 frag(v2f i) : SV_Target
#endif
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); //Insert
half alpha = 1;
// uint samplecount = GetRenderTargetSampleCount();
bool inEraseRange = distance(_EraserPosition, i.position) < _EraseDistance;
bool eraseColor = false;
UNITY_BRANCH
if (!i.isLine)
{
float2 coord = i.uv0.xy;
coord.x -= 0.5;
coord.y -= 1./3.;
float circle = length(coord);
float size = 1.0 / 3.0;
float pwidth = max(fwidth(circle), 0.0001);
alpha = saturate((size - circle) / pwidth);
#ifndef OUTLINE_PASS
bool triangleOutline = 1 - TriangleInsideQuad(i.uv0, float2(0.5,0.94), float2(-0.02,0.03), float2(1.02,0.03));
if (inEraseRange && triangleOutline)
{
eraseColor = true;
alpha = 1;
}
#endif
}
else
{
float size = abs(i.uv0.y - 0.5) * 2;
size = 1-size;
float pwidth = max(fwidth(size), 0.0001);
alpha = saturate((size) / pwidth);
alpha = saturate(alpha + pwidth);
}
// alpha = alpha * samplecount + 0.5;
// cov = (1u << (uint)(alpha)) - 1u;
if (alpha < 0.1) discard;
half3 color = _GradientLength > 0 ? i.gradient : _Color;
#ifdef OUTLINE_PASS
color = HueShift(color, _OutlineHue);
color *= _OutlineValue;
#endif
if (eraseColor)
{
color = float3(1,0.1,0.1);
}
#ifdef _VRC_LIGHTVOLUMES
float3 up = float3(0,1,0);
float3 dir = normalize(_WorldSpaceCameraPos.xyz - i.position);
float3 L0, L1r, L1g, L1b;
LightVolumeSH(i.position, L0, L1r, L1g, L1b);
float3 sh9Dir = L1r.xyz * 0.333333 + L1g.xyz * 0.333333 + L1b.xyz * 0.333333;
float3 sh9DirAbs = float3(sh9Dir.x, abs(sh9Dir.y), sh9Dir.z);
half3 sh = LightVolumeEvaluate(sh9DirAbs, L0, L1r, L1g, L1b);
if (any(float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w) != L0))
color.rgb *= lerp(1.0, sh, _LightVolumesInfluence);
#endif
return half4(color, alpha);
}
ENDCG
SubShader
{
Tags
{
"RenderType"="Opaque"
"DisableBatching" = "True"
}
// PC with Geometry shader
Pass
{
Cull Back
Tags { "LightMode" = "ForwardBase" "Queue"="AlphaTest" "RenderType"="TransparentCutout" }
AlphaToMask On
Stencil
{
Ref 128
Comp Always
Pass Replace
}
CGPROGRAM
#pragma target 5.0
#pragma require geometry
#pragma geometry geom
#pragma only_renderers d3d11
ENDCG
}
// Outline Pass PC only
Pass
{
Cull Back
Tags { "LightMode" = "Always" "Queue"="AlphaTest" "RenderType"="TransparentCutout" }
// ZWrite Off
// Blend SrcAlpha OneMinusSrcAlpha
AlphaToMask On
Stencil
{
Ref 128
Comp NotEqual
}
CGPROGRAM
#pragma target 5.0
#pragma require geometry
#pragma geometry geom
#pragma only_renderers d3d11
#pragma multi_compile OUTLINE_PASS
ENDCG
}
// Pass
// {
// Name "ShadowCaster"
// Tags { "LightMode" = "ShadowCaster" }
// ZWrite On ZTest LEqual Cull Off
// CGPROGRAM
// #pragma multi_compile_shadowcaster
// ENDCG
// }
}
SubShader
{
Tags
{
"RenderType"="Opaque"
"DisableBatching" = "True"
}
// quest
Pass
{
Cull Back
Tags { "LightMode" = "ForwardBase" "Queue"="AlphaTest" "RenderType"="TransparentCutout" }
AlphaToMask On
CGPROGRAM
#pragma exclude_renderers d3d11
ENDCG
}
}
}