├── src ├── index.css ├── App.css ├── Experience.jsx ├── main.jsx ├── mesh │ ├── Mesh.jsx │ └── useMaterial.jsx ├── App.jsx ├── ResizeHandler.js ├── LightEnvironment.jsx └── WebGPUCanvas.jsx ├── bun.lockb ├── vite.config.js ├── .gitignore ├── index.html ├── README.md ├── package.json ├── LICENSE ├── eslint.config.js └── public └── vite.svg /src/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mustache-dev/R3F-WebGPU-template/HEAD/bun.lockb -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | *{ 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /src/Experience.jsx: -------------------------------------------------------------------------------- 1 | import { LightEnvironment } from "./LightEnvironment"; 2 | 3 | import { Mesh } from "./mesh/Mesh"; 4 | 5 | export const Experience = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import App from './App.jsx' 5 | 6 | createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /src/mesh/Mesh.jsx: -------------------------------------------------------------------------------- 1 | import { useMaterial } from './useMaterial'; 2 | 3 | export const Mesh = () => { 4 | const { colorNode, key } = useMaterial(); 5 | return ( 6 | 7 | 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import './App.css' 2 | import { WebGPUCanvas } from './WebGPUCanvas'; 3 | import { Experience } from './Experience'; 4 | 5 | function App() { 6 | 7 | return ( 8 | <> 9 | 10 | 11 | 12 | 13 | 14 | ) 15 | } 16 | 17 | export default App 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # R3F WebGPU + TSL Template 2 | 3 | This is a template for creating Three.js WebGPU projects with React Three Fiber and TSL. 4 | 5 | ## Getting Started 6 | 7 | To get started, run the following commands: 8 | 9 | ```bash 10 | bun install 11 | bun run dev 12 | ``` 13 | 14 | ## Features 15 | 16 | - React Three Fiber 17 | - WebGPU 18 | - TSL 19 | - ESLint 20 | - Prettier 21 | - Bun 22 | - Vite 23 | 24 | ## License 25 | 26 | MIT 27 | 28 | -------------------------------------------------------------------------------- /src/mesh/useMaterial.jsx: -------------------------------------------------------------------------------- 1 | import { useFrame } from "@react-three/fiber"; 2 | import { 3 | uniform, 4 | float, 5 | vec4, 6 | } from "three/tsl"; 7 | 8 | export const useMaterial = () => { 9 | const uTime = uniform(float(0.0)); 10 | 11 | const finalColor = vec4(1.0, 1.0, 1.0, 1.0); 12 | 13 | useFrame((state, delta) => { 14 | uTime.value -= delta * 0.2; 15 | }); 16 | 17 | return { 18 | key: uTime.id, 19 | colorNode: finalColor, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /src/ResizeHandler.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | export function ResizeHandler({ quality, rendererRef }) { 4 | useEffect(() => { 5 | const handleResize = () => { 6 | if (rendererRef.current) { 7 | rendererRef.current.setSize(window.innerWidth, window.innerHeight); 8 | } 9 | }; 10 | 11 | window.addEventListener("resize", handleResize); 12 | 13 | // Cleanup 14 | return () => window.removeEventListener("resize", handleResize); 15 | }, [quality]); 16 | 17 | return null; 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gpu", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@react-three/drei": "^9.121.3", 14 | "@react-three/fiber": "^8.17.12", 15 | "react": "^18.3.1", 16 | "react-dom": "^18.3.1", 17 | "three": "^0.172.0" 18 | }, 19 | "devDependencies": { 20 | "@eslint/js": "^9.17.0", 21 | "@types/react": "^18.3.18", 22 | "@types/react-dom": "^18.3.5", 23 | "@vitejs/plugin-react-swc": "^3.5.0", 24 | "eslint": "^9.17.0", 25 | "eslint-plugin-react": "^7.37.2", 26 | "eslint-plugin-react-hooks": "^5.0.0", 27 | "eslint-plugin-react-refresh": "^0.4.16", 28 | "globals": "^15.14.0", 29 | "vite": "^6.0.5" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/LightEnvironment.jsx: -------------------------------------------------------------------------------- 1 | import { Environment } from "@react-three/drei"; 2 | import { OrbitControls } from "@react-three/drei"; 3 | 4 | export const LightEnvironment = () => { 5 | return ( 6 | <> 7 | 20 | 24 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Lunakepio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import react from 'eslint-plugin-react' 4 | import reactHooks from 'eslint-plugin-react-hooks' 5 | import reactRefresh from 'eslint-plugin-react-refresh' 6 | 7 | export default [ 8 | { ignores: ['dist'] }, 9 | { 10 | files: ['**/*.{js,jsx}'], 11 | languageOptions: { 12 | ecmaVersion: 2020, 13 | globals: globals.browser, 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | ecmaFeatures: { jsx: true }, 17 | sourceType: 'module', 18 | }, 19 | }, 20 | settings: { react: { version: '18.3' } }, 21 | plugins: { 22 | react, 23 | 'react-hooks': reactHooks, 24 | 'react-refresh': reactRefresh, 25 | }, 26 | rules: { 27 | ...js.configs.recommended.rules, 28 | ...react.configs.recommended.rules, 29 | ...react.configs['jsx-runtime'].rules, 30 | ...reactHooks.configs.recommended.rules, 31 | 'react/jsx-no-target-blank': 'off', 32 | 'react-refresh/only-export-components': [ 33 | 'warn', 34 | { allowConstantExport: true }, 35 | ], 36 | 'react/no-unknown-property': 'off', 37 | 'react/prop-types': 'off', 38 | }, 39 | }, 40 | ] 41 | -------------------------------------------------------------------------------- /src/WebGPUCanvas.jsx: -------------------------------------------------------------------------------- 1 | import { Canvas, extend } from "@react-three/fiber"; 2 | import { useRef, useState } from "react"; 3 | import * as THREE from "three/webgpu"; 4 | import { ResizeHandler } from "./ResizeHandler"; 5 | 6 | extend(THREE); 7 | 8 | export const WebGPUCanvas = ({ quality, children }) => { 9 | const rendererRef = useRef(); 10 | const [frameloop, setFrameloop] = useState("never"); 11 | return ( 12 | { 14 | state.setSize(window.innerWidth, window.innerHeight); 15 | }} 16 | frameloop={frameloop} 17 | dpr={quality === "default" ? 1 : [1, 1.5]} 18 | camera={{ 19 | position: [5, 0, 0], 20 | near: 0.1, 21 | far: 50, 22 | fov: 65, 23 | // zoom: 1, 24 | }} 25 | shadows={"variance"} 26 | gl={(canvas) => { 27 | const renderer = new THREE.WebGPURenderer({ 28 | canvas, 29 | powerPreference: "high-performance", 30 | antialias: false, 31 | alpha: false, 32 | stencil: false, 33 | }); 34 | 35 | // Initialize WebGPU and store renderer reference 36 | renderer.init().then(() => setFrameloop("always")); 37 | rendererRef.current = renderer; 38 | return renderer; 39 | }} 40 | > 41 | {children} 42 | 43 | 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------