297 lines
15 KiB
GLSL
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);
|
|
}
|