Skip to content

Commit 0435e3a

Browse files
Feat/radial gradient effect (#25)
- Added Radial Gradient. - Gradients now properly overlay the mask color instead of completely replacing it.
2 parents 9933332 + cfc9566 commit 0435e3a

File tree

4 files changed

+186
-2
lines changed

4 files changed

+186
-2
lines changed

examples/tests/dynamic-shader.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default async function ({ renderer }: ExampleSettings) {
4040
angle: 220,
4141
colors: [
4242
0xff0000ff, 0x00ff00ff, 0xff0000ff, 0x0000ffff, 0xffff00ff,
43-
0xff0000ff,
43+
0xff000000,
4444
],
4545
},
4646
},
@@ -200,6 +200,28 @@ export default async function ({ renderer }: ExampleSettings) {
200200
parent: renderer.root,
201201
});
202202

203+
const t7 = renderer.createNode({
204+
x: 1000,
205+
y: 700,
206+
width: 750,
207+
height: 250,
208+
shader: renderer.makeShader('DynamicShader', {
209+
effects: [
210+
{
211+
type: 'radialGradient',
212+
props: {
213+
colors: [0xff0000ff, 0x00ff00ff, 0x00000000],
214+
stops: [0.1, 0.4, 1.0],
215+
height: 200,
216+
width: 1000,
217+
pivot: [0, 0.5],
218+
},
219+
},
220+
],
221+
}),
222+
parent: renderer.root,
223+
});
224+
203225
/*
204226
* End: Sprite Map Demo
205227
*/

src/core/renderers/webgl/shaders/DynamicShader.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { BorderBottomEffect } from './effects/BorderBottomEffect.js';
3636
import { BorderLeftEffect } from './effects/BorderLeftEffect.js';
3737
import { GlitchEffect } from './effects/GlitchEffect.js';
3838
import { FadeOutEffect } from './effects/FadeOutEffect.js';
39+
import { RadialGradientEffect } from './effects/RadialGradientEffect.js';
3940

4041
export interface DynamicShaderProps
4142
extends DimensionsShaderProp,
@@ -52,6 +53,7 @@ export interface EffectMap {
5253
borderTop: typeof BorderTopEffect;
5354
fadeOut: typeof FadeOutEffect;
5455
linearGradient: typeof LinearGradientEffect;
56+
radialGradient: typeof RadialGradientEffect;
5557
grayscale: typeof GrayscaleEffect;
5658
glitch: typeof GlitchEffect;
5759
}
@@ -65,6 +67,7 @@ const Effects = {
6567
borderTop: BorderTopEffect,
6668
fadeOut: FadeOutEffect,
6769
linearGradient: LinearGradientEffect,
70+
radialGradient: RadialGradientEffect,
6871
grayscale: GrayscaleEffect,
6972
glitch: GlitchEffect,
7073
};

