Files
Reimagined/shaderpacks/Reimagined - High/shaders/lib/atmospherics/endCrystalVortex.glsl
T
2026-05-07 21:38:27 +03:00

297 lines
15 KiB
GLSL

#ifndef ENDCRYSTAL_SAMPLER_DEFINE
uniform isampler2D endcrystal_sampler;
#endif
const float healing_boundRadius = 6.0;
const float healing_ballRadius = 3.5;
const float healing_beamRadius = 0.6;
const float vortex_cylinderRadius = 3.0;
const float vortex_ballRadius = 5.0;
const float death_radius = 70.0;
#ifndef INCLUDE_ENDER_BEAMS
#ifdef GBUFFERS_WATER
float vlFactor = 0.5;
#endif
#endif
vec3 beamPurple = normalize(endColorBeam * endColorBeam * endColorBeam) * (2.5 - 1.0 * vlFactor) * E_BEAM_I;
vec3 endDragonColM = sqrt(endOrangeCol);
vec3 beamColM = sqrt(beamPurple);
float GetBallRadius(float state) {
return vortex_ballRadius * (1.0 + 4.0 * sqrt(1.0 - state));
}
float VortexWidth(float x, float ballRadius) {
if (x > 0.5 * ballRadius) {
float expScale = sqrt(0.75) * ballRadius - vortex_cylinderRadius;
return vortex_cylinderRadius + expScale * exp( -sqrt(1.0/3.0) / expScale * (x - 0.5 * ballRadius));
} else if (x > -ballRadius) {
return sqrt(pow2(ballRadius) - pow2(x));
}
return 0.0;
}
vec4 SampleEndCrystalVortex(vec3 relPos, vec2 state, vec2 noiseOffset) {
float thisBallRadius = GetBallRadius(state.x);
float beamFactor = smoothstep(-thisBallRadius, thisBallRadius, relPos.y);
float featureWidth = VortexWidth(relPos.y, thisBallRadius);
vec2 horizontalScaledPos = featureWidth > 0.0 ? relPos.xz / featureWidth : vec2(2.0);
float featureDist = length(horizontalScaledPos);
if (length(relPos.xz) > featureWidth) {
return vec4(0);
}
float beamStrength = 2.5 * beamFactor * (cos(featureDist * 3.1416) * 0.5 + 0.5) * pow2(max(0.0, 1 - pow2(0.005 / (0.9 * state.x + 0.1) / pow2(pow2(state.y)) * relPos.y))) * state.x;
float spiralStrength = 200 * beamFactor * pow(featureDist, 7) * pow2(1.0 - featureDist) * pow2(max(0.0, 1 - pow2(0.02 / (0.6 * state.x * state.x + 0.4) / state.y * relPos.y)));
float spiralAngle = (0.4 / vortex_cylinderRadius * relPos.y - 0.2 * pow2(min(0.0, -2.5 + relPos.y / thisBallRadius))) / (state.x + 0.2);
vec2 spiralPos = mat2(cos(spiralAngle), -sin(spiralAngle), sin(spiralAngle), cos(spiralAngle)) * horizontalScaledPos;
vec4 beamNoise = texture2DLod(noisetex, noiseOffset + 5.0 / noiseTextureResolution * horizontalScaledPos, 0.0);
vec4 beamNoise2 = texture2DLod(noisetex, noiseOffset + 5.0 / noiseTextureResolution * vec2(relPos.y * 0.02 + 2.7 * beamNoise.gb - 3.6 * frameTimeCounter * 0.5), 0.0);
vec4 spiralNoise = texture2DLod(noisetex, noiseOffset + 5.0 / noiseTextureResolution * spiralPos, 0.0);
vec4 spiralNoise2 = texture2DLod(noisetex, noiseOffset + 20.0 / noiseTextureResolution * spiralPos, 0.0);
return vec4(beamStrength * beamNoise.r * beamNoise2.r * endDragonColM + spiralStrength * pow2(spiralNoise.r) * (0.5 + spiralNoise2.r) * beamColM, beamStrength + spiralStrength) * 0.3;
}
vec4 SingleEndCrystalVortex(vec3 start, vec3 direction, vec3 center, vec2 state, float dither) {
const float stepSize = 0.5;
float invHorizontalDirLen = 1.0 / length(direction.xz);
float thisBallRadius = GetBallRadius(state.x);
float closestProgress = clamp(
dot(center.xz - start.xz, direction.xz) * pow2(invHorizontalDirLen),
-thisBallRadius * invHorizontalDirLen,
1.0 + thisBallRadius * invHorizontalDirLen);
vec3 closestPos = start + closestProgress * direction;
float closestDist = length(closestPos.xz - center.xz);
if (closestDist > thisBallRadius) {
return vec4(0);
}
float startProgress = closestProgress - sqrt((thisBallRadius * thisBallRadius - closestDist * closestDist)) * invHorizontalDirLen;
float endProgress = min(1.0, 2 * closestProgress - startProgress);
startProgress = max(0.0, startProgress);
vec2 noiseOffset = (center.xz + cameraPosition.xz + vec2(3.0, 1.6) * frameTimeCounter) * 0.005;
vec4 colour = vec4(0);
float dist = startProgress + dither * invHorizontalDirLen * stepSize;
for (int k = 0; k < 100; k++) {
if (dist > endProgress) break;
colour += SampleEndCrystalVortex(start + dist * direction - center, state, noiseOffset);
dist += invHorizontalDirLen * stepSize;
}
return colour * stepSize * smoothstep(0.0, 1.0, state.x);
}
float EndCrystalBeamWidth(float x, float len) {
x = 0.5 * len - abs(x - 0.5 * len);
if (x <= -healing_ballRadius) return 0.0;
if (x < 0.5 * healing_ballRadius) return sqrt(pow2(healing_ballRadius) - pow2(x));
float expScale = sqrt(0.75) * healing_ballRadius - healing_beamRadius;
return healing_beamRadius + expScale * exp( -sqrt(1.0/3.0) / expScale * (x - 0.5 * healing_ballRadius));
}
vec4 SampleEndCrystalBeam(vec3 relPos, float len) {
float beamWidth = EndCrystalBeamWidth(relPos.x, len);
if (beamWidth > 0.0001) {
float beamFactor = smoothstep(0.0, 2.0 * healing_ballRadius, 0.5 * len - abs(relPos.x - 0.5 * len));
float noisyTime = frameTimeCounter + 0.4 * texture2DLod(noisetex, vec2(3.0 / noiseTextureResolution, frameTimeCounter / (0.45 * noiseTextureResolution)), 0.0).r;
relPos.yz /= beamWidth;
float strength = 0.0;
vec3 healBeamColor = vec3(0);
for (int k = 0; k < 3; k++) {
vec2 noiseCoords = vec2(0.2 / noiseTextureResolution * relPos.x, 0 + vec2(k, 6 * k) / noiseTextureResolution);
vec4 zapNoise0 = texture2DLod(noisetex, noiseCoords + floor(8.0 * noisyTime) / noiseTextureResolution, 0.0);
vec4 zapNoise1 = texture2DLod(noisetex, 3.3 * noiseCoords + floor(8.0 * noisyTime) / noiseTextureResolution, 0.0);
vec4 zapNoise2 = texture2DLod(noisetex, 6.8 * noiseCoords + (15.0 * frameTimeCounter) / noiseTextureResolution, 0.0);
vec2 thisRelPos = relPos.yz + beamFactor / beamWidth * (6.0 * zapNoise0.rb + 1.6 * zapNoise1.rb + 1.2 * zapNoise2.rb - (3.0 + 0.8 + 0.6));
vec4 sideNoise = texture2DLod(noisetex, (7.0 * thisRelPos.xy) / noiseTextureResolution, 0.0);
vec3 colorNoise = texture2DLod(noisetex, 4.0 * noiseCoords + floor(12.0 * noisyTime) / noiseTextureResolution, 0.0).rgb;
float centerDist0 = length(thisRelPos.xy);
float centerDist = centerDist0 - 1.2;
strength = max(strength, clamp( -centerDist, 0.0, 0.2) * pow2(max(0.0, 1.0 - pow2((centerDist0 - 1.0) * beamWidth * 0.5))) * mix(1.0, sideNoise.b, beamWidth / healing_ballRadius));
healBeamColor = mix(clamp01(saturateColors(beamColM, 0.8) - sideNoise.rgb * 0.08), saturateColors(beamColM, 1.3) * 1.3, colorNoise);
}
return strength / beamWidth * vec4(healBeamColor * 0.5, 1.0) + 0.2 * beamFactor * exp(-6.0 * dot(relPos.yz, relPos.yz)) * vec4(endDragonColM * 2.2, 1.0);
}
return vec4(0.0);
}
vec4 EndCrystalBeam(vec3 start, vec3 direction, vec3 startPos, vec3 endPos, float dither) {
vec3 startDiff = start - startPos;
vec3 beamDirection = endPos - startPos;
mat3 rotMat;
rotMat[0] = normalize(beamDirection);
rotMat[1] = normalize(cross(beamDirection, vec3(-2e-4, 1, 1e-5)));
rotMat[2] = cross(rotMat[0], rotMat[1]);
start *= rotMat;
startPos *= rotMat;
beamDirection *= rotMat;
direction *= rotMat;
const float stepSize = 0.5;
float invHorizontalDirLen = 1.0 / length(direction.yz);
float closestProgress = clamp(
dot(startPos.yz - start.yz, direction.yz) * pow2(invHorizontalDirLen),
-healing_boundRadius * invHorizontalDirLen,
1.0 + healing_boundRadius * invHorizontalDirLen);
vec3 closestPos = start + closestProgress * direction;
float closestDist = length(closestPos.yz - startPos.yz);
if (closestDist > healing_boundRadius) {
return vec4(0);
}
float startProgress = closestProgress - sqrt((healing_boundRadius * healing_boundRadius - closestDist * closestDist)) * invHorizontalDirLen;
float endProgress = min(1.0, 2 * closestProgress - startProgress);
startProgress = max(0.0, startProgress);
vec4 colour = vec4(0);
float dist = startProgress + dither * invHorizontalDirLen * stepSize;
for (int k = 0; k < 100; k++) {
if (dist > endProgress) break;
colour += SampleEndCrystalBeam(start + dist * direction - startPos, beamDirection.x);
dist += invHorizontalDirLen * stepSize;
}
return 3.0 * log(length(colour) * stepSize + 1.0) * normalize(colour + 0.0000001);
}
float GetDragonDeathFactor(float dragonDeathTime) {
return 0.02 * dragonDeathTime * exp(0.1 * dragonDeathTime);
}
vec4 SampleDeathBuildup(vec3 relPos, float dragonDeathTime) {
float effectFactor = GetDragonDeathFactor(dragonDeathTime);
float effectRadius = death_radius * effectFactor;
float sizeNoiseFactor = 1.0 + 0.3 * texture2DLod(noisetex, vec2(0.2, dragonDeathTime * 5.0 / noiseTextureResolution), 0.0).r;
float centerDist = length(relPos) / effectRadius;
relPos *= sizeNoiseFactor;
float angle = centerDist * 5.0 / log(dragonDeathTime * 0.6 + 1.0);
mat2 rotMat = mat2(
cos(angle), sin(angle),
-sin(angle), cos(angle)
);
relPos.xz = rotMat * relPos.xz;
vec2 val = pow(fract(hash23(floor(0.8 * relPos + 2.7 * sign(relPos) * exp(0.3 * dragonDeathTime)))), vec2(40.0 * pow2(centerDist))) * (1.0 - centerDist);
return 0.1 * (vec4(beamColM, 1.0) * (val.x + 0.4 * exp(-8.0 * pow2(centerDist))) + vec4(endDragonColM, 1.0) * (val.y + 0.1 * exp(-3.0 * pow2(centerDist))));
}
vec4 DragonDeathAnimation(vec3 start, vec3 direction, vec3 dragonPos, float dragonDeathTime, float dragonDeathFactor, float dither) {
float dirLen = length(direction);
float closestProgress = dot(dragonPos - start, direction) / pow2(dirLen);
vec4 colour = vec4(0);
if (dragonDeathFactor >= 0.99) {
float effectRadius = death_radius * GetDragonDeathFactor(dragonDeathTime);
vec3 closestPos = start + closestProgress * direction;
float closestDist = length(closestPos - dragonPos);
if (closestDist >= effectRadius) return vec4(0.0);
float stepSize = 0.5 / dirLen;
float startProgress = closestProgress - sqrt(pow2(effectRadius) - pow2(closestDist)) / dirLen;
float endProgress = min(1.0, 2.0 * closestProgress - startProgress);
startProgress = max(0.0, startProgress);
float dist = startProgress + stepSize * dither;
for (int k = 0; k < 150; k++) {
if (dist > endProgress) break;
colour += SampleDeathBuildup(start + dist * direction - dragonPos, dragonDeathTime);
dist += stepSize;
}
colour *= stepSize * dirLen;
} else {
vec3 closestPos = start + clamp(closestProgress, 0.0, 1.0) * direction;
float closestDist = length(dragonPos - closestPos);
colour = vec4(endDragonColM + 0.5 * beamColM, 1.0) * (0.4 * death_radius * (1.0 - exp(-dirLen/(4.0 * death_radius))) * exp(-10.0 * (1.0 - dragonDeathFactor) - closestDist * closestDist / (death_radius * death_radius)) * dragonDeathFactor);
}
return colour;
}
vec4 EndCrystalVortices(vec3 start, vec3 direction, float dither) {
vec4 color = vec4(0);
#if END_CRYSTAL_VORTEX_INTERNAL / 2 == 1 || DRAGON_DEATH_EFFECT_INTERNAL > 0
ivec4 rawDragonPos = ivec4(
texelFetch(endcrystal_sampler, ivec2(35, 5), 0).r,
texelFetch(endcrystal_sampler, ivec2(35, 6), 0).r,
texelFetch(endcrystal_sampler, ivec2(35, 7), 0).r,
texelFetch(endcrystal_sampler, ivec2(35, 8), 0).r
);
vec3 dragonPos = rawDragonPos.xyz != ivec3(0) ? 0.0001 * rawDragonPos.xyz : vec3(0.5, 80.5, 0.5) - cameraPosition;
#endif
#if END_CRYSTAL_VORTEX_INTERNAL / 2 == 1
vec3[15] healBeamEndPositions;
int isTarget = 0;
int healBeamCount = 15;
for (int k = 0; k < 15; k++) {
ivec4 rawPos = ivec4(
texelFetch(endcrystal_sampler, ivec2(20 + k, 5), 0).r,
texelFetch(endcrystal_sampler, ivec2(20 + k, 6), 0).r,
texelFetch(endcrystal_sampler, ivec2(20 + k, 7), 0).r,
texelFetch(endcrystal_sampler, ivec2(20 + k, 8), 0).r
);
if (rawPos.w == 0) {
healBeamCount = k;
break;
}
healBeamEndPositions[k] = vec3(rawPos.xyz) / rawPos.w;
isTarget |= (length(healBeamEndPositions[k].xz + cameraPosition.xz - 0.5) < 4.5 || length(dragonPos - healBeamEndPositions[k]) < 5.0) ? 1 << k : 0;
}
#endif
#if END_CRYSTAL_VORTEX_INTERNAL % 2 == 1
for (int k = 0; k < 20; k++) {
if (texelFetch(endcrystal_sampler, ivec2(k, 8), 0).r <= 0) continue;
ivec4 rawPos = ivec4(
texelFetch(endcrystal_sampler, ivec2(k, 5), 0).r,
texelFetch(endcrystal_sampler, ivec2(k, 6), 0).r,
texelFetch(endcrystal_sampler, ivec2(k, 7), 0).r,
texelFetch(endcrystal_sampler, ivec2(k, 8), 0).r
);
if (rawPos.w <= 0) {
continue;
}
int age = texelFetch(endcrystal_sampler, ivec2(k, 9), 0).r;
vec3 pos = rawPos.xyz * 0.0001;
#if END_CRYSTAL_VORTEX_INTERNAL / 2 == 1
for (int i = 0; i < healBeamCount; i++) {
isTarget |= length(pos - healBeamEndPositions[i]) < 4.5 ? 1<<(i+15) : 0;
}
#endif
vec2 state = vec2(clamp(rawPos.w / 15000.0, 0.0, 1.0), 1.00001 - exp(-0.0001 * age));
if (length(pos) > min(shadowDistance, far) * 0.9 && state.x < 0.999) {
state.y = state.x;
state.x = 1.0;
}
vec4 thisVortexCol = pow2(SingleEndCrystalVortex(start, direction, pos, state, dither));
color += thisVortexCol;
}
#endif
#if END_CRYSTAL_VORTEX_INTERNAL / 2 == 1
for (int k = 0; k < healBeamCount; k++) {
for (int l = k+1; l < healBeamCount; l++) {
if (
((isTarget >> k & 1) == 0 ^^ (isTarget >> l & 1) == 0)
#if END_CRYSTAL_VORTEX_INTERNAL % 2 == 1
&& ((isTarget >> k + 15 & 1) == 0 ^^ (isTarget >> l + 15 & 1) == 0)
#endif
) {
vec3 pos0 = healBeamEndPositions[k];
vec3 pos1 = healBeamEndPositions[l];
if (pos0.y > pos1.y) {
vec3 tmp = pos0;
pos0 = pos1;
pos1 = tmp;
}
color += pow2(EndCrystalBeam(start, direction, pos0, pos1, dither));
}
}
}
#endif
#if DRAGON_DEATH_EFFECT_INTERNAL > 0
int isDying = texelFetch(endcrystal_sampler, ivec2(35, 0), 0).r;
float dragonDeathTime = 0.0001 * rawDragonPos.w;
float dragonDeathFactor = 0.0001 * isDying;
// dragonDeathTime = mod(frameTimeCounter, 22.0);
// dragonDeathFactor = 2.2 - 0.1 * dragonDeathTime;
// dragonPos = vec3(0, 80, 0) - cameraPosition;
if (dragonDeathFactor > 0.001) {
color += pow2(DragonDeathAnimation(start, direction, dragonPos, dragonDeathTime, dragonDeathFactor, dither));
}
#endif
return sqrt(color) * (1.0 - maxBlindnessDarkness);
}