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
+123
View File
@@ -0,0 +1,123 @@
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));
// Keep a broad shared surface-normal transition; color footprints are blended separately.
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);
vec2 colorAxis=1.-smoothstep(vec2(-.06),vec2(.06),edge);
float colorXWeight=colorAxis.x*(1.-colorAxis.y),colorYWeight=(1.-colorAxis.x)*colorAxis.y;
float colorDiagonalWeight=colorAxis.x*colorAxis.y,colorCenterWeight=(1.-colorAxis.x)*(1.-colorAxis.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;
// The .06-.16 band must retain the independent center transform to avoid reintroducing UV compression.
if(style<=.5&&(weights.x>0.||weights.y>0.)){
color=vec4(0.);
if(colorCenterWeight>0.)color+=sampleCell(paneUv,centerAffine+centerTransform*(worldDelta/uPaneSize),strength,blurStep)*colorCenterWeight;
if(colorXWeight>0.){
vec2 xBlurStep=cellSize*mix(.006,.012,hash(cell+vec2(neighborSign.x,0.)+vec2(5.)));
color+=sampleCell(paneUv,xAffine+xTransform*(worldDelta/uPaneSize),strength,xBlurStep)*colorXWeight;
}
if(colorYWeight>0.){
vec2 yBlurStep=cellSize*mix(.006,.012,hash(cell+vec2(0.,neighborSign.y)+vec2(5.)));
color+=sampleCell(paneUv,yAffine+yTransform*(worldDelta/uPaneSize),strength,yBlurStep)*colorYWeight;
}
if(colorDiagonalWeight>0.){
vec2 diagonalBlurStep=cellSize*mix(.006,.012,hash(cell+neighborSign+vec2(5.)));
color+=sampleCell(paneUv,diagonalAffine+diagonalTransform*(worldDelta/uPaneSize),strength,diagonalBlurStep)*colorDiagonalWeight;
}
}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>
}
+131
View File
@@ -0,0 +1,131 @@
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,vec2 cellSize){
vec2 center=mix(pane,sourceUv,strength);
// Limit Mosaic shifts to reduce missing strips at cell edges.
if(uMosaicStyle<=.5){
vec2 d=(center-pane)/cellSize;
vec2 dSafe=d/sqrt(vec2(1.)+(d/.18)*(d/.18));
center=pane+dSafe*cellSize;
}
vec2 blurOffset=strength*blurStep;
vec4 color=samplePane(center)*.4;
color+=samplePane(center+vec2(blurOffset.x,0.))*.15;
color+=samplePane(center-vec2(blurOffset.x,0.))*.15;
color+=samplePane(center+vec2(0.,blurOffset.y))*.15;
color+=samplePane(center-vec2(0.,blurOffset.y))*.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));
// Keep a broad shared surface-normal transition; color footprints are blended separately.
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);
vec2 colorAxis=1.-smoothstep(vec2(-.06),vec2(.06),edge);
float colorXWeight=colorAxis.x*(1.-colorAxis.y),colorYWeight=(1.-colorAxis.x)*colorAxis.y;
float colorDiagonalWeight=colorAxis.x*colorAxis.y,colorCenterWeight=(1.-colorAxis.x)*(1.-colorAxis.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;
// The .06-.16 band must retain the independent center transform to avoid reintroducing UV compression.
if(style<=.5&&(weights.x>0.||weights.y>0.)){
color=vec4(0.);
if(colorCenterWeight>0.)color+=sampleCell(paneUv,centerAffine+centerTransform*(worldDelta/uPaneSize),strength,blurStep,cellSize)*colorCenterWeight;
if(colorXWeight>0.){
vec2 xBlurStep=cellSize*mix(.006,.012,hash(cell+vec2(neighborSign.x,0.)+vec2(5.)));
color+=sampleCell(paneUv,xAffine+xTransform*(worldDelta/uPaneSize),strength,xBlurStep,cellSize)*colorXWeight;
}
if(colorYWeight>0.){
vec2 yBlurStep=cellSize*mix(.006,.012,hash(cell+vec2(0.,neighborSign.y)+vec2(5.)));
color+=sampleCell(paneUv,yAffine+yTransform*(worldDelta/uPaneSize),strength,yBlurStep,cellSize)*colorYWeight;
}
if(colorDiagonalWeight>0.){
vec2 diagonalBlurStep=cellSize*mix(.006,.012,hash(cell+neighborSign+vec2(5.)));
color+=sampleCell(paneUv,diagonalAffine+diagonalTransform*(worldDelta/uPaneSize),strength,diagonalBlurStep,cellSize)*colorDiagonalWeight;
}
}else{
color=sampleCell(paneUv,sampleUv,strength,blurStep,cellSize);
}
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: 44 KiB

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: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

+48
View File
@@ -0,0 +1,48 @@
# 인접 셀 얼굴 단절 수정 QA
2026-09-13. 사용자 첨부에서 dog3(두 마리)의 얼굴 좌우가 인접 셀 사이에서 크게 어긋나는 문제를 재현하고 보정했다. Astra 설계·독립 검토, Luna 실제 코드 수정. 로컬 테스트용 사진이며 외부 게시하지 않는다.
## 놓친 조건과 원인
사용자 탭에서 읽은 실제 설정: dog3 / 조각 유리 / Grid8 / Distortion.90 / 굴곡+.65 / 광택.15 / 사진 확대1 / X0 / Y0 / Camera Offset.30. 창 크기는 885×920이었고 시점 이동 상태였다. 최초 화면을 dog3-before.png에 저장했다. 사용자의 정확한 카메라 이동 수치는 보존하지 못했으므로 이후 비교는 중앙과 재현 가능한 좌우 대각 드래그로 분리했다.
이전 QA는 dog3를 Grid12/.75, 구도1.3/.5/0으로 개별 조정했다. 사진을 전환해도 광학 설정이 유지되는 실제 사용 조건을 확인하지 않아 큰 셀과 작은 얼굴의 실패를 놓쳤다. 이번 통과 기준은 동일 Grid8/.90에서 기본 구도 사진을 전환해도 얼굴이 과도하게 분리되지 않는 것이다.
직접 원인은 독립 셀의 샘플 영역 사이에 생기는 간격이다. 색 혼합으로 두 결과를 연결해도 그 사이의 원본 얼굴 정보는 복원되지 않는다. 경계 폭이나 전체 블러만 늘리는 대신 샘플 중심의 과도한 이동을 제한했다.
## 구현 및 비교
수정 제품 파일: src/shaders/mosaic.frag 한 파일. C2 원본은 before-mosaic.frag, .18 후보는 cap18-mosaic.frag에 보존했다.
sampleCell에서 먼저 Distortion 보간을 적용한 샘플 중심을 계산하고, 원래 pane 좌표와의 차이를 cellSize로 나눈 d를 구한다. 조각 스타일만 각 축에 dSafe = d / sqrt(1 + (d / .12)^2)를 적용한다. 제한된 중심에서 기존 5탭 퍼짐을 샘플한다. 중심/이웃/일반 셀 내부 모든 경로에 같은 제한을 적용한다.
법선/곡면/광택 공유, 배율분포, .06색 연결/.16법선 연결, 촘촘 유리의 효과 계산과 Distortion0 우회는 유지했다. 새 텍스처 샘플이나 얼굴 검출, 사진별 자동설정은 추가하지 않았다.
| 후보 | 관찰 | 결정 |
|---|---|---|
| 기존C2 | 두 얼굴의 눈/코가 셀 경계에서 크게 이동하고 얼굴 일부가 생략됨 | 수정 대상 |
| D .18 | 얼굴 연결 개선, 큰 조각은 상대적으로 더 보존. 시점에 따른 경계 잘림 잔존 | 비교용 보존 |
| E .12 | 정면 차이는 D와 작지만 더 보수적으로 이동 제한. 좌우 대각 드래그에서 두 눈·코 식별 유지, 큰 도약 완화 | 최종채택 |
E 채택은 이번 요청의 얼굴 연결성을 우선한 판단이다. 주변의 큰 확대 조각과 레퍼런스의 정보량 차이는 약해지는 대가가 있다. 완전한 경계 연속성·모든 사진의 눈 보존을 보장하는 해법은 아니다. 각축 샘플 중심 이동은 .12셀 미만이지만 서로 다른 두 셀 중심 사이의 차이는 .24셀까지 남을 수 있고, 퍼짐 탭은 별도 오프셋을 갖는다.
## 실행한 QA
- D/E 각각 npm run build (tsc -b && vite build) 통과.
- 사용자 탭 원래 사진/설정/드래그 상태를 변경 전에 캡처. 제어 비교는 1280×720 중앙의 Grid8/.90/+ .65/.15, dog3 구도1/0/0.
- E dog3: 중앙, 좌상 방향 드래그(500,350→200,250), 초기화 후 우하 방향(300,350→650,450). 두 얼굴 눈·코 식별 유지, 창틀과 유리 정렬 확인. 모든 프레임을 분석한 것은 아니다.
- 390×844 dog3: 같은 설정과 구도에서 중앙 및 드래그(120,230→265,275) 확인. 실제 휴대폰 하드웨어 검증은 아니다.
- 5장 공통 광학 설정 유지하며 전환: Grid8/.90/+ .65/.15. 추가4장은 각 기본 구도1/0/0, 기존 sample은 앱 기존 구도2.4/-.10/-.35. dog1-common, dog2-common, dog3-cap12, dog4-common, sample-common 이미지 보존. 작은 얼굴·검은 털의 표정 가독성 한계는 남는다.
- dog1의 기존 확대 구도2.3/-.10/-.30에서도 D/E 비교: 눈·코 크기 차이는 남지만 주변 큰 면은 약해짐. dog1-before, dog1-cap18, dog1-cap12 이미지.
- dog3 곡면0/광택0에서 출력 확인, Distortion0 기존 우회 출력 확인, 촘촘 유리28/.85 실제 출력 확인.
- 브라우저 error 로그0. Astra 독립 정적 검토에서 모든 sampleCell 경로 동일적용, 강도후 제한, 기존 퍼짐과 샘플수 보존에 actionable 코드결함 없음.
이번에 Reeded/Frosted·영상·자이로·업로드 전체 회귀와 실제 GPU 성능 측정은 재실행하지 않았다. 직전 작업의 확인과 구분한다. Depth는 기존 sample에만 있는 수작업 테스트 근사이며 이번 변경은 Depth/2-pass 코드를 수정하지 않았다.
## 앞으로 유지할 회귀 기준
사진별 최적 설정만으로 통과시키지 않는다. 공통 광학 설정으로 추가 사진을 전환한 기본 구도, 작은 얼굴이 셀 경계에 걸리는 dog3, 좌우/대각 드래그, 모바일을 먼저 확인한다. 심한 잘림을 구도나 Grid 변경만으로 감추지 않는다.
## 실행
npm run dev -- --host 127.0.0.1 후 http://127.0.0.1:5173/. 이번 결과 화면은 dog3, Grid8/.90/+ .65/.15, 사진1/0/0으로 남긴다. 다음 개선이 필요하면 단순히 한도를 다시 키우기보다 인접 셀의 샘플 영역 간격을 직접 제어하는 방식을 별도 실험한다.
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB