Repository: ektogamat/r3f-webgpu-starter Branch: main Commit: c87883c66090 Files: 24 Total size: 34.7 KB Directory structure: gitextract_zhgr58d5/ ├── .gitignore ├── README.md ├── package.json ├── public/ │ ├── darth-transformed.glb │ ├── favicon/ │ │ ├── about.txt │ │ └── site.webmanifest │ ├── hall-transformed.glb │ ├── index.html │ ├── naboo_royal_starship-transformed.glb │ └── probe-transformed.glb └── src/ ├── App.js ├── components/ │ ├── Darth.js │ ├── Hall.js │ ├── JetEngineMaterial.js │ ├── Light_Environment.js │ ├── ManciniCanvas.js │ ├── Overlay.js │ ├── Probe.js │ ├── ResizeHandler.js │ ├── Royal.js │ ├── VaderScene.js │ └── WebGPUPostProcessing.js ├── index.js └── styles.css ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* .pnpm-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Snowpack dependency directory (https://snowpack.dev/) web_modules/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional stylelint cache .stylelintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variable files .env .env.development.local .env.test.local .env.production.local .env.local # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache # Next.js build output .next out # Nuxt.js build / generate output .nuxt dist # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and not Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # vuepress v2.x temp and cache directory .temp .cache # Docusaurus cache and generated files .docusaurus # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port # Stores VSCode versions used for testing VSCode extensions .vscode-test # yarn v2 .yarn/cache .yarn/unplugged .yarn/build-state.yml .yarn/install-state.gz .pnp.* .vercel ================================================ FILE: README.md ================================================ # React Three Fiber WebGPU Post Processing

by Anderson Mancini

[![screenshot](https://r3f-webgpu-post-processing.vercel.app/social.jpg)](https://r3f-webgpu-post-processing.vercel.app/) A very simple scene to demonstrate how to integrate Threejs WebGPU with React Three Fiber using Post Processing effects. [See the demo here](https://r3f-webgpu-post-processing.vercel.app/) ### Getting Started using this demo project Download and install Node.js on your computer (https://nodejs.org/en/download/). Then, open VSCODE, drag the project folder to it. Open VSCODE terminal and install dependencies (you need to do this only in the first time) ```shell npm install ``` Run this command in your terminal to open a local server at localhost:3000 ```shell npm run start ``` ### Can you leave a star please? I genuinely appreciate your support! If you're willing to show your appreciation, you can give me a star on GitHub 🎉 or consider buying a coffee to support my development at https://www.buymeacoffee.com/andersonmancini. The funds received will be utilized to create more valuable content about Three.js and invest in acquiring new courses. Thank you for your consideration! ================================================ FILE: package.json ================================================ { "name": "basic-demo", "version": "1.0.0", "description": "Shows how to form self-contained components with their own state and user interaction.", "keywords": [ "interaction", "pointer-events" ], "main": "src/index.js", "dependencies": { "@react-three/drei": "^9.99.0", "@react-three/fiber": "^8.15.0", "@types/three": "^0.177.0", "maath": "0.10.8", "react": "18.2.0", "react-dom": "18.2.0", "react-scripts": "5.0.1", "three": "^0.177.0", "three-mesh-bvh": "^0.8.0" }, "scripts": { "start": "set HOST=0.0.0.0 && react-scripts start", "build": "GENERATE_SOURCEMAP=false react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject", "deploy": "vercel --prod" }, "browserslist": [ ">1%", "not dead", "not ie <= 11", "not op_mini all" ] } ================================================ FILE: public/favicon/about.txt ================================================ This favicon was generated using the following graphics from Twitter Twemoji: - Graphics Title: 1f441-200d-1f5e8.svg - Graphics Author: Copyright 2020 Twitter, Inc and other contributors (https://github.com/twitter/twemoji) - Graphics Source: https://github.com/twitter/twemoji/blob/master/assets/svg/1f441-200d-1f5e8.svg - Graphics License: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/) ================================================ FILE: public/favicon/site.webmanifest ================================================ {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} ================================================ FILE: public/index.html ================================================ WebGPU Post Processing Example - By Anderson Mancini
================================================ FILE: src/App.js ================================================ import { useState } from "react"; import { Loader } from "@react-three/drei"; import { WebGPUPostProcessing } from "./components/WebGPUPostProcessing"; import { Hall } from "./components/Hall"; import { Overlay } from "./components/Overlay"; import RoyalNaboo from "./components/Royal"; import { Light_Environment } from "./components/Light_Environment"; import { VaderScene } from "./components/VaderScene"; import { ManciniCanvas } from "./components/ManciniCanvas"; export default function App() { const [currentScene, setCurrentScene] = useState("vader"); const [quality, setQuality] = useState("default"); const [isPostProcessingEnabled, setIsPostProcessingEnabled] = useState(true); // Disable frameloop by default, waiting for WebGPU to be ready return ( <> {isPostProcessingEnabled && ( )} ); } ================================================ FILE: src/components/Darth.js ================================================ /* Auto-generated by: https://github.com/pmndrs/gltfjsx Author: AFTONBLADET (https://sketchfab.com/wallander) License: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/) Source: https://sketchfab.com/3d-models/darth-vader-by-makeamo-5b3371f4789c41eeaa691c9a3dfe1a96 Title: Darth Vader by Makeamo */ import React, { useRef } from "react"; import { useGLTF } from "@react-three/drei"; export function Darth(props) { const { nodes, materials } = useGLTF("/darth-transformed.glb"); materials.Sabel_svart.emissiveIntensity = 20; materials.darthvader_VaderConsolesFlickermat.roughness = 0.2; materials.darthvader_VaderCapemat.roughness = 0.2; materials.darthvader_VaderCapemat.metalness = 0.7; materials.darthvader_VaderHelmetRimmat.roughness = 0.3; materials.darthvader_VaderHelmetRimmat.metalness = 0.6; materials.darthvader_VaderBodyArmourmat.roughness = 0.2; materials.darthvader_VaderBodyArmourmat.metalness = 0.9; return ( ); } useGLTF.preload("/darth-transformed.glb"); ================================================ FILE: src/components/Hall.js ================================================ /* Auto-generated by: https://github.com/pmndrs/gltfjsx */ import React, { useRef } from "react"; import { useGLTF } from "@react-three/drei"; import { FrontSide } from "three"; export function Hall(props) { const { nodes, materials } = useGLTF("/hall-transformed.glb"); materials.VenatorV3_SmallDoor_WallLight.emissiveIntensity = 2.6; materials.Venator_Floor.color.set("black"); materials.Venator_Floor.roughness = 0.95; materials.Venator_Floor.normalMap = null; // materials.Venator_Floor.roughnessMap = null; // materials.Venator_Floor.roughnessMap = null; // materials.Venator_Floor.metalnessMap = null; // materials.Venator_Floor.emissiveIntensity = 0; materials.Venator_Floor.metalness = 1; materials.Venator_Floor.metalnessMap = null; materials.VenatorV3_WallPanels.color.set("grey"); materials.VenatorV3_WallPanels.roughness = 0.8; materials.VenatorV3_WallPanels.metalness = 0.5; materials.VenatorV3_WallPanels.side = FrontSide; materials.VenatorV3_LargeDoor.side = FrontSide; materials.VenatorV3_SmallDoor_WallLight.side = FrontSide; return ( {/* */} ); } useGLTF.preload("/hall-transformed.glb"); ================================================ FILE: src/components/JetEngineMaterial.js ================================================ import { useFrame } from "@react-three/fiber"; import { uniform, float, vec2, sin, cos, uv, mod, div, sub, length, pow, abs, vec3, add, mul, } from "three/tsl"; export function useJetEngineMaterial() { // Create uniform and shader calculations const uTime = uniform(float(0.0)); const TAU = float(6.28318530718); const MAX_ITER = float(8); const inten = float(0.007); // Get UV coordinates and time const currentUV = uv(); const currentTime = mul(uTime, float(2.5)); // Rotate UV coordinates const angle = float(3.147); const s = sin(angle); const c = cos(angle); // Create rotation matrix manually const rotatedX = add(mul(currentUV.x, c), mul(currentUV.y, s)); const rotatedY = sub(mul(currentUV.x, s), mul(currentUV.y, c)); const uvRotated = add(vec2(rotatedX, rotatedY), mul(uTime, float(2.4))); // Calculate base coordinates const p = sub(mod(mul(uvRotated, TAU), TAU), float(256.0)); // Initialize accumulator let accumulator = float(0.9); // Unroll the loop for (let i = 0; i < 8; i++) { const t = mul(currentTime, sub(float(1.0), div(float(3.5), float(i + 1)))); const px = add(cos(sub(t, p.x)), sin(add(t, p.y))); const py = add(sin(sub(t, p.y)), cos(add(t, p.x))); const iVec = add(p, vec2(px, py)); const lenVec = vec2( div(p.x, div(sin(add(iVec.x, t)), inten)), div(p.y, div(cos(add(iVec.y, t)), inten)) ); accumulator = add(accumulator, div(float(1.1), length(lenVec))); } // Final color calculations accumulator = div(accumulator, float(MAX_ITER)); accumulator = sub(float(1.1), pow(accumulator, float(1.5))); const engineColor = pow(abs(accumulator), float(30.0)); const finalColor = add( vec3(engineColor).mul(vec3(0, 0.5, 1.0)), vec3(0.0, 0.0, 0.0) ).mul(float(25.0)); // Update time in animation frame useFrame((state, delta) => { uTime.value += delta * 0.5; }); return { key: uTime.id, colorNode: finalColor, }; } ================================================ FILE: src/components/Light_Environment.js ================================================ import { Environment } from "@react-three/drei"; import { OrbitControls } from "@react-three/drei"; export function Light_Environment() { return ( <> ); } ================================================ FILE: src/components/ManciniCanvas.js ================================================ import { Canvas, extend } from "@react-three/fiber"; import { useRef, useState } from "react"; import * as THREE from "three/webgpu"; import { ResizeHandler } from "./ResizeHandler"; extend(THREE); export function ManciniCanvas({ quality, children }) { const rendererRef = useRef(); const [frameloop, setFrameloop] = useState("never"); return ( { state.setSize(window.innerWidth, window.innerHeight); }} frameloop={frameloop} dpr={quality === "default" ? 1 : [1, 1.5]} camera={{ position: [18.6, -0.6, 0], near: 0.1, far: 50, fov: 65, // zoom: 1, }} shadows={"variance"} gl={(canvas) => { const renderer = new THREE.WebGPURenderer({ canvas, powerPreference: "high-performance", antialias: false, alpha: false, stencil: false, }); // Initialize WebGPU and store renderer reference renderer.init().then(() => setFrameloop("always")); rendererRef.current = renderer; return renderer; }} > {children} ); } ================================================ FILE: src/components/Overlay.js ================================================ export function Overlay({ isPostProcessingEnabled, setIsPostProcessingEnabled, currentScene, setCurrentScene, quality, setQuality, }) { return (

R3F WebGPU

This is a demo of React Three Fiber using post processing with threejs and WebGPU, featuring Screen Space Reflections.

Created by Anderson Mancini

); } const SvgIcon = (props) => ( ); ================================================ FILE: src/components/Probe.js ================================================ import React, { useRef } from "react"; import { useGLTF } from "@react-three/drei"; export function Probe(props) { const { nodes, materials } = useGLTF("/probe-transformed.glb"); const lightMaterial = materials.light.clone(); lightMaterial.emissiveIntensity = 10; lightMaterial.emissive.set(props.color); return ( ); } useGLTF.preload("/probe-transformed.glb"); ================================================ FILE: src/components/ResizeHandler.js ================================================ import { useEffect } from "react"; export function ResizeHandler({ quality, rendererRef }) { useEffect(() => { const handleResize = () => { if (rendererRef.current) { rendererRef.current.setSize(window.innerWidth, window.innerHeight); } }; window.addEventListener("resize", handleResize); // Cleanup return () => window.removeEventListener("resize", handleResize); }, [quality]); return null; } ================================================ FILE: src/components/Royal.js ================================================ /* Auto-generated by: https://github.com/pmndrs/gltfjsx Command: npx gltfjsx@6.1.4 naboo_royal_starship.glb --transform --simplify Author: hobbit84 (https://sketchfab.com/hobbit84) License: CC-BY-NC-4.0 (http://creativecommons.org/licenses/by-nc/4.0/) Source: https://sketchfab.com/3d-models/naboo-royal-starship-j-type-327-nubian-f631077977754b5591298ecfa201380b Title: Naboo Royal Starship (J-type 327 Nubian) */ import React from "react"; import { Float, useGLTF } from "@react-three/drei"; import { AdditiveBlending, DoubleSide } from "three"; import { useJetEngineMaterial } from "./JetEngineMaterial"; export default function RoyalNaboo(props) { const { nodes, materials } = useGLTF("/naboo_royal_starship-transformed.glb"); const { key, colorNode } = useJetEngineMaterial(); materials["blinn1.002"].roughness = 0.15; materials["blinn1.002"].metalness = 1; materials["blinn1.002"].roughnessMap = null; return ( ); } useGLTF.preload("/naboo_royal_starship-transformed.glb"); ================================================ FILE: src/components/VaderScene.js ================================================ import { Float } from "@react-three/drei"; import { Probe } from "./Probe"; import { Darth } from "./Darth"; export function VaderScene() { return ( <> ); } ================================================ FILE: src/components/WebGPUPostProcessing.js ================================================ import * as THREE from "three/webgpu"; import { pass, mrt, output, transformedNormalView, metalness, blendColor, depth, emissive, } from "three/tsl"; import { bloom } from "three/addons/tsl/display/BloomNode.js"; import { ssr } from "three/addons/tsl/display/SSRNode.js"; import { smaa } from "three/addons/tsl/display/SMAANode.js"; import { useThree, useFrame } from "@react-three/fiber"; import { useEffect, useRef } from "react"; export function WebGPUPostProcessing({ strength = 2.5, radius = 0.5, quality = "default", }) { const { gl: renderer, scene, camera, size } = useThree(); const postProcessingRef = useRef(null); useEffect(() => { if (!renderer || !scene || !camera) return; // Create post-processing setup with specific filters const scenePass = pass(scene, camera, { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, }); // Setup Multiple Render Targets (MRT) scenePass.setMRT( mrt({ output: output, normal: transformedNormalView, metalness: metalness, emissive: emissive, }) ); // Get texture nodes const scenePassColor = scenePass.getTextureNode("output"); const scenePassNormal = scenePass.getTextureNode("normal"); const scenePassDepth = scenePass.getTextureNode("depth"); const scenePassMetalness = scenePass.getTextureNode("metalness"); const scenePassEmissive = scenePass.getTextureNode("emissive"); // Create SSR pass const ssrPass = ssr( scenePassColor, scenePassDepth, scenePassNormal, scenePassMetalness, camera ); ssrPass.resolutionScale = 0.65; ssrPass.maxDistance.value = 0.65; ssrPass.opacity.value = 0.85; ssrPass.thickness.value = 0.015; // Create bloom pass const bloomPass = bloom(scenePassEmissive, strength, radius, 0.6); // Blend SSR over beauty with SMAA const outputNode = smaa(blendColor(scenePassColor.add(bloomPass), ssrPass)); // Setup post-processing const postProcessing = new THREE.PostProcessing(renderer); postProcessing.outputNode = outputNode; postProcessingRef.current = postProcessing; // Handle window resize if (postProcessingRef.current.setSize) { postProcessingRef.current.setSize(size.width, size.height); postProcessingRef.current.needsUpdate = true; } return () => { postProcessingRef.current = null; }; }, [renderer, scene, camera, size, strength, radius, quality]); useFrame(({ gl, scene, camera }) => { if (postProcessingRef.current) { gl.clear(); postProcessingRef.current.render(); } }, 1); return null; } ================================================ FILE: src/index.js ================================================ import { createRoot } from 'react-dom/client' import './styles.css' import App from './App' createRoot(window.root).render() ================================================ FILE: src/styles.css ================================================ @import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"); :root { --bg-background: #111827; --clr-card: #1f2937; --clr-1: #6420aa; --clr-2: #ff3ea5; --clr-3: #ff7ed4; } * { box-sizing: border-box; } html, body, canvas { width: 100%; height: 100%; margin: 0; padding: 0; font-family: "Inter", sans-serif; overflow: hidden; touch-action: none; } body { background: #020202; } .overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000; pointer-events: none; display: flex; justify-content: center; align-items: flex-start; } header { background-image: linear-gradient( to bottom, #020202, rgba(0, 0, 0, 0.775), #02020200 ); width: 100%; height: 170px; display: flex; flex-direction: column; justify-content: center; align-items: center; } header h1 { margin: 0; padding: 0; color: white; font-size: 3rem; font-weight: 300; letter-spacing: -0.04em; text-transform: uppercase; text-align: center; } header h1 span { font-weight: 900; } header p { color: white; font-size: 0.8rem; font-weight: 200; letter-spacing: -0.04em; text-align: center; margin-top: 0.5rem; padding: 0; max-width: 500px; } footer { width: 100%; height: 110px; display: flex; justify-content: center; align-items: center; position: absolute; bottom: 0; } .footer-buttons { display: flex; gap: 2rem; } @property --gradient-angle { syntax: ""; initial-value: 90deg; inherits: false; } footer button { color: white; padding: 1rem 1.3rem; border-radius: 0.5rem; box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); font-size: 0.45rem; text-transform: uppercase; letter-spacing: 0.06em; font-weight: 300; pointer-events: all; cursor: pointer; position: relative; cursor: pointer; border: none; border-radius: 50px; background-color: var(--clr-card); transition: all 0.3s ease; } footer button:hover { background: rgb(65, 60, 79); color: white; } footer button:hover:after { transform: scale(1.5); filter: blur(2rem); transition: all 0.3s ease; } footer button::after, footer button::before { content: " "; position: absolute; z-index: -1; inset: -0.06rem; background: conic-gradient( from var(--gradient-angle), var(--clr-card), var(--clr-1), var(--clr-2), var(--clr-3), var(--clr-2), var(--clr-1), var(--clr-card) ); border-radius: inherit; animation: rotate 2.5s ease-in-out infinite; transition: all 0.3s ease; } footer button.toggle::before { content: " "; position: absolute; z-index: -1; inset: -0.06rem; background: conic-gradient( from var(--gradient-angle), var(--clr-card), var(--clr-1), var(--clr-2), var(--clr-3), var(--clr-2), var(--clr-1), var(--clr-card) ); border-radius: inherit; animation: rotate2 3.5s ease-in-out infinite; transition: all 0.3s ease; } footer button::after { filter: blur(3rem); } @keyframes rotate { 0% { --gradient-angle: 0deg; } 100% { --gradient-angle: 360deg; } } @keyframes rotate2 { 0% { --gradient-angle: 0deg; } 50% { --gradient-angle: 120deg; } 100% { --gradient-angle: 360deg; } } .footer-text { color: white; font-size: 0.8rem; font-weight: 200; letter-spacing: -0.04em; text-align: center; padding: 0; position: absolute; bottom: 20px; left: 20px; width: max-content; opacity: 0.3; } .footer-text a { color: white; font-weight: 400; text-decoration: none; pointer-events: all; } .download-button { position: fixed; bottom: 20px; right: 20px; background-color: transparent; border: 1px solid white; border-radius: 50px; padding: 10px 10px; text-align: center; cursor: pointer; z-index: 1000; pointer-events: all; display: flex; opacity: 0.3; } .download-button img { width: 20px; height: 20px; vertical-align: middle; } .download-button:hover { background-color: #ffffff7b; } /* Mobile */ @media (max-width: 768px) { header h1 { font-size: 2.5rem; } header p { font-size: 0.9rem; max-width: 300px; } .download-button { display: none; } .footer-buttons { gap: 0.5rem; } footer { height: 150px; } footer button { padding: 0.9rem 1.3rem; font-size: 0.5rem; } footer button.toggle-quality { display: none; } .footer-text { font-size: 0.6rem; bottom: 10px; left: 50%; transform: translateX(-50%); } }