Skip to content

Commit 05cc046

Browse files
authored
Add SSRPass (screen space reflection) (#20156)
* SSRPass init * a * a * a * a * a * a * a * a * a * use resolution & delete depthRenderMaterial * SSRPassPerspective * a * a * a * a * a * a * a * use pointToLineDistance, maybe ok * a * almost OK * increase FOV * a * a * a * A * A * a * clean * clean * screenshot * a * gui * a * new pointToLineDistance function * #define MAX_STEP & onresize replace MAX_STEP * bug fix * a * mobile bug fix * surfDist affected by clipW * performance improvement * a * SSRPassSelective * a * selective ok * selective ok * a * a * use traverse for selective reflect first, try other methods after * Configurable encoding * support OrthographicCamera * traverseVisible * SSRPass_bouncing * a * a * performance improvement * a * No need to calculate clipW separately * performance: Use the same skip strategy as viewZ for vZ * clean * use lineLineIntersection instead of pointToLineDistance, can get accurate relfectRayLen. Add DistanceAttenuation feature. * lineLineIntersection & DistanceAttenuation. Use lineLineIntersection instead of pointToLineDistance, can get accurate relfectRayLen. Add DistanceAttenuation feature. * default DistanceAttenuation on * bugfix: OrthographicCamera support * infiniteThick * normal noise * a * a * fit morphTargets * Separate blur setting * performance: use #ifdef instead of if * a * New d1viewPosition calc method, seems same performance, but more concise and clear * Use Perspective-Correct Interpolation instead of lineLineIntersection * performance & priliminary angleCompensation * NormalBlending * screenshot * a * Resolve conflict of examples/files.js * bugfix: maxDistance and attenuation should compared by distance perpendicular to reflectNormal (use pointPlaneDistance function) * fresnel * calculated maxReflectRayLen * bugfix: Character morphTarget normalMaterial. * bugfix: Character morphTarget normalMaterial. * change default scene to bunny * surfDist compensation by angle * With Refelector.js, default off. Now just add a reflector to hide the apparent flaw of SSR, need more integration afterwards. * Let Reflector.js can handle opacity by depthTexture. * fix the "jumpiness" of the reflection * 1. Exclude Reflector from SSRPass if Reflector is on. 2. Increase reflector distanceAttenuation. 3. Change isOrbitControls to autoRotate. * Decrease the vertical offset between SSR and Reflector. * Revert plane.depthWrite = false & Revert to reflector.position.y = plane.position.y + .0001; * Remove the noise setting. * Use pointToLineDistance instead of viewReflectRayZ to calculate away, thus become stride irrelavent. * Performance: perform pointToLineDistance only if viewReflectRayZ-sD<=vZ. * 1. Reduce the size of the reflector to prevent glitch caused by low precision. 2. reflectorRenderTarget resize. 3. Clean up. * Fix: .getClearColor() now requires a Color as an argument. * postprocessing with reflector performance problem quick hack. https://discourse.threejs.org/t/hows-the-reflection-made-in-this-example/23597/8 * Turn on GroundReflector by default & some clean up. * Let groundReflector also follow settings. * Fix zigzag problem between ground and objects. * gui folder
1 parent e34efe2 commit 05cc046

File tree

6 files changed

+1616
-0
lines changed

6 files changed

+1616
-0
lines changed

examples/files.json

+1
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@
261261
"webgl_postprocessing_smaa",
262262
"webgl_postprocessing_sobel",
263263
"webgl_postprocessing_ssao",
264+
"webgl_postprocessing_ssr",
264265
"webgl_postprocessing_taa",
265266
"webgl_postprocessing_unreal_bloom",
266267
"webgl_postprocessing_unreal_bloom_selective"
+333
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
import {
2+
Color,
3+
LinearFilter,
4+
MathUtils,
5+
Matrix4,
6+
Mesh,
7+
PerspectiveCamera,
8+
Plane,
9+
RGBFormat,
10+
ShaderMaterial,
11+
UniformsUtils,
12+
Vector3,
13+
Vector4,
14+
WebGLRenderTarget,
15+
DepthTexture,
16+
UnsignedShortType,
17+
NearestFilter
18+
} from '../../../build/three.module.js';
19+
20+
var Reflector = function ( geometry, options ) {
21+
22+
Mesh.call( this, geometry );
23+
24+
this.type = 'Reflector';
25+
26+
var scope = this;
27+
28+
options = options || {};
29+
30+
var color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F );
31+
var textureWidth = options.textureWidth || 512;
32+
var textureHeight = options.textureHeight || 512;
33+
var clipBias = options.clipBias || 0;
34+
var shader = options.shader || Reflector.ReflectorShader;
35+
var useDepthTexture = options.useDepthTexture
36+
var yAxis = new Vector3(0, 1, 0);
37+
var vecTemp0 = new Vector3();
38+
var vecTemp1 = new Vector3();
39+
40+
//
41+
42+
scope.needsUpdate = false;
43+
scope.maxDistance = Reflector.ReflectorShader.uniforms.maxDistance.value
44+
scope.opacity = Reflector.ReflectorShader.uniforms.opacity.value
45+
46+
scope._isDistanceAttenuation = Reflector.ReflectorShader.defines.isDistanceAttenuation
47+
Object.defineProperty(scope, 'isDistanceAttenuation', {
48+
get() {
49+
return scope._isDistanceAttenuation
50+
},
51+
set(val) {
52+
if (scope._isDistanceAttenuation === val) return
53+
scope._isDistanceAttenuation = val
54+
scope.material.defines.isDistanceAttenuation = val
55+
scope.material.needsUpdate = true
56+
}
57+
})
58+
59+
scope._isFresnel = Reflector.ReflectorShader.defines.isFresnel
60+
Object.defineProperty(scope, 'isFresnel', {
61+
get() {
62+
return scope._isFresnel
63+
},
64+
set(val) {
65+
if (scope._isFresnel === val) return
66+
scope._isFresnel = val
67+
scope.material.defines.isFresnel = val
68+
scope.material.needsUpdate = true
69+
}
70+
})
71+
72+
var reflectorPlane = new Plane();
73+
var normal = new Vector3();
74+
var reflectorWorldPosition = new Vector3();
75+
var cameraWorldPosition = new Vector3();
76+
var rotationMatrix = new Matrix4();
77+
var lookAtPosition = new Vector3( 0, 0, - 1 );
78+
var clipPlane = new Vector4();
79+
80+
var view = new Vector3();
81+
var target = new Vector3();
82+
var q = new Vector4();
83+
84+
var textureMatrix = new Matrix4();
85+
var virtualCamera = new PerspectiveCamera();
86+
87+
if( useDepthTexture ){
88+
var depthTexture = new DepthTexture();
89+
depthTexture.type = UnsignedShortType;
90+
depthTexture.minFilter = NearestFilter;
91+
depthTexture.maxFilter = NearestFilter;
92+
}
93+
94+
var parameters = {
95+
minFilter: LinearFilter,
96+
magFilter: LinearFilter,
97+
format: RGBFormat,
98+
depthTexture: useDepthTexture ? depthTexture : null,
99+
};
100+
101+
var renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, parameters );
102+
103+
if ( ! MathUtils.isPowerOfTwo( textureWidth ) || ! MathUtils.isPowerOfTwo( textureHeight ) ) {
104+
105+
renderTarget.texture.generateMipmaps = false;
106+
107+
}
108+
109+
var material = new ShaderMaterial( {
110+
transparent: useDepthTexture,
111+
defines: Object.assign({
112+
useDepthTexture: useDepthTexture
113+
}, Reflector.ReflectorShader.defines),
114+
uniforms: UniformsUtils.clone( shader.uniforms ),
115+
fragmentShader: shader.fragmentShader,
116+
vertexShader: shader.vertexShader
117+
} );
118+
119+
material.uniforms[ 'tDiffuse' ].value = renderTarget.texture;
120+
material.uniforms[ 'color' ].value = color;
121+
material.uniforms[ 'textureMatrix' ].value = textureMatrix;
122+
if (useDepthTexture) {
123+
material.uniforms[ 'tDepth' ].value = renderTarget.depthTexture;
124+
}
125+
126+
this.material = material;
127+
128+
this.doRender = function ( renderer, scene, camera ) {
129+
130+
material.uniforms['maxDistance'].value = scope.maxDistance * (camera.position.length() / camera.position.y);
131+
///todo: Temporary hack,
132+
// need precise calculation like this https://github.com/mrdoob/three.js/pull/20156/commits/8181946068e386d14a283cbd4f8877bc7ae066d3 ,
133+
// after fully understand http://www.terathon.com/lengyel/Lengyel-Oblique.pdf .
134+
135+
material.uniforms['opacity'].value = scope.opacity;
136+
137+
vecTemp0.copy(camera.position).normalize();
138+
vecTemp1.copy(vecTemp0).reflect(yAxis);
139+
material.uniforms['fresnel'].value = (vecTemp0.dot( vecTemp1 ) + 1.) / 2.; ///todo: Also need to use glsl viewPosition and viewNormal per pixel.
140+
// console.log(material.uniforms['fresnel'].value)
141+
142+
reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld );
143+
cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld );
144+
145+
rotationMatrix.extractRotation( scope.matrixWorld );
146+
147+
normal.set( 0, 0, 1 );
148+
normal.applyMatrix4( rotationMatrix );
149+
150+
view.subVectors( reflectorWorldPosition, cameraWorldPosition );
151+
152+
// Avoid rendering when reflector is facing away
153+
154+
if ( view.dot( normal ) > 0 ) return;
155+
156+
view.reflect( normal ).negate();
157+
view.add( reflectorWorldPosition );
158+
159+
rotationMatrix.extractRotation( camera.matrixWorld );
160+
161+
lookAtPosition.set( 0, 0, - 1 );
162+
lookAtPosition.applyMatrix4( rotationMatrix );
163+
lookAtPosition.add( cameraWorldPosition );
164+
165+
target.subVectors( reflectorWorldPosition, lookAtPosition );
166+
target.reflect( normal ).negate();
167+
target.add( reflectorWorldPosition );
168+
169+
virtualCamera.position.copy( view );
170+
virtualCamera.up.set( 0, 1, 0 );
171+
virtualCamera.up.applyMatrix4( rotationMatrix );
172+
virtualCamera.up.reflect( normal );
173+
virtualCamera.lookAt( target );
174+
175+
virtualCamera.far = camera.far; // Used in WebGLBackground
176+
177+
virtualCamera.updateMatrixWorld();
178+
virtualCamera.projectionMatrix.copy( camera.projectionMatrix );
179+
180+
// Update the texture matrix
181+
textureMatrix.set(
182+
0.5, 0.0, 0.0, 0.5,
183+
0.0, 0.5, 0.0, 0.5,
184+
0.0, 0.0, 0.5, 0.5,
185+
0.0, 0.0, 0.0, 1.0
186+
);
187+
textureMatrix.multiply( virtualCamera.projectionMatrix );
188+
textureMatrix.multiply( virtualCamera.matrixWorldInverse );
189+
textureMatrix.multiply( scope.matrixWorld );
190+
191+
// Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
192+
// Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
193+
reflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition );
194+
reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse );
195+
196+
clipPlane.set( reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant );
197+
198+
var projectionMatrix = virtualCamera.projectionMatrix;
199+
200+
q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ];
201+
q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ];
202+
q.z = - 1.0;
203+
q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ];
204+
205+
// Calculate the scaled plane vector
206+
clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) );
207+
208+
// Replacing the third row of the projection matrix
209+
projectionMatrix.elements[ 2 ] = clipPlane.x;
210+
projectionMatrix.elements[ 6 ] = clipPlane.y;
211+
projectionMatrix.elements[ 10 ] = clipPlane.z + 1.0 - clipBias;
212+
projectionMatrix.elements[ 14 ] = clipPlane.w;
213+
214+
// Render
215+
216+
renderTarget.texture.encoding = renderer.outputEncoding;
217+
218+
// scope.visible = false;
219+
220+
var currentRenderTarget = renderer.getRenderTarget();
221+
222+
var currentXrEnabled = renderer.xr.enabled;
223+
var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
224+
225+
renderer.xr.enabled = false; // Avoid camera modification
226+
renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows
227+
228+
renderer.setRenderTarget( renderTarget );
229+
230+
renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897
231+
232+
if ( renderer.autoClear === false ) renderer.clear();
233+
renderer.render( scene, virtualCamera );
234+
235+
renderer.xr.enabled = currentXrEnabled;
236+
renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
237+
238+
renderer.setRenderTarget( currentRenderTarget );
239+
240+
// Restore viewport
241+
242+
var viewport = camera.viewport;
243+
244+
if ( viewport !== undefined ) {
245+
246+
renderer.state.viewport( viewport );
247+
248+
}
249+
250+
// scope.visible = true;
251+
252+
};
253+
254+
this.getRenderTarget = function () {
255+
256+
return renderTarget;
257+
258+
};
259+
260+
};
261+
262+
Reflector.prototype = Object.create( Mesh.prototype );
263+
Reflector.prototype.constructor = Reflector;
264+
265+
Reflector.ReflectorShader = { ///todo: Will conflict with Reflector.js?
266+
267+
defines: {
268+
isDistanceAttenuation: true,
269+
isFresnel: true,
270+
},
271+
272+
uniforms: {
273+
274+
color: { value: null },
275+
tDiffuse: { value: null },
276+
tDepth: { value: null },
277+
textureMatrix: { value: null },
278+
maxDistance: { value: 180 },
279+
opacity: { value: .5 },
280+
fresnel: { value: null },
281+
282+
},
283+
284+
vertexShader: [
285+
'uniform mat4 textureMatrix;',
286+
'varying vec4 vUv;',
287+
288+
'void main() {',
289+
290+
' vUv = textureMatrix * vec4( position, 1.0 );',
291+
292+
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
293+
294+
'}'
295+
].join( '\n' ),
296+
297+
fragmentShader: `
298+
uniform vec3 color;
299+
uniform sampler2D tDiffuse;
300+
uniform sampler2D tDepth;
301+
uniform float maxDistance;
302+
uniform float opacity;
303+
uniform float fresnel;
304+
varying vec4 vUv;
305+
float blendOverlay( float base, float blend ) {
306+
return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );
307+
}
308+
vec3 blendOverlay( vec3 base, vec3 blend ) {
309+
return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) );
310+
}
311+
void main() {
312+
vec4 base = texture2DProj( tDiffuse, vUv );
313+
#ifdef useDepthTexture
314+
float op=opacity;
315+
float depth = texture2DProj( tDepth, vUv ).r;
316+
if(depth>maxDistance) discard;
317+
#ifdef isDistanceAttenuation
318+
float ratio=1.-(depth/maxDistance);
319+
float attenuation=ratio*ratio;
320+
op=opacity*attenuation;
321+
#endif
322+
#ifdef isFresnel
323+
op*=fresnel;
324+
#endif
325+
gl_FragColor = vec4( blendOverlay( base.rgb, color ), op );
326+
#else
327+
gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 );
328+
#endif
329+
}
330+
`,
331+
};
332+
333+
export { Reflector };

0 commit comments

Comments
 (0)