Skip to content

Commit

Permalink
Particles: apply perlin noise shift as turbulence.
Browse files Browse the repository at this point in the history
  • Loading branch information
kosua20 committed Aug 27, 2023
1 parent c555c51 commit 75eab94
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 4 deletions.
16 changes: 13 additions & 3 deletions resources/shaders/particles.vert
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ uniform vec3 baseColor[SETS_COUNT];
uniform vec2 inverseScreenSize;
uniform sampler2D textureParticles;
uniform vec2 inverseTextureSize;
uniform sampler2D textureNoise;

uniform int globalId;
uniform float duration;
Expand All @@ -21,6 +22,9 @@ uniform float expansionFactor = 1.0;
uniform float speedScaling = 0.2;
uniform float keyboardHeight = 0.25;

uniform float turbulenceStrength;
uniform float turbulenceScale;

uniform int minNote;
uniform float notesCount;
uniform bool horizontalMode = false;
Expand Down Expand Up @@ -86,13 +90,19 @@ void main(){
float xshift = -1.0 + ((shifts[globalId] - shifts[int(minNote)]) * 2.0 + 1.0) / notesCount;
// Combine global shift (due to note id) and local shift (based on read position).
vec2 globalShift = vec2(xshift, (2.0 * keyboardHeight - 1.0)-0.02);
vec2 localShift = 0.003 * scale * v + shift * duration * vec2(1.0,0.5);
vec2 localShift = shift * duration * vec2(1.0,0.5);
vec2 vertexShift = 0.003 * scale * v;

float screenRatio = inverseScreenSize.y/inverseScreenSize.x;
vec2 screenScaling = vec2(1.0, horizontalMode ? (1.0/screenRatio) : screenRatio);

vec2 finalPos = globalShift + screenScaling * localShift;

vec2 particlePos = globalShift + screenScaling * localShift;

vec2 curlNoise = textureLod(textureNoise, turbulenceScale * particlePos.xy / screenScaling, 0).gb;
curlNoise.x = 2.0 * curlNoise.x - 1.0;
vec2 curlShift = 0.01 * turbulenceStrength * time * curlNoise;

vec2 finalPos = particlePos + screenScaling * (vertexShift + curlShift);
// Discard particles that reached the end of their trajectories by putting them off-screen.
finalPos = mix(vec2(-200.0),finalPos, position.z);
// Output final particle position.
Expand Down
2 changes: 1 addition & 1 deletion src/resources/shaders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const std::unordered_map<std::string, std::string> shaders = {
{ "flashes_frag", "#version 330\n #define SETS_COUNT 12\n in INTERFACE {\n vec2 uv;\n float onChannel;\n float id;\n } In;\n uniform sampler2D textureFlash;\n uniform float time;\n uniform vec3 baseColor[SETS_COUNT];\n uniform float haloIntensity;\n uniform float haloInnerRadius;\n uniform float haloOuterRadius;\n uniform int texRowCount;\n uniform int texColCount;\n out vec4 fragColor;\n float rand(vec2 co){\n return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);\n }\n void main(){\n \n // If not on, discard flash immediatly.\n int cid = int(In.onChannel);\n float mask = 0.0;\n const float atlasSpeed = 15.0;\n const float safetyMargin = 0.05;\n // If up half, read from texture atlas.\n if(In.uv.y > 0.0){\n // Select a sprite, depending on time and flash id.\n int atlasShift = int(floor(mod(atlasSpeed * time, texRowCount*texColCount)) + floor(rand(In.id * vec2(time,1.0))));\n ivec2 atlasIndex = ivec2(atlasShift % texColCount, atlasShift / texColCount);\n // Scale UV to fit in one sprite from atlas.\n vec2 localUV = clamp(In.uv * vec2(1.0, 2.0) + vec2(0.5, 0.0), safetyMargin, 1.0-safetyMargin);\n \n // Read in black and white texture do determine opacity (mask).\n vec2 finalUV = (vec2(atlasIndex) + localUV)/vec2(texColCount, texRowCount);\n mask = texture(textureFlash,finalUV).r;\n }\n if(cid < 0){\n discard;\n }\n \n // Colored sprite.\n vec4 spriteColor = vec4(baseColor[cid], mask);\n \n // Circular halo effect.\n float haloAlpha = 1.0 - smoothstep(haloInnerRadius, haloOuterRadius, length(In.uv) / 0.5);\n vec4 haloColor;\n haloColor.rgb = baseColor[cid] + haloIntensity * vec3(1.0);\n haloColor.a = haloAlpha * 0.92;\n \n // Mix the sprite color and the halo effect.\n fragColor = mix(spriteColor, haloColor, haloColor.a);\n \n // Boost intensity.\n fragColor *= 1.1;\n // Premultiplied alpha.\n fragColor.rgb *= fragColor.a;\n }\n "},
{ "notes_vert", "#version 330\n layout(location = 0) in vec2 v;\n layout(location = 1) in vec4 id; //note id, start, duration, is minor\n layout(location = 2) in float channel; //note id, start, duration, is minor\n uniform float time;\n uniform float mainSpeed;\n uniform float minorsWidth = 1.0;\n uniform float keyboardHeight = 0.25;\n uniform bool reverseMode;\n uniform bool horizontalMode;\n vec2 flipIfNeeded(vec2 inPos){\n return horizontalMode ? vec2(inPos.y, -inPos.x) : inPos;\n }\n uniform int minNoteMajor;\n uniform float notesCount;\n out INTERFACE {\n vec2 uv;\n vec2 noteSize;\n flat vec2 noteCorner;\n float isMinor;\n float channel;\n } Out;\n #define MAJOR_COUNT 75\n const int minorIds[MAJOR_COUNT] = int[](1, 3, 0, 6, 8, 10, 0, 13, 15, 0, 18, 20, 22, 0, 25, 27, 0, 30, 32, 34, 0, 37, 39, 0, 42, 44, 46, 0, 49, 51, 0, 54, 56, 58, 0, 61, 63, 0, 66, 68, 70, 0, 73, 75, 0, 78, 80, 82, 0, 85, 87, 0, 90, 92, 94, 0, 97, 99, 0, 102, 104, 106, 0, 109, 111, 0, 114, 116, 118, 0, 121, 123, 0, 126, 0);\n float minorShift(int id){\n if(id == 1 || id == 6){\n return -0.1;\n }\n if(id == 3 || id == 10){\n return 0.1;\n }\n return 0.0;\n }\n void main(){\n \n float scalingFactor = id.w != 0.0 ? minorsWidth : 1.0;\n // Size of the note : width, height based on duration and current speed.\n Out.noteSize = vec2(0.9*2.0/notesCount * scalingFactor, id.z*mainSpeed);\n \n // Compute note shift.\n // Horizontal shift based on note id, width of keyboard, and if the note is minor or not.\n // Vertical shift based on note start time, current time, speed, and height of the note quad.\n //float a = (1.0/(notesCount-1.0)) * (2.0 - 2.0/notesCount);\n //float b = -1.0 + 1.0/notesCount;\n // This should be in -1.0, 1.0.\n // input: id.x is in [0 MAJOR_COUNT]\n // we want minNote to -1+1/c, maxNote to 1-1/c\n float a = 2.0;\n float b = -notesCount + 1.0 - 2.0 * float(minNoteMajor);\n float horizLoc = (id.x * a + b + id.w) / notesCount;\n float vertLoc = 2.0 * keyboardHeight - 1.0;\n vertLoc += (reverseMode ? -1.0 : 1.0) * (Out.noteSize.y * 0.5 + mainSpeed * (id.y - time));\n vec2 noteShift = vec2(horizLoc, vertLoc);\n noteShift.x += id.w * minorShift(minorIds[int(id.x)] % 12) * Out.noteSize.x;\n // Scale uv.\n Out.uv = Out.noteSize * v;\n Out.isMinor = id.w;\n Out.channel = channel;\n // Output position.\n gl_Position = vec4(flipIfNeeded(Out.noteSize * v + noteShift), 0.0 , 1.0);\n // Offset of bottom left corner.\n Out.noteCorner = flipIfNeeded(Out.noteSize * vec2(-0.5, reverseMode ? 0.5 : -0.5) + noteShift);\n }\n "},
{ "notes_frag", "#version 330\n #define SETS_COUNT 12\n in INTERFACE {\n vec2 uv;\n vec2 noteSize;\n flat vec2 noteCorner;\n float isMinor;\n float channel;\n } In;\n uniform vec3 baseColor[SETS_COUNT];\n uniform vec3 minorColor[SETS_COUNT];\n uniform vec2 inverseScreenSize;\n uniform float time;\n uniform float mainSpeed;\n uniform float colorScale;\n uniform float keyboardHeight = 0.25;\n uniform float fadeOut;\n uniform float edgeWidth;\n uniform float edgeBrightness;\n uniform float cornerRadius;\n uniform bool horizontalMode;\n uniform bool reverseMode;\n uniform sampler2D majorTexture;\n uniform sampler2D minorTexture;\n uniform bool useMajorTexture;\n uniform bool useMinorTexture;\n uniform vec2 texturesScale;\n uniform vec2 texturesStrength;\n uniform bool scrollMajorTexture;\n uniform bool scrollMinorTexture;\n out vec4 fragColor;\n vec2 flipIfNeeded(vec2 inPos){\n return horizontalMode ? vec2(inPos.y, -inPos.x) : inPos;\n }\n void main(){\n \n vec2 normalizedCoord = vec2(gl_FragCoord.xy) * inverseScreenSize;\n vec3 tinting = vec3(1.0);\n vec2 tintingUV = 2.0 * normalizedCoord - 1.0;\n // Preserve screen pixel density, corrected for aspect ratio (on X so that preserving scrolling speed is easier).\n vec2 aspectRatio = vec2(inverseScreenSize.y / inverseScreenSize.x, 1.0);\n vec2 tintingScale = aspectRatio;\n tintingScale.x *= (horizontalMode && !reverseMode ? -1.0 : 1.0);\n tintingScale.y *= (!horizontalMode && reverseMode ? -1.0 : 1.0);\n if(useMajorTexture){\n vec2 texUVOffset = scrollMajorTexture ? In.noteCorner : vec2(0.0);\n vec2 texUV = texturesScale.x * tintingScale * (tintingUV - texUVOffset);\n texUV = flipIfNeeded(texUV);\n // Only on major notes.\n float intensity = (1.0 - In.isMinor) * texturesStrength.x;\n tinting = mix(tinting, texture(majorTexture, texUV).rgb, intensity);\n }\n if(useMinorTexture){\n vec2 texUVOffset = scrollMinorTexture ? In.noteCorner : vec2(0.0);\n vec2 texUV = texturesScale.y * tintingScale * (tintingUV - texUVOffset);\n texUV = flipIfNeeded(texUV);\n // Only on minor notes.\n float intensity = In.isMinor * texturesStrength.y;\n tinting = mix(tinting, texture(minorTexture, texUV).rgb, intensity);\n }\n \n \n // Rounded corner (super-ellipse equation).\n vec2 ellipseCoords = abs(In.uv / (0.5 * In.noteSize));\n vec2 ellipseExps = In.noteSize / max(cornerRadius, 1e-3);\n float radiusPosition = pow(ellipseCoords.x, ellipseExps.x) + pow(ellipseCoords.y, ellipseExps.y);\n // Fragment color.\n int cid = int(In.channel);\n fragColor.rgb = tinting * colorScale * mix(baseColor[cid], minorColor[cid], In.isMinor);\n // Apply scaling factor to edge.\n float deltaPix = fwidth(In.uv.x) * 4.0;\n float edgeIntensity = smoothstep(1.0 - edgeWidth - deltaPix, 1.0 - edgeWidth + deltaPix, radiusPosition);\n fragColor.rgb *= (1.0f + (edgeBrightness - 1.0f) * edgeIntensity);\n // If lower area of the screen, discard fragment as it should be hidden behind the keyboard.\n if((horizontalMode ? normalizedCoord.x : normalizedCoord.y) < keyboardHeight){\n discard;\n }\n if( radiusPosition > 1.0){\n discard;\n }\n \n float distFromBottom = horizontalMode ? normalizedCoord.x : normalizedCoord.y;\n float fadeOutFinal = min(fadeOut, 0.9999);\n distFromBottom = max(distFromBottom - fadeOutFinal, 0.0) / (1.0 - fadeOutFinal);\n float alpha = 1.0 - distFromBottom;\n fragColor.a = alpha;\n }\n "},
{ "particles_vert", "#version 330\n #define SETS_COUNT 12\n layout(location = 0) in vec2 v;\n uniform float time;\n uniform float scale;\n uniform vec3 baseColor[SETS_COUNT];\n uniform vec2 inverseScreenSize;\n uniform sampler2D textureParticles;\n uniform vec2 inverseTextureSize;\n uniform int globalId;\n uniform float duration;\n uniform int channel;\n uniform int texCount;\n uniform float colorScale;\n uniform float expansionFactor = 1.0;\n uniform float speedScaling = 0.2;\n uniform float keyboardHeight = 0.25;\n uniform int minNote;\n uniform float notesCount;\n uniform bool horizontalMode = false;\n vec2 flipIfNeeded(vec2 inPos){\n return horizontalMode ? vec2(inPos.y, -inPos.x) : inPos;\n }\n const float shifts[128] = float[](\n 0,0.5,1,1.5,2,3,3.5,4,4.5,5,5.5,6,7,7.5,8,8.5,9,10,10.5,11,11.5,12,12.5,13,14,14.5,15,15.5,16,17,17.5,18,18.5,19,19.5,20,21,21.5,22,22.5,23,24,24.5,25,25.5,26,26.5,27,28,28.5,29,29.5,30,31,31.5,32,32.5,33,33.5,34,35,35.5,36,36.5,37,38,38.5,39,39.5,40,40.5,41,42,42.5,43,43.5,44,45,45.5,46,46.5,47,47.5,48,49,49.5,50,50.5,51,52,52.5,53,53.5,54,54.5,55,56,56.5,57,57.5,58,59,59.5,60,60.5,61,61.5,62,63,63.5,64,64.5,65,66,66.5,67,67.5,68,68.5,69,70,70.5,71,71.5,72,73,73.5,74\n );\n out INTERFACE {\n vec4 color;\n vec2 uv;\n float id;\n } Out;\n float rand(vec2 co){\n return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);\n }\n void main(){\n Out.id = float(gl_InstanceID % texCount);\n Out.uv = v + 0.5;\n // Fade color based on time.\n Out.color = vec4(colorScale * baseColor[channel], 1.0-time*time);\n \n float localTime = speedScaling * time * duration;\n float particlesCount = 1.0/inverseTextureSize.y;\n \n // Pick particle id at random.\n float particleId = float(gl_InstanceID) + floor(particlesCount * 10.0 * rand(vec2(globalId,globalId)));\n float textureId = mod(particleId,particlesCount);\n float particleShift = floor(particleId/particlesCount);\n \n // Particle uv, in pixels.\n vec2 particleUV = vec2(localTime / inverseTextureSize.x + 10.0 * particleShift, textureId);\n // UV in [0,1]\n particleUV = (particleUV+0.5)*vec2(1.0,-1.0)*inverseTextureSize;\n // Avoid wrapping.\n particleUV.x = clamp(particleUV.x,0.0,1.0);\n // We want to skip reading from the very beginning of the trajectories because they are identical.\n // particleUV.x = 0.95 * particleUV.x + 0.05;\n // Read corresponding trajectory to get particle current position.\n vec3 position = texture(textureParticles, particleUV).xyz;\n // Center position (from [0,1] to [-0.5,0.5] on x axis.\n position.x -= 0.5;\n \n // Compute shift, randomly disturb it.\n vec2 shift = 0.5*position.xy;\n float random = rand(vec2(particleId + float(globalId),time*0.000002+100.0*float(globalId)));\n shift += vec2(0.0,0.1*random);\n \n // Scale shift with time (expansion effect).\n shift = shift*time*expansionFactor;\n // and with altitude of the particle (ditto).\n shift.x *= max(0.5, pow(shift.y,0.3));\n \n // Horizontal shift is based on the note ID.\n float xshift = -1.0 + ((shifts[globalId] - shifts[int(minNote)]) * 2.0 + 1.0) / notesCount;\n // Combine global shift (due to note id) and local shift (based on read position).\n vec2 globalShift = vec2(xshift, (2.0 * keyboardHeight - 1.0)-0.02);\n vec2 localShift = 0.003 * scale * v + shift * duration * vec2(1.0,0.5);\n float screenRatio = inverseScreenSize.y/inverseScreenSize.x;\n vec2 screenScaling = vec2(1.0, horizontalMode ? (1.0/screenRatio) : screenRatio);\n vec2 finalPos = globalShift + screenScaling * localShift;\n \n // Discard particles that reached the end of their trajectories by putting them off-screen.\n finalPos = mix(vec2(-200.0),finalPos, position.z);\n // Output final particle position.\n gl_Position = vec4(flipIfNeeded(finalPos), 0.0, 1.0);\n \n \n }\n "},
{ "particles_vert", "#version 330\n #define SETS_COUNT 12\n layout(location = 0) in vec2 v;\n uniform float time;\n uniform float scale;\n uniform vec3 baseColor[SETS_COUNT];\n uniform vec2 inverseScreenSize;\n uniform sampler2D textureParticles;\n uniform vec2 inverseTextureSize;\n uniform sampler2D textureNoise;\n uniform int globalId;\n uniform float duration;\n uniform int channel;\n uniform int texCount;\n uniform float colorScale;\n uniform float expansionFactor = 1.0;\n uniform float speedScaling = 0.2;\n uniform float keyboardHeight = 0.25;\n uniform float turbulenceStrength;\n uniform float turbulenceScale;\n uniform int minNote;\n uniform float notesCount;\n uniform bool horizontalMode = false;\n vec2 flipIfNeeded(vec2 inPos){\n return horizontalMode ? vec2(inPos.y, -inPos.x) : inPos;\n }\n const float shifts[128] = float[](\n 0,0.5,1,1.5,2,3,3.5,4,4.5,5,5.5,6,7,7.5,8,8.5,9,10,10.5,11,11.5,12,12.5,13,14,14.5,15,15.5,16,17,17.5,18,18.5,19,19.5,20,21,21.5,22,22.5,23,24,24.5,25,25.5,26,26.5,27,28,28.5,29,29.5,30,31,31.5,32,32.5,33,33.5,34,35,35.5,36,36.5,37,38,38.5,39,39.5,40,40.5,41,42,42.5,43,43.5,44,45,45.5,46,46.5,47,47.5,48,49,49.5,50,50.5,51,52,52.5,53,53.5,54,54.5,55,56,56.5,57,57.5,58,59,59.5,60,60.5,61,61.5,62,63,63.5,64,64.5,65,66,66.5,67,67.5,68,68.5,69,70,70.5,71,71.5,72,73,73.5,74\n );\n out INTERFACE {\n vec4 color;\n vec2 uv;\n float id;\n } Out;\n float rand(vec2 co){\n return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);\n }\n void main(){\n Out.id = float(gl_InstanceID % texCount);\n Out.uv = v + 0.5;\n // Fade color based on time.\n Out.color = vec4(colorScale * baseColor[channel], 1.0-time*time);\n \n float localTime = speedScaling * time * duration;\n float particlesCount = 1.0/inverseTextureSize.y;\n \n // Pick particle id at random.\n float particleId = float(gl_InstanceID) + floor(particlesCount * 10.0 * rand(vec2(globalId,globalId)));\n float textureId = mod(particleId,particlesCount);\n float particleShift = floor(particleId/particlesCount);\n \n // Particle uv, in pixels.\n vec2 particleUV = vec2(localTime / inverseTextureSize.x + 10.0 * particleShift, textureId);\n // UV in [0,1]\n particleUV = (particleUV+0.5)*vec2(1.0,-1.0)*inverseTextureSize;\n // Avoid wrapping.\n particleUV.x = clamp(particleUV.x,0.0,1.0);\n // We want to skip reading from the very beginning of the trajectories because they are identical.\n // particleUV.x = 0.95 * particleUV.x + 0.05;\n // Read corresponding trajectory to get particle current position.\n vec3 position = texture(textureParticles, particleUV).xyz;\n // Center position (from [0,1] to [-0.5,0.5] on x axis.\n position.x -= 0.5;\n \n // Compute shift, randomly disturb it.\n vec2 shift = 0.5*position.xy;\n float random = rand(vec2(particleId + float(globalId),time*0.000002+100.0*float(globalId)));\n shift += vec2(0.0,0.1*random);\n \n // Scale shift with time (expansion effect).\n shift = shift*time*expansionFactor;\n // and with altitude of the particle (ditto).\n shift.x *= max(0.5, pow(shift.y,0.3));\n \n // Horizontal shift is based on the note ID.\n float xshift = -1.0 + ((shifts[globalId] - shifts[int(minNote)]) * 2.0 + 1.0) / notesCount;\n // Combine global shift (due to note id) and local shift (based on read position).\n vec2 globalShift = vec2(xshift, (2.0 * keyboardHeight - 1.0)-0.02);\n vec2 localShift = shift * duration * vec2(1.0,0.5);\n vec2 vertexShift = 0.003 * scale * v;\n float screenRatio = inverseScreenSize.y/inverseScreenSize.x;\n vec2 screenScaling = vec2(1.0, horizontalMode ? (1.0/screenRatio) : screenRatio);\n vec2 particlePos = globalShift + screenScaling * localShift;\n vec2 curlNoise = textureLod(textureNoise, turbulenceScale * particlePos.xy / screenScaling, 0).gb;\n curlNoise.x = 2.0 * curlNoise.x - 1.0;\n vec2 curlShift = 0.01 * turbulenceStrength * time * curlNoise;\n vec2 finalPos = particlePos + screenScaling * (vertexShift + curlShift);\n // Discard particles that reached the end of their trajectories by putting them off-screen.\n finalPos = mix(vec2(-200.0),finalPos, position.z);\n // Output final particle position.\n gl_Position = vec4(flipIfNeeded(finalPos), 0.0, 1.0);\n \n \n }\n "},
{ "particles_frag", "#version 330\n in INTERFACE {\n vec4 color;\n vec2 uv;\n float id;\n } In;\n uniform sampler2DArray lookParticles;\n out vec4 fragColor;\n void main(){\n float alpha = texture(lookParticles, vec3(In.uv, In.id)).r;\n fragColor = In.color;\n fragColor.a *= alpha;\n }\n "},
{ "particlesblur_vert", "#version 330\n layout(location = 0) in vec3 v;\n out INTERFACE {\n vec2 uv;\n } Out ;\n void main(){\n \n // We directly output the position.\n gl_Position = vec4(v, 1.0);\n // Output the UV coordinates computed from the positions.\n Out.uv = v.xy * 0.5 + 0.5;\n \n }\n "},
{ "particlesblur_frag", "#version 330\n in INTERFACE {\n vec2 uv;\n } In ;\n uniform sampler2D screenTexture;\n uniform vec2 inverseScreenSize;\n uniform vec3 backgroundColor = vec3(0.0);\n uniform float attenuationFactor = 0.99;\n uniform float time;\n out vec4 fragColor;\n vec4 blur(vec2 uv, bool vert){\n vec4 color = 0.2270270270 * texture(screenTexture, uv);\n vec2 pixelOffset = vert ? vec2(0.0, inverseScreenSize.y) : vec2(inverseScreenSize.x, 0.0);\n vec2 texCoordOffset0 = 1.3846153846 * pixelOffset;\n vec4 col0 = texture(screenTexture, uv + texCoordOffset0) + texture(screenTexture, uv - texCoordOffset0);\n color += 0.3162162162 * col0;\n vec2 texCoordOffset1 = 3.2307692308 * pixelOffset;\n vec4 col1 = texture(screenTexture, uv + texCoordOffset1) + texture(screenTexture, uv - texCoordOffset1);\n color += 0.0702702703 * col1;\n return color;\n }\n void main(){\n \n // Gaussian blur separated in two 1D convolutions, relying on bilinear interpolation to\n // sample multiple pixels at once with the proper weights.\n vec4 color = blur(In.uv, time > 0.5);\n // Include decay for fade out.\n fragColor = mix(vec4(backgroundColor, 0.0), color, attenuationFactor);\n \n }\n "},
Expand Down

0 comments on commit 75eab94

Please sign in to comment.