diff --git a/.storybook/Setup.tsx b/.storybook/Setup.tsx index 7ff2d6c7b..c9959ae9e 100644 --- a/.storybook/Setup.tsx +++ b/.storybook/Setup.tsx @@ -4,12 +4,18 @@ import { Canvas } from 'react-three-fiber' import { OrbitControls } from '../src' -export function Setup({ children, cameraPosition = new THREE.Vector3(-5, 5, 5), controls = true, ...restProps }) { +export function Setup({ + children, + cameraFov = 75, + cameraPosition = new THREE.Vector3(-5, 5, 5), + controls = true, + ...restProps +}) { return ( diff --git a/.storybook/public/normal_floor.jpeg b/.storybook/public/normal_floor.jpeg new file mode 100644 index 000000000..11532561a Binary files /dev/null and b/.storybook/public/normal_floor.jpeg differ diff --git a/.storybook/public/roughness_floor.jpeg b/.storybook/public/roughness_floor.jpeg new file mode 100644 index 000000000..e6b07dc9b Binary files /dev/null and b/.storybook/public/roughness_floor.jpeg differ diff --git a/.storybook/stories/Reflector.stories.tsx b/.storybook/stories/Reflector.stories.tsx index febf63870..eb2018459 100644 --- a/.storybook/stories/Reflector.stories.tsx +++ b/.storybook/stories/Reflector.stories.tsx @@ -3,44 +3,94 @@ import { useFrame } from 'react-three-fiber' import { Vector3, Mesh } from 'three' import { Setup } from '../Setup' - -import { Reflector, Box } from '../../src' +import { Reflector, useTexture, TorusKnot, Box } from '../../src' export default { title: 'Misc/Reflector', component: Reflector, - decorators: [(storyFn) => {storyFn()}], + decorators: [ + (storyFn) => ( + + {' '} + {storyFn()} + + ), + ], } -function ReflectorScene() { +function ReflectorScene({ blur, depthScale }: { blur?: [number, number]; depthScale?: number }) { + const roughness = useTexture('roughness_floor.jpeg') + const normal = useTexture('normal_floor.jpeg') + const $box = React.useRef(null!) useFrame(({ clock }) => { - $box.current.position.y += Math.sin(clock.getElapsedTime()) / 100 + $box.current.position.y += Math.sin(clock.getElapsedTime()) / 25 $box.current.rotation.y = clock.getElapsedTime() / 2 }) return ( <> - {(Material, props) => } + {(Material, props) => ( + + )} - - - + + + + + + + ) } -export const ReflectorSt = () => +export const ReflectorSt = () => ( + + + +) ReflectorSt.storyName = 'Default' + +export const ReflectorPlain = () => ( + + + +) +ReflectorPlain.storyName = 'Plain' + +export const ReflectorBlur = () => ( + + + +) +ReflectorBlur.storyName = 'Blur' + +export const ReflectorDepth = () => ( + + + +) +ReflectorDepth.storyName = 'Depth' diff --git a/README.md b/README.md index 26564a8ef..a25faa19f 100644 --- a/README.md +++ b/README.md @@ -552,6 +552,14 @@ Easily add reflections and/or blur to a planar surface. This reflector can also minDepthThreshold={0.9} // Lower edge for the depthTexture interpolation (default = 0) maxDepthThreshold={1} // Upper edge for the depthTexture interpolation (default = 0) depthScale={1} // Scale the depth factor (0 = no depth, default = 0) + depthToBlurRatioBias={0.25} // Adds a bias factor to the depthTexture before calculating the blur amount [blurFactor = blurTexture * (depthTexture + bias)]. It accepts values between 0 and 1, default is 0.25. An amount > 0 of bias makes sure that the blurTexture is not too sharp because of the multiplication with the depthTexture + debug={0} /* Depending on the assigned value, one of the following channels is shown: + 0 = no debug + 1 = depth channel + 2 = roughness channel + 3 = base channel + 4 = blur channel + */ > {(Material, props) => } diff --git a/src/core/Reflector.tsx b/src/core/Reflector.tsx index a26a10b3d..0e6effeb4 100644 --- a/src/core/Reflector.tsx +++ b/src/core/Reflector.tsx @@ -29,6 +29,8 @@ export type ReflectorProps = Omit, @@ -56,8 +58,10 @@ export function Reflector({ minDepthThreshold = 0.9, maxDepthThreshold = 1, depthScale = 0, + depthToBlurRatioBias = 0.25, mirror, children, + debug = 0, ...props }: ReflectorProps) { blur = Array.isArray(blur) ? blur : [blur, blur] @@ -141,7 +145,16 @@ export function Reflector({ fbo1.depthTexture.format = DepthFormat fbo1.depthTexture.type = UnsignedShortType const fbo2 = new WebGLRenderTarget(resolution, resolution, parameters) - const blurpass = new BlurPass({ gl, resolution, width: blur[0], height: blur[1] }) + const blurpass = new BlurPass({ + gl, + resolution, + width: blur[0], + height: blur[1], + minDepthThreshold, + maxDepthThreshold, + depthScale, + depthToBlurRatioBias, + }) const reflectorProps = { mirror, textureMatrix, @@ -154,6 +167,11 @@ export function Reflector({ minDepthThreshold, maxDepthThreshold, depthScale, + depthToBlurRatioBias, + transparent: true, + debug, + 'defines-USE_BLUR': hasBlur, + 'defines-USE_DEPTH': depthScale > 0, } return [fbo1, fbo2, blurpass, reflectorProps] }, [ @@ -168,6 +186,8 @@ export function Reflector({ minDepthThreshold, maxDepthThreshold, depthScale, + depthToBlurRatioBias, + debug, ]) useFrame(() => { diff --git a/src/materials/BlurPass.tsx b/src/materials/BlurPass.tsx index 0f4d79d72..544b24a68 100644 --- a/src/materials/BlurPass.tsx +++ b/src/materials/BlurPass.tsx @@ -2,11 +2,12 @@ import { Mesh, BufferGeometry, BufferAttribute, - Camera, LinearFilter, Scene, WebGLRenderTarget, WebGLRenderer, + Camera, + Vector2, } from 'three' import { ConvolutionMaterial } from './ConvolutionMaterial' @@ -16,6 +17,10 @@ export interface BlurPassProps { resolution: number width?: number height?: number + minDepthThreshold?: number + maxDepthThreshold?: number + depthScale?: number + depthToBlurRatioBias?: number } export class BlurPass { @@ -27,7 +32,16 @@ export class BlurPass { readonly screen: Mesh renderToScreen: boolean = false - constructor({ gl, resolution, width = 500, height = 500 }: BlurPassProps) { + constructor({ + gl, + resolution, + width = 500, + height = 500, + minDepthThreshold = 0, + maxDepthThreshold = 1, + depthScale = 0, + depthToBlurRatioBias = 0.25, + }: BlurPassProps) { this.renderTargetA = new WebGLRenderTarget(resolution, resolution, { minFilter: LinearFilter, magFilter: LinearFilter, @@ -38,8 +52,14 @@ export class BlurPass { this.renderTargetB = this.renderTargetA.clone() this.convolutionMaterial = new ConvolutionMaterial() this.convolutionMaterial.setTexelSize(1.0 / width, 1.0 / height) + this.convolutionMaterial.setResolution(new Vector2(width, height)) this.scene = new Scene() this.camera = new Camera() + this.convolutionMaterial.uniforms.minDepthThreshold.value = minDepthThreshold + this.convolutionMaterial.uniforms.maxDepthThreshold.value = maxDepthThreshold + this.convolutionMaterial.uniforms.depthScale.value = depthScale + this.convolutionMaterial.uniforms.depthToBlurRatioBias.value = depthToBlurRatioBias + this.convolutionMaterial.defines.USE_DEPTH = depthScale > 0 const vertices = new Float32Array([-1, -1, 0, 3, -1, 0, -1, 3, 0]) const uvs = new Float32Array([0, 0, 2, 0, 0, 2]) const geometry = new BufferGeometry() @@ -57,6 +77,7 @@ export class BlurPass { const renderTargetB = this.renderTargetB let material = this.convolutionMaterial let uniforms = material.uniforms + uniforms.depthBuffer.value = inputBuffer.depthTexture const kernel = material.kernel let lastRT = inputBuffer let destRT diff --git a/src/materials/ConvolutionMaterial.tsx b/src/materials/ConvolutionMaterial.tsx index def61c6e1..c7dff0636 100644 --- a/src/materials/ConvolutionMaterial.tsx +++ b/src/materials/ConvolutionMaterial.tsx @@ -6,41 +6,73 @@ export class ConvolutionMaterial extends ShaderMaterial { super({ uniforms: { inputBuffer: new Uniform(null), + depthBuffer: new Uniform(null), + resolution: new Uniform(new Vector2()), texelSize: new Uniform(new Vector2()), halfTexelSize: new Uniform(new Vector2()), kernel: new Uniform(0.0), scale: new Uniform(1.0), + cameraNear: new Uniform(0.0), + cameraFar: new Uniform(1.0), + minDepthThreshold: new Uniform(0.0), + maxDepthThreshold: new Uniform(1.0), + depthScale: new Uniform(0.0), + depthToBlurRatioBias: new Uniform(0.25), }, fragmentShader: `#include #include uniform sampler2D inputBuffer; + uniform sampler2D depthBuffer; + uniform float cameraNear; + uniform float cameraFar; + uniform float minDepthThreshold; + uniform float maxDepthThreshold; + uniform float depthScale; + uniform float depthToBlurRatioBias; + varying vec2 vUv; varying vec2 vUv0; varying vec2 vUv1; varying vec2 vUv2; varying vec2 vUv3; + void main() { - vec4 sum = texture2D(inputBuffer, vUv0); - sum += texture2D(inputBuffer, vUv1); - sum += texture2D(inputBuffer, vUv2); - sum += texture2D(inputBuffer, vUv3); - gl_FragColor = sum * 0.25; + float depthFactor = 0.0; + + #ifdef USE_DEPTH + vec4 depth = texture2D(depthBuffer, vUv); + depthFactor = smoothstep(minDepthThreshold, maxDepthThreshold, 1.0-(depth.r * depth.a)); + depthFactor *= depthScale; + depthFactor = max(0.0, min(1.0, depthFactor + 0.25)); + #endif + + vec4 sum = texture2D(inputBuffer, mix(vUv0, vUv, depthFactor)); + sum += texture2D(inputBuffer, mix(vUv1, vUv, depthFactor)); + sum += texture2D(inputBuffer, mix(vUv2, vUv, depthFactor)); + sum += texture2D(inputBuffer, mix(vUv3, vUv, depthFactor)); + gl_FragColor = sum * 0.25 ; + #include }`, vertexShader: `uniform vec2 texelSize; uniform vec2 halfTexelSize; uniform float kernel; uniform float scale; + varying vec2 vUv; varying vec2 vUv0; varying vec2 vUv1; varying vec2 vUv2; varying vec2 vUv3; + void main() { vec2 uv = position.xy * 0.5 + 0.5; + vUv = uv; + vec2 dUv = (texelSize * vec2(kernel) + halfTexelSize) * scale; vUv0 = vec2(uv.x - dUv.x, uv.y + dUv.y); vUv1 = vec2(uv.x + dUv.x, uv.y + dUv.y); vUv2 = vec2(uv.x + dUv.x, uv.y - dUv.y); vUv3 = vec2(uv.x - dUv.x, uv.y - dUv.y); + gl_Position = vec4(position.xy, 1.0, 1.0); }`, blending: NoBlending, @@ -57,4 +89,7 @@ export class ConvolutionMaterial extends ShaderMaterial { this.uniforms.texelSize.value.set(x, y) this.uniforms.halfTexelSize.value.set(x, y).multiplyScalar(0.5) } + setResolution(resolution: Vector2) { + this.uniforms.resolution.value.copy(resolution) + } } diff --git a/src/materials/MeshReflectorMaterial.tsx b/src/materials/MeshReflectorMaterial.tsx index 5ce6796e1..e94053136 100644 --- a/src/materials/MeshReflectorMaterial.tsx +++ b/src/materials/MeshReflectorMaterial.tsx @@ -3,6 +3,7 @@ import { Matrix4, MeshStandardMaterial, Texture } from 'three' type UninitializedUniform = { value: Value | null } export class MeshReflectorMaterial extends MeshStandardMaterial { + private _debug: { value: number } = { value: 0 } private _tDepth: UninitializedUniform = { value: null } private _tDiffuse: UninitializedUniform = { value: null } private _tDiffuseBlur: UninitializedUniform = { value: null } @@ -14,12 +15,14 @@ export class MeshReflectorMaterial extends MeshStandardMaterial { private _minDepthThreshold: { value: number } = { value: 0.9 } private _maxDepthThreshold: { value: number } = { value: 1 } private _depthScale: { value: number } = { value: 0 } + private _depthToBlurRatioBias: { value: number } = { value: 0.25 } constructor(parameters = {}) { super(parameters) this.setValues(parameters) } onBeforeCompile(shader) { + shader.uniforms.debug = this._debug shader.uniforms.hasBlur = this._hasBlur shader.uniforms.tDiffuse = this._tDiffuse shader.uniforms.tDepth = this._tDepth @@ -31,6 +34,7 @@ export class MeshReflectorMaterial extends MeshStandardMaterial { shader.uniforms.minDepthThreshold = this._minDepthThreshold shader.uniforms.maxDepthThreshold = this._maxDepthThreshold shader.uniforms.depthScale = this._depthScale + shader.uniforms.depthToBlurRatioBias = this._depthToBlurRatioBias shader.vertexShader = ` uniform mat4 textureMatrix; varying vec4 my_vUv; @@ -42,6 +46,7 @@ export class MeshReflectorMaterial extends MeshStandardMaterial { gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );` ) shader.fragmentShader = ` + uniform int debug; uniform sampler2D tDiffuse; uniform sampler2D tDiffuseBlur; uniform sampler2D tDepth; @@ -54,34 +59,62 @@ export class MeshReflectorMaterial extends MeshStandardMaterial { uniform float minDepthThreshold; uniform float maxDepthThreshold; uniform float depthScale; + uniform float depthToBlurRatioBias; varying vec4 my_vUv; ${shader.fragmentShader}` shader.fragmentShader = shader.fragmentShader.replace( '#include ', `#include - vec4 depth = texture2DProj(tDepth, my_vUv ); vec4 base = texture2DProj(tDiffuse, my_vUv); vec4 blur = texture2DProj(tDiffuseBlur, my_vUv); + + vec4 merge = base; + float depthFactor = 0.0001; + float blurFactor = 0.0; - float depthFactor = smoothstep(minDepthThreshold, maxDepthThreshold, 1.0-(depth.r * depth.a)); - depthFactor *= depthScale; - depthFactor = min(1.0, depthFactor); + #ifdef USE_DEPTH + vec4 depth = texture2DProj(tDepth, my_vUv); + depthFactor = smoothstep(minDepthThreshold, maxDepthThreshold, 1.0-(depth.r * depth.a)); + depthFactor *= depthScale; + depthFactor = max(0.0001, min(1.0, depthFactor)); + + #ifdef USE_BLUR + blur = blur * min(1.0, depthFactor + depthToBlurRatioBias); + merge = merge * min(1.0, depthFactor + 0.5);; + #else + merge = merge * depthFactor; + #endif + + #endif float reflectorRoughnessFactor = roughness; #ifdef USE_ROUGHNESSMAP vec4 reflectorTexelRoughness = texture2D( roughnessMap, vUv ); reflectorRoughnessFactor *= reflectorTexelRoughness.g; #endif - - vec4 merge = base; - if (hasBlur) { - float blurFactor = min(1.0, mixBlur * reflectorRoughnessFactor); + + #ifdef USE_BLUR + blurFactor = min(1.0, mixBlur * reflectorRoughnessFactor); merge = mix(merge, blur, blurFactor); - } - merge += mix(merge, base, depthFactor); + #endif + diffuseColor.rgb = diffuseColor.rgb * ((1.0 - min(1.0, mirror)) + merge.rgb * mixStrength); - diffuseColor = sRGBToLinear(diffuseColor);` + diffuseColor = sRGBToLinear(diffuseColor); + + if (debug == 1) { + diffuseColor = sRGBToLinear(vec4(vec3(depthFactor), 1.0)); + } + if (debug == 2) { + diffuseColor = sRGBToLinear(vec4(vec3(blurFactor), 1.0)); + } + if (debug == 3) { + diffuseColor = sRGBToLinear(texture2DProj(tDiffuse, my_vUv)); + } + if (debug == 4) { + diffuseColor = sRGBToLinear(texture2DProj(tDiffuseBlur, my_vUv)); + } + ` ) } get tDiffuse(): Texture | null { @@ -150,6 +183,18 @@ export class MeshReflectorMaterial extends MeshStandardMaterial { set depthScale(v: number) { this._depthScale.value = v } + get debug(): number { + return this._debug.value + } + set debug(v: number) { + this._debug.value = v + } + get depthToBlurRatioBias(): number { + return this._depthToBlurRatioBias.value + } + set depthToBlurRatioBias(v: number) { + this._depthToBlurRatioBias.value = v + } } export type MeshReflectorMaterialImpl = { @@ -163,4 +208,5 @@ export type MeshReflectorMaterialImpl = { minDepthThreshold: number maxDepthThreshold: number depthScale: number + depthToBlurRatioBias: number } & JSX.IntrinsicElements['meshStandardMaterial']