1import { ExpoWebGLRenderingContext, GLView } from 'expo-gl'; 2import { Renderer, TextureLoader } from 'expo-three'; 3import * as React from 'react'; 4import { LayoutChangeEvent, PixelRatio, StyleSheet, View } from 'react-native'; 5import { BoxGeometry, Mesh, MeshBasicMaterial, PerspectiveCamera, Scene } from 'three'; 6import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'; 7import { GlitchPass } from 'three/examples/jsm/postprocessing/GlitchPass'; 8import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'; 9 10import TitledSwitch from '../../components/TitledSwitch'; 11 12export default function GLThreeComposer() { 13 const [isComposerEnabled, setComposerEnabled] = React.useState(false); 14 const [hasGL, setHasGL] = React.useState(false); 15 16 const animationFrameId = React.useRef(-1); 17 const gl = React.useRef<null | ExpoWebGLRenderingContext>(null); 18 const camera = React.useRef<null | PerspectiveCamera>(null); 19 const glitchPass = React.useRef<null | GlitchPass>(null); 20 const scene = React.useRef<null | Scene>(null); 21 const renderer = React.useRef<null | Renderer>(null); 22 const composer = React.useRef<null | EffectComposer>(null); 23 const cubes = React.useRef<{ angularVelocity: { x: number; y: number }; mesh: Mesh }[]>([]); 24 25 React.useEffect(() => { 26 return () => { 27 cancelAnimationFrame(animationFrameId.current); 28 animationFrameId.current = 0; 29 }; 30 }, []); 31 32 React.useEffect(() => { 33 if (glitchPass.current) glitchPass.current.enabled = isComposerEnabled; 34 }, [isComposerEnabled, glitchPass.current]); 35 36 React.useEffect(() => { 37 if (!gl.current || scene.current) { 38 return; 39 } 40 41 scene.current = new Scene(); 42 camera.current = new PerspectiveCamera( 43 75, 44 gl.current.drawingBufferWidth / gl.current.drawingBufferHeight, 45 0.1, 46 1000 47 ); 48 49 renderer.current = new Renderer({ gl: gl.current }); 50 renderer.current.setSize(gl.current.drawingBufferWidth, gl.current.drawingBufferHeight); 51 renderer.current.setClearColor(0xffffff); 52 53 composer.current = new EffectComposer(renderer.current); 54 composer.current.addPass(new RenderPass(scene.current, camera.current)); 55 glitchPass.current = new GlitchPass(); 56 glitchPass.current.enabled = false; 57 composer.current.addPass(glitchPass.current); 58 59 const geometry = new BoxGeometry(1, 1, 1); 60 const material = new MeshBasicMaterial({ 61 transparent: true, 62 map: new TextureLoader().load(require('../../../assets/images/nikki.png')), 63 }); 64 65 cubes.current = Array(24) 66 .fill(0) 67 .map(() => { 68 const mesh = new Mesh(geometry, material); 69 scene.current!.add(mesh); 70 mesh.position.x = 3 - 6 * Math.random(); 71 mesh.position.y = 3 - 6 * Math.random(); 72 mesh.position.z = -5 * Math.random(); 73 const angularVelocity = { 74 x: 0.1 * Math.random(), 75 y: 0.1 * Math.random(), 76 }; 77 return { mesh, angularVelocity }; 78 }); 79 80 camera.current.position.z = 3; 81 82 const animate = () => { 83 animationFrameId.current = requestAnimationFrame(animate); 84 cubes.current.forEach(({ mesh, angularVelocity }) => { 85 mesh.rotation.x += angularVelocity.x; 86 mesh.rotation.y += angularVelocity.y; 87 }); 88 89 if (composer.current) composer.current.render(); 90 if (gl.current) { 91 gl.current.endFrameEXP(); 92 } 93 }; 94 95 animate(); 96 renderer.current.render(scene.current, camera.current); 97 gl.current.endFrameEXP(); 98 }, [gl.current, hasGL]); 99 100 const onLayout = React.useCallback(({ nativeEvent: { layout } }: LayoutChangeEvent) => { 101 if (camera.current) { 102 camera.current.aspect = layout.width / layout.height; 103 camera.current.updateProjectionMatrix(); 104 } 105 if (renderer.current) { 106 const scale = PixelRatio.get(); 107 renderer.current.setSize(layout.width * scale, layout.height * scale); 108 } 109 if (composer.current) { 110 composer.current.setSize(layout.width, layout.height); 111 } 112 }, []); 113 114 return ( 115 <View style={styles.flex}> 116 <GLView 117 style={styles.flex} 118 onLayout={onLayout} 119 onContextCreate={(context: ExpoWebGLRenderingContext) => { 120 gl.current = context; 121 setHasGL(true); 122 }} 123 /> 124 <TitledSwitch 125 style={{ position: 'absolute', justifyContent: 'flex-end', top: 0, left: 8, right: 8 }} 126 title="Use Composer" 127 value={isComposerEnabled} 128 setValue={setComposerEnabled} 129 /> 130 </View> 131 ); 132} 133 134GLThreeComposer.title = 'three.js glitch and film effects'; 135 136const styles = StyleSheet.create({ 137 flex: { 138 flex: 1, 139 }, 140}); 141