src/core/renderers/webgl/shaders/effects/LinearGradientEffect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export class LinearGradientEffect extends ShaderEffect {
176176
stopCalc = (dist - stops[i]) / (stops[i + 1] - stops[i]);
177177
colorOut = mix(colorOut, colors[i + 1], clamp(stopCalc, 0.0, 1.0));
178178
}
179-
return colorOut;
179+
return mix(maskColor, colorOut, colorOut.a);
180180
`;
181181
};
182182
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* If not stated otherwise in this file or this component's LICENSE file the
3+
* following copyright and licenses apply:
4+
*
5+
* Copyright 2023 Comcast Cable Communications Management, LLC.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the License);
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
import { getNormalizedRgbaComponents } from '../../../../lib/utils.js';
20+
import {
21+
type DefaultEffectProps,
22+
ShaderEffect,
23+
type ShaderEffectUniforms,
24+
} from './ShaderEffect.js';
25+
26+
/**
27+
* Properties of the {@link RadialGradientEffect} effect
28+
*/
29+
export interface RadialGradientEffectProps extends DefaultEffectProps {
30+
/**
31+
* Array of colors to be used in the RadialGradientEffect
32+
*
33+
* @default [0xff000000, 0xffffffff]
34+
*/
35+
colors?: number[];
36+
/**
37+
* Array of color stops
38+
*/
39+
stops?: number[];
40+
/**
41+
* Width of the RadialGradientEffect
42+
*/
43+
width?: number;
44+
/**
45+
* height of the RadialGradientEffect
46+
*
47+
* @remarks if not defined uses the width value
48+
*/
49+
height?: number;
50+
/**
51+
* center point of where the RadialGradientEffect is drawn
52+
*/
53+
pivot?: number[];
54+
}
55+
56+
export class RadialGradientEffect extends ShaderEffect {
57+
static z$__type__Props: RadialGradientEffectProps;
58+
override readonly name = 'radialGradient';
59+
60+
static override getEffectKey(props: RadialGradientEffectProps): string {
61+
return `radialGradient${props.colors!.length}`;
62+
}
63+
64+
static override resolveDefaults(
65+
props: RadialGradientEffectProps,
66+
): Required<RadialGradientEffectProps> {
67+
const colors = props.colors ?? [0xff000000, 0xffffffff];
68+
69+
let stops = props.stops;
70+
if (!stops) {
71+
stops = [];
72+
const calc = colors.length - 1;
73+
for (let i = 0; i < colors.length; i++) {
74+
stops.push(i * (1 / calc));
75+
}
76+
}
77+
return {
78+
colors,
79+
stops,
80+
width: props.width ?? 0,
81+
height: props.height ?? props.width ?? 0,
82+
pivot: props.pivot ?? [0.5, 0.5],
83+
};
84+
}
85+
86+
static override uniforms: ShaderEffectUniforms = {
87+
width: {
88+
value: 0,
89+
method: 'uniform1f',
90+
type: 'float',
91+
},
92+
height: {
93+
value: 0,
94+
method: 'uniform1f',
95+
type: 'float',
96+
},
97+
pivot: {
98+
value: [0.5, 0.5],
99+
method: 'uniform2fv',
100+
type: 'vec2',
101+
},
102+
colors: {
103+
value: 0xffffffff,
104+
validator: (rgbas: number[]): number[] => {
105+
const cols = rgbas.map((rgbas) => getNormalizedRgbaComponents(rgbas));
106+
return cols.reduce((acc, val) => acc.concat(val), [] as number[]);
107+
},
108+
size: (props: RadialGradientEffectProps) => props.colors!.length,
109+
method: 'uniform4fv',
110+
type: 'vec4',
111+
},
112+
stops: {
113+
value: [],
114+
validator: (
115+
value: number[],
116+
props: RadialGradientEffectProps,
117+
): number[] => {
118+
const colors = props.colors ?? [];
119+
let stops = value;
120+
const tmp: number[] = value;
121+
if (stops.length === 0 || (stops && stops.length !== colors.length)) {
122+
for (let i = 0; i < colors.length; i++) {
123+
if (stops[i]) {
124+
tmp[i] = stops[i]!;
125+
if (stops[i - 1] === undefined && tmp[i - 2] !== undefined) {
126+
tmp[i - 1] = tmp[i - 2]! + (stops[i]! - tmp[i - 2]!) / 2;
127+
}
128+
} else {
129+
tmp[i] = i * (1 / (colors.length - 1));
130+
}
131+
}
132+
stops = tmp;
133+
}
134+
return tmp;
135+
},
136+
size: (props: RadialGradientEffectProps) => props.colors!.length,
137+
method: 'uniform1fv',
138+
type: 'float',
139+
},
140+
};
141+
142+
static override onColorize = (props: RadialGradientEffectProps) => {
143+
const colors = props.colors!.length || 1;
144+
return `
145+
vec2 point = v_textureCoordinate.xy * u_dimensions;
146+
vec2 projection = vec2(pivot.x * u_dimensions.x, pivot.y * u_dimensions.y);
147+
148+
float dist = length((point - projection) / vec2(width, height));
149+
150+
float stopCalc = (dist - stops[0]) / (stops[1] - stops[0]);
151+
vec4 colorOut = mix(colors[0], colors[1], stopCalc);
152+
for(int i = 1; i < ${colors}-1; i++) {
153+
stopCalc = (dist - stops[i]) / (stops[i + 1] - stops[i]);
154+
colorOut = mix(colorOut, colors[i + 1], clamp(stopCalc, 0.0, 1.0));
155+
}
156+
return mix(maskColor, colorOut, colorOut.a);
157+
`;
158+
};
159+
}

0 commit comments

Comments
 (0)