@@ -199,6 +199,116 @@ export const pluginTemplate = definePlugin({
199199 // var finalColor: vec4f;
200200 textureStore(resultTexture, id.xy, finalColor);
201201 }
202+
203+ // Drop-in replacement for mix() that works with vec3f rgb colors
204+ fn mixOklch(color1: vec3<f32>, color2: vec3<f32>, t: f32) -> vec3<f32> {
205+ // RGB -> Linear RGB
206+ let linearColor1 = vec3<f32>(
207+ select(color1.r / 12.92, pow((color1.r + 0.055) / 1.055, 2.4), color1.r <= 0.04045),
208+ select(color1.g / 12.92, pow((color1.g + 0.055) / 1.055, 2.4), color1.g <= 0.04045),
209+ select(color1.b / 12.92, pow((color1.b + 0.055) / 1.055, 2.4), color1.b <= 0.04045),
210+ );
211+
212+ let linearColor2 = vec3<f32>(
213+ select(color2.r / 12.92, pow((color2.r + 0.055) / 1.055, 2.4), color2.r <= 0.04045),
214+ select(color2.g / 12.92, pow((color2.g + 0.055) / 1.055, 2.4), color2.g <= 0.04045),
215+ select(color2.b / 12.92, pow((color2.b + 0.055) / 1.055, 2.4), color2.b <= 0.04045),
216+ );
217+
218+ // Linear RGB -> LMS
219+ let lms1 = mat3x3<f32>(
220+ 0.4122214708, 0.5363325363, 0.0514459929,
221+ 0.2119034982, 0.6806995451, 0.1073969566,
222+ 0.0883024619, 0.2817188376, 0.6299787005
223+ ) * linearColor1;
224+
225+ let lms2 = mat3x3<f32>(
226+ 0.4122214708, 0.5363325363, 0.0514459929,
227+ 0.2119034982, 0.6806995451, 0.1073969566,
228+ 0.0883024619, 0.2817188376, 0.6299787005
229+ ) * linearColor2;
230+
231+ // LMS -> Oklab
232+ let lms1_pow = vec3<f32>(pow(lms1.x, 1.0/3.0), pow(lms1.y, 1.0/3.0), pow(lms1.z, 1.0/3.0));
233+ let lms2_pow = vec3<f32>(pow(lms2.x, 1.0/3.0), pow(lms2.y, 1.0/3.0), pow(lms2.z, 1.0/3.0));
234+
235+ let oklabMatrix = mat3x3<f32>(
236+ 0.2104542553, 0.7936177850, -0.0040720468,
237+ 1.9779984951, -2.4285922050, 0.4505937099,
238+ 0.0259040371, 0.7827717662, -0.8086757660
239+ );
240+
241+ let oklab1 = oklabMatrix * lms1_pow;
242+ let oklab2 = oklabMatrix * lms2_pow;
243+
244+ // Oklab -> OKLCH
245+ let L1 = oklab1.x;
246+ let L2 = oklab2.x;
247+ let C1 = sqrt(oklab1.y * oklab1.y + oklab1.z * oklab1.z);
248+ let C2 = sqrt(oklab2.y * oklab2.y + oklab2.z * oklab2.z);
249+ let H1 = atan2(oklab1.z, oklab1.y);
250+ let H2 = atan2(oklab2.z, oklab2.y);
251+
252+ // 色相の補間(最短経路)
253+ let hDiff = H2 - H1;
254+ let hDiffAdjusted = select(
255+ hDiff,
256+ hDiff - 2.0 * 3.14159265359,
257+ hDiff > 3.14159265359
258+ );
259+ let hDiffFinal = select(
260+ hDiffAdjusted,
261+ hDiffAdjusted + 2.0 * 3.14159265359,
262+ hDiffAdjusted < -3.14159265359
263+ );
264+
265+ let L = mix(L1, L2, t);
266+ let C = mix(C1, C2, t);
267+ let H = H1 + t * hDiffFinal;
268+
269+ // OKLCH -> Oklab
270+ let a = C * cos(H);
271+ let b = C * sin(H);
272+
273+ // Oklab -> LMS
274+ let oklabInverseMatrix = mat3x3<f32>(
275+ 1.0, 0.3963377774, 0.2158037573,
276+ 1.0, -0.1055613458, -0.0638541728,
277+ 1.0, -0.0894841775, -1.2914855480
278+ );
279+
280+ let lms_pow = oklabInverseMatrix * vec3<f32>(L, a, b);
281+ let lms = vec3<f32>(
282+ pow(lms_pow.x, 3.0),
283+ pow(lms_pow.y, 3.0),
284+ pow(lms_pow.z, 3.0)
285+ );
286+
287+ // LMS -> Linear RGB
288+ let lmsToRgbMatrix = mat3x3<f32>(
289+ 4.0767416621, -3.3077115913, 0.2309699292,
290+ -1.2684380046, 2.6097574011, -0.3413193965,
291+ -0.0041960863, -0.7034186147, 1.7076147010
292+ );
293+
294+ let linearRgb = lmsToRgbMatrix * lms;
295+
296+ // Linear RGB -> RGB
297+ let rgbResult = vec3<f32>(
298+ select(12.92 * linearRgb.r, 1.055 * pow(linearRgb.r, 1.0/2.4) - 0.055, linearRgb.r <= 0.0031308),
299+ select(12.92 * linearRgb.g, 1.055 * pow(linearRgb.g, 1.0/2.4) - 0.055, linearRgb.g <= 0.0031308),
300+ select(12.92 * linearRgb.b, 1.055 * pow(linearRgb.b, 1.0/2.4) - 0.055, linearRgb.b <= 0.0031308),
301+ );
302+
303+ return clamp(rgbResult, vec3<f32>(0.0), vec3<f32>(1.0));
304+ }
305+
306+ fn mixOklchVec4(color1: vec4<f32>, color2: vec4<f32>, t: f32) -> vec4<f32> {
307+ return vec4<f32>(
308+ mixOklch(color1.rgb, color2.rgb, t),
309+ mix(color1.a, color2.a, t)
310+ );
311+ }
202312 ` ;
203313
204314 const shader = device . createShaderModule ( {
0 commit comments