Dear Magnificent Community and Developers,
There's a PixiJs filter which is based on a shader ""Godrays" by alaingalvan", but it is required to somehow achieve such a filter/shader which does lighten certain areas where "lights" exist on a transparent stage without darkening the background.
The current version creates dark places ('0x000000') where the "lights" are not shown, but the darkness must not surpass the background ('0x333333' or '0xffffff' as in CSS), so it would look like a transparent filter in the result. This is the incorrect behavior since it creates black background where the "lights" are (it is correct that it affects the background):
const uniformData = {
time: {
type: 'float',
value: 0.0
},
lacunarity: {
type: 'float',
value: 30.0
},
gain: {
type: 'float',
value: 1.0
},
parallel: {
type: 'b',
value: true
},
light: {
type: 'v2',
value: [0.0, 0.0]
},
dimensions: {
type: 'v2',
value: [800, 400]
},
aspect: {
type: 'float',
value: 1.0
}
};
// 3D gradient Noise
// MIT License
// Copyright © 2013 Inigo Quilez
// https://www.shadertoy.com/view/Xsl3Dl
// Original: https://codepen.io/alaingalvan/pen/gOoEpW
const fragSource = '' +
`precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
uniform vec4 filterArea;
uniform vec2 dimensions;
uniform vec2 light;
uniform bool parallel;
uniform float aspect;
uniform float gain;
uniform float lacunarity;
uniform float time;
vec3 hash(vec3 p) {
p = vec3(
dot(p, vec3(127.1, 311.7, 74.7)),
dot(p, vec3(269.5, 183.3, 246.1)),
dot(p, vec3(113.5, 271.9, 124.6))
);
return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}
float noise(in vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
vec3 u = f * f * (3.0 - 2.0 * f);
return mix(
mix(
mix(
dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)),
dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)),
u.x
),
mix(
dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)),
dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)),
u.x
),
u.y
),
mix(
mix(
dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)),
dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)),
u.x
),
mix(
dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),
dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)),
u.x
),
u.y
),
u.z
);
}
float turb(vec3 pos, float lacunarity, float gain) {
float f, totalGain;
totalGain = gain;
vec3 q = 2.0 * pos;
f = totalGain * noise(q);
q = q * 2.01 * lacunarity;
totalGain *= gain;
f += totalGain * noise(q);
q = q * 3.02 * lacunarity;
totalGain *= gain;
f += totalGain * noise(q);
q = q * 3.03 * lacunarity;
totalGain *= gain;
f += totalGain * noise(q);
q = q * 3.01 * lacunarity;
totalGain *= gain;
f += totalGain * noise(q);
q = q * 3.99 * lacunarity;
totalGain *= gain;
f += totalGain * noise(q);
q = q * 3.98 * lacunarity;
totalGain *= gain;
f += totalGain * noise(q);
f = 3.0 * f;
return abs(f);
}
void main(void) {
gl_FragColor = texture2D(uSampler, vTextureCoord);
float d = 0.0;
vec2 coord = vTextureCoord;
if (parallel) {
float _cos = light.x;
float _sin = light.y;
d = (_cos * coord.x) + (_sin * coord.y * aspect);
} else {
float dx = coord.x - light.x / dimensions.x;
float dy = (coord.y - light.y / dimensions.y) * aspect;
float dis = sqrt(dx * dx + dy * dy) + 0.00001;
d = dy / dis;
}
vec2 dir = vec2(d, d);
float noise = turb(vec3(dir, 0.0) + vec3(time, 0.0, 62.1 + time) * 0.1, lacunarity, gain);
vec4 mist = vec4(noise, noise, noise, 1.0);
noise = mix(noise, 0.0, 0.3);
mist *= 1.0 - coord.y;
mist = clamp(mist, 0.0, 1.0);
gl_FragColor += mist;
}`;
class Rays extends PIXI.Filter
{
_options = {
angle: 30,
lacunarity: 1.5,
gain: 0.4,
parallel: Math.round(Math.random()),
speed: 0.0003
}
_timeInit = null;
constructor()
{
super(null, fragSource, uniformData);
this._timeInit = Date.now() - Math.floor(Math.random() * 99999999);
}
apply(filterManager, input, output, clearMode, _currentState)
{
this.uniforms.time = (Date.now() - this._timeInit) * this._options.speed;
this.uniforms.lacunarity = this._options.lacunarity;
this.uniforms.gain = this._options.gain;
this.uniforms.parallel = this._options.parallel;
const radians = this._options.angle * Math.PI / 180.0;
this.uniforms.light[0] = Math.cos(radians);
this.uniforms.light[1] = Math.sin(radians);
const {width, height} = input.filterFrame;
this.uniforms.dimensions[0] = width;
this.uniforms.dimensions[1] = height;
this.uniforms.aspect = height / width;
filterManager.applyFilter(this, input, output, clearMode);
}
}
// -------------------------------
class App
{
_app = null;
_resources = null;
constructor() {
this._app = new PIXI.Application({
view: canvas,
width: 800,
height: 600,
transparent: true,
resolution: window.devicePixelRatio
});
this._init();
}
addRelativeFeather(x, y) {
const feather = new PIXI.Sprite(this._resources.feather.texture);
const scale = Math.max(
this._app.screen.width / (feather.width * 3),
this._app.screen.height / (feather.height * 3)
);
feather.scale.set(scale, scale);
feather.position.set(x * feather.width, y * feather.height);
feather.filters = [new Rays()];
this._app.stage.addChild(feather);
}
_main() {
this.addRelativeFeather(0, 0);
this.addRelativeFeather(1, 0);
this.addRelativeFeather(2, 0);
this.addRelativeFeather(0, 1);
this.addRelativeFeather(2, 1);
}
_init() {
const loader = PIXI.Loader.shared;
loader.add({
name: 'feather',
// Icon "feather": https://www.flaticon.com/free-icon/feathers_6981026
url: 'https://cdn-icons-png.flaticon.com/512/6981/6981026.png',
});
loader.onComplete.once((loaderProcessed, resources) => {
this._resources = resources;
this._main();
});
loader.load();
}
}
const app = new App();
* {
margin: 0;
padding: 0;
background: #333;
}
<script src="https://pixijs.download/v6.5.8/pixi.js"></script>
<canvas id="canvas"></canvas>
Is it possible using this filter/shader? Is it correct that the issue is in the color matrix ("float noise(in vec3 p)")?
Also, it's probably the mist = clamp(mist, 0.0, 1.0);
where the second parameter might assume the minimum allowed if that makes sense.
Would it be correct to somehow base a pixel color on the source and only increase its "gain" instead?
I would highly appreciate any suggestion since I have already tried various options but it still darkens the origin!
Best and kind regards ✨
const uniformData = {
time: {
type: 'float',
value: 0.0
},
lacunarity: {
type: 'float',
value: 30.0
},
gain: {
type: 'float',
value: 1.0
},
parallel: {
type: 'b',
value: true
},
light: {
type: 'v2',
value: [0.0, 0.0]
},
dimensions: {
type: 'v2',
value: [800, 400]
},
aspect: {
type: 'float',
value: 1.0
}
};
// 3D gradient Noise
// MIT License
// Copyright © 2013 Inigo Quilez
// https://www.shadertoy.com/view/Xsl3Dl
// Original: https://codepen.io/alaingalvan/pen/gOoEpW
const fragSource = '' +
`precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
uniform vec4 filterArea;
uniform vec2 dimensions;
uniform vec2 light;
uniform bool parallel;
uniform float aspect;
uniform float gain;
uniform float lacunarity;
uniform float time;
vec3 hash(vec3 p) {
p = vec3(
dot(p, vec3(127.1, 311.7, 74.7)),
dot(p, vec3(269.5, 183.3, 246.1)),
dot(p, vec3(113.5, 271.9, 124.6))
);
return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}
float noise(in vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
vec3 u = f * f * (3.0 - 2.0 * f);
return mix(
mix(
mix(
dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)),
dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)),
u.x
),
mix(
dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)),
dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)),
u.x
),
u.y
),
mix(
mix(
dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)),
dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)),
u.x
),
mix(
dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),
dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)),
u.x
),
u.y
),
u.z
);
}
float turb(vec3 pos, float lacunarity, float gain) {
float f, totalGain;
totalGain = gain;
vec3 q = 2.0 * pos;
f = totalGain * noise(q);
q = q * 2.01 * lacunarity;
totalGain *= gain;
f += totalGain * noise(q);
q = q * 3.02 * lacunarity;
totalGain *= gain;
f += totalGain * noise(q);
q = q * 3.03 * lacunarity;
totalGain *= gain;
f += totalGain * noise(q);
q = q * 3.01 * lacunarity;
totalGain *= gain;
f += totalGain * noise(q);
q = q * 3.99 * lacunarity;
totalGain *= gain;
f += totalGain * noise(q);
q = q * 3.98 * lacunarity;
totalGain *= gain;
f += totalGain * noise(q);
f = 3.0 * f;
return abs(f);
}
void main(void) {
gl_FragColor = texture2D(uSampler, vTextureCoord);
float d = 0.0;
vec2 coord = vTextureCoord;
if (parallel) {
float _cos = light.x;
float _sin = light.y;
d = (_cos * coord.x) + (_sin * coord.y * aspect);
} else {
float dx = coord.x - light.x / dimensions.x;
float dy = (coord.y - light.y / dimensions.y) * aspect;
float dis = sqrt(dx * dx + dy * dy) + 0.00001;
d = dy / dis;
}
vec2 dir = vec2(d, d);
float noise = turb(vec3(dir, 0.0) + vec3(time, 0.0, 62.1 + time) * 0.1, lacunarity, gain);
vec4 mist = vec4(noise, noise, noise, noise);
noise = mix(noise, 0.0, 0.3);
mist *= 1.0 - coord.y;
mist = clamp(mist, 0.0, 1.0);
gl_FragColor += mist;
}`;
class Rays extends PIXI.Filter
{
_options = {
angle: 30,
lacunarity: 1.5,
gain: 0.4,
parallel: Math.round(Math.random()),
speed: 0.0003
}
_timeInit = null;
constructor()
{
super(null, fragSource, uniformData);
this._timeInit = Date.now() - Math.floor(Math.random() * 99999999);
}
apply(filterManager, input, output, clearMode, _currentState)
{
this.uniforms.time = (Date.now() - this._timeInit) * this._options.speed;
this.uniforms.lacunarity = this._options.lacunarity;
this.uniforms.gain = this._options.gain;
this.uniforms.parallel = this._options.parallel;
const radians = this._options.angle * Math.PI / 180.0;
this.uniforms.light[0] = Math.cos(radians);
this.uniforms.light[1] = Math.sin(radians);
const {width, height} = input.filterFrame;
this.uniforms.dimensions[0] = width;
this.uniforms.dimensions[1] = height;
this.uniforms.aspect = height / width;
filterManager.applyFilter(this, input, output, clearMode);
}
}
// -------------------------------
class App
{
_app = null;
_resources = null;
constructor() {
this._app = new PIXI.Application({
view: canvas,
width: 800,
height: 600,
transparent: true,
resolution: window.devicePixelRatio
});
this._init();
}
addRelativeFeather(x, y) {
const feather = new PIXI.Sprite(this._resources.feather.texture);
const scale = Math.max(
this._app.screen.width / (feather.width * 3),
this._app.screen.height / (feather.height * 3)
);
feather.scale.set(scale, scale);
feather.position.set(x * feather.width, y * feather.height);
feather.filters = [new Rays()];
this._app.stage.addChild(feather);
}
_main() {
this.addRelativeFeather(0, 0);
this.addRelativeFeather(1, 0);
this.addRelativeFeather(2, 0);
this.addRelativeFeather(0, 1);
this.addRelativeFeather(2, 1);
}
_init() {
const loader = PIXI.Loader.shared;
loader.add({
name: 'feather',
// Icon "feather": https://www.flaticon.com/free-icon/feathers_6981026
url: 'https://cdn-icons-png.flaticon.com/512/6981/6981026.png',
});
loader.onComplete.once((loaderProcessed, resources) => {
this._resources = resources;
this._main();
});
loader.load();
}
}
const app = new App();
* {
margin: 0;
padding: 0;
background: #333;
}
<script src="https://pixijs.download/v6.5.8/pixi.js"></script>
<canvas id="canvas"></canvas>
The shader defines the following vec4 for painting with hardcoded 1.0 alpha:
vec4 mist = vec4(noise, noise, noise, 1.0);
Use the whiteness of the noise value as alpha as following to get rid of the black background:
vec4 mist = vec4(noise, noise, noise, noise);