Skip to content

Commit

Permalink
feat: Add ShadowAlpha (#1729)
Browse files Browse the repository at this point in the history
* feat: Add ShadowAlpha

* fix: Add note about artifacts in ShadowAlpha
  • Loading branch information
FarazzShaikh authored Dec 11, 2023
1 parent 7dd1993 commit 6769989
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 0 deletions.
41 changes: 41 additions & 0 deletions .storybook/stories/ShadowAlpha.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as React from 'react'

import { Setup } from '../Setup'

import { useFrame } from '@react-three/fiber'
import { BufferGeometry, MeshStandardMaterial, type Mesh } from 'three'
import { Icosahedron, Plane, ShadowAlpha } from '../../src'

export default {
title: 'Misc/ShadowAlpha',
component: ShadowAlpha,
decorators: [(storyFn) => <Setup lights={false}> {storyFn()}</Setup>],
}

function ShadowAlphaScene() {
const mesh = React.useRef<Mesh<BufferGeometry, MeshStandardMaterial>>(null!)

useFrame(({ clock }) => {
const time = clock.elapsedTime
mesh.current.material.opacity = Math.sin(time * 2) * 0.5 + 0.5
})

return (
<>
<Icosahedron castShadow ref={mesh} args={[1, 2]} position-y={2}>
<meshStandardMaterial color="lightblue" transparent />
<ShadowAlpha />
</Icosahedron>

<Plane receiveShadow args={[4, 4]} rotation={[-Math.PI / 2, 0, 0]}>
<meshStandardMaterial color="white" />
</Plane>

<directionalLight castShadow position={[10, 40, 10]} />
<ambientLight intensity={0.5} />
</>
)
}

export const ShadowAlphaSt = () => <ShadowAlphaScene />
ShadowAlphaSt.storyName = 'Default'
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ The `native` route of the library **does not** export `Html` or `Loader`. The de
<li><a href="#useenvironment">useEnvironment</a></li>
<li><a href="#usematcaptexture">useMatcapTexture</a></li>
<li><a href="#usenormaltexture">useNormalTexture</a></li>
<li><a href="#shadowalpha">ShadowAlpha</a></li>
</ul>
</ul>
</td>
Expand Down Expand Up @@ -4767,3 +4768,21 @@ return (
...
)
```
#### ShadowAlpha
Makes an object's shadow respect its opacity and alphaMap.
```jsx
<mesh>
<geometry />
<material transparent opacity={0.5} />

<ShadowAlpha
opacity={undefined} // number. Override the opacity of the shadow.
alphaMap={undefined} // THREE.Texture. Override the alphaMap of the shadow
/>
</mesh>
```
> Note: This component uses Screendoor transparency using a dither pattern. This pattern is notacible when the camera gets close to the shadow.
122 changes: 122 additions & 0 deletions src/core/ShadowAlpha.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* Integration and compilation: Faraz Shaikh (https://twitter.com/CantBeFaraz)
*
* Based on:
* - https://gkjohnson.github.io/threejs-sandbox/screendoor-transparency/ by Garrett Johnson (https://github.com/gkjohnson)
*
* Note:
* - Must depreciate in favor of https://github.com/mrdoob/three.js/issues/10600 when it's ready.
*/

import { useFrame } from '@react-three/fiber'
import * as React from 'react'
import * as THREE from 'three'

interface ShadowAlphaProps {
opacity?: number
alphaMap?: THREE.Texture | boolean
}

export function ShadowAlpha({ opacity, alphaMap }: ShadowAlphaProps) {
const depthMaterialRef = React.useRef<THREE.MeshDepthMaterial>(null!)
const distanceMaterialRef = React.useRef<THREE.MeshDistanceMaterial>(null!)

const uShadowOpacity = React.useRef({
value: 1,
})

const uAlphaMap = React.useRef({
value: null,
})

const uHasAlphaMap = React.useRef({
value: false,
})

React.useLayoutEffect(() => {
depthMaterialRef.current.onBeforeCompile = distanceMaterialRef.current.onBeforeCompile = (shader) => {
// Need to get the "void main" line dynamically because the lines for
// MeshDistanceMaterial and MeshDepthMaterial are different 🤦‍♂️
const mainLineStart = shader.fragmentShader.indexOf('void main')
let mainLine = ''
let ch
let i = mainLineStart
while (ch !== '\n' && i < mainLineStart + 100) {
ch = shader.fragmentShader.charAt(i)
mainLine += ch
i++
}
mainLine = mainLine.trim()

shader.vertexShader = shader.vertexShader.replace(
'void main() {',
`
varying vec2 custom_vUv;
void main() {
custom_vUv = uv;
`
)

shader.fragmentShader = shader.fragmentShader.replace(
mainLine,
`
uniform float uShadowOpacity;
uniform sampler2D uAlphaMap;
uniform bool uHasAlphaMap;
varying vec2 custom_vUv;
float bayerDither2x2( vec2 v ) {
return mod( 3.0 * v.y + 2.0 * v.x, 4.0 );
}
float bayerDither4x4( vec2 v ) {
vec2 P1 = mod( v, 2.0 );
vec2 P2 = mod( floor( 0.5 * v ), 2.0 );
return 4.0 * bayerDither2x2( P1 ) + bayerDither2x2( P2 );
}
void main() {
float alpha =
uHasAlphaMap ?
uShadowOpacity * texture2D(uAlphaMap, custom_vUv).x
: uShadowOpacity;
if( ( bayerDither4x4( floor( mod( gl_FragCoord.xy, 4.0 ) ) ) ) / 16.0 >= alpha ) discard;
`
)

shader.uniforms['uShadowOpacity'] = uShadowOpacity.current
shader.uniforms['uAlphaMap'] = uAlphaMap.current
shader.uniforms['uHasAlphaMap'] = uHasAlphaMap.current
}
}, [])

useFrame(() => {
const parent = (depthMaterialRef.current as any).__r3f?.parent
if (parent) {
const parentMainMaterial = parent.material
if (parentMainMaterial) {
uShadowOpacity.current.value = opacity ?? parentMainMaterial.opacity

if (alphaMap === false) {
uAlphaMap.current.value = null
uHasAlphaMap.current.value = false
} else {
uAlphaMap.current.value = alphaMap || parentMainMaterial.alphaMap
uHasAlphaMap.current.value = !!uAlphaMap.current.value
}
}
}
})

return (
<>
<meshDepthMaterial ref={depthMaterialRef} attach="customDepthMaterial" depthPacking={THREE.RGBADepthPacking} />
<meshDistanceMaterial ref={distanceMaterialRef} attach="customDistanceMaterial" />
</>
)
}
1 change: 1 addition & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export * from './useEnvironment'
export * from './useMatcapTexture'
export * from './useNormalTexture'
export * from './Wireframe'
export * from './ShadowAlpha'

// Performance
export * from './Points'
Expand Down

1 comment on commit 6769989

@vercel
Copy link

@vercel vercel bot commented on 6769989 Dec 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.