Created
March 3, 2023 09:19
-
-
Save xkisu/01188a68d5465b1a15c06cf119d012f7 to your computer and use it in GitHub Desktop.
PixiJS smoke effect in React
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {BlurFilter, Filter, RENDERER_TYPE, TextStyle} from 'pixi.js'; | |
import {Container, Sprite, Stage, Text, useApp, useTick} from '@pixi/react'; | |
import {PropsWithChildren, useMemo} from 'react'; | |
// Adapted from https://gist.github.com/OmarShehata/9650b8ee419db3696ce555f10712d499 | |
// ref: https://codepen.io/omarshe7ta/pen/xVeWWy | |
// debugging ref: https://www.html5gamedevs.com/topic/44542-need-help-of-migrating-shader-from-v4-to-v5/ | |
// https://www.pixiplayground.com/#/edit/6ThqOOz-SVJe3AyvkbnaU | |
// https://github.com/pixijs/pixijs/wiki/v5-Creating-filters | |
const fragShader = `#ifdef GL_ES | |
precision mediump float; | |
#endif | |
// Cant be named 'resolution' since it's a default uniform | |
// provided by PIXI.Filter(), see: https://pixijs.download/dev/docs/PIXI.Filter.html | |
// And the PIXI's resolution uniform is the ratio of screen (CSS) pixels to real pixels. | |
uniform vec2 dimensions; | |
uniform float time; | |
uniform float alpha; | |
uniform vec2 speed; | |
uniform float shift; | |
float rand(vec2 n) { | |
//This is just a compounded expression to simulate a random number based on a seed given as n | |
return fract(cos(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); | |
} | |
float noise(vec2 n) { | |
//Uses the rand function to generate noise | |
const vec2 d = vec2(0.0, 1.0); | |
vec2 b = floor(n), f = smoothstep(vec2(0.0), vec2(1.0), fract(n)); | |
return mix(mix(rand(b), rand(b + d.yx), f.x), mix(rand(b + d.xy), rand(b + d.yy), f.x), f.y); | |
} | |
float fbm(vec2 n) { | |
// fbm stands for 'Fractal Brownian Motion', | |
// see more in: https://thebookofshaders.com/13/ | |
// https://en.wikipedia.org/wiki/Fractional_Brownian_motion | |
float total = 0.0, amplitude = 1.0; | |
for (int i = 0; i < 4; i++) { | |
total += noise(n) * amplitude; | |
n += n; | |
amplitude *= 0.5; | |
} | |
return total; | |
} | |
void main() { | |
//This is where our shader comes together | |
const vec3 c1 = vec3(126.0/255.0, 0.0/255.0, 97.0/255.0); | |
const vec3 c2 = vec3(173.0/255.0, 0.0/255.0, 161.4/255.0); | |
const vec3 c3 = vec3(0.2, 0.0, 0.0); | |
const vec3 c4 = vec3(164.0/255.0, 1.0/255.0, 214.4/255.0); | |
const vec3 c5 = vec3(0.1); | |
const vec3 c6 = vec3(0.9); | |
//This is how "packed" the smoke is in our area. Try changing 8.0 to 1.0, or something else | |
vec2 p = gl_FragCoord.xy * 8.0 / dimensions.xx; | |
//The fbm function takes p as its seed (so each pixel looks different) and time (so it shifts over time) | |
float q = fbm(p - time * 0.1); | |
vec2 r = vec2(fbm(p + q + time * speed.x - p.x - p.y), fbm(p + q - time * speed.y)); | |
vec3 c = mix(c1, c2, fbm(p + r)) + mix(c3, c4, r.x) - mix(c5, c6, r.y); | |
float grad = gl_FragCoord.y / dimensions.y; | |
gl_FragColor = vec4(c * cos(shift * gl_FragCoord.y / dimensions.y), 1.0); | |
// gl_FragColor.xyz *= 1.0-grad; | |
} | |
` | |
/* | |
#ifdef GL_ES | |
precision mediump float; | |
#endif | |
uniform float time; | |
void main() { | |
gl_FragColor = vec4(abs(sin(time)),0.0,0.0,1.0); | |
}*/ | |
function SmokeContainer ({ children }: PropsWithChildren<{}>) { | |
const app = useApp(); | |
if (app.renderer.type == RENDERER_TYPE.WEBGL) { | |
console.log("using webgl") | |
} else { | |
console.log("using canvas") | |
} | |
// let scale = [1,1] | |
app.renderer.resize(window.innerWidth, window.innerHeight); | |
// window.onresize = function() { | |
// console.log('resized') | |
// // app.renderer.resize(window.innerWidth, window.innerHeight); | |
// // app.resize() | |
// } | |
const smokeShader = useMemo(() => new Filter(undefined, fragShader, { | |
dimensions: [app.renderer.width, app.renderer.height], | |
alpha: 1.0, | |
shift: 1.6, | |
time: 1, | |
speed: { x: 0.7, y: 0.4 } | |
}), []); | |
var count = 0 | |
useTick(delta => { | |
count += 0.01 // *delta | |
smokeShader.uniforms.time = count; | |
}) | |
return ( | |
<Sprite | |
image="/pixi_v3_github-pad.png" | |
anchor={[0,0]} | |
width={app.renderer.width} | |
height={app.renderer.height} | |
filters={[smokeShader]} | |
/> | |
) | |
} | |
export default function SmokeBackground () { | |
const blurFilter = useMemo(() => new BlurFilter(4), []); | |
return ( | |
<Stage options={{ | |
backgroundColor: 0xffffff, | |
backgroundAlpha: 0, | |
antialias: true, | |
resizeTo: window | |
}}> | |
<SmokeContainer /> | |
<Container> | |
<Sprite | |
image="https://pixijs.io/pixi-react/img/bunny.png" | |
x={400} | |
y={270} | |
anchor={{ x: 0.5, y: 0.5 }} | |
filters={[blurFilter]} | |
/> | |
<Text | |
text="Hello World" | |
anchor={{ x: 0.5, y: 0.5 }} | |
style={new TextStyle({ | |
fill: 'white' | |
})} | |
/> | |
</Container> | |
</Stage> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment