Files
site-portfolio/public/vendor/paper-shaders/shaders/image-dithering.js
T
root daa623fe73 feat(mesh-bg): fade-in шейдера фона + modulepreload графа
- canvas стартует с opacity:0 и плавно проявляется (0.9s) при .is-ready
- initPageMeshShader ставит is-ready после первого кадра
- modulepreload page-mesh-shader.js для параллельной загрузки с main.js
- пересборка страниц (build:pages)
2026-09-24 21:37:40 +02:00

202 lines
5.5 KiB
JavaScript

/* * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Paper Shaders *
* https://github.com/paper-design/shaders *
* * * * * * * * * * * * * * * * * * * * * * * * * * * */
import {} from "../shader-sizing.js";
import { proceduralHash21, declarePI } from "../shader-utils.js";
const imageDitheringFragmentShader = `#version 300 es
precision mediump float;
uniform vec2 u_resolution;
uniform float u_pixelRatio;
uniform float u_originX;
uniform float u_originY;
uniform float u_worldWidth;
uniform float u_worldHeight;
uniform float u_fit;
uniform float u_scale;
uniform float u_rotation;
uniform float u_offsetX;
uniform float u_offsetY;
uniform vec4 u_colorFront;
uniform vec4 u_colorBack;
uniform vec4 u_colorHighlight;
uniform sampler2D u_image;
uniform float u_imageAspectRatio;
uniform float u_type;
uniform float u_pxSize;
uniform bool u_originalColors;
uniform bool u_inverted;
uniform float u_colorSteps;
out vec4 fragColor;
${proceduralHash21}
${declarePI}
float getUvFrame(vec2 uv, vec2 pad) {
float aa = 0.0001;
float left = smoothstep(-pad.x, -pad.x + aa, uv.x);
float right = smoothstep(1.0 + pad.x, 1.0 + pad.x - aa, uv.x);
float bottom = smoothstep(-pad.y, -pad.y + aa, uv.y);
float top = smoothstep(1.0 + pad.y, 1.0 + pad.y - aa, uv.y);
return left * right * bottom * top;
}
vec2 getImageUV(vec2 uv) {
vec2 boxOrigin = vec2(.5 - u_originX, u_originY - .5);
float r = u_rotation * PI / 180.;
mat2 graphicRotation = mat2(cos(r), sin(r), -sin(r), cos(r));
vec2 graphicOffset = vec2(-u_offsetX, u_offsetY);
vec2 imageBoxSize;
if (u_fit == 1.) { // contain
imageBoxSize.x = min(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio;
} else if (u_fit == 2.) { // cover
imageBoxSize.x = max(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio;
} else {
imageBoxSize.x = min(10.0, 10.0 / u_imageAspectRatio * u_imageAspectRatio);
}
imageBoxSize.y = imageBoxSize.x / u_imageAspectRatio;
vec2 imageBoxScale = u_resolution.xy / imageBoxSize;
vec2 imageUV = uv;
imageUV *= imageBoxScale;
imageUV += boxOrigin * (imageBoxScale - 1.);
imageUV += graphicOffset;
imageUV /= u_scale;
imageUV.x *= u_imageAspectRatio;
imageUV = graphicRotation * imageUV;
imageUV.x /= u_imageAspectRatio;
imageUV += .5;
imageUV.y = 1. - imageUV.y;
return imageUV;
}
const int bayer2x2[4] = int[4](0, 2, 3, 1);
const int bayer4x4[16] = int[16](
0, 8, 2, 10,
12, 4, 14, 6,
3, 11, 1, 9,
15, 7, 13, 5
);
const int bayer8x8[64] = int[64](
0, 32, 8, 40, 2, 34, 10, 42,
48, 16, 56, 24, 50, 18, 58, 26,
12, 44, 4, 36, 14, 46, 6, 38,
60, 28, 52, 20, 62, 30, 54, 22,
3, 35, 11, 43, 1, 33, 9, 41,
51, 19, 59, 27, 49, 17, 57, 25,
15, 47, 7, 39, 13, 45, 5, 37,
63, 31, 55, 23, 61, 29, 53, 21
);
float getBayerValue(vec2 uv, int size) {
ivec2 pos = ivec2(fract(uv / float(size)) * float(size));
int index = pos.y * size + pos.x;
if (size == 2) {
return float(bayer2x2[index]) / 4.0;
} else if (size == 4) {
return float(bayer4x4[index]) / 16.0;
} else if (size == 8) {
return float(bayer8x8[index]) / 64.0;
}
return 0.0;
}
void main() {
float pxSize = u_pxSize * u_pixelRatio;
vec2 pxSizeUV = gl_FragCoord.xy - .5 * u_resolution;
pxSizeUV /= pxSize;
vec2 canvasPixelizedUV = (floor(pxSizeUV) + .5) * pxSize;
vec2 normalizedUV = canvasPixelizedUV / u_resolution;
vec2 imageUV = getImageUV(normalizedUV);
vec2 ditheringNoiseUV = canvasPixelizedUV;
vec4 image = texture(u_image, imageUV);
float frame = getUvFrame(imageUV, pxSize / u_resolution);
int type = int(floor(u_type));
float dithering = 0.0;
float lum = dot(vec3(.2126, .7152, .0722), image.rgb);
lum = u_inverted ? (1. - lum) : lum;
switch (type) {
case 1: {
dithering = step(hash21(ditheringNoiseUV), lum);
} break;
case 2:
dithering = getBayerValue(pxSizeUV, 2);
break;
case 3:
dithering = getBayerValue(pxSizeUV, 4);
break;
default :
dithering = getBayerValue(pxSizeUV, 8);
break;
}
float colorSteps = max(floor(u_colorSteps), 1.);
vec3 color = vec3(0.0);
float opacity = 1.;
dithering -= .5;
float brightness = clamp(lum + dithering / colorSteps, 0.0, 1.0);
brightness = mix(0.0, brightness, frame);
brightness = mix(0.0, brightness, image.a);
float quantLum = floor(brightness * colorSteps + 0.5) / colorSteps;
quantLum = mix(0.0, quantLum, frame);
if (u_originalColors == true) {
vec3 normColor = image.rgb / max(lum, 0.001);
color = normColor * quantLum;
float quantAlpha = floor(image.a * colorSteps + 0.5) / colorSteps;
opacity = mix(quantLum, 1., quantAlpha);
} else {
vec3 fgColor = u_colorFront.rgb * u_colorFront.a;
float fgOpacity = u_colorFront.a;
vec3 bgColor = u_colorBack.rgb * u_colorBack.a;
float bgOpacity = u_colorBack.a;
vec3 hlColor = u_colorHighlight.rgb * u_colorHighlight.a;
float hlOpacity = u_colorHighlight.a;
fgColor = mix(fgColor, hlColor, step(1.02 - .02 * u_colorSteps, brightness));
fgOpacity = mix(fgOpacity, hlOpacity, step(1.02 - .02 * u_colorSteps, brightness));
color = fgColor * quantLum;
opacity = fgOpacity * quantLum;
color += bgColor * (1.0 - opacity);
opacity += bgOpacity * (1.0 - opacity);
}
fragColor = vec4(color, opacity);
}
`;
const DitheringTypes = {
"random": 1,
"2x2": 2,
"4x4": 3,
"8x8": 4
};
export {
DitheringTypes,
imageDitheringFragmentShader
};
//# sourceMappingURL=image-dithering.js.map