Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(reflector): adds debug prop and improves depth blurring #288

Merged
merged 12 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .storybook/Setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Canvas
colorManagement
shadowMap
camera={{ position: cameraPosition }}
camera={{ position: cameraPosition, fov: cameraFov }}
pixelRatio={window.devicePixelRatio}
{...restProps}
>
Expand Down
Binary file added .storybook/public/normal_floor.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .storybook/public/roughness_floor.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 67 additions & 17 deletions .storybook/stories/Reflector.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => <Setup cameraPosition={new Vector3(-2, 2, 6)}> {storyFn()}</Setup>],
decorators: [
(storyFn) => (
<Setup cameraFov={20} cameraPosition={new Vector3(-2, 2, 6)}>
{' '}
{storyFn()}
</Setup>
),
],
}

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<Mesh>(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 (
<>
<Reflector
position={[0, 0, 0]}
resolution={512}
resolution={1024}
args={[10, 10]}
mirror={0.5}
mirror={0.75}
mixBlur={10}
mixStrength={2}
rotation={[-Math.PI / 2, 0, Math.PI / 2]}
blur={[400, 100]}
minDepthThreshold={0.5}
maxDepthThreshold={1}
depthScale={1}
blur={blur || [0, 0]}
minDepthThreshold={0.8}
maxDepthThreshold={1.2}
depthScale={depthScale || 0}
depthToBlurRatioBias={0.2}
debug={0}
>
{(Material, props) => <Material color="#a0a0a0" metalness={0.5} normalScale={[1, 1]} {...props} />}
{(Material, props) => (
<Material
color="#a0a0a0"
metalness={0.5}
roughnessMap={roughness}
roughness={1}
normalMap={normal}
{...props}
/>
)}
</Reflector>

<Box position={[-2, 1, -1]} material-color="hotpink" material-wireframe />
<Box args={[2, 2, 2]} ref={$box} position={[0, 1, 0]} material-color="hotpink" />
<Box position={[2, 1, 1]} material-color="hotpink" material-wireframe />
<Box args={[2, 3, 0.2]} position={[0, 1.6, -3]}>
<meshPhysicalMaterial color="hotpink" />
</Box>
<TorusKnot args={[0.5, 0.2, 128, 32]} ref={$box} position={[0, 1, 0]}>
<meshPhysicalMaterial color="hotpink" />
</TorusKnot>
<spotLight intensity={1} position={[10, 6, 10]} penumbra={1} angle={0.3} />
</>
)
}

export const ReflectorSt = () => <ReflectorScene />
export const ReflectorSt = () => (
<React.Suspense fallback={null}>
<ReflectorScene blur={[500, 500]} depthScale={2} />
</React.Suspense>
)
ReflectorSt.storyName = 'Default'

export const ReflectorPlain = () => (
<React.Suspense fallback={null}>
<ReflectorScene />
</React.Suspense>
)
ReflectorPlain.storyName = 'Plain'

export const ReflectorBlur = () => (
<React.Suspense fallback={null}>
<ReflectorScene blur={[500, 500]} />
</React.Suspense>
)
ReflectorBlur.storyName = 'Blur'

export const ReflectorDepth = () => (
<React.Suspense fallback={null}>
<ReflectorScene depthScale={2} />
</React.Suspense>
)
ReflectorDepth.storyName = 'Depth'
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) => <Material {...props}>}
</Reflector>
Expand Down
22 changes: 21 additions & 1 deletion src/core/Reflector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export type ReflectorProps = Omit<JSX.IntrinsicElements['mesh'], 'args' | 'child
minDepthThreshold?: number
maxDepthThreshold?: number
depthScale?: number
depthToBlurRatioBias?: number
debug?: number
children: {
(
Component: React.ElementType<JSX.IntrinsicElements['meshReflectorMaterial']>,
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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,
Expand All @@ -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]
}, [
Expand All @@ -168,6 +186,8 @@ export function Reflector({
minDepthThreshold,
maxDepthThreshold,
depthScale,
depthToBlurRatioBias,
debug,
])

useFrame(() => {
Expand Down
25 changes: 23 additions & 2 deletions src/materials/BlurPass.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import {
Mesh,
BufferGeometry,
BufferAttribute,
Camera,
LinearFilter,
Scene,
WebGLRenderTarget,
WebGLRenderer,
Camera,
Vector2,
} from 'three'

import { ConvolutionMaterial } from './ConvolutionMaterial'
Expand All @@ -16,6 +17,10 @@ export interface BlurPassProps {
resolution: number
width?: number
height?: number
minDepthThreshold?: number
maxDepthThreshold?: number
depthScale?: number
depthToBlurRatioBias?: number
}

export class BlurPass {
Expand All @@ -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,
Expand All @@ -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()
Expand All @@ -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
Expand Down
45 changes: 40 additions & 5 deletions src/materials/ConvolutionMaterial.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <common>
#include <dithering_pars_fragment>
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 <dithering_fragment>
}`,
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,
Expand All @@ -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)
}
}
Loading