first commit

This commit is contained in:
2026-10-06 12:16:46 +09:00
commit 619d980098
415 changed files with 22283 additions and 0 deletions
@@ -0,0 +1,88 @@
uniform sampler2D uSceneTexture;
uniform mat4 uContentViewProjection,uContentInverseViewProjection;
uniform vec3 uContentCameraPosition,uLightPosition,uBackgroundColor;
uniform float uGlassZ,uGridSize,uStrength,uMosaicStyle,uCurvature,uGlossStrength;
uniform vec2 uViewOffset,uPetSize,uPaneSize;
uniform vec4 uPaneRect;
varying vec2 vUv;
vec2 projectUv(vec3 p){vec4 c=uContentViewProjection*vec4(p,1.);float w=abs(c.w)<.0001?(c.w<0.?-.0001:.0001):c.w;return c.xy/w*.5+.5;}
vec4 sampleScene(vec2 uv){return texture2D(uSceneTexture,clamp(uv,0.,1.));}
vec4 samplePane(vec2 pane){return sampleScene(projectUv(vec3((pane-.5)*uPaneSize,uGlassZ)));}
float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}
vec2 hash2(vec2 p){return vec2(hash(p),hash(p+vec2(19.19,73.73)));}
float safeZ(float z){return abs(z)<.0001?(z<0.?-.0001:.0001):z;}
vec3 bend(vec3 i,vec3 n){vec3 r=refract(i,n,.75);return length(r)<.0001?i:r;}
float softLimit(float value,float limit){return value*limit/(limit+abs(value));}
void evaluateCell(vec2 cell,vec2 local,vec2 grid,vec2 cellSize,vec2 cellDimensions,float cellWorld,float style,out vec2 affineUv,out mat2 transform,out vec2 curvedSlope,out vec2 facetSlope){
vec2 random=hash2(cell),widths;
if(style>.5){
widths=mix(vec2(.82),vec2(1.12),hash2(cell+vec2(11.4,9.8)));
}else{
float widthSeed=hash(cell+vec2(53.1,27.4));
if(widthSeed<.3)widths=mix(vec2(.90),vec2(1.08),hash2(cell+vec2(7.2,3.1)));
else if(widthSeed<.9){
widths=mix(vec2(.46),vec2(.70),hash2(cell+vec2(7.2,3.1)));
float area=sqrt(widths.x*widths.y),longWidth=area*1.41421356,shortWidth=area*.70710678;
widths=hash(cell+vec2(101.3,37.7))<.5?vec2(longWidth,shortWidth):vec2(shortWidth,longWidth);
}
else widths=mix(vec2(1.10),vec2(1.28),hash2(cell+vec2(7.2,3.1)));
}
float angle=(random.x-.5)*.08,c=cos(angle),s=sin(angle);
transform=mat2(widths.x*c,-widths.y*s,widths.x*s,widths.y*c);
vec2 sourceCenter=(cell+.5)/grid+(random-.5)*.16*cellSize;
affineUv=sourceCenter+transform*(local*cellSize);
// Positive height bulges toward the observer (+Z); negative height is concave.
float curvature=clamp(uCurvature,-1.,1.),excess=max(curvature-.65,0.),surfaceCurvature=curvature-(.25/.35)*excess*excess;
float height=.009*cellWorld*surfaceCurvature*mix(.92,1.08,hash(cell+vec2(41.7,17.3)));
vec2 q=clamp(2.*local,-1.,1.),q2=q*q,q3=q2*q,b=1.-q2*q2;
vec2 skew=(hash2(cell+vec2(23.8,61.2))-.5)*.28;float asym=1.+dot(skew,q);
vec2 dhdq=height*vec2(-4.*q3.x*b.y*asym+b.x*b.y*skew.x,-4.*q3.y*b.x*asym+b.x*b.y*skew.y);
vec2 grad=-2.*dhdq/cellDimensions;
curvedSlope=grad+(random-.5)*.012;facetSlope=(random-.5)*.012;
}
void main(){
if(uStrength<=.001){gl_FragColor=sampleScene(vUv);
#include <colorspace_fragment>
return;}
vec4 f=uContentInverseViewProjection*vec4(vUv*2.-1.,1.,1.);float fw=abs(f.w)<.0001?(f.w<0.?-.0001:.0001):f.w;vec3 farWorld=f.xyz/fw;vec3 i=normalize(farWorld-uContentCameraPosition);float gt=(uGlassZ-uContentCameraPosition.z)/safeZ(i.z);vec3 gh=uContentCameraPosition+i*gt;
vec2 paneUv=gh.xy/uPaneSize+.5;vec2 grid=vec2(uGridSize,max(1.,uGridSize*uPaneSize.y/uPaneSize.x));vec2 gridUv=paneUv*grid,cell=floor(gridUv),local=fract(gridUv)-.5,cellSize=1./grid;float style=clamp(uMosaicStyle,0.,1.),cellWorld=min(uPaneSize.x/grid.x,uPaneSize.y/grid.y);
if(paneUv.x<0.||paneUv.x>1.||paneUv.y<0.||paneUv.y>1.){gl_FragColor=vec4(uBackgroundColor,1.);
#include <colorspace_fragment>
return;}
vec2 cellDimensions=uPaneSize/grid;
vec2 affineUv,curvedSlope,facetSlope;mat2 transform;
evaluateCell(cell,local,grid,cellSize,cellDimensions,cellWorld,style,affineUv,transform,curvedSlope,facetSlope);
vec2 edge=.5-abs(local),neighborSign=mix(vec2(-1.),vec2(1.),step(vec2(0.),local));
vec2 blendAxis=1.-smoothstep(vec2(-.16),vec2(.16),edge);
vec2 weights=blendAxis;
if(weights.x>0.||weights.y>0.){
vec2 neighborAffine,neighborCurved,neighborFacet;mat2 neighborTransform;
vec2 xWeight=vec2(weights.x*(1.-weights.y),0.);
vec2 yWeight=vec2(0.,(1.-weights.x)*weights.y);
float diagonalWeight=weights.x*weights.y;
float centerWeight=(1.-weights.x)*(1.-weights.y);
affineUv*=centerWeight;transform*=centerWeight;curvedSlope*=centerWeight;facetSlope*=centerWeight;
if(xWeight.x>0.){
vec2 offset=vec2(neighborSign.x,0.);
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
affineUv+=neighborAffine*xWeight.x;transform+=neighborTransform*xWeight.x;curvedSlope+=neighborCurved*xWeight.x;facetSlope+=neighborFacet*xWeight.x;
}
if(yWeight.y>0.){
vec2 offset=vec2(0.,neighborSign.y);
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
affineUv+=neighborAffine*yWeight.y;transform+=neighborTransform*yWeight.y;curvedSlope+=neighborCurved*yWeight.y;facetSlope+=neighborFacet*yWeight.y;
}
if(diagonalWeight>0.){
vec2 offset=neighborSign;
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
affineUv+=neighborAffine*diagonalWeight;transform+=neighborTransform*diagonalWeight;curvedSlope+=neighborCurved*diagonalWeight;facetSlope+=neighborFacet*diagonalWeight;
}
}
vec3 nCurved=normalize(vec3(curvedSlope,1.)),nFacet=normalize(vec3(facetSlope,1.));
vec3 straightHit=gh+i*(-gh.z/safeZ(i.z));vec3 facetRay=bend(i,nFacet),curvedRay=bend(i,nCurved);vec3 facetHit=gh+facetRay*(-gh.z/safeZ(facetRay.z)),curvedHit=gh+curvedRay*(-gh.z/safeZ(curvedRay.z));vec2 base=facetHit.xy-straightHit.xy,curve=curvedHit.xy-facetHit.xy;vec2 worldDelta=vec2(softLimit(base.x,cellWorld*.08),softLimit(base.y,cellWorld*.08));vec2 d=curve/cellWorld;worldDelta+=vec2(.3*d.x/(.3+abs(d.x)),.3*d.y/(.3+abs(d.y)))*cellWorld;
vec2 sampleUv=affineUv+transform*(worldDelta/uPaneSize);
float edgeDistance=min(min(paneUv.x,paneUv.y),min(1.-paneUv.x,1.-paneUv.y));float edgeWeight=smoothstep(0.,max(cellSize.x,cellSize.y)*.75,edgeDistance);float strength=clamp(uStrength,0.,1.)*edgeWeight;vec2 blurStep=cellSize*mix(.006,.012,hash(cell+vec2(5.)));vec2 centerUv=mix(paneUv,sampleUv,strength);vec4 color=samplePane(centerUv)*.4;color+=samplePane(mix(paneUv,sampleUv+vec2(blurStep.x,0.),strength))*.15;color+=samplePane(mix(paneUv,sampleUv-vec2(blurStep.x,0.),strength))*.15;color+=samplePane(mix(paneUv,sampleUv+vec2(0.,blurStep.y),strength))*.15;color+=samplePane(mix(paneUv,sampleUv-vec2(0.,blurStep.y),strength))*.15;color.a=1.;
vec3 v=normalize(uContentCameraPosition-gh),reflected=reflect(-v,nCurved); float lightT=(uLightPosition.z-gh.z)/safeZ(reflected.z); vec2 lightHit=gh.xy+reflected.xy*lightT; vec2 halfSize=vec2(.32,.42)*min(uPaneSize.x,uPaneSize.y); vec2 windowCoord=abs(lightHit-uLightPosition.xy)/halfSize; vec2 windowMask=1.-smoothstep(vec2(.45),vec2(1.15),windowCoord); float spec=windowMask.x*windowMask.y*step(.0001,reflected.z)*step(0.,lightT); float gloss=spec*clamp(uGlossStrength,0.,1.)*.12*edgeWeight*clamp(uStrength,0.,1.); color.rgb=mix(color.rgb,vec3(1.),gloss); gl_FragColor=color;
#include <colorspace_fragment>
}
@@ -0,0 +1,84 @@
uniform sampler2D uSceneTexture;
uniform mat4 uContentViewProjection,uContentInverseViewProjection;
uniform vec3 uContentCameraPosition,uLightPosition,uBackgroundColor;
uniform float uGlassZ,uGridSize,uStrength,uMosaicStyle,uCurvature,uGlossStrength;
uniform vec2 uViewOffset,uPetSize,uPaneSize;
uniform vec4 uPaneRect;
varying vec2 vUv;
vec2 projectUv(vec3 p){vec4 c=uContentViewProjection*vec4(p,1.);float w=abs(c.w)<.0001?(c.w<0.?-.0001:.0001):c.w;return c.xy/w*.5+.5;}
vec4 sampleScene(vec2 uv){return texture2D(uSceneTexture,clamp(uv,0.,1.));}
vec4 samplePane(vec2 pane){return sampleScene(projectUv(vec3((pane-.5)*uPaneSize,uGlassZ)));}
float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}
vec2 hash2(vec2 p){return vec2(hash(p),hash(p+vec2(19.19,73.73)));}
float safeZ(float z){return abs(z)<.0001?(z<0.?-.0001:.0001):z;}
vec3 bend(vec3 i,vec3 n){vec3 r=refract(i,n,.75);return length(r)<.0001?i:r;}
float softLimit(float value,float limit){return value*limit/(limit+abs(value));}
void evaluateCell(vec2 cell,vec2 local,vec2 grid,vec2 cellSize,vec2 cellDimensions,float cellWorld,float style,out vec2 affineUv,out mat2 transform,out vec2 curvedSlope,out vec2 facetSlope){
vec2 random=hash2(cell),widths;
if(style>.5){
widths=mix(vec2(.82),vec2(1.12),hash2(cell+vec2(11.4,9.8)));
}else{
float widthSeed=hash(cell+vec2(53.1,27.4));
if(widthSeed<.6)widths=mix(vec2(.90),vec2(1.08),hash2(cell+vec2(7.2,3.1)));
else if(widthSeed<.9)widths=mix(vec2(.46),vec2(.70),hash2(cell+vec2(7.2,3.1)));
else widths=mix(vec2(1.10),vec2(1.28),hash2(cell+vec2(7.2,3.1)));
}
float angle=(random.x-.5)*.08,c=cos(angle),s=sin(angle);
transform=mat2(widths.x*c,-widths.y*s,widths.x*s,widths.y*c);
vec2 sourceCenter=(cell+.5)/grid+(random-.5)*.16*cellSize;
affineUv=sourceCenter+transform*(local*cellSize);
// Positive height bulges toward the observer (+Z); negative height is concave.
float curvature=clamp(uCurvature,-1.,1.),excess=max(curvature-.65,0.),surfaceCurvature=curvature-(.25/.35)*excess*excess;
float height=.009*cellWorld*surfaceCurvature*mix(.92,1.08,hash(cell+vec2(41.7,17.3)));
vec2 q=clamp(2.*local,-1.,1.),q2=q*q,q3=q2*q,b=1.-q2*q2;
vec2 skew=(hash2(cell+vec2(23.8,61.2))-.5)*.28;float asym=1.+dot(skew,q);
vec2 dhdq=height*vec2(-4.*q3.x*b.y*asym+b.x*b.y*skew.x,-4.*q3.y*b.x*asym+b.x*b.y*skew.y);
vec2 grad=-2.*dhdq/cellDimensions;
curvedSlope=grad+(random-.5)*.012;facetSlope=(random-.5)*.012;
}
void main(){
if(uStrength<=.001){gl_FragColor=sampleScene(vUv);
#include <colorspace_fragment>
return;}
vec4 f=uContentInverseViewProjection*vec4(vUv*2.-1.,1.,1.);float fw=abs(f.w)<.0001?(f.w<0.?-.0001:.0001):f.w;vec3 farWorld=f.xyz/fw;vec3 i=normalize(farWorld-uContentCameraPosition);float gt=(uGlassZ-uContentCameraPosition.z)/safeZ(i.z);vec3 gh=uContentCameraPosition+i*gt;
vec2 paneUv=gh.xy/uPaneSize+.5;vec2 grid=vec2(uGridSize,max(1.,uGridSize*uPaneSize.y/uPaneSize.x));vec2 gridUv=paneUv*grid,cell=floor(gridUv),local=fract(gridUv)-.5,cellSize=1./grid;float style=clamp(uMosaicStyle,0.,1.),cellWorld=min(uPaneSize.x/grid.x,uPaneSize.y/grid.y);
if(paneUv.x<0.||paneUv.x>1.||paneUv.y<0.||paneUv.y>1.){gl_FragColor=vec4(uBackgroundColor,1.);
#include <colorspace_fragment>
return;}
vec2 cellDimensions=uPaneSize/grid;
vec2 affineUv,curvedSlope,facetSlope;mat2 transform;
evaluateCell(cell,local,grid,cellSize,cellDimensions,cellWorld,style,affineUv,transform,curvedSlope,facetSlope);
vec2 edge=.5-abs(local),neighborSign=mix(vec2(-1.),vec2(1.),step(vec2(0.),local));
vec2 blendAxis=1.-smoothstep(vec2(-.16),vec2(.16),edge);
vec2 weights=blendAxis;
if(weights.x>0.||weights.y>0.){
vec2 neighborAffine,neighborCurved,neighborFacet;mat2 neighborTransform;
vec2 xWeight=vec2(weights.x*(1.-weights.y),0.);
vec2 yWeight=vec2(0.,(1.-weights.x)*weights.y);
float diagonalWeight=weights.x*weights.y;
float centerWeight=(1.-weights.x)*(1.-weights.y);
affineUv*=centerWeight;transform*=centerWeight;curvedSlope*=centerWeight;facetSlope*=centerWeight;
if(xWeight.x>0.){
vec2 offset=vec2(neighborSign.x,0.);
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
affineUv+=neighborAffine*xWeight.x;transform+=neighborTransform*xWeight.x;curvedSlope+=neighborCurved*xWeight.x;facetSlope+=neighborFacet*xWeight.x;
}
if(yWeight.y>0.){
vec2 offset=vec2(0.,neighborSign.y);
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
affineUv+=neighborAffine*yWeight.y;transform+=neighborTransform*yWeight.y;curvedSlope+=neighborCurved*yWeight.y;facetSlope+=neighborFacet*yWeight.y;
}
if(diagonalWeight>0.){
vec2 offset=neighborSign;
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
affineUv+=neighborAffine*diagonalWeight;transform+=neighborTransform*diagonalWeight;curvedSlope+=neighborCurved*diagonalWeight;facetSlope+=neighborFacet*diagonalWeight;
}
}
vec3 nCurved=normalize(vec3(curvedSlope,1.)),nFacet=normalize(vec3(facetSlope,1.));
vec3 straightHit=gh+i*(-gh.z/safeZ(i.z));vec3 facetRay=bend(i,nFacet),curvedRay=bend(i,nCurved);vec3 facetHit=gh+facetRay*(-gh.z/safeZ(facetRay.z)),curvedHit=gh+curvedRay*(-gh.z/safeZ(curvedRay.z));vec2 base=facetHit.xy-straightHit.xy,curve=curvedHit.xy-facetHit.xy;vec2 worldDelta=vec2(softLimit(base.x,cellWorld*.08),softLimit(base.y,cellWorld*.08));vec2 d=curve/cellWorld;worldDelta+=vec2(.3*d.x/(.3+abs(d.x)),.3*d.y/(.3+abs(d.y)))*cellWorld;
vec2 sampleUv=affineUv+transform*(worldDelta/uPaneSize);
float edgeDistance=min(min(paneUv.x,paneUv.y),min(1.-paneUv.x,1.-paneUv.y));float edgeWeight=smoothstep(0.,max(cellSize.x,cellSize.y)*.75,edgeDistance);float strength=clamp(uStrength,0.,1.)*edgeWeight;vec2 blurStep=cellSize*mix(.006,.012,hash(cell+vec2(5.)));vec2 centerUv=mix(paneUv,sampleUv,strength);vec4 color=samplePane(centerUv)*.4;color+=samplePane(mix(paneUv,sampleUv+vec2(blurStep.x,0.),strength))*.15;color+=samplePane(mix(paneUv,sampleUv-vec2(blurStep.x,0.),strength))*.15;color+=samplePane(mix(paneUv,sampleUv+vec2(0.,blurStep.y),strength))*.15;color+=samplePane(mix(paneUv,sampleUv-vec2(0.,blurStep.y),strength))*.15;color.a=1.;
vec3 v=normalize(uContentCameraPosition-gh),reflected=reflect(-v,nCurved); float lightT=(uLightPosition.z-gh.z)/safeZ(reflected.z); vec2 lightHit=gh.xy+reflected.xy*lightT; vec2 halfSize=vec2(.32,.42)*min(uPaneSize.x,uPaneSize.y); vec2 windowCoord=abs(lightHit-uLightPosition.xy)/halfSize; vec2 windowMask=1.-smoothstep(vec2(.45),vec2(1.15),windowCoord); float spec=windowMask.x*windowMask.y*step(.0001,reflected.z)*step(0.,lightT); float gloss=spec*clamp(uGlossStrength,0.,1.)*.12*edgeWeight*clamp(uStrength,0.,1.); color.rgb=mix(color.rgb,vec3(1.),gloss); gl_FragColor=color;
#include <colorspace_fragment>
}
@@ -0,0 +1,118 @@
uniform sampler2D uSceneTexture;
uniform mat4 uContentViewProjection,uContentInverseViewProjection;
uniform vec3 uContentCameraPosition,uLightPosition,uBackgroundColor;
uniform float uGlassZ,uGridSize,uStrength,uMosaicStyle,uCurvature,uGlossStrength;
uniform vec2 uViewOffset,uPetSize,uPaneSize;
uniform vec4 uPaneRect;
varying vec2 vUv;
vec2 projectUv(vec3 p){vec4 c=uContentViewProjection*vec4(p,1.);float w=abs(c.w)<.0001?(c.w<0.?-.0001:.0001):c.w;return c.xy/w*.5+.5;}
vec4 sampleScene(vec2 uv){return texture2D(uSceneTexture,clamp(uv,0.,1.));}
vec4 samplePane(vec2 pane){return sampleScene(projectUv(vec3((pane-.5)*uPaneSize,uGlassZ)));}
vec4 sampleCell(vec2 pane,vec2 sourceUv,float strength,vec2 blurStep){
vec4 color=samplePane(mix(pane,sourceUv,strength))*.4;
color+=samplePane(mix(pane,sourceUv+vec2(blurStep.x,0.),strength))*.15;
color+=samplePane(mix(pane,sourceUv-vec2(blurStep.x,0.),strength))*.15;
color+=samplePane(mix(pane,sourceUv+vec2(0.,blurStep.y),strength))*.15;
color+=samplePane(mix(pane,sourceUv-vec2(0.,blurStep.y),strength))*.15;
return color;
}
float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}
vec2 hash2(vec2 p){return vec2(hash(p),hash(p+vec2(19.19,73.73)));}
float safeZ(float z){return abs(z)<.0001?(z<0.?-.0001:.0001):z;}
vec3 bend(vec3 i,vec3 n){vec3 r=refract(i,n,.75);return length(r)<.0001?i:r;}
float softLimit(float value,float limit){return value*limit/(limit+abs(value));}
void evaluateCell(vec2 cell,vec2 local,vec2 grid,vec2 cellSize,vec2 cellDimensions,float cellWorld,float style,out vec2 affineUv,out mat2 transform,out vec2 curvedSlope,out vec2 facetSlope){
vec2 random=hash2(cell),widths;
if(style>.5){
widths=mix(vec2(.82),vec2(1.12),hash2(cell+vec2(11.4,9.8)));
}else{
float widthSeed=hash(cell+vec2(53.1,27.4));
if(widthSeed<.3)widths=mix(vec2(.90),vec2(1.08),hash2(cell+vec2(7.2,3.1)));
else if(widthSeed<.9)widths=mix(vec2(.46),vec2(.70),hash2(cell+vec2(7.2,3.1)));
else widths=mix(vec2(1.10),vec2(1.28),hash2(cell+vec2(7.2,3.1)));
}
float angle=(random.x-.5)*.08,c=cos(angle),s=sin(angle);
transform=mat2(widths.x*c,-widths.y*s,widths.x*s,widths.y*c);
vec2 sourceCenter=(cell+.5)/grid+(random-.5)*.16*cellSize;
affineUv=sourceCenter+transform*(local*cellSize);
// Positive height bulges toward the observer (+Z); negative height is concave.
float curvature=clamp(uCurvature,-1.,1.),excess=max(curvature-.65,0.),surfaceCurvature=curvature-(.25/.35)*excess*excess;
float height=.009*cellWorld*surfaceCurvature*mix(.92,1.08,hash(cell+vec2(41.7,17.3)));
vec2 q=clamp(2.*local,-1.,1.),q2=q*q,q3=q2*q,b=1.-q2*q2;
vec2 skew=(hash2(cell+vec2(23.8,61.2))-.5)*.28;float asym=1.+dot(skew,q);
vec2 dhdq=height*vec2(-4.*q3.x*b.y*asym+b.x*b.y*skew.x,-4.*q3.y*b.x*asym+b.x*b.y*skew.y);
vec2 grad=-2.*dhdq/cellDimensions;
curvedSlope=grad+(random-.5)*.012;facetSlope=(random-.5)*.012;
}
void main(){
if(uStrength<=.001){gl_FragColor=sampleScene(vUv);
#include <colorspace_fragment>
return;}
vec4 f=uContentInverseViewProjection*vec4(vUv*2.-1.,1.,1.);float fw=abs(f.w)<.0001?(f.w<0.?-.0001:.0001):f.w;vec3 farWorld=f.xyz/fw;vec3 i=normalize(farWorld-uContentCameraPosition);float gt=(uGlassZ-uContentCameraPosition.z)/safeZ(i.z);vec3 gh=uContentCameraPosition+i*gt;
vec2 paneUv=gh.xy/uPaneSize+.5;vec2 grid=vec2(uGridSize,max(1.,uGridSize*uPaneSize.y/uPaneSize.x));vec2 gridUv=paneUv*grid,cell=floor(gridUv),local=fract(gridUv)-.5,cellSize=1./grid;float style=clamp(uMosaicStyle,0.,1.),cellWorld=min(uPaneSize.x/grid.x,uPaneSize.y/grid.y);
if(paneUv.x<0.||paneUv.x>1.||paneUv.y<0.||paneUv.y>1.){gl_FragColor=vec4(uBackgroundColor,1.);
#include <colorspace_fragment>
return;}
vec2 cellDimensions=uPaneSize/grid;
vec2 affineUv,curvedSlope,facetSlope;mat2 transform;
evaluateCell(cell,local,grid,cellSize,cellDimensions,cellWorld,style,affineUv,transform,curvedSlope,facetSlope);
vec2 edge=.5-abs(local),neighborSign=mix(vec2(-1.),vec2(1.),step(vec2(0.),local));
vec2 blendAxis=1.-smoothstep(vec2(-.16),vec2(.16),edge);
vec2 weights=blendAxis;
vec2 centerAffine=affineUv;mat2 centerTransform=transform;
vec2 xAffine=vec2(0.),yAffine=vec2(0.),diagonalAffine=vec2(0.);
mat2 xTransform=mat2(0.),yTransform=mat2(0.),diagonalTransform=mat2(0.);
float xSplitWeight=weights.x*(1.-weights.y),ySplitWeight=(1.-weights.x)*weights.y;
float diagonalSplitWeight=weights.x*weights.y,centerSplitWeight=(1.-weights.x)*(1.-weights.y);
if(weights.x>0.||weights.y>0.){
vec2 neighborAffine,neighborCurved,neighborFacet;mat2 neighborTransform;
vec2 xWeight=vec2(xSplitWeight,0.);
vec2 yWeight=vec2(0.,ySplitWeight);
affineUv*=centerSplitWeight;transform*=centerSplitWeight;curvedSlope*=centerSplitWeight;facetSlope*=centerSplitWeight;
if(xWeight.x>0.){
vec2 offset=vec2(neighborSign.x,0.);
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
xAffine=neighborAffine;xTransform=neighborTransform;
affineUv+=neighborAffine*xWeight.x;transform+=neighborTransform*xWeight.x;curvedSlope+=neighborCurved*xWeight.x;facetSlope+=neighborFacet*xWeight.x;
}
if(yWeight.y>0.){
vec2 offset=vec2(0.,neighborSign.y);
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
yAffine=neighborAffine;yTransform=neighborTransform;
affineUv+=neighborAffine*yWeight.y;transform+=neighborTransform*yWeight.y;curvedSlope+=neighborCurved*yWeight.y;facetSlope+=neighborFacet*yWeight.y;
}
if(diagonalSplitWeight>0.){
vec2 offset=neighborSign;
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
diagonalAffine=neighborAffine;diagonalTransform=neighborTransform;
affineUv+=neighborAffine*diagonalSplitWeight;transform+=neighborTransform*diagonalSplitWeight;curvedSlope+=neighborCurved*diagonalSplitWeight;facetSlope+=neighborFacet*diagonalSplitWeight;
}
}
vec3 nCurved=normalize(vec3(curvedSlope,1.)),nFacet=normalize(vec3(facetSlope,1.));
vec3 straightHit=gh+i*(-gh.z/safeZ(i.z));vec3 facetRay=bend(i,nFacet),curvedRay=bend(i,nCurved);vec3 facetHit=gh+facetRay*(-gh.z/safeZ(facetRay.z)),curvedHit=gh+curvedRay*(-gh.z/safeZ(curvedRay.z));vec2 base=facetHit.xy-straightHit.xy,curve=curvedHit.xy-facetHit.xy;vec2 worldDelta=vec2(softLimit(base.x,cellWorld*.08),softLimit(base.y,cellWorld*.08));vec2 d=curve/cellWorld;worldDelta+=vec2(.3*d.x/(.3+abs(d.x)),.3*d.y/(.3+abs(d.y)))*cellWorld;
vec2 sampleUv=affineUv+transform*(worldDelta/uPaneSize);
float edgeDistance=min(min(paneUv.x,paneUv.y),min(1.-paneUv.x,1.-paneUv.y));float edgeWeight=smoothstep(0.,max(cellSize.x,cellSize.y)*.75,edgeDistance);float strength=clamp(uStrength,0.,1.)*edgeWeight;vec2 blurStep=cellSize*mix(.006,.012,hash(cell+vec2(5.)));
vec4 color;
if(style<=.5&&(weights.x>0.||weights.y>0.)){
color=vec4(0.);
if(centerSplitWeight>0.)color+=sampleCell(paneUv,centerAffine+centerTransform*(worldDelta/uPaneSize),strength,blurStep)*centerSplitWeight;
if(xSplitWeight>0.){
vec2 xBlurStep=cellSize*mix(.006,.012,hash(cell+vec2(neighborSign.x,0.)+vec2(5.)));
color+=sampleCell(paneUv,xAffine+xTransform*(worldDelta/uPaneSize),strength,xBlurStep)*xSplitWeight;
}
if(ySplitWeight>0.){
vec2 yBlurStep=cellSize*mix(.006,.012,hash(cell+vec2(0.,neighborSign.y)+vec2(5.)));
color+=sampleCell(paneUv,yAffine+yTransform*(worldDelta/uPaneSize),strength,yBlurStep)*ySplitWeight;
}
if(diagonalSplitWeight>0.){
vec2 diagonalBlurStep=cellSize*mix(.006,.012,hash(cell+neighborSign+vec2(5.)));
color+=sampleCell(paneUv,diagonalAffine+diagonalTransform*(worldDelta/uPaneSize),strength,diagonalBlurStep)*diagonalSplitWeight;
}
}else{
color=sampleCell(paneUv,sampleUv,strength,blurStep);
}
color.a=1.;
vec3 v=normalize(uContentCameraPosition-gh),reflected=reflect(-v,nCurved); float lightT=(uLightPosition.z-gh.z)/safeZ(reflected.z); vec2 lightHit=gh.xy+reflected.xy*lightT; vec2 halfSize=vec2(.32,.42)*min(uPaneSize.x,uPaneSize.y); vec2 windowCoord=abs(lightHit-uLightPosition.xy)/halfSize; vec2 windowMask=1.-smoothstep(vec2(.45),vec2(1.15),windowCoord); float spec=windowMask.x*windowMask.y*step(.0001,reflected.z)*step(0.,lightT); float gloss=spec*clamp(uGlossStrength,0.,1.)*.12*edgeWeight*clamp(uStrength,0.,1.); color.rgb=mix(color.rgb,vec3(1.),gloss); gl_FragColor=color;
#include <colorspace_fragment>
}
@@ -0,0 +1,84 @@
uniform sampler2D uSceneTexture;
uniform mat4 uContentViewProjection,uContentInverseViewProjection;
uniform vec3 uContentCameraPosition,uLightPosition,uBackgroundColor;
uniform float uGlassZ,uGridSize,uStrength,uMosaicStyle,uCurvature,uGlossStrength;
uniform vec2 uViewOffset,uPetSize,uPaneSize;
uniform vec4 uPaneRect;
varying vec2 vUv;
vec2 projectUv(vec3 p){vec4 c=uContentViewProjection*vec4(p,1.);float w=abs(c.w)<.0001?(c.w<0.?-.0001:.0001):c.w;return c.xy/w*.5+.5;}
vec4 sampleScene(vec2 uv){return texture2D(uSceneTexture,clamp(uv,0.,1.));}
vec4 samplePane(vec2 pane){return sampleScene(projectUv(vec3((pane-.5)*uPaneSize,uGlassZ)));}
float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}
vec2 hash2(vec2 p){return vec2(hash(p),hash(p+vec2(19.19,73.73)));}
float safeZ(float z){return abs(z)<.0001?(z<0.?-.0001:.0001):z;}
vec3 bend(vec3 i,vec3 n){vec3 r=refract(i,n,.75);return length(r)<.0001?i:r;}
float softLimit(float value,float limit){return value*limit/(limit+abs(value));}
void evaluateCell(vec2 cell,vec2 local,vec2 grid,vec2 cellSize,vec2 cellDimensions,float cellWorld,float style,out vec2 affineUv,out mat2 transform,out vec2 curvedSlope,out vec2 facetSlope){
vec2 random=hash2(cell),widths;
if(style>.5){
widths=mix(vec2(.82),vec2(1.12),hash2(cell+vec2(11.4,9.8)));
}else{
float widthSeed=hash(cell+vec2(53.1,27.4));
if(widthSeed<.3)widths=mix(vec2(.90),vec2(1.08),hash2(cell+vec2(7.2,3.1)));
else if(widthSeed<.9)widths=mix(vec2(.46),vec2(.70),hash2(cell+vec2(7.2,3.1)));
else widths=mix(vec2(1.10),vec2(1.28),hash2(cell+vec2(7.2,3.1)));
}
float angle=(random.x-.5)*.08,c=cos(angle),s=sin(angle);
transform=mat2(widths.x*c,-widths.y*s,widths.x*s,widths.y*c);
vec2 sourceCenter=(cell+.5)/grid+(random-.5)*.16*cellSize;
affineUv=sourceCenter+transform*(local*cellSize);
// Positive height bulges toward the observer (+Z); negative height is concave.
float curvature=clamp(uCurvature,-1.,1.),excess=max(curvature-.65,0.),surfaceCurvature=curvature-(.25/.35)*excess*excess;
float height=.009*cellWorld*surfaceCurvature*mix(.92,1.08,hash(cell+vec2(41.7,17.3)));
vec2 q=clamp(2.*local,-1.,1.),q2=q*q,q3=q2*q,b=1.-q2*q2;
vec2 skew=(hash2(cell+vec2(23.8,61.2))-.5)*.28;float asym=1.+dot(skew,q);
vec2 dhdq=height*vec2(-4.*q3.x*b.y*asym+b.x*b.y*skew.x,-4.*q3.y*b.x*asym+b.x*b.y*skew.y);
vec2 grad=-2.*dhdq/cellDimensions;
curvedSlope=grad+(random-.5)*.012;facetSlope=(random-.5)*.012;
}
void main(){
if(uStrength<=.001){gl_FragColor=sampleScene(vUv);
#include <colorspace_fragment>
return;}
vec4 f=uContentInverseViewProjection*vec4(vUv*2.-1.,1.,1.);float fw=abs(f.w)<.0001?(f.w<0.?-.0001:.0001):f.w;vec3 farWorld=f.xyz/fw;vec3 i=normalize(farWorld-uContentCameraPosition);float gt=(uGlassZ-uContentCameraPosition.z)/safeZ(i.z);vec3 gh=uContentCameraPosition+i*gt;
vec2 paneUv=gh.xy/uPaneSize+.5;vec2 grid=vec2(uGridSize,max(1.,uGridSize*uPaneSize.y/uPaneSize.x));vec2 gridUv=paneUv*grid,cell=floor(gridUv),local=fract(gridUv)-.5,cellSize=1./grid;float style=clamp(uMosaicStyle,0.,1.),cellWorld=min(uPaneSize.x/grid.x,uPaneSize.y/grid.y);
if(paneUv.x<0.||paneUv.x>1.||paneUv.y<0.||paneUv.y>1.){gl_FragColor=vec4(uBackgroundColor,1.);
#include <colorspace_fragment>
return;}
vec2 cellDimensions=uPaneSize/grid;
vec2 affineUv,curvedSlope,facetSlope;mat2 transform;
evaluateCell(cell,local,grid,cellSize,cellDimensions,cellWorld,style,affineUv,transform,curvedSlope,facetSlope);
vec2 edge=.5-abs(local),neighborSign=mix(vec2(-1.),vec2(1.),step(vec2(0.),local));
vec2 blendAxis=1.-smoothstep(vec2(-.16),vec2(.16),edge);
vec2 weights=blendAxis;
if(weights.x>0.||weights.y>0.){
vec2 neighborAffine,neighborCurved,neighborFacet;mat2 neighborTransform;
vec2 xWeight=vec2(weights.x*(1.-weights.y),0.);
vec2 yWeight=vec2(0.,(1.-weights.x)*weights.y);
float diagonalWeight=weights.x*weights.y;
float centerWeight=(1.-weights.x)*(1.-weights.y);
affineUv*=centerWeight;transform*=centerWeight;curvedSlope*=centerWeight;facetSlope*=centerWeight;
if(xWeight.x>0.){
vec2 offset=vec2(neighborSign.x,0.);
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
affineUv+=neighborAffine*xWeight.x;transform+=neighborTransform*xWeight.x;curvedSlope+=neighborCurved*xWeight.x;facetSlope+=neighborFacet*xWeight.x;
}
if(yWeight.y>0.){
vec2 offset=vec2(0.,neighborSign.y);
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
affineUv+=neighborAffine*yWeight.y;transform+=neighborTransform*yWeight.y;curvedSlope+=neighborCurved*yWeight.y;facetSlope+=neighborFacet*yWeight.y;
}
if(diagonalWeight>0.){
vec2 offset=neighborSign;
evaluateCell(cell+offset,local-offset,grid,cellSize,cellDimensions,cellWorld,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet);
affineUv+=neighborAffine*diagonalWeight;transform+=neighborTransform*diagonalWeight;curvedSlope+=neighborCurved*diagonalWeight;facetSlope+=neighborFacet*diagonalWeight;
}
}
vec3 nCurved=normalize(vec3(curvedSlope,1.)),nFacet=normalize(vec3(facetSlope,1.));
vec3 straightHit=gh+i*(-gh.z/safeZ(i.z));vec3 facetRay=bend(i,nFacet),curvedRay=bend(i,nCurved);vec3 facetHit=gh+facetRay*(-gh.z/safeZ(facetRay.z)),curvedHit=gh+curvedRay*(-gh.z/safeZ(curvedRay.z));vec2 base=facetHit.xy-straightHit.xy,curve=curvedHit.xy-facetHit.xy;vec2 worldDelta=vec2(softLimit(base.x,cellWorld*.08),softLimit(base.y,cellWorld*.08));vec2 d=curve/cellWorld;worldDelta+=vec2(.3*d.x/(.3+abs(d.x)),.3*d.y/(.3+abs(d.y)))*cellWorld;
vec2 sampleUv=affineUv+transform*(worldDelta/uPaneSize);
float edgeDistance=min(min(paneUv.x,paneUv.y),min(1.-paneUv.x,1.-paneUv.y));float edgeWeight=smoothstep(0.,max(cellSize.x,cellSize.y)*.75,edgeDistance);float strength=clamp(uStrength,0.,1.)*edgeWeight;vec2 blurStep=cellSize*mix(.006,.012,hash(cell+vec2(5.)));vec2 centerUv=mix(paneUv,sampleUv,strength);vec4 color=samplePane(centerUv)*.4;color+=samplePane(mix(paneUv,sampleUv+vec2(blurStep.x,0.),strength))*.15;color+=samplePane(mix(paneUv,sampleUv-vec2(blurStep.x,0.),strength))*.15;color+=samplePane(mix(paneUv,sampleUv+vec2(0.,blurStep.y),strength))*.15;color+=samplePane(mix(paneUv,sampleUv-vec2(0.,blurStep.y),strength))*.15;color.a=1.;
vec3 v=normalize(uContentCameraPosition-gh),reflected=reflect(-v,nCurved); float lightT=(uLightPosition.z-gh.z)/safeZ(reflected.z); vec2 lightHit=gh.xy+reflected.xy*lightT; vec2 halfSize=vec2(.32,.42)*min(uPaneSize.x,uPaneSize.y); vec2 windowCoord=abs(lightHit-uLightPosition.xy)/halfSize; vec2 windowMask=1.-smoothstep(vec2(.45),vec2(1.15),windowCoord); float spec=windowMask.x*windowMask.y*step(.0001,reflected.z)*step(0.,lightT); float gloss=spec*clamp(uGlossStrength,0.,1.)*.12*edgeWeight*clamp(uStrength,0.,1.); color.rgb=mix(color.rgb,vec3(1.),gloss); gl_FragColor=color;
#include <colorspace_fragment>
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+65
View File
@@ -0,0 +1,65 @@
# Mosaic 개선 실험 결과
2026-09-13. 사용자 요청에 따라 원인 분석 후 실제 개선을 수행했다. Astra 설계·독립 검토, Luna 코드 수정, 주 에이전트 브라우저 검증. 사진은 로컬 테스트용이며 외부 게시하지 않았다.
## 채택 결과
최종 C2: 조각 유리의 근원본/확대/축소 기대 비중을 60/30/10에서 30/60/10으로 변경했다. 기존 샘플 폭 범위·중심 이동·퍼짐 반경은 유지한다. 실제 얼굴 영역의 셀 비율은 해시와 구도에 따라 다르다.
유리 법선은 기존 .16셀 폭으로 연결한다. 조각 유리의 RGB는 각 셀의 독립 affine/transform에 공통 굴절 변위를 적용해 기존 5탭으로 샘플한 뒤 .06셀 폭에서 색으로 연결한다. .06~.16 띠에서도 독립 중심 셀 샘플을 유지해 혼합 UV의 압축이 재도입되지 않게 했다. 촘촘 유리는 기존 UV 경로를 사용한다.
공유 법선, 굴곡 기본+.65, 굴곡0의 기울기/샘플 변환, 광택 후처리1회, 기존2-pass 및 수작업 Depth 경로를 유지했다. Distortion<=.001의 raw RenderTarget 우회도 유지한다.
## 고정 비교 조건과 후보
1280×720, dog1, 확대2.3 / X-.10 / Y-.30, 조각 유리 Grid8 / Distortion.90, 시점 중앙. A/B/C/C2 비교는 광택0, 굴곡+.65. 평탄0 캡처는 별도 진단이며 동일 굴곡 비교와 구분한다. final 캡처는 광택.15로 복원했다.
| 후보 | 변경 | 실제 관찰 | 결정 |
|---|---|---|---|
| 원본 | baseline-mosaic.frag | 기존 QA의 원본에 가까운 넓은 영역, 물결 인상 | 원본 보존 |
| A | 비중만30/60/10 | 확대면 증가, 입 왼쪽 수평 압축선 부각 | 단독 미채택 |
| B | A의 확대 셀 면적을 유지하며 축비율2:1/1:2 | 오른눈 세로 늘어짐·코 비대칭 증가 | 제외 |
| C | A + .16폭 완전 색 혼합 | 입 왼쪽 압축선 감소, 넓은 중첩/흐림으로 사각면 약화 | 단독 미채택 |
| C2 | C의 색 혼합만.06, 법선.16 유지 | C보다 사각면 명확, A보다 입 압축선 감소, 눈·코·혀 식별 유지 | 채택 |
원본 비교는 기존 docs/qa-five-photos/dog1-tuned.png와 docs/qa-followup/dog1-flat.png를 참고했다. 이번 A/B/C/C2는 같은 브라우저에서 새로 캡처했다. 이전 캡처와 이번 캡처는 저장 경로·인코딩이 달라 픽셀 단위 유사도 평가를 하지 않았다.
## 사진별 최종 검증
| 사진 | 확대 / X / Y | Grid / Distortion / 굴곡 / 광택 | 관찰 |
|---|---|---|---|
| sample | 3 / -.15 / -.35 | 8 / .90 / +.65 / .15 | 사각 얼굴 조각 증가, 두눈·코·입 식별. 눈/입 경계 잘림은 남음 |
| dog1 | 2.3 / -.10 / -.30 | 8 / .90 / +.65 / .15 | 가장 안정적인 비교. 확대면·사각 경계 개선, 코·혀 약한 중첩 잔존 |
| dog2 | 2.2 / +.60 / -.60 | 8 / .90 / +.65 / .15 | 볼/귀/배경 조각 차이 증가. 어두운 눈 주변 구분은 여전히 제한적 |
| dog3 | 1.3 / +.50 / 0 | 12 / .75 / +.65 / .15 | 두 얼굴 식별 유지. 작은 얼굴과 배경 세부의 한계 |
| dog4 | 1.55 / +.10 / -.85 | 12 / .65 / +.35 / .15 | 눈·코·입 식별, 사각 윤곽 증가. 측면 코의 조각 잘림 남음 |
사진 전환은 위 값을 자동 적용하지 않는다. 기존과 같이 수동 적용했다. 추가4장은 Depth 없는 평면 입력이다.
## 실행한 검증
- 모든 후보 및 최종 C2의 npm run build (tsc -b && vite build) 통과. 셰이더 실제 컴파일/화면은 브라우저에서 별도로 확인했다.
- 실제 브라우저 1280×720: 5장 위 설정으로 렌더링·사진 전환 확인.
- 390×844: dog1/dog4 동일 설정, 두 사진의 수평 드래그에서 창틀/셀 정렬과 표정 확인. 데스크톱 dog1 수평 드래그도 확인. 전체 드래그 경로에 대한 프레임 단위 분석은 하지 않았다.
- Glass OFF에서 기존 sample 기본 구도2.4/-.10/-.35 parallax 재확인: cameraX0→.063 관측 시 nose projectedX531.509→529.658, body350.477→350.490. 위치 변화가 부위마다 다름. Depth는 수작업 근사이며 AI 추정/측정값이 아니다.
- 촘촘 유리 Grid28/.85, Reeded, Frosted 실제 렌더링을 확인했다. 수치적 이전버전 동일성 비교나 영상 전체 회귀로 해석하지 않는다.
- Distortion0에서 기존 창틀 밖 raw 출력 유지 확인.
- QA 브라우저 error 로그0.
- Astra 최종 정적 검토: 가중치 분리·이웃좌표 초기화·셀별 퍼짐·법선 공유에 즉시 수정할 결함 없음.
## 성능과 남은 한계
조각 경계의 텍스처 샘플은 활성셀당5탭, 최악20탭이다. .06 색 혼합에서 완전한 정사각 셀을 가정한 이론 평균은 약6.27탭으로 기존5보다 증가한다. GPU의 분기실행·캐시·해상도가 영향을 주므로 실제 실행시간/FPS로 환산하지 않았다. 실제 휴대폰 성능·자이로·영상·일반 업로드·수동 Depth 업로드 전체 회귀는 미검증이다.
레퍼런스2 달성으로 판정하지 않는다. 셀별 큰 정보량 대비는 여전히 부족하고, 검은 눈·코·혀 및 측면 얼굴의 경계 잘림/중첩이 남는다. 12셀 정량 분류 등 앞서 제안한 모든 평가표를 실행한 것은 아니며 이번 채택은 고정 구도의 수동 시각 비교에 따른 증분 개선이다. 다음 개선은 한 축 확대를 다시 강하게 하기보다 얼굴을 가로지르는 셀의 위치 제어 또는 낮은 정보량 셀의 제한적 퍼짐을 개별 실험하는 방향이다.
## 파일과 재현
- 제품 수정: src/shaders/mosaic.frag 한 파일.
- 원본/후보: baseline-mosaic.frag, distribution-mosaic.frag, anisotropic-mosaic.frag, color-boundary-mosaic.frag.
- 비교 이미지: dog1-distribution.png, dog1-anisotropic.png, dog1-color-boundary.png, dog1-color-narrow.png, dog1-final.png 및 각 평탄 이미지.
- 회귀 이미지: sample-final.png, dog2-final.png, dog3-final.png, dog4-final.png, dog1/dog4-mobile*.png, sample-noglass*.png, sample-compact.png, sample-reeded.png, sample-frosted.png, sample-strength0.png.
- 실행: npm run dev -- --host 127.0.0.1, http://127.0.0.1:5173/. 프로젝트 실행 중인 서버에서도 변경 반영됨.
## 후속 수정
사용자의 dog3 공통 설정(Grid8/.90, 구도1/0/0) 제보로 인접 셀 얼굴 단절이 확인되었다. 이 문서의 C2 채택만으로 해당 조건을 통과한 것은 아니다. 후속 보정과 확대 효과 감소의 절충은 ../qa-cell-continuity/review.md를 따른다.
Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB