uniform sampler2D uSceneTexture,uSceneDepth; uniform sampler2D uProtectionDepthMap; uniform mat4 uContentViewProjection,uContentInverseViewProjection; uniform mat4 uPetWorldToLocal; uniform vec3 uContentCameraPosition,uLightPosition,uBackgroundColor; uniform float uGlassZ,uSourceZ,uGridSize,uStrength,uMosaicStyle,uCurvature,uGlossStrength; uniform vec2 uViewOffset,uPetSize,uPaneSize; uniform vec2 uDepthEvidenceRange,uDepthCorrection; uniform float uEnvironmentStrength; uniform vec4 uPaneRect; uniform sampler2D uRegionPalette; uniform sampler2D uRegionAtlas,uRegionHistory; uniform vec2 uRegionAtlasSize,uRegionGridSize,uRegionPaletteSize; uniform float uRegionStrength,uRegionAvailable; 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)));} vec2 cellBlurStep(vec2 cell,vec2 cellSize,float style){ if(style<=.5)return cellSize*mix(.006,.018,hash(cell+vec2(5.))); return cellSize*mix(.006,.012,hash(cell+vec2(5.))); } vec4 sampleCell(vec2 pane,vec2 sourceUv,float strength,vec2 cell,vec2 cellSize,float style){ vec2 center=mix(pane,sourceUv,strength); vec2 blurOffset=strength*cellBlurStep(cell,cellSize,style); vec4 color; if(style<=.5){ color=samplePane(center)*.6; color+=samplePane(center+vec2(blurOffset.x,0.))*.1; color+=samplePane(center-vec2(blurOffset.x,0.))*.1; color+=samplePane(center+vec2(0.,blurOffset.y))*.1; color+=samplePane(center-vec2(0.,blurOffset.y))*.1; }else{ 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 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));} float surfaceCurvature(){float curvature=clamp(uCurvature,-1.,1.),excess=max(curvature-.65,0.);return curvature-(.25/.35)*excess*excess;} float signedCurvatureScale(){return clamp(surfaceCurvature()/.65,-1.25,1.25);} float regionDistance(vec3 a,vec3 b){vec3 d=a-b;float y=dot(d,vec3(.299,.587,.114));return dot(d,d)+.35*y*y;} float regionLuminance(vec3 c){return dot(c,vec3(.299,.587,.114));} vec3 regionColor(vec2 cell,float index){vec2 uv=(vec2(cell.x*5.+index,cell.y)+.5)/uRegionPaletteSize;return texture2D(uRegionPalette,uv).rgb;} float nearestRegion(vec3 color,vec2 cell){float best=0.,distance=regionDistance(color,regionColor(cell,0.));for(int i=1;i<4;i++){float candidate=regionDistance(color,regionColor(cell,float(i)));if(candidate1.||sceneUv.y<0.||sceneUv.y>1.)return false; float depth=texture2D(uSceneDepth,sceneUv).x; if(depth>=.999999)return false; vec4 clip=vec4(sceneUv*2.-1.,depth*2.-1.,1.); vec4 world=uContentInverseViewProjection*clip; if(abs(world.w)<.0001)return false; worldPoint=world.xyz/world.w; return true; } float regionSurfaceValid(vec2 sceneUv,vec3 glassHit){ vec3 worldPoint; if(!reconstructScenePoint(sceneUv,worldPoint))return 0.; float axialGap=glassHit.z-worldPoint.z; if(axialGap<=.0001)return 0.; return 1.; } float regionProtectionWeight(vec2 sceneUv){ vec3 worldPoint; if(!reconstructScenePoint(sceneUv,worldPoint))return 0.; vec3 localPoint=(uPetWorldToLocal*vec4(worldPoint,1.)).xyz; if(localPoint.x<-.5||localPoint.x>.5||localPoint.y<-.5||localPoint.y>.5)return 0.; vec2 sourceUv=localPoint.xy+.5; float sourceDepth=texture2D(uProtectionDepthMap,sourceUv).r; if(uDepthEvidenceRange.y-uDepthEvidenceRange.x<.0001)return 0.; return smoothstep(uDepthEvidenceRange.x,uDepthEvidenceRange.y,sourceDepth); } // Screen-depth refinement of an artistic ray. Occluded surfaces are unavailable; // retain the original sample if the visible-depth solve cannot converge. vec3 refineSceneHit(vec3 glassHit,vec3 ray,vec3 artisticHit,float cellWorld){ vec3 planeHit=glassHit+ray*((uSourceZ-glassHit.z)/safeZ(ray.z)); vec3 origin=glassHit+vec3(artisticHit.xy-planeHit.xy,0.); vec3 hit=artisticHit,surface; for(int iteration=0;iteration<3;iteration++){ if(!reconstructScenePoint(projectUv(hit),surface))return artisticHit; vec3 localPoint=(uPetWorldToLocal*vec4(surface,1.)).xyz; if(any(greaterThan(abs(localPoint.xy),vec2(.5)))||surface.z>=uGlassZ-.0001)return artisticHit; hit=origin+ray*((surface.z-origin.z)/safeZ(ray.z)); } if(!reconstructScenePoint(projectUv(hit),surface))return artisticHit; if(surface.z>=uGlassZ-.0001)return artisticHit; float confidence=1.-smoothstep(cellWorld*.05,cellWorld*.25,abs(hit.z-surface.z)); // Compare at the same reference Z, so only the projected sampling position changes. vec3 viewRay=hit-uContentCameraPosition; vec3 projected=uContentCameraPosition+viewRay*((uSourceZ-uContentCameraPosition.z)/safeZ(viewRay.z)); vec2 delta=projected.xy-artisticHit.xy; float limit=cellWorld*uDepthCorrection.y; delta*=min(1.,limit/max(length(delta),.00001)); return artisticHit+vec3(delta*uDepthCorrection.x*confidence,0.); } #ifdef REGION_ATLAS float regionAtlasEvidence(vec2 sceneUv,vec3 color,vec3 glassHit){ if(regionSurfaceValid(sceneUv,glassHit)<=0.)return 0.; return regionProtectionWeight(sceneUv)*(1.-smoothstep(.10,.20,regionLuminance(color))); } vec4 regionAtlasProbe(vec3 sceneHit,vec2 worldOffset,vec3 glassHit){ vec2 probeSceneUv=projectUv(sceneHit+vec3(worldOffset,0.)); vec4 probe=sampleScene(probeSceneUv); return vec4(probe.rgb,regionAtlasEvidence(probeSceneUv,probe.rgb,glassHit)); } vec4 regionAtlasScore(vec3 centerColor,float centerEvidence,vec3 probeColor,float probeEvidence){ float evidenceGain=probeEvidence-centerEvidence,darkGain=regionLuminance(centerColor)-regionLuminance(probeColor),blend=0.; if(probeEvidence>.05&&evidenceGain>.02&&darkGain>.015)blend=min(.4,evidenceGain*2.)*min(1.,max(0.,darkGain*4.)); return vec4(blend,probeEvidence,0.,0.); } vec3 regionAtlasRepresentative(vec3 centerColor,float centerEvidence,vec3 sceneHit,vec3 glassHit,out float evidence){ vec3 bestColor=centerColor;float bestEvidence=centerEvidence,bestBlend=0.; vec2 probeStep=uPaneSize/(uRegionGridSize*8.)*.25; vec4 probe,score; probe=regionAtlasProbe(sceneHit,vec2(probeStep.x,0.),glassHit);score=regionAtlasScore(centerColor,centerEvidence,probe.rgb,probe.a);if(score.x>bestBlend){bestColor=mix(centerColor,probe.rgb,score.x);bestEvidence=max(centerEvidence,probe.a);bestBlend=score.x;} probe=regionAtlasProbe(sceneHit,vec2(-probeStep.x,0.),glassHit);score=regionAtlasScore(centerColor,centerEvidence,probe.rgb,probe.a);if(score.x>bestBlend){bestColor=mix(centerColor,probe.rgb,score.x);bestEvidence=max(centerEvidence,probe.a);bestBlend=score.x;} probe=regionAtlasProbe(sceneHit,vec2(0.,probeStep.y),glassHit);score=regionAtlasScore(centerColor,centerEvidence,probe.rgb,probe.a);if(score.x>bestBlend){bestColor=mix(centerColor,probe.rgb,score.x);bestEvidence=max(centerEvidence,probe.a);bestBlend=score.x;} probe=regionAtlasProbe(sceneHit,vec2(0.,-probeStep.y),glassHit);score=regionAtlasScore(centerColor,centerEvidence,probe.rgb,probe.a);if(score.x>bestBlend){bestColor=mix(centerColor,probe.rgb,score.x);bestEvidence=max(centerEvidence,probe.a);bestBlend=score.x;} evidence=bestEvidence; return bestColor; } #endif void evaluateCell(vec2 cell,vec2 local,vec2 grid,vec2 cellSize,vec2 cellDimensions,float cellWorld,float opticalTravel,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))); 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); float centerJitter=.16; vec2 sourceCenter=(cell+.5)/grid+(random-.5)*centerJitter*cellSize; affineUv=sourceCenter+transform*(local*cellSize); }else{ // Default style keeps a single continuous image footprint across cells. transform=mat2(1.); affineUv=(cell+.5)/grid+local*cellSize; } // Positive height bulges toward the observer (+Z); negative height is concave. float curvature=clamp(uCurvature,-1.,1.),surfaceCurvatureValue=surfaceCurvature(); vec2 q=clamp(2.*local,-1.,1.),q2=q*q,q3=q2*q,b=1.-q2*q2; if(style>.5){ float amplitude=mix(.92,1.08,hash(cell+vec2(41.7,17.3))),height=.009*cellWorld*surfaceCurvatureValue*amplitude; 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; }else{ curvedSlope=.30*cellDimensions/max(abs(opticalTravel),.6)*signedCurvatureScale()*sin(6.2831853*local); facetSlope=vec2(0.); } } void main(){ vec2 screenUv=vUv; #ifdef REGION_ATLAS vec2 atlasPixel=floor(vUv*uRegionAtlasSize),atlasCell=floor(atlasPixel/8.),atlasLocal=(mod(atlasPixel,8.)+.5)/8.; vec2 atlasPaneUv=(atlasCell+atlasLocal)/uRegionGridSize; screenUv=projectUv(vec3((atlasPaneUv-.5)*uPaneSize,uGlassZ)); #endif if(uStrength<=.001){ #ifdef REGION_ATLAS gl_FragColor=vec4(sampleScene(screenUv).rgb,0.); #else gl_FragColor=sampleScene(screenUv); #include #endif return;} vec4 f=uContentInverseViewProjection*vec4(screenUv*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); #ifdef REGION_ATLAS paneUv=atlasPaneUv;grid= uRegionGridSize;gridUv=paneUv*grid;cell=floor(gridUv);local=fract(gridUv)-.5;cellSize=1./grid;cellWorld=min(uPaneSize.x/grid.x,uPaneSize.y/grid.y); #endif if(paneUv.x<0.||paneUv.x>1.||paneUv.y<0.||paneUv.y>1.){ #ifdef REGION_ATLAS gl_FragColor=vec4(uBackgroundColor,0.); #else gl_FragColor=vec4(uBackgroundColor,1.); #include #endif return;} vec2 cellDimensions=uPaneSize/grid; float opticalTravel=-max(abs(uSourceZ-gh.z),.6); vec2 affineUv,curvedSlope,facetSlope;mat2 transform; evaluateCell(cell,local,grid,cellSize,cellDimensions,cellWorld,opticalTravel,style,affineUv,transform,curvedSlope,facetSlope); if(style<=.5){ vec2 smoothFacet=.003*vec2(sin(6.2831853*paneUv.x),sin(6.2831853*paneUv.y)); curvedSlope+=smoothFacet;facetSlope=smoothFacet; }else{ vec2 edge=.5-abs(local),neighborSign=mix(vec2(-1.),vec2(1.),step(vec2(0.),local)); float normalBlend=.16; vec2 weights=1.-smoothstep(vec2(-normalBlend),vec2(normalBlend),edge); 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,opticalTravel,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,opticalTravel,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet); 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,opticalTravel,style,neighborAffine,neighborTransform,neighborCurved,neighborFacet); 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*((uSourceZ-gh.z)/safeZ(i.z)); vec3 curvedRay=bend(i,nCurved),curvedHit=gh+curvedRay*((uSourceZ-gh.z)/safeZ(curvedRay.z)); // Keep the artistic Mosaic optical path independent of physical glass-to-photo contact distance. vec2 rawDelta=(curvedRay.xy/safeZ(curvedRay.z)-i.xy/safeZ(i.z))*opticalTravel; vec2 worldDelta=vec2(softLimit(rawDelta.x,cellWorld*.55),softLimit(rawDelta.y,cellWorld*.55)); if(style>.5){ vec3 facetRay=bend(i,nFacet);vec3 facetHit=gh+facetRay*((uSourceZ-gh.z)/safeZ(facetRay.z)); vec2 base=facetHit.xy-straightHit.xy,curve=curvedHit.xy-facetHit.xy; 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; }else{ vec3 referenceI=normalize(gh-vec3(0.,0.,uContentCameraPosition.z)),referenceRay=bend(referenceI,nCurved); vec2 incidenceDelta=curvedRay.xy/max(-curvedRay.z,.0001)-referenceRay.xy/max(-referenceRay.z,.0001); vec2 boundedIncidence=incidenceDelta/sqrt(incidenceDelta*incidenceDelta+vec2(.025*.025)); vec2 cellEnvelope=.5+.5*cos(6.2831853*local); worldDelta+=boundedIncidence*cellEnvelope*cellDimensions*signedCurvatureScale()*.18; } vec2 sampleUv=affineUv+transform*(worldDelta/uPaneSize); float edgeDistance=min(min(paneUv.x,paneUv.y),min(1.-paneUv.x,1.-paneUv.y));float edgeTaper=style<=.5?1.25:.75;float edgeWeight=smoothstep(0.,max(cellSize.x,cellSize.y)*edgeTaper,edgeDistance);float strength=clamp(uStrength,0.,1.)*edgeWeight; vec4 color;vec3 sceneHit=straightHit+vec3(worldDelta*strength,0.);vec2 finalSceneUv=projectUv(sceneHit); if(style<=.5){ sceneHit=refineSceneHit(gh,normalize(mix(i,curvedRay,strength)),sceneHit,cellWorld); finalSceneUv=projectUv(sceneHit); } #ifdef REGION_ATLAS vec3 atlasCenterColor=sampleScene(finalSceneUv).rgb; float atlasEvidence=regionProtectionWeight(finalSceneUv)*(1.-smoothstep(.10,.20,regionLuminance(atlasCenterColor))); #endif #ifdef REGION_ATLAS if(style<=.5){ float representativeEvidence=atlasEvidence; color=vec4(regionAtlasRepresentative(atlasCenterColor,atlasEvidence,sceneHit,gh,representativeEvidence),representativeEvidence); atlasEvidence=representativeEvidence; }else{ color=sampleCell(paneUv,sampleUv,strength,cell,cellSize,style); } #else if(style<=.5){ color=sampleScene(finalSceneUv); }else{ color=sampleCell(paneUv,sampleUv,strength,cell,cellSize,style); } #endif #ifndef REGION_ATLAS if(uRegionAvailable>.5&&uRegionStrength>.001&&uMosaicStyle<.5){ vec3 p0=regionColor(cell,0.),p1=regionColor(cell,1.),p2=regionColor(cell,2.),p3=regionColor(cell,3.); float ownLabel=nearestRegion(color.rgb,p0,p1,p2,p3);vec4 votes=vec4(0.);float totalWeight=0.,ownWeight=0.; vec2 texelCoord=fract(gridUv)*8.-.5,anchor=floor(texelCoord+.5); for(int y=-1;y<=1;y++)for(int x=-1;x<=1;x++){ vec2 offset=vec2(float(x),float(y)),delta=anchor+offset-texelCoord; float weight=pow(max(0.,1.-length(delta)/1.5),2.); if(weight>0.){ vec3 atlasColor=regionAtlasColor(cell,anchor+offset);float label=nearestRegion(atlasColor,p0,p1,p2,p3); votes+=vec4(step(abs(label-vec4(0.,1.,2.,3.)),vec4(.1)))*weight;totalWeight+=weight; ownWeight+=step(abs(label-ownLabel),.1)*weight; } } float winningLabel=ownLabel,winningWeight=ownWeight; if(votes.x>winningWeight){winningLabel=0.;winningWeight=votes.x;}if(votes.y>winningWeight){winningLabel=1.;winningWeight=votes.y;}if(votes.z>winningWeight){winningLabel=2.;winningWeight=votes.z;}if(votes.w>winningWeight){winningLabel=3.;winningWeight=votes.w;} bool simplify=totalWeight>0.&&winningWeight/totalWeight>.55&&ownWeight/totalWeight<.40; float radialLabel=simplify?winningLabel:ownLabel;vec3 radialColor=selectRegion(p0,p1,p2,p3,radialLabel); vec2 historyTexel=clamp(floor(fract(gridUv)*8.),vec2(0.),vec2(7.)); vec2 historyUv=(cell*8.+historyTexel+.5)/uRegionAtlasSize; vec4 splitHistory=texture2D(uRegionHistory,historyUv); vec3 splitColor=splitHistory.a>.00001?splitHistory.rgb/splitHistory.a:radialColor; vec3 candidateColor=mix(radialColor,splitColor,splitHistory.a); color.rgb=mix(color.rgb,candidateColor,clamp(uRegionStrength*clamp(uStrength,0.,1.)*regionSurfaceValid(finalSceneUv,gh),0.,1.)); } #endif #ifndef REGION_ATLAS 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 surfaceReflectionWeight=mix(.35,1.,edgeWeight); float glossStrength=clamp(uGlossStrength,0.,1.); float spec=windowMask.x*windowMask.y*step(.0001,reflected.z)*step(0.,lightT); float specGloss=spec*glossStrength*.12*surfaceReflectionWeight*clamp(uStrength,0.,1.); vec2 broadWindowMask=1.-smoothstep(vec2(.15),vec2(1.75),windowCoord); float broadFacing=pow(max(dot(nCurved,v),0.),2.); float broadReflection=broadWindowMask.x*broadWindowMask.y*broadFacing*glossStrength*.035*surfaceReflectionWeight*clamp(uStrength,0.,1.); color.rgb=mix(color.rgb,vec3(1.),specGloss+broadReflection); gl_FragColor=color; // Common, static room environment. Reflected direction varies with the same // curved normal as transmission; no image relighting or timed sparkle. float fresnel=.04+.96*pow(1.-clamp(dot(nCurved,v),0.,1.),5.); float environmentWeight=uEnvironmentStrength*glossStrength*(.35+.65*fresnel)*surfaceReflectionWeight*clamp(uStrength,0.,1.); vec3 environmentColor=mix(vec3(.10,.13,.18),vec3(.65,.72,.80),smoothstep(-.35,.65,reflected.y)); if(style<=.5)color.rgb=mix(color.rgb,environmentColor,environmentWeight); gl_FragColor=color; #include #else color.a=atlasEvidence; gl_FragColor=color; #endif }