Spaces:
Running
Running
import { | |
Matrix4, | |
Vector2 | |
} from 'three'; | |
/** | |
* References: | |
* https://lettier.github.io/3d-game-shaders-for-beginners/screen-space-reflection.html | |
*/ | |
const SSRShader = { | |
name: 'SSRShader', | |
defines: { | |
MAX_STEP: 0, | |
PERSPECTIVE_CAMERA: true, | |
DISTANCE_ATTENUATION: true, | |
FRESNEL: true, | |
INFINITE_THICK: false, | |
SELECTIVE: false, | |
}, | |
uniforms: { | |
'tDiffuse': { value: null }, | |
'tNormal': { value: null }, | |
'tMetalness': { value: null }, | |
'tDepth': { value: null }, | |
'cameraNear': { value: null }, | |
'cameraFar': { value: null }, | |
'resolution': { value: new Vector2() }, | |
'cameraProjectionMatrix': { value: new Matrix4() }, | |
'cameraInverseProjectionMatrix': { value: new Matrix4() }, | |
'opacity': { value: .5 }, | |
'maxDistance': { value: 180 }, | |
'cameraRange': { value: 0 }, | |
'thickness': { value: .018 } | |
}, | |
vertexShader: /* glsl */` | |
varying vec2 vUv; | |
void main() { | |
vUv = uv; | |
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); | |
} | |
`, | |
fragmentShader: /* glsl */` | |
// precision highp float; | |
precision highp sampler2D; | |
varying vec2 vUv; | |
uniform sampler2D tDepth; | |
uniform sampler2D tNormal; | |
uniform sampler2D tMetalness; | |
uniform sampler2D tDiffuse; | |
uniform float cameraRange; | |
uniform vec2 resolution; | |
uniform float opacity; | |
uniform float cameraNear; | |
uniform float cameraFar; | |
uniform float maxDistance; | |
uniform float thickness; | |
uniform mat4 cameraProjectionMatrix; | |
uniform mat4 cameraInverseProjectionMatrix; | |
#include <packing> | |
float pointToLineDistance(vec3 x0, vec3 x1, vec3 x2) { | |
//x0: point, x1: linePointA, x2: linePointB | |
//https://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html | |
return length(cross(x0-x1,x0-x2))/length(x2-x1); | |
} | |
float pointPlaneDistance(vec3 point,vec3 planePoint,vec3 planeNormal){ | |
// https://mathworld.wolfram.com/Point-PlaneDistance.html | |
//// https://en.wikipedia.org/wiki/Plane_(geometry) | |
//// http://paulbourke.net/geometry/pointlineplane/ | |
float a=planeNormal.x,b=planeNormal.y,c=planeNormal.z; | |
float x0=point.x,y0=point.y,z0=point.z; | |
float x=planePoint.x,y=planePoint.y,z=planePoint.z; | |
float d=-(a*x+b*y+c*z); | |
float distance=(a*x0+b*y0+c*z0+d)/sqrt(a*a+b*b+c*c); | |
return distance; | |
} | |
float getDepth( const in vec2 uv ) { | |
return texture2D( tDepth, uv ).x; | |
} | |
float getViewZ( const in float depth ) { | |
#ifdef PERSPECTIVE_CAMERA | |
return perspectiveDepthToViewZ( depth, cameraNear, cameraFar ); | |
#else | |
return orthographicDepthToViewZ( depth, cameraNear, cameraFar ); | |
#endif | |
} | |
vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) { | |
vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc | |
clipPosition *= clipW; //clip | |
return ( cameraInverseProjectionMatrix * clipPosition ).xyz;//view | |
} | |
vec3 getViewNormal( const in vec2 uv ) { | |
return unpackRGBToNormal( texture2D( tNormal, uv ).xyz ); | |
} | |
vec2 viewPositionToXY(vec3 viewPosition){ | |
vec2 xy; | |
vec4 clip=cameraProjectionMatrix*vec4(viewPosition,1); | |
xy=clip.xy;//clip | |
float clipW=clip.w; | |
xy/=clipW;//NDC | |
xy=(xy+1.)/2.;//uv | |
xy*=resolution;//screen | |
return xy; | |
} | |
void main(){ | |
#ifdef SELECTIVE | |
float metalness=texture2D(tMetalness,vUv).r; | |
if(metalness==0.) return; | |
#endif | |
float depth = getDepth( vUv ); | |
float viewZ = getViewZ( depth ); | |
if(-viewZ>=cameraFar) return; | |
float clipW = cameraProjectionMatrix[2][3] * viewZ+cameraProjectionMatrix[3][3]; | |
vec3 viewPosition=getViewPosition( vUv, depth, clipW ); | |
vec2 d0=gl_FragCoord.xy; | |
vec2 d1; | |
vec3 viewNormal=getViewNormal( vUv ); | |
#ifdef PERSPECTIVE_CAMERA | |
vec3 viewIncidentDir=normalize(viewPosition); | |
vec3 viewReflectDir=reflect(viewIncidentDir,viewNormal); | |
#else | |
vec3 viewIncidentDir=vec3(0,0,-1); | |
vec3 viewReflectDir=reflect(viewIncidentDir,viewNormal); | |
#endif | |
float maxReflectRayLen=maxDistance/dot(-viewIncidentDir,viewNormal); | |
// dot(a,b)==length(a)*length(b)*cos(theta) // https://www.mathsisfun.com/algebra/vectors-dot-product.html | |
// if(a.isNormalized&&b.isNormalized) dot(a,b)==cos(theta) | |
// maxDistance/maxReflectRayLen=cos(theta) | |
// maxDistance/maxReflectRayLen==dot(a,b) | |
// maxReflectRayLen==maxDistance/dot(a,b) | |
vec3 d1viewPosition=viewPosition+viewReflectDir*maxReflectRayLen; | |
#ifdef PERSPECTIVE_CAMERA | |
if(d1viewPosition.z>-cameraNear){ | |
//https://tutorial.math.lamar.edu/Classes/CalcIII/EqnsOfLines.aspx | |
float t=(-cameraNear-viewPosition.z)/viewReflectDir.z; | |
d1viewPosition=viewPosition+viewReflectDir*t; | |
} | |
#endif | |
d1=viewPositionToXY(d1viewPosition); | |
float totalLen=length(d1-d0); | |
float xLen=d1.x-d0.x; | |
float yLen=d1.y-d0.y; | |
float totalStep=max(abs(xLen),abs(yLen)); | |
float xSpan=xLen/totalStep; | |
float ySpan=yLen/totalStep; | |
for(float i=0.;i<float(MAX_STEP);i++){ | |
if(i>=totalStep) break; | |
vec2 xy=vec2(d0.x+i*xSpan,d0.y+i*ySpan); | |
if(xy.x<0.||xy.x>resolution.x||xy.y<0.||xy.y>resolution.y) break; | |
float s=length(xy-d0)/totalLen; | |
vec2 uv=xy/resolution; | |
float d = getDepth(uv); | |
float vZ = getViewZ( d ); | |
if(-vZ>=cameraFar) continue; | |
float cW = cameraProjectionMatrix[2][3] * vZ+cameraProjectionMatrix[3][3]; | |
vec3 vP=getViewPosition( uv, d, cW ); | |
#ifdef PERSPECTIVE_CAMERA | |
// https://comp.nus.edu.sg/~lowkl/publications/lowk_persp_interp_techrep.pdf | |
float recipVPZ=1./viewPosition.z; | |
float viewReflectRayZ=1./(recipVPZ+s*(1./d1viewPosition.z-recipVPZ)); | |
#else | |
float viewReflectRayZ=viewPosition.z+s*(d1viewPosition.z-viewPosition.z); | |
#endif | |
// if(viewReflectRayZ>vZ) continue; // will cause "npm run make-screenshot webgl_postprocessing_ssr" high probability hang. | |
// https://github.com/mrdoob/three.js/pull/21539#issuecomment-821061164 | |
if(viewReflectRayZ<=vZ){ | |
bool hit; | |
#ifdef INFINITE_THICK | |
hit=true; | |
#else | |
float away=pointToLineDistance(vP,viewPosition,d1viewPosition); | |
float minThickness; | |
vec2 xyNeighbor=xy; | |
xyNeighbor.x+=1.; | |
vec2 uvNeighbor=xyNeighbor/resolution; | |
vec3 vPNeighbor=getViewPosition(uvNeighbor,d,cW); | |
minThickness=vPNeighbor.x-vP.x; | |
minThickness*=3.; | |
float tk=max(minThickness,thickness); | |
hit=away<=tk; | |
#endif | |
if(hit){ | |
vec3 vN=getViewNormal( uv ); | |
if(dot(viewReflectDir,vN)>=0.) continue; | |
float distance=pointPlaneDistance(vP,viewPosition,viewNormal); | |
if(distance>maxDistance) break; | |
float op=opacity; | |
#ifdef DISTANCE_ATTENUATION | |
float ratio=1.-(distance/maxDistance); | |
float attenuation=ratio*ratio; | |
op=opacity*attenuation; | |
#endif | |
#ifdef FRESNEL | |
float fresnelCoe=(dot(viewIncidentDir,viewReflectDir)+1.)/2.; | |
op*=fresnelCoe; | |
#endif | |
vec4 reflectColor=texture2D(tDiffuse,uv); | |
gl_FragColor.xyz=reflectColor.xyz; | |
gl_FragColor.a=op; | |
break; | |
} | |
} | |
} | |
} | |
` | |
}; | |
const SSRDepthShader = { | |
name: 'SSRDepthShader', | |
defines: { | |
'PERSPECTIVE_CAMERA': 1 | |
}, | |
uniforms: { | |
'tDepth': { value: null }, | |
'cameraNear': { value: null }, | |
'cameraFar': { value: null }, | |
}, | |
vertexShader: /* glsl */` | |
varying vec2 vUv; | |
void main() { | |
vUv = uv; | |
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); | |
} | |
`, | |
fragmentShader: /* glsl */` | |
uniform sampler2D tDepth; | |
uniform float cameraNear; | |
uniform float cameraFar; | |
varying vec2 vUv; | |
#include <packing> | |
float getLinearDepth( const in vec2 uv ) { | |
#if PERSPECTIVE_CAMERA == 1 | |
float fragCoordZ = texture2D( tDepth, uv ).x; | |
float viewZ = perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar ); | |
return viewZToOrthographicDepth( viewZ, cameraNear, cameraFar ); | |
#else | |
return texture2D( tDepth, uv ).x; | |
#endif | |
} | |
void main() { | |
float depth = getLinearDepth( vUv ); | |
float d = 1.0 - depth; | |
// d=(d-.999)*1000.; | |
gl_FragColor = vec4( vec3( d ), 1.0 ); | |
} | |
` | |
}; | |
const SSRBlurShader = { | |
name: 'SSRBlurShader', | |
uniforms: { | |
'tDiffuse': { value: null }, | |
'resolution': { value: new Vector2() }, | |
'opacity': { value: .5 }, | |
}, | |
vertexShader: /* glsl */` | |
varying vec2 vUv; | |
void main() { | |
vUv = uv; | |
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); | |
} | |
`, | |
fragmentShader: /* glsl */` | |
uniform sampler2D tDiffuse; | |
uniform vec2 resolution; | |
varying vec2 vUv; | |
void main() { | |
//reverse engineering from PhotoShop blur filter, then change coefficient | |
vec2 texelSize = ( 1.0 / resolution ); | |
vec4 c=texture2D(tDiffuse,vUv); | |
vec2 offset; | |
offset=(vec2(-1,0))*texelSize; | |
vec4 cl=texture2D(tDiffuse,vUv+offset); | |
offset=(vec2(1,0))*texelSize; | |
vec4 cr=texture2D(tDiffuse,vUv+offset); | |
offset=(vec2(0,-1))*texelSize; | |
vec4 cb=texture2D(tDiffuse,vUv+offset); | |
offset=(vec2(0,1))*texelSize; | |
vec4 ct=texture2D(tDiffuse,vUv+offset); | |
// float coeCenter=.5; | |
// float coeSide=.125; | |
float coeCenter=.2; | |
float coeSide=.2; | |
float a=c.a*coeCenter+cl.a*coeSide+cr.a*coeSide+cb.a*coeSide+ct.a*coeSide; | |
vec3 rgb=(c.rgb*c.a*coeCenter+cl.rgb*cl.a*coeSide+cr.rgb*cr.a*coeSide+cb.rgb*cb.a*coeSide+ct.rgb*ct.a*coeSide)/a; | |
gl_FragColor=vec4(rgb,a); | |
} | |
` | |
}; | |
export { SSRShader, SSRDepthShader, SSRBlurShader }; | |