@mirage-engine/painter 0.2.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/mirage-painter.js +159 -103
- package/dist/mirage-painter.umd.js +57 -27
- package/dist/src/BoxGenerator.d.ts +3 -3
- package/dist/src/Painter.d.ts +3 -2
- package/dist/src/dev/devShader.d.ts +5 -0
- package/dist/src/types/common.d.ts +4 -0
- package/package.json +1 -1
- package/src/BoxGenerator.ts +134 -33
- package/src/Painter.ts +6 -2
- package/src/dev/devShader.ts +114 -0
- package/src/types/common.ts +6 -0
package/CHANGELOG.md
CHANGED
package/dist/mirage-painter.js
CHANGED
|
@@ -1,138 +1,193 @@
|
|
|
1
1
|
import * as l from "three";
|
|
2
|
-
function
|
|
3
|
-
const
|
|
4
|
-
`),
|
|
5
|
-
return
|
|
6
|
-
const
|
|
7
|
-
let
|
|
8
|
-
for (let
|
|
9
|
-
const
|
|
10
|
-
|
|
2
|
+
function h(e, r, o) {
|
|
3
|
+
const i = r.split(`
|
|
4
|
+
`), a = [];
|
|
5
|
+
return i.forEach((n) => {
|
|
6
|
+
const t = n.split(" ");
|
|
7
|
+
let c = t[0];
|
|
8
|
+
for (let u = 1; u < t.length; u++) {
|
|
9
|
+
const s = t[u];
|
|
10
|
+
e.measureText(c + " " + s).width < o ? c += " " + s : (a.push(c), c = s);
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
}),
|
|
12
|
+
a.push(c);
|
|
13
|
+
}), a;
|
|
14
14
|
}
|
|
15
|
-
function m(
|
|
16
|
-
const
|
|
17
|
-
if (!
|
|
15
|
+
function m(e, r, o, i, a = 2) {
|
|
16
|
+
const n = document.createElement("canvas"), t = n.getContext("2d");
|
|
17
|
+
if (!t)
|
|
18
18
|
throw new Error("[Mirage] Failed to create canvas context");
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
let
|
|
25
|
-
|
|
19
|
+
const u = (window.devicePixelRatio || 1) * a;
|
|
20
|
+
n.width = o * u, n.height = i * u, t.scale(u, u), t.font = r.font, t.fillStyle = r.color, t.textBaseline = "top", t.globalAlpha = 1;
|
|
21
|
+
const s = h(t, e, o), v = r.lineHeight;
|
|
22
|
+
s.forEach((B, p) => {
|
|
23
|
+
const g = p * v + 2;
|
|
24
|
+
let C = 0;
|
|
25
|
+
r.textAlign === "center" ? C = o / 2 : r.textAlign === "right" && (C = o), t.textAlign = r.textAlign, t.fillText(B, C, g);
|
|
26
26
|
});
|
|
27
|
-
const
|
|
28
|
-
return
|
|
27
|
+
const d = new l.CanvasTexture(n);
|
|
28
|
+
return d.colorSpace = l.SRGBColorSpace, d.minFilter = l.LinearFilter, d.magFilter = l.LinearFilter, d.needsUpdate = !0, d;
|
|
29
29
|
}
|
|
30
|
-
function
|
|
31
|
-
return typeof
|
|
30
|
+
function f(e) {
|
|
31
|
+
return typeof e == "number" ? e : parseFloat(e) || 0;
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
function b(e, r) {
|
|
34
|
+
if (!r) {
|
|
35
|
+
e.set(0, 0, 0, 0);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const o = r.split("/")[0].trim().split(/\s+/), i = f(o[0]), a = f(o[1] ?? o[0]), n = f(o[2] ?? o[0]), t = f(o[3] ?? o[1] ?? o[0]);
|
|
39
|
+
e.set(i, a, n, t);
|
|
40
|
+
}
|
|
41
|
+
const O = (
|
|
42
|
+
/* glsl */
|
|
43
|
+
`
|
|
34
44
|
varying vec2 vUv;
|
|
45
|
+
varying vec4 vScreenPos;
|
|
35
46
|
void main() {
|
|
36
47
|
vUv = uv;
|
|
37
48
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
49
|
+
vScreenPos = gl_Position;
|
|
38
50
|
}
|
|
39
|
-
|
|
51
|
+
`
|
|
52
|
+
), T = (
|
|
53
|
+
/* glsl */
|
|
54
|
+
`
|
|
55
|
+
|
|
40
56
|
varying vec2 vUv;
|
|
41
57
|
|
|
42
58
|
uniform vec2 uSize;
|
|
43
|
-
uniform
|
|
59
|
+
uniform vec4 uBorderRadius;
|
|
44
60
|
uniform float uBorderWidth;
|
|
45
|
-
uniform vec3
|
|
61
|
+
uniform vec3 uBgColor;
|
|
46
62
|
uniform vec3 uBorderColor;
|
|
47
63
|
uniform float uOpacity;
|
|
48
64
|
uniform float uBgOpacity;
|
|
65
|
+
uniform float uBorderOpacity;
|
|
66
|
+
|
|
67
|
+
#INJECT_DECLARATIONS
|
|
49
68
|
|
|
50
|
-
// SDF 박스 함수
|
|
51
69
|
float sdRoundedBox(vec2 p, vec2 b, float r) {
|
|
52
70
|
vec2 q = abs(p) - b + r;
|
|
71
|
+
|
|
53
72
|
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
|
|
54
73
|
}
|
|
55
74
|
|
|
56
75
|
void main() {
|
|
57
76
|
vec2 p = (vUv - 0.5) * uSize;
|
|
58
77
|
vec2 halfSize = uSize * 0.5;
|
|
59
|
-
|
|
60
|
-
float d = sdRoundedBox(p, halfSize, uRadius);
|
|
61
|
-
|
|
62
|
-
float smoothEdge = 1.0;
|
|
63
78
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
float borderAlpha = 0.0;
|
|
79
|
+
#INJECT_UV_MODIFIER
|
|
67
80
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
81
|
+
// color decision pipeline
|
|
82
|
+
vec4 baseColor = vec4(uBgColor, uBgOpacity);
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
#INJECT_BASE_COLOR
|
|
85
|
+
|
|
86
|
+
// sdf shape pipeline
|
|
87
|
+
vec2 xRadii = mix(uBorderRadius.xw, uBorderRadius.yz, step(0.0, p.x));
|
|
88
|
+
float r = mix(xRadii.y, xRadii.x, step(0.0, p.y));
|
|
89
|
+
float d = sdRoundedBox(p, halfSize, r);
|
|
90
|
+
|
|
91
|
+
float aa = 1.0;
|
|
92
|
+
float bgMask = 1.0 - smoothstep(0.0, aa, d);
|
|
93
|
+
|
|
94
|
+
float halfBorder = uBorderWidth * 0.5;
|
|
95
|
+
float borderD = abs(d + halfBorder) - halfBorder;
|
|
96
|
+
float borderAlpha = (1.0 - smoothstep(0.0, aa, borderD)) * uBorderOpacity;
|
|
97
|
+
if (uBorderWidth <= 0.01) {
|
|
98
|
+
borderAlpha = 0.0;
|
|
77
99
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
float
|
|
81
|
-
|
|
100
|
+
|
|
101
|
+
// final blending (border + background)
|
|
102
|
+
float aFront = borderAlpha;
|
|
103
|
+
float aBack = baseColor.a;
|
|
104
|
+
float aOut = aFront + aBack * (1.0 - aFront);
|
|
105
|
+
|
|
106
|
+
float safeAlpha = max(aOut, 0.0001);
|
|
107
|
+
vec3 cOut = (uBorderColor * aFront + baseColor.rgb * aBack * (1.0 - aFront)) / safeAlpha;
|
|
108
|
+
vec4 finalColor = vec4(cOut, aOut);
|
|
109
|
+
|
|
110
|
+
// final color control (Tint, Noise)
|
|
111
|
+
#INJECT_COLOR_MODIFIER
|
|
112
|
+
|
|
113
|
+
float finalOpacity = finalColor.a * bgMask * uOpacity;
|
|
82
114
|
if (finalOpacity < 0.001) discard;
|
|
83
115
|
|
|
84
|
-
gl_FragColor = vec4(
|
|
116
|
+
gl_FragColor = vec4(finalColor.rgb, finalOpacity);
|
|
117
|
+
|
|
118
|
+
#include <colorspace_fragment>
|
|
85
119
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
120
|
+
`
|
|
121
|
+
);
|
|
122
|
+
function x(e) {
|
|
123
|
+
if (!e || e === "transparent")
|
|
89
124
|
return { color: new l.Color(16777215), alpha: 0 };
|
|
90
|
-
const
|
|
125
|
+
const r = e.match(
|
|
91
126
|
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/
|
|
92
127
|
);
|
|
93
|
-
if (
|
|
94
|
-
const
|
|
95
|
-
return { color: new l.Color(`rgb(${
|
|
128
|
+
if (r) {
|
|
129
|
+
const o = parseInt(r[1], 10), i = parseInt(r[2], 10), a = parseInt(r[3], 10), n = r[4] !== void 0 ? parseFloat(r[4]) : 1;
|
|
130
|
+
return { color: new l.Color(`rgb(${o}, ${i}, ${a})`), alpha: n };
|
|
96
131
|
}
|
|
97
|
-
return { color: new l.Color(
|
|
132
|
+
return { color: new l.Color(e), alpha: 1 };
|
|
98
133
|
}
|
|
99
|
-
function
|
|
100
|
-
const n =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
134
|
+
function R(e, r, o, i = null, a) {
|
|
135
|
+
const n = i !== null, t = n ? (
|
|
136
|
+
/* glsl */
|
|
137
|
+
`
|
|
138
|
+
uniform sampler2D uTexture;
|
|
139
|
+
varying vec4 vScreenPos;
|
|
140
|
+
`
|
|
141
|
+
) : "", c = n ? (
|
|
142
|
+
/* glsl */
|
|
143
|
+
`
|
|
144
|
+
vec2 screenUv = (vScreenPos.xy / vScreenPos.w) * 0.5 + 0.5;
|
|
145
|
+
vec2 resultUv = screenUv;
|
|
146
|
+
${(a == null ? void 0 : a.uvModifier) || ""}
|
|
147
|
+
`
|
|
148
|
+
) : "", u = n ? (
|
|
149
|
+
/* glsl */
|
|
150
|
+
`
|
|
151
|
+
baseColor = texture2D(uTexture, resultUv);
|
|
152
|
+
`
|
|
153
|
+
) : "", s = (a == null ? void 0 : a.colorModifier) || "", v = T.replace("#INJECT_DECLARATIONS", t).replace("#INJECT_UV_MODIFIER", c).replace("#INJECT_BASE_COLOR", u).replace("#INJECT_COLOR_MODIFIER", s), d = x(e.backgroundColor), B = x(e.borderColor), p = {
|
|
154
|
+
uSize: { value: new l.Vector2(r, o) },
|
|
155
|
+
uBorderRadius: { value: new l.Vector4(0, 0, 0, 0) },
|
|
156
|
+
uBorderWidth: { value: f(e.borderWidth) },
|
|
157
|
+
uBgColor: { value: d.color },
|
|
158
|
+
uBorderColor: { value: B.color },
|
|
159
|
+
uOpacity: { value: e.opacity ?? 1 },
|
|
160
|
+
uBgOpacity: { value: d.alpha },
|
|
161
|
+
uBorderOpacity: { value: B.alpha },
|
|
162
|
+
uTexture: { value: null }
|
|
108
163
|
};
|
|
109
|
-
return new l.ShaderMaterial({
|
|
110
|
-
uniforms:
|
|
111
|
-
vertexShader:
|
|
112
|
-
fragmentShader:
|
|
164
|
+
return b(p.uBorderRadius.value, e.borderRadius), n && (p.uTexture.value = i), new l.ShaderMaterial({
|
|
165
|
+
uniforms: p,
|
|
166
|
+
vertexShader: O,
|
|
167
|
+
fragmentShader: v,
|
|
113
168
|
transparent: !0,
|
|
114
169
|
side: l.FrontSide
|
|
115
170
|
// for better performance
|
|
116
171
|
});
|
|
117
172
|
}
|
|
118
|
-
function
|
|
119
|
-
const
|
|
120
|
-
|
|
173
|
+
function E(e, r, o, i, a) {
|
|
174
|
+
const n = x(r.backgroundColor), t = x(r.borderColor);
|
|
175
|
+
e.uniforms.uSize.value.set(o, i), b(e.uniforms.uBorderRadius.value, r.borderRadius), e.uniforms.uBorderWidth.value = f(r.borderWidth), e.uniforms.uBgColor.value.copy(n.color), e.uniforms.uBorderColor.value.copy(t.color), e.uniforms.uOpacity.value = r.opacity ?? 1, e.uniforms.uBgOpacity.value = n.alpha, e.uniforms.uBorderOpacity.value = t.alpha, e.uniforms.uTexture && a !== void 0 && (e.uniforms.uTexture.value = a);
|
|
121
176
|
}
|
|
122
|
-
const
|
|
123
|
-
create(o,
|
|
124
|
-
if (
|
|
125
|
-
return
|
|
126
|
-
if (
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
e,
|
|
130
|
-
n,
|
|
177
|
+
const S = {
|
|
178
|
+
create(e, r, o, i, a, n = 2, t = null, c) {
|
|
179
|
+
if (e === "BOX")
|
|
180
|
+
return R(r, i, a, t, c);
|
|
181
|
+
if (e === "TEXT") {
|
|
182
|
+
const u = m(
|
|
183
|
+
o || "",
|
|
131
184
|
r,
|
|
132
|
-
i
|
|
185
|
+
i,
|
|
186
|
+
a,
|
|
187
|
+
n
|
|
133
188
|
);
|
|
134
189
|
return new l.MeshBasicMaterial({
|
|
135
|
-
map:
|
|
190
|
+
map: u,
|
|
136
191
|
transparent: !0,
|
|
137
192
|
side: l.FrontSide,
|
|
138
193
|
color: 16777215
|
|
@@ -140,31 +195,32 @@ const O = {
|
|
|
140
195
|
}
|
|
141
196
|
return new l.MeshBasicMaterial({ visible: !1 });
|
|
142
197
|
},
|
|
143
|
-
update(
|
|
144
|
-
if (
|
|
145
|
-
|
|
198
|
+
update(e, r, o, i, a, n, t = 2, c) {
|
|
199
|
+
if (r === "BOX")
|
|
200
|
+
E(
|
|
201
|
+
e,
|
|
146
202
|
o,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
203
|
+
a,
|
|
204
|
+
n,
|
|
205
|
+
c
|
|
150
206
|
);
|
|
151
|
-
else if (
|
|
152
|
-
const u =
|
|
207
|
+
else if (r === "TEXT") {
|
|
208
|
+
const u = e;
|
|
153
209
|
u.map && u.map.dispose();
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
210
|
+
const s = m(
|
|
211
|
+
i || "",
|
|
212
|
+
o,
|
|
213
|
+
a,
|
|
214
|
+
n,
|
|
215
|
+
t
|
|
160
216
|
);
|
|
161
|
-
u.map =
|
|
217
|
+
u.map = s, u.needsUpdate = !0;
|
|
162
218
|
}
|
|
163
219
|
}
|
|
164
220
|
};
|
|
165
221
|
export {
|
|
166
|
-
|
|
167
|
-
|
|
222
|
+
S as Painter,
|
|
223
|
+
R as createBoxMaterial,
|
|
168
224
|
m as createTextTexture,
|
|
169
|
-
|
|
225
|
+
E as updateBoxMaterial
|
|
170
226
|
};
|
|
@@ -1,55 +1,85 @@
|
|
|
1
|
-
(function(
|
|
2
|
-
`),
|
|
1
|
+
(function(s,v){typeof exports=="object"&&typeof module<"u"?v(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],v):(s=typeof globalThis<"u"?globalThis:s||self,v(s.MiragePainter={},s.THREE))})(this,function(s,v){"use strict";function S(e){const r=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const o in e)if(o!=="default"){const i=Object.getOwnPropertyDescriptor(e,o);Object.defineProperty(r,o,i.get?i:{enumerable:!0,get:()=>e[o]})}}return r.default=e,Object.freeze(r)}const c=S(v);function E(e,r,o){const i=r.split(`
|
|
2
|
+
`),t=[];return i.forEach(n=>{const a=n.split(" ");let l=a[0];for(let u=1;u<a.length;u++){const d=a[u];e.measureText(l+" "+d).width<o?l+=" "+d:(t.push(l),l=d)}t.push(l)}),t}function m(e,r,o,i,t=2){const n=document.createElement("canvas"),a=n.getContext("2d");if(!a)throw new Error("[Mirage] Failed to create canvas context");const u=(window.devicePixelRatio||1)*t;n.width=o*u,n.height=i*u,a.scale(u,u),a.font=r.font,a.fillStyle=r.color,a.textBaseline="top",a.globalAlpha=1;const d=E(a,e,o),x=r.lineHeight;d.forEach((b,B)=>{const R=B*x+2;let C=0;r.textAlign==="center"?C=o/2:r.textAlign==="right"&&(C=o),a.textAlign=r.textAlign,a.fillText(b,C,R)});const f=new c.CanvasTexture(n);return f.colorSpace=c.SRGBColorSpace,f.minFilter=c.LinearFilter,f.magFilter=c.LinearFilter,f.needsUpdate=!0,f}function p(e){return typeof e=="number"?e:parseFloat(e)||0}function h(e,r){if(!r){e.set(0,0,0,0);return}const o=r.split("/")[0].trim().split(/\s+/),i=p(o[0]),t=p(o[1]??o[0]),n=p(o[2]??o[0]),a=p(o[3]??o[1]??o[0]);e.set(i,t,n,a)}const M=`
|
|
3
3
|
varying vec2 vUv;
|
|
4
|
+
varying vec4 vScreenPos;
|
|
4
5
|
void main() {
|
|
5
6
|
vUv = uv;
|
|
6
7
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
8
|
+
vScreenPos = gl_Position;
|
|
7
9
|
}
|
|
8
|
-
`,
|
|
10
|
+
`,w=`
|
|
11
|
+
|
|
9
12
|
varying vec2 vUv;
|
|
10
13
|
|
|
11
14
|
uniform vec2 uSize;
|
|
12
|
-
uniform
|
|
15
|
+
uniform vec4 uBorderRadius;
|
|
13
16
|
uniform float uBorderWidth;
|
|
14
|
-
uniform vec3
|
|
17
|
+
uniform vec3 uBgColor;
|
|
15
18
|
uniform vec3 uBorderColor;
|
|
16
19
|
uniform float uOpacity;
|
|
17
20
|
uniform float uBgOpacity;
|
|
21
|
+
uniform float uBorderOpacity;
|
|
22
|
+
|
|
23
|
+
#INJECT_DECLARATIONS
|
|
18
24
|
|
|
19
|
-
// SDF 박스 함수
|
|
20
25
|
float sdRoundedBox(vec2 p, vec2 b, float r) {
|
|
21
26
|
vec2 q = abs(p) - b + r;
|
|
27
|
+
|
|
22
28
|
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
void main() {
|
|
26
32
|
vec2 p = (vUv - 0.5) * uSize;
|
|
27
33
|
vec2 halfSize = uSize * 0.5;
|
|
28
|
-
|
|
29
|
-
float d = sdRoundedBox(p, halfSize, uRadius);
|
|
30
|
-
|
|
31
|
-
float smoothEdge = 1.0;
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
#INJECT_UV_MODIFIER
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (uBorderWidth > 0.01) {
|
|
38
|
-
borderAlpha = (1.0 - smoothstep(0.0, smoothEdge, d)) - fillAlpha;
|
|
39
|
-
}
|
|
37
|
+
// color decision pipeline
|
|
38
|
+
vec4 baseColor = vec4(uBgColor, uBgOpacity);
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
#INJECT_BASE_COLOR
|
|
41
|
+
|
|
42
|
+
// sdf shape pipeline
|
|
43
|
+
vec2 xRadii = mix(uBorderRadius.xw, uBorderRadius.yz, step(0.0, p.x));
|
|
44
|
+
float r = mix(xRadii.y, xRadii.x, step(0.0, p.y));
|
|
45
|
+
float d = sdRoundedBox(p, halfSize, r);
|
|
46
|
+
|
|
47
|
+
float aa = 1.0;
|
|
48
|
+
float bgMask = 1.0 - smoothstep(0.0, aa, d);
|
|
49
|
+
|
|
50
|
+
float halfBorder = uBorderWidth * 0.5;
|
|
51
|
+
float borderD = abs(d + halfBorder) - halfBorder;
|
|
52
|
+
float borderAlpha = (1.0 - smoothstep(0.0, aa, borderD)) * uBorderOpacity;
|
|
53
|
+
if (uBorderWidth <= 0.01) {
|
|
54
|
+
borderAlpha = 0.0;
|
|
46
55
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
float
|
|
50
|
-
|
|
56
|
+
|
|
57
|
+
// final blending (border + background)
|
|
58
|
+
float aFront = borderAlpha;
|
|
59
|
+
float aBack = baseColor.a;
|
|
60
|
+
float aOut = aFront + aBack * (1.0 - aFront);
|
|
61
|
+
|
|
62
|
+
float safeAlpha = max(aOut, 0.0001);
|
|
63
|
+
vec3 cOut = (uBorderColor * aFront + baseColor.rgb * aBack * (1.0 - aFront)) / safeAlpha;
|
|
64
|
+
vec4 finalColor = vec4(cOut, aOut);
|
|
65
|
+
|
|
66
|
+
// final color control (Tint, Noise)
|
|
67
|
+
#INJECT_COLOR_MODIFIER
|
|
68
|
+
|
|
69
|
+
float finalOpacity = finalColor.a * bgMask * uOpacity;
|
|
51
70
|
if (finalOpacity < 0.001) discard;
|
|
52
71
|
|
|
53
|
-
gl_FragColor = vec4(
|
|
72
|
+
gl_FragColor = vec4(finalColor.rgb, finalOpacity);
|
|
73
|
+
|
|
74
|
+
#include <colorspace_fragment>
|
|
54
75
|
}
|
|
55
|
-
`;function
|
|
76
|
+
`;function g(e){if(!e||e==="transparent")return{color:new c.Color(16777215),alpha:0};const r=e.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(r){const o=parseInt(r[1],10),i=parseInt(r[2],10),t=parseInt(r[3],10),n=r[4]!==void 0?parseFloat(r[4]):1;return{color:new c.Color(`rgb(${o}, ${i}, ${t})`),alpha:n}}return{color:new c.Color(e),alpha:1}}function O(e,r,o,i=null,t){const n=i!==null,a=n?`
|
|
77
|
+
uniform sampler2D uTexture;
|
|
78
|
+
varying vec4 vScreenPos;
|
|
79
|
+
`:"",l=n?`
|
|
80
|
+
vec2 screenUv = (vScreenPos.xy / vScreenPos.w) * 0.5 + 0.5;
|
|
81
|
+
vec2 resultUv = screenUv;
|
|
82
|
+
${(t==null?void 0:t.uvModifier)||""}
|
|
83
|
+
`:"",u=n?`
|
|
84
|
+
baseColor = texture2D(uTexture, resultUv);
|
|
85
|
+
`:"",d=(t==null?void 0:t.colorModifier)||"",x=w.replace("#INJECT_DECLARATIONS",a).replace("#INJECT_UV_MODIFIER",l).replace("#INJECT_BASE_COLOR",u).replace("#INJECT_COLOR_MODIFIER",d),f=g(e.backgroundColor),b=g(e.borderColor),B={uSize:{value:new c.Vector2(r,o)},uBorderRadius:{value:new c.Vector4(0,0,0,0)},uBorderWidth:{value:p(e.borderWidth)},uBgColor:{value:f.color},uBorderColor:{value:b.color},uOpacity:{value:e.opacity??1},uBgOpacity:{value:f.alpha},uBorderOpacity:{value:b.alpha},uTexture:{value:null}};return h(B.uBorderRadius.value,e.borderRadius),n&&(B.uTexture.value=i),new c.ShaderMaterial({uniforms:B,vertexShader:M,fragmentShader:x,transparent:!0,side:c.FrontSide})}function T(e,r,o,i,t){const n=g(r.backgroundColor),a=g(r.borderColor);e.uniforms.uSize.value.set(o,i),h(e.uniforms.uBorderRadius.value,r.borderRadius),e.uniforms.uBorderWidth.value=p(r.borderWidth),e.uniforms.uBgColor.value.copy(n.color),e.uniforms.uBorderColor.value.copy(a.color),e.uniforms.uOpacity.value=r.opacity??1,e.uniforms.uBgOpacity.value=n.alpha,e.uniforms.uBorderOpacity.value=a.alpha,e.uniforms.uTexture&&t!==void 0&&(e.uniforms.uTexture.value=t)}const y={create(e,r,o,i,t,n=2,a=null,l){if(e==="BOX")return O(r,i,t,a,l);if(e==="TEXT"){const u=m(o||"",r,i,t,n);return new c.MeshBasicMaterial({map:u,transparent:!0,side:c.FrontSide,color:16777215})}return new c.MeshBasicMaterial({visible:!1})},update(e,r,o,i,t,n,a=2,l){if(r==="BOX")T(e,o,t,n,l);else if(r==="TEXT"){const u=e;u.map&&u.map.dispose();const d=m(i||"",o,t,n,a);u.map=d,u.needsUpdate=!0}}};s.Painter=y,s.createBoxMaterial=O,s.createTextTexture=m,s.updateBoxMaterial=T,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BoxStyles } from './types';
|
|
1
|
+
import { BoxStyles, ShaderHooks } from './types';
|
|
2
2
|
import * as THREE from "three";
|
|
3
|
-
export declare function createBoxMaterial(styles: BoxStyles, width: number, height: number): THREE.ShaderMaterial;
|
|
4
|
-
export declare function updateBoxMaterial(material: THREE.ShaderMaterial, styles: BoxStyles, width: number, height: number): void;
|
|
3
|
+
export declare function createBoxMaterial(styles: BoxStyles, width: number, height: number, texture?: THREE.Texture | null, hooks?: ShaderHooks): THREE.ShaderMaterial;
|
|
4
|
+
export declare function updateBoxMaterial(material: THREE.ShaderMaterial, styles: BoxStyles, width: number, height: number, texture?: THREE.Texture | null): void;
|
package/dist/src/Painter.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { ShaderHooks } from './types';
|
|
1
2
|
import * as THREE from "three";
|
|
2
3
|
export declare const Painter: {
|
|
3
|
-
create(type: "BOX" | "TEXT", styles: any, content: string, width: number, height: number, quality?: number): THREE.Material;
|
|
4
|
-
update(material: THREE.Material, type: "BOX" | "TEXT", styles: any, content: string, width: number, height: number, quality?: number): void;
|
|
4
|
+
create(type: "BOX" | "TEXT", styles: any, content: string, width: number, height: number, quality?: number, texture?: THREE.Texture | null, shaderHooks?: ShaderHooks): THREE.Material;
|
|
5
|
+
update(material: THREE.Material, type: "BOX" | "TEXT", styles: any, content: string, width: number, height: number, quality?: number, texture?: THREE.Texture | null): void;
|
|
5
6
|
};
|
package/package.json
CHANGED
package/src/BoxGenerator.ts
CHANGED
|
@@ -1,65 +1,102 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
|
-
import { BoxStyles } from "./types";
|
|
2
|
+
import { BoxStyles, ShaderHooks } from "./types";
|
|
3
|
+
// [for dev]
|
|
4
|
+
// import { glassHooks } from "./dev/devShader";
|
|
3
5
|
|
|
4
6
|
function parsePixelValue(value: string | number): number {
|
|
5
7
|
if (typeof value === "number") return value;
|
|
6
8
|
return parseFloat(value) || 0;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
function setBorderRadius(target: THREE.Vector4, radius: string) {
|
|
12
|
+
if (!radius) {
|
|
13
|
+
target.set(0, 0, 0, 0);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const arr = radius.split("/")[0].trim().split(/\s+/);
|
|
17
|
+
const tl = parsePixelValue(arr[0]);
|
|
18
|
+
const tr = parsePixelValue(arr[1] ?? arr[0]);
|
|
19
|
+
const br = parsePixelValue(arr[2] ?? arr[0]);
|
|
20
|
+
const bl = parsePixelValue(arr[3] ?? arr[1] ?? arr[0]);
|
|
21
|
+
|
|
22
|
+
target.set(tl, tr, br, bl);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const vertexShader = /* glsl */ `
|
|
10
26
|
varying vec2 vUv;
|
|
27
|
+
varying vec4 vScreenPos;
|
|
11
28
|
void main() {
|
|
12
29
|
vUv = uv;
|
|
13
30
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
31
|
+
vScreenPos = gl_Position;
|
|
14
32
|
}
|
|
15
33
|
`;
|
|
16
34
|
|
|
17
|
-
const
|
|
35
|
+
const fragmentShaderTemplate = /* glsl */ `
|
|
36
|
+
|
|
18
37
|
varying vec2 vUv;
|
|
19
38
|
|
|
20
39
|
uniform vec2 uSize;
|
|
21
|
-
uniform
|
|
40
|
+
uniform vec4 uBorderRadius;
|
|
22
41
|
uniform float uBorderWidth;
|
|
23
|
-
uniform vec3
|
|
42
|
+
uniform vec3 uBgColor;
|
|
24
43
|
uniform vec3 uBorderColor;
|
|
25
44
|
uniform float uOpacity;
|
|
26
45
|
uniform float uBgOpacity;
|
|
46
|
+
uniform float uBorderOpacity;
|
|
47
|
+
|
|
48
|
+
#INJECT_DECLARATIONS
|
|
27
49
|
|
|
28
|
-
// SDF 박스 함수
|
|
29
50
|
float sdRoundedBox(vec2 p, vec2 b, float r) {
|
|
30
51
|
vec2 q = abs(p) - b + r;
|
|
52
|
+
|
|
31
53
|
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
|
|
32
54
|
}
|
|
33
55
|
|
|
34
56
|
void main() {
|
|
35
57
|
vec2 p = (vUv - 0.5) * uSize;
|
|
36
58
|
vec2 halfSize = uSize * 0.5;
|
|
37
|
-
|
|
38
|
-
float d = sdRoundedBox(p, halfSize, uRadius);
|
|
39
|
-
|
|
40
|
-
float smoothEdge = 1.0;
|
|
41
59
|
|
|
42
|
-
|
|
60
|
+
#INJECT_UV_MODIFIER
|
|
43
61
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (uBorderWidth > 0.01) {
|
|
47
|
-
borderAlpha = (1.0 - smoothstep(0.0, smoothEdge, d)) - fillAlpha;
|
|
48
|
-
}
|
|
62
|
+
// color decision pipeline
|
|
63
|
+
vec4 baseColor = vec4(uBgColor, uBgOpacity);
|
|
49
64
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
#INJECT_BASE_COLOR
|
|
66
|
+
|
|
67
|
+
// sdf shape pipeline
|
|
68
|
+
vec2 xRadii = mix(uBorderRadius.xw, uBorderRadius.yz, step(0.0, p.x));
|
|
69
|
+
float r = mix(xRadii.y, xRadii.x, step(0.0, p.y));
|
|
70
|
+
float d = sdRoundedBox(p, halfSize, r);
|
|
71
|
+
|
|
72
|
+
float aa = 1.0;
|
|
73
|
+
float bgMask = 1.0 - smoothstep(0.0, aa, d);
|
|
74
|
+
|
|
75
|
+
float halfBorder = uBorderWidth * 0.5;
|
|
76
|
+
float borderD = abs(d + halfBorder) - halfBorder;
|
|
77
|
+
float borderAlpha = (1.0 - smoothstep(0.0, aa, borderD)) * uBorderOpacity;
|
|
78
|
+
if (uBorderWidth <= 0.01) {
|
|
79
|
+
borderAlpha = 0.0;
|
|
55
80
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
float
|
|
59
|
-
|
|
81
|
+
|
|
82
|
+
// final blending (border + background)
|
|
83
|
+
float aFront = borderAlpha;
|
|
84
|
+
float aBack = baseColor.a;
|
|
85
|
+
float aOut = aFront + aBack * (1.0 - aFront);
|
|
86
|
+
|
|
87
|
+
float safeAlpha = max(aOut, 0.0001);
|
|
88
|
+
vec3 cOut = (uBorderColor * aFront + baseColor.rgb * aBack * (1.0 - aFront)) / safeAlpha;
|
|
89
|
+
vec4 finalColor = vec4(cOut, aOut);
|
|
90
|
+
|
|
91
|
+
// final color control (Tint, Noise)
|
|
92
|
+
#INJECT_COLOR_MODIFIER
|
|
93
|
+
|
|
94
|
+
float finalOpacity = finalColor.a * bgMask * uOpacity;
|
|
60
95
|
if (finalOpacity < 0.001) discard;
|
|
61
96
|
|
|
62
|
-
gl_FragColor = vec4(
|
|
97
|
+
gl_FragColor = vec4(finalColor.rgb, finalOpacity);
|
|
98
|
+
|
|
99
|
+
#include <colorspace_fragment>
|
|
63
100
|
}
|
|
64
101
|
`;
|
|
65
102
|
|
|
@@ -77,10 +114,8 @@ function parseColor(colorStr: string) {
|
|
|
77
114
|
const g = parseInt(rgbaMatch[2], 10);
|
|
78
115
|
const b = parseInt(rgbaMatch[3], 10);
|
|
79
116
|
const a = rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1.0;
|
|
80
|
-
|
|
81
117
|
return { color: new THREE.Color(`rgb(${r}, ${g}, ${b})`), alpha: a };
|
|
82
118
|
}
|
|
83
|
-
|
|
84
119
|
return { color: new THREE.Color(colorStr), alpha: 1.0 };
|
|
85
120
|
}
|
|
86
121
|
|
|
@@ -88,19 +123,79 @@ export function createBoxMaterial(
|
|
|
88
123
|
styles: BoxStyles,
|
|
89
124
|
width: number,
|
|
90
125
|
height: number,
|
|
126
|
+
texture: THREE.Texture | null = null,
|
|
127
|
+
hooks?: ShaderHooks,
|
|
91
128
|
): THREE.ShaderMaterial {
|
|
129
|
+
const hasTexture = texture !== null;
|
|
130
|
+
// [for dev]
|
|
131
|
+
// const activeHooks = hasTexture ? glassHooks : hooks;
|
|
132
|
+
|
|
133
|
+
const declChunk = hasTexture
|
|
134
|
+
? /* glsl */ `
|
|
135
|
+
uniform sampler2D uTexture;
|
|
136
|
+
varying vec4 vScreenPos;
|
|
137
|
+
`
|
|
138
|
+
: "";
|
|
139
|
+
|
|
140
|
+
// [un dev]
|
|
141
|
+
const uvChunk = hasTexture
|
|
142
|
+
? /* glsl */ `
|
|
143
|
+
vec2 screenUv = (vScreenPos.xy / vScreenPos.w) * 0.5 + 0.5;
|
|
144
|
+
vec2 resultUv = screenUv;
|
|
145
|
+
${hooks?.uvModifier || ""}
|
|
146
|
+
`
|
|
147
|
+
: "";
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
// [for dev]
|
|
151
|
+
// const uvChunk = hasTexture
|
|
152
|
+
// ? /* glsl */ `
|
|
153
|
+
// vec2 screenUv = (vScreenPos.xy / vScreenPos.w) * 0.5 + 0.5;
|
|
154
|
+
// vec2 resultUv = screenUv;
|
|
155
|
+
// ${activeHooks?.uvModifier || ""}
|
|
156
|
+
// `
|
|
157
|
+
// : "";
|
|
158
|
+
|
|
159
|
+
const baseColorChunk = hasTexture
|
|
160
|
+
? /* glsl */ `
|
|
161
|
+
baseColor = texture2D(uTexture, resultUv);
|
|
162
|
+
`
|
|
163
|
+
: "";
|
|
164
|
+
|
|
165
|
+
// [un dev]
|
|
166
|
+
const colorModChunk = hooks?.colorModifier || "";
|
|
167
|
+
|
|
168
|
+
// [for dev]
|
|
169
|
+
// const colorModChunk = hasTexture
|
|
170
|
+
// ? activeHooks?.colorModifier || ""
|
|
171
|
+
// : hooks?.colorModifier || "";
|
|
172
|
+
|
|
173
|
+
const fragmentShader = fragmentShaderTemplate
|
|
174
|
+
.replace("#INJECT_DECLARATIONS", declChunk)
|
|
175
|
+
.replace("#INJECT_UV_MODIFIER", uvChunk)
|
|
176
|
+
.replace("#INJECT_BASE_COLOR", baseColorChunk)
|
|
177
|
+
.replace("#INJECT_COLOR_MODIFIER", colorModChunk);
|
|
178
|
+
|
|
179
|
+
// uniform setting
|
|
92
180
|
const parsedBg = parseColor(styles.backgroundColor);
|
|
93
181
|
const parsedBorder = parseColor(styles.borderColor);
|
|
94
|
-
|
|
95
182
|
const uniforms = {
|
|
96
183
|
uSize: { value: new THREE.Vector2(width, height) },
|
|
97
|
-
|
|
184
|
+
uBorderRadius: { value: new THREE.Vector4(0, 0, 0, 0) },
|
|
98
185
|
uBorderWidth: { value: parsePixelValue(styles.borderWidth) },
|
|
99
|
-
|
|
186
|
+
uBgColor: { value: parsedBg.color },
|
|
100
187
|
uBorderColor: { value: parsedBorder.color },
|
|
101
188
|
uOpacity: { value: styles.opacity ?? 1.0 },
|
|
102
189
|
uBgOpacity: { value: parsedBg.alpha },
|
|
190
|
+
uBorderOpacity: { value: parsedBorder.alpha },
|
|
191
|
+
uTexture: { value: null as THREE.Texture | null },
|
|
103
192
|
};
|
|
193
|
+
// border radius value initialize
|
|
194
|
+
setBorderRadius(uniforms.uBorderRadius.value, styles.borderRadius);
|
|
195
|
+
|
|
196
|
+
if (hasTexture) {
|
|
197
|
+
uniforms.uTexture.value = texture;
|
|
198
|
+
}
|
|
104
199
|
|
|
105
200
|
const material = new THREE.ShaderMaterial({
|
|
106
201
|
uniforms: uniforms,
|
|
@@ -118,18 +213,24 @@ export function updateBoxMaterial(
|
|
|
118
213
|
styles: BoxStyles,
|
|
119
214
|
width: number,
|
|
120
215
|
height: number,
|
|
216
|
+
texture?: THREE.Texture | null,
|
|
121
217
|
) {
|
|
122
218
|
const parsedBg = parseColor(styles.backgroundColor);
|
|
123
219
|
const parsedBorder = parseColor(styles.borderColor);
|
|
124
220
|
|
|
125
221
|
material.uniforms.uSize.value.set(width, height);
|
|
126
222
|
|
|
127
|
-
material.uniforms.
|
|
223
|
+
// material.uniforms.uBorderRadius.value = parsePixelValue(styles.borderRadius);
|
|
224
|
+
setBorderRadius(material.uniforms.uBorderRadius.value, styles.borderRadius);
|
|
128
225
|
material.uniforms.uBorderWidth.value = parsePixelValue(styles.borderWidth);
|
|
129
226
|
|
|
130
|
-
material.uniforms.
|
|
227
|
+
material.uniforms.uBgColor.value.copy(parsedBg.color);
|
|
131
228
|
material.uniforms.uBorderColor.value.copy(parsedBorder.color);
|
|
132
229
|
|
|
133
230
|
material.uniforms.uOpacity.value = styles.opacity ?? 1.0;
|
|
134
231
|
material.uniforms.uBgOpacity.value = parsedBg.alpha;
|
|
232
|
+
material.uniforms.uBorderOpacity.value = parsedBorder.alpha;
|
|
233
|
+
if (material.uniforms.uTexture && texture !== undefined) {
|
|
234
|
+
material.uniforms.uTexture.value = texture;
|
|
235
|
+
}
|
|
135
236
|
}
|
package/src/Painter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
|
-
import { BoxStyles, TextStyles } from "./types";
|
|
2
|
+
import { BoxStyles, TextStyles, ShaderHooks} from "./types";
|
|
3
3
|
import { createBoxMaterial, updateBoxMaterial } from "./BoxGenerator";
|
|
4
4
|
import { createTextTexture } from "./TextGenerator";
|
|
5
5
|
|
|
@@ -11,9 +11,11 @@ export const Painter = {
|
|
|
11
11
|
width: number,
|
|
12
12
|
height: number,
|
|
13
13
|
quality: number = 2,
|
|
14
|
+
texture: THREE.Texture | null = null,
|
|
15
|
+
shaderHooks?: ShaderHooks
|
|
14
16
|
): THREE.Material {
|
|
15
17
|
if (type === "BOX") {
|
|
16
|
-
return createBoxMaterial(styles as BoxStyles, width, height);
|
|
18
|
+
return createBoxMaterial(styles as BoxStyles, width, height, texture, shaderHooks);
|
|
17
19
|
} else if (type === "TEXT") {
|
|
18
20
|
const texture = createTextTexture(
|
|
19
21
|
content || "",
|
|
@@ -42,6 +44,7 @@ export const Painter = {
|
|
|
42
44
|
width: number,
|
|
43
45
|
height: number,
|
|
44
46
|
quality: number = 2,
|
|
47
|
+
texture?: THREE.Texture | null
|
|
45
48
|
) {
|
|
46
49
|
if (type === "BOX") {
|
|
47
50
|
updateBoxMaterial(
|
|
@@ -49,6 +52,7 @@ export const Painter = {
|
|
|
49
52
|
styles as BoxStyles,
|
|
50
53
|
width,
|
|
51
54
|
height,
|
|
55
|
+
texture
|
|
52
56
|
);
|
|
53
57
|
} else if (type === "TEXT") {
|
|
54
58
|
const basicMat = material as THREE.MeshBasicMaterial;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export const isTraveler = true;
|
|
2
|
+
|
|
3
|
+
export const glassHooks = {
|
|
4
|
+
uvModifier: /* glsl */ `
|
|
5
|
+
// 1. 박스 크기에 비례한 두께감
|
|
6
|
+
// float thickness_uv = min(uSize.x, uSize.y) * 0.5;
|
|
7
|
+
float thickness_uv = 25.0;
|
|
8
|
+
float index = 1.3; // 굴절률 (공기: 1.0, 유리: 1.5~1.9)
|
|
9
|
+
float base_height = thickness_uv * 1.5;
|
|
10
|
+
|
|
11
|
+
// 2. 로컬 SDF 계산 (p는 픽셀 단위)
|
|
12
|
+
vec2 xRadii_uv = mix(uBorderRadius.xw, uBorderRadius.yz, step(0.0, p.x));
|
|
13
|
+
float r_uv = mix(xRadii_uv.y, xRadii_uv.x, step(0.0, p.y));
|
|
14
|
+
float d_uv = sdRoundedBox(p, halfSize, r_uv);
|
|
15
|
+
|
|
16
|
+
// 3. 3D 노말(Normal) 계산
|
|
17
|
+
float dx_uv = dFdx(d_uv);
|
|
18
|
+
float dy_uv = dFdy(d_uv);
|
|
19
|
+
if (abs(dx_uv) < 0.001 && abs(dy_uv) < 0.001) {
|
|
20
|
+
vec2 fallbackDir = normalize(p);
|
|
21
|
+
dx_uv = fallbackDir.x; dy_uv = fallbackDir.y;
|
|
22
|
+
}
|
|
23
|
+
float n_cos_uv = max(thickness_uv + d_uv, 0.0) / max(thickness_uv, 0.001);
|
|
24
|
+
float n_sin_uv = sqrt(max(1.0 - n_cos_uv * n_cos_uv, 0.0));
|
|
25
|
+
vec3 normal_uv = normalize(vec3(dx_uv * n_cos_uv, dy_uv * n_cos_uv, n_sin_uv));
|
|
26
|
+
|
|
27
|
+
// 4. Z 높이 계산
|
|
28
|
+
float h = 0.0;
|
|
29
|
+
if (d_uv < -thickness_uv) {
|
|
30
|
+
h = thickness_uv;
|
|
31
|
+
} else if (d_uv < 0.0) {
|
|
32
|
+
float x = thickness_uv + d_uv;
|
|
33
|
+
h = sqrt(max(thickness_uv * thickness_uv - x * x, 0.0));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 5. 굴절(Refraction) 적용
|
|
37
|
+
vec3 incident = vec3(0.0, 0.0, -1.0);
|
|
38
|
+
vec3 refract_vec = refract(incident, normal_uv, 1.0 / index);
|
|
39
|
+
|
|
40
|
+
float denom = dot(vec3(0.0, 0.0, -1.0), refract_vec);
|
|
41
|
+
float refract_length = (h + base_height) / max(denom, 0.001);
|
|
42
|
+
|
|
43
|
+
// offsetPx는 '박스 내부의 픽셀 단위' 왜곡 거리입니다.
|
|
44
|
+
vec2 offsetPx = refract_vec.xy * refract_length;
|
|
45
|
+
|
|
46
|
+
// [다시 복구!] 큰 배경 텍스쳐를 자르기 위해 screenUv를 사용
|
|
47
|
+
// 화면 1픽셀이 전체 screenUv(0~1)에서 차지하는 비율을 편미분으로 동적 계산
|
|
48
|
+
vec2 pixelToUv = vec2(dFdx(screenUv.x), dFdy(screenUv.y));
|
|
49
|
+
if (abs(pixelToUv.x) < 0.000001) pixelToUv.x = 1.0 / 1920.0; // 안전 장치
|
|
50
|
+
if (abs(pixelToUv.y) < 0.000001) pixelToUv.y = 1.0 / 1080.0; // 안전 장치
|
|
51
|
+
|
|
52
|
+
// 픽셀 오프셋을 화면 UV 오프셋으로 변환
|
|
53
|
+
vec2 distortOffset = offsetPx * pixelToUv;
|
|
54
|
+
|
|
55
|
+
// vUv 대신 screenUv에서 왜곡값을 빼서 최종 배경 픽셀을 당겨옵니다.
|
|
56
|
+
resultUv = screenUv - distortOffset;
|
|
57
|
+
`,
|
|
58
|
+
|
|
59
|
+
// colorModifier: /* glsl */ `
|
|
60
|
+
// // float thickness_c = min(uSize.x, uSize.y) * 0.5;
|
|
61
|
+
// float thickness_c = 25.0;
|
|
62
|
+
// float dx_c = dFdx(d);
|
|
63
|
+
// float dy_c = dFdy(d);
|
|
64
|
+
// if (abs(dx_c) < 0.001 && abs(dy_c) < 0.001) {
|
|
65
|
+
// vec2 fallbackDir = normalize(p);
|
|
66
|
+
// dx_c = fallbackDir.x; dy_c = fallbackDir.y;
|
|
67
|
+
// }
|
|
68
|
+
// float n_cos_c = max(thickness_c + d, 0.0) / max(thickness_c, 0.001);
|
|
69
|
+
// float n_sin_c = sqrt(max(1.0 - n_cos_c * n_cos_c, 0.0));
|
|
70
|
+
// vec3 normal_c = normalize(vec3(dx_c * n_cos_c, dy_c * n_cos_c, n_sin_c));
|
|
71
|
+
|
|
72
|
+
// float edgeReflection = smoothstep(-5.0, 0.0, d) * smoothstep(0.0, -5.0, d);
|
|
73
|
+
// float fresnel = pow(1.0 - max(dot(normal_c, vec3(0.0, 0.0, 1.0)), 0.0), 3.0);
|
|
74
|
+
|
|
75
|
+
// finalColor.rgb += vec3(1.0) * (edgeReflection * 0.4 + fresnel * 0.5);
|
|
76
|
+
// `,
|
|
77
|
+
colorModifier: /* glsl */ `
|
|
78
|
+
// 1. 노말 계산 (기존과 동일)
|
|
79
|
+
float thickness_c = 25.0;
|
|
80
|
+
float dx_c = dFdx(d);
|
|
81
|
+
float dy_c = dFdy(d);
|
|
82
|
+
if (abs(dx_c) < 0.001 && abs(dy_c) < 0.001) {
|
|
83
|
+
vec2 fallbackDir = normalize(p);
|
|
84
|
+
dx_c = fallbackDir.x; dy_c = fallbackDir.y;
|
|
85
|
+
}
|
|
86
|
+
float n_cos_c = max(thickness_c + d, 0.0) / max(thickness_c, 0.001);
|
|
87
|
+
float n_sin_c = sqrt(max(1.0 - n_cos_c * n_cos_c, 0.0));
|
|
88
|
+
vec3 normal_c = normalize(vec3(dx_c * n_cos_c, dy_c * n_cos_c, n_sin_c));
|
|
89
|
+
|
|
90
|
+
// ==========================================
|
|
91
|
+
// [애플 스타일 리퀴드 글래스 틴트 & 질감 추가]
|
|
92
|
+
// ==========================================
|
|
93
|
+
|
|
94
|
+
// A. 틴트(Tint) 조작
|
|
95
|
+
// 밝은 테마라면 vec3(1.0), 어두운 테마라면 vec3(0.1) 등을 사용
|
|
96
|
+
vec3 tintColor = vec3(0.03, 0.03, 0.8);
|
|
97
|
+
float tintStrength = 0.3; // 틴트 강도 (0.1 ~ 0.3 사이 추천)
|
|
98
|
+
finalColor.rgb = mix(finalColor.rgb, tintColor, tintStrength);
|
|
99
|
+
|
|
100
|
+
// B. 프로스트 글래스 노이즈 (시각적 밀도감)
|
|
101
|
+
// vUv를 이용한 간단한 난수 생성으로 화면 픽셀 단위의 미세한 질감을 줌
|
|
102
|
+
float noise = fract(sin(dot(vUv, vec2(12.9898, 78.233))) * 43758.5453) * 2.0 - 1.0;
|
|
103
|
+
finalColor.rgb += noise * 0.01; // 노이즈 강도 (너무 강하면 지저분해짐)
|
|
104
|
+
|
|
105
|
+
// C. 프레넬(Fresnel) 및 날카로운 단면 반사광
|
|
106
|
+
float fresnel = pow(1.0 - max(dot(normal_c, vec3(0.0, 0.0, 1.0)), 0.0), 4.0);
|
|
107
|
+
|
|
108
|
+
// 기존의 두꺼운 15px 빛 맺힘 대신 1.5px~2px 수준의 예리한 테두리 하이라이트
|
|
109
|
+
float sharpEdge = smoothstep(-5.0, 0.0, d) * smoothstep(0.0, -5.0, d);
|
|
110
|
+
|
|
111
|
+
// 빛 합성 (테두리가 살짝 빛나며 유리 질감을 완성)
|
|
112
|
+
finalColor.rgb += vec3(1.0) * (sharpEdge * 0.6 + fresnel * 0.3);
|
|
113
|
+
`,
|
|
114
|
+
};
|