├── public
├── robots.txt
├── dog.glb
├── duck.glb
├── icons
│ ├── share.png
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── apple-touch-icon.png
│ ├── android-icon-192x192.png
│ ├── android-icon-512x512.png
│ └── safari-pinned-tab.svg
├── img
│ ├── scores
│ │ ├── lighthouse.md
│ │ └── lighthouse.svg
│ └── logo.svg
└── manifest.json
├── .eslintignore
├── src
├── helpers
│ ├── global.js
│ └── components
│ │ └── Three.jsx
├── templates
│ ├── Shader
│ │ ├── glsl
│ │ │ ├── shader.vert
│ │ │ └── shader.frag
│ │ └── Shader.jsx
│ ├── Scroll.jsx
│ └── hooks
│ │ └── usePostprocess.jsx
└── components
│ ├── canvas
│ ├── Scene.jsx
│ ├── View.jsx
│ └── Examples.jsx
│ └── dom
│ └── Layout.jsx
├── .prettierignore
├── postcss.config.js
├── jsconfig.json
├── sandbox.config.json
├── .editorconfig
├── .eslintrc
├── .vscode
└── settings.json
├── app
├── global.css
├── layout.jsx
├── blob
│ └── page.jsx
├── head.jsx
└── page.jsx
├── tailwind.config.js
├── .prettierrc
├── .gitignore
├── LICENSE
├── package.json
├── next.config.js
└── README.md
/public/robots.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | .next
2 | dist
3 | node_modules/
4 |
--------------------------------------------------------------------------------
/public/dog.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmndrs/react-three-next/HEAD/public/dog.glb
--------------------------------------------------------------------------------
/public/duck.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmndrs/react-three-next/HEAD/public/duck.glb
--------------------------------------------------------------------------------
/src/helpers/global.js:
--------------------------------------------------------------------------------
1 | import tunnel from 'tunnel-rat'
2 |
3 | export const r3f = tunnel()
4 |
--------------------------------------------------------------------------------
/public/icons/share.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmndrs/react-three-next/HEAD/public/icons/share.png
--------------------------------------------------------------------------------
/public/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmndrs/react-three-next/HEAD/public/icons/favicon.ico
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | build/*
2 | dist/*
3 | public/*
4 | .next/*
5 | node_modules/*
6 | package.json
7 | *.log
8 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmndrs/react-three-next/HEAD/public/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmndrs/react-three-next/HEAD/public/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmndrs/react-three-next/HEAD/public/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/icons/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmndrs/react-three-next/HEAD/public/icons/android-icon-192x192.png
--------------------------------------------------------------------------------
/public/icons/android-icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pmndrs/react-three-next/HEAD/public/icons/android-icon-512x512.png
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["app/*", "src/*"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/public/img/scores/lighthouse.md:
--------------------------------------------------------------------------------
1 | npm install -g lighthouse-badges && lighthouse-badges --urls http://r3f-next-starter.vercel.app/ -o public/img/scores
2 |
--------------------------------------------------------------------------------
/src/templates/Shader/glsl/shader.vert:
--------------------------------------------------------------------------------
1 | varying vec2 vUv;
2 | void main() {
3 | vUv = uv;
4 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
5 | }
6 |
--------------------------------------------------------------------------------
/sandbox.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "infiniteLoopProtection": true,
3 | "hardReloadOnChange": false,
4 | "view": "browser",
5 | "container": {
6 | "node": "12"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/helpers/components/Three.jsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { r3f } from '@/helpers/global'
4 |
5 | export const Three = ({ children }) => {
6 | return {children}
7 | }
8 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | indent_style = space
7 | indent_size = 2
8 |
9 | [{*.json,.*.yml}]
10 | indent_style = space
11 | indent_size = 2
--------------------------------------------------------------------------------
/src/templates/Shader/glsl/shader.frag:
--------------------------------------------------------------------------------
1 | uniform float time;
2 | uniform vec3 color;
3 | varying vec2 vUv;
4 | #pragma glslify: random = require(glsl-random)
5 |
6 | void main() {
7 | gl_FragColor.rgba = vec4(color + sin(time) * 0.2, 1.0);
8 | }
9 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next", "prettier", "plugin:tailwindcss/recommended"],
3 | "rules": {
4 | "import/prefer-default-export": "off",
5 | "no-console": "warn",
6 | "no-var": "error",
7 | "no-html-link-for-pages": "off"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnPaste": true,
3 | "editor.formatOnSave": true,
4 | "editor.codeActionsOnSave": {
5 | "source.fixAll": "explicit"
6 | },
7 | "eslint.validate": ["javascript"],
8 | "eslint.workingDirectories": [{ "mode": "auto" }]
9 | }
10 |
--------------------------------------------------------------------------------
/app/global.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | * {
7 | box-sizing: border-box;
8 | }
9 |
10 | html,
11 | body,
12 | #root {
13 | width: 100%;
14 | height: 100%;
15 | margin: 0;
16 | padding: 0;
17 | overflow: hidden;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | content: ['./app/**/*.{js,ts,jsx,tsx}', './src/**/*.{js,ts,jsx,tsx}'], // remove unused styles in production
4 | darkMode: 'media', // or 'media' or 'class'
5 | theme: {
6 | extend: {},
7 | },
8 | variants: {
9 | extend: {},
10 | },
11 | plugins: [],
12 | }
13 |
--------------------------------------------------------------------------------
/public/img/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "bracketSpacing": true,
4 | "embeddedLanguageFormatting": "auto",
5 | "htmlWhitespaceSensitivity": "css",
6 | "insertPragma": false,
7 | "jsxSingleQuote": true,
8 | "printWidth": 120,
9 | "proseWrap": "preserve",
10 | "quoteProps": "as-needed",
11 | "requirePragma": false,
12 | "semi": false,
13 | "singleQuote": true,
14 | "tabWidth": 2,
15 | "trailingComma": "all",
16 | "useTabs": false,
17 | "vueIndentScriptAndStyle": false
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/canvas/Scene.jsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Canvas } from '@react-three/fiber'
4 | import { Preload } from '@react-three/drei'
5 | import { r3f } from '@/helpers/global'
6 | import * as THREE from 'three'
7 |
8 | export default function Scene({ ...props }) {
9 | // Everything defined in here will persist between route changes, only children are swapped
10 | return (
11 | (state.gl.toneMapping = THREE.AgXToneMapping)}
13 | >
14 | {/* @ts-ignore */}
15 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 |
4 | # testing
5 | /coverage
6 |
7 | # next.js
8 | /.next/
9 | /out/
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | *.pem
17 |
18 | # debug
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
23 | # local env files
24 | .env.local
25 | .env.development.local
26 | .env.test.local
27 | .env.production.local
28 |
29 | # vercel
30 | .vercel
31 |
32 | # next-pwa
33 | /public/workbox-*.js
34 | /public/sw.js
35 |
36 | # prefer yarn than npm
37 | package-lock.json
38 |
39 | report.*.json
40 |
41 | # .vscode Debugging
42 | .vscode/*-debug-profile
43 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "R3F Next Starter",
3 | "short_name": "R3F Next Starter",
4 | "icons": [
5 | {
6 | "src": "/icons/apple-touch-icon.png",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon",
9 | "purpose": "any maskable"
10 | },
11 | {
12 | "src": "/icons/android-icon-192x192.png",
13 | "sizes": "192x192",
14 | "type": "image/png",
15 | "purpose": "any maskable"
16 | },
17 | {
18 | "src": "/icons/android-icon-512x512.png",
19 | "sizes": "512x512",
20 | "type": "image/png",
21 | "purpose": "any maskable"
22 | }
23 | ],
24 | "start_url": "/",
25 | "scope": "/",
26 | "display": "standalone",
27 | "theme_color": "#000000",
28 | "background_color": "#ffffff"
29 | }
30 |
--------------------------------------------------------------------------------
/app/layout.jsx:
--------------------------------------------------------------------------------
1 | import { Layout } from '@/components/dom/Layout'
2 | import '@/global.css'
3 |
4 | export const metadata = {
5 | title: 'Next.js + Three.js',
6 | description: 'A minimal starter for Nextjs + React-three-fiber and Threejs.',
7 | }
8 |
9 | export default function RootLayout({ children }) {
10 | return (
11 |
12 | {/*
13 |
18 | {/* To avoid FOUT with styled-components wrap Layout with StyledComponentsRegistry https://beta.nextjs.org/docs/styling/css-in-js#styled-components */}
19 | {children}
20 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/dom/Layout.jsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useRef } from 'react'
4 | import dynamic from 'next/dynamic'
5 | const Scene = dynamic(() => import('@/components/canvas/Scene'), { ssr: false })
6 |
7 | const Layout = ({ children }) => {
8 | const ref = useRef()
9 |
10 | return (
11 |
21 | {children}
22 |
34 |
35 | )
36 | }
37 |
38 | export { Layout }
39 |
--------------------------------------------------------------------------------
/src/templates/Shader/Shader.jsx:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 | import * as THREE from 'three'
3 | import { extend, useFrame } from '@react-three/fiber'
4 | import { shaderMaterial } from '@react-three/drei'
5 | import vertex from './glsl/shader.vert'
6 | import fragment from './glsl/shader.frag'
7 | import { forwardRef, useImperativeHandle, useRef } from 'react'
8 |
9 | const ShaderImpl = shaderMaterial(
10 | {
11 | time: 0,
12 | color: new THREE.Color(0.05, 0.0, 0.025),
13 | },
14 | vertex,
15 | fragment,
16 | )
17 |
18 | extend({ ShaderImpl })
19 |
20 | // eslint-disable-next-line react/display-name
21 | const Shader = forwardRef(({ children, ...props }, ref) => {
22 | const localRef = useRef()
23 |
24 | useImperativeHandle(ref, () => localRef.current)
25 |
26 | useFrame((_, delta) => (localRef.current.time += delta))
27 | return
28 | })
29 |
30 | export default Shader
31 |
--------------------------------------------------------------------------------
/public/icons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/public/img/scores/lighthouse.svg:
--------------------------------------------------------------------------------
1 | lighthouse: 94% lighthouse lighthouse 94% 94%
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2021 Scott Chacon and others
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/src/components/canvas/View.jsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { forwardRef, Suspense, useImperativeHandle, useRef } from 'react'
4 | import { OrbitControls, PerspectiveCamera, View as ViewImpl } from '@react-three/drei'
5 | import { Three } from '@/helpers/components/Three'
6 |
7 | export const Common = ({ color }) => (
8 |
9 | {color && }
10 |
11 |
12 |
13 |
14 |
15 | )
16 |
17 | const View = forwardRef(({ children, orbit, ...props }, ref) => {
18 | const localRef = useRef(null)
19 | useImperativeHandle(ref, () => localRef.current)
20 |
21 | return (
22 | <>
23 |
24 |
25 |
26 | {children}
27 | {orbit && }
28 |
29 |
30 | >
31 | )
32 | })
33 | View.displayName = 'View'
34 |
35 | export { View }
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-three-next",
3 | "version": "2.0.0",
4 | "authors": [
5 | "Renaud ROHLINGER "
6 | ],
7 | "license": "MIT",
8 | "private": true,
9 | "engines": {
10 | "node": ">=14"
11 | },
12 | "scripts": {
13 | "lint": "next lint --fix --dir app",
14 | "dev": "next dev",
15 | "build": "next build",
16 | "analyze": "ANALYZE=true next build",
17 | "start": "next start"
18 | },
19 | "dependencies": {
20 | "@ducanh2912/next-pwa": "^10.0.0",
21 | "@react-three/drei": "^9.92.7",
22 | "@react-three/fiber": "^8.15.12",
23 | "glsl-random": "^0.0.5",
24 | "next": "^14.0.4",
25 | "react": "^18.2.0",
26 | "react-dom": "^18.2.0",
27 | "three": "^0.160.0",
28 | "three-stdlib": "^2.28.9",
29 | "tunnel-rat": "^0.1.2"
30 | },
31 | "devDependencies": {
32 | "@next/bundle-analyzer": "^14.0.4",
33 | "autoprefixer": "^10.4.16",
34 | "eslint": "^8.56.0",
35 | "eslint-config-next": "^14.0.4",
36 | "eslint-config-prettier": "^9.1.0",
37 | "eslint-plugin-tailwindcss": "^3.13.0",
38 | "file-loader": "^6.2.0",
39 | "glslify": "^7.1.1",
40 | "glslify-loader": "^2.0.0",
41 | "postcss": "^8.4.32",
42 | "prettier": "^3.1.1",
43 | "raw-loader": "^4.0.2",
44 | "tailwindcss": "^3.4.0",
45 | "url-loader": "^4.1.1"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/blob/page.jsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import dynamic from 'next/dynamic'
4 |
5 | const Blob = dynamic(() => import('@/components/canvas/Examples').then((mod) => mod.Blob), { ssr: false })
6 | const View = dynamic(() => import('@/components/canvas/View').then((mod) => mod.View), {
7 | ssr: false,
8 | loading: () => (
9 |
19 | ),
20 | })
21 | const Common = dynamic(() => import('@/components/canvas/View').then((mod) => mod.Common), { ssr: false })
22 |
23 | export default function Page() {
24 | return (
25 | <>
26 |
27 |
28 |
Next + React Three Fiber
29 |
Next 3D Starter
30 |
A minimalist starter for React, React-three-fiber and Threejs.
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | >
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/src/templates/Scroll.jsx:
--------------------------------------------------------------------------------
1 | // https://github.com/studio-freight/lenis
2 | // TODO refactor for app-directory
3 | // See https://github.com/pmndrs/react-three-next/pull/123
4 |
5 | // 1 - wrap with in _app.jsx
6 | // 2 - add wherever in the canvas
7 | // 3 - enjoy
8 | import { addEffect, useFrame } from '@react-three/fiber'
9 | import Lenis from '@studio-freight/lenis'
10 | import { useEffect } from 'react'
11 | import { useRef } from 'react'
12 | import * as THREE from 'three'
13 |
14 | const state = {
15 | top: 0,
16 | progress: 0,
17 | }
18 |
19 | const { damp } = THREE.MathUtils
20 |
21 | export default function Scroll({ children }) {
22 | const content = useRef(null)
23 | const wrapper = useRef(null)
24 |
25 | useEffect(() => {
26 | const lenis = new Lenis({
27 | wrapper: wrapper.current,
28 | content: content.current,
29 | duration: 1.2,
30 | easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), // https://www.desmos.com/calculator/brs54l4xou
31 | direction: 'vertical', // vertical, horizontal
32 | gestureDirection: 'vertical', // vertical, horizontal, both
33 | smooth: true,
34 | smoothTouch: false,
35 | touchMultiplier: 2,
36 | infinite: false,
37 | })
38 |
39 | lenis.on('scroll', ({ scroll, progress }) => {
40 | state.top = scroll
41 | state.progress = progress
42 | })
43 | const effectSub = addEffect((time) => lenis.raf(time))
44 | return () => {
45 | effectSub()
46 | lenis.destroy()
47 | }
48 | }, [])
49 |
50 | return (
51 |
60 |
66 | {children}
67 |
68 |
69 | )
70 | }
71 |
72 | export const ScrollTicker = ({ smooth = 9999999 }) => {
73 | useFrame(({ viewport, camera }, delta) => {
74 | camera.position.y = damp(camera.position.y, -state.progress * viewport.height, smooth, delta)
75 | })
76 |
77 | return null
78 | }
79 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withBundleAnalyzer = require('@next/bundle-analyzer')({
2 | enabled: process.env.ANALYZE === 'true',
3 | })
4 |
5 | /**
6 | * A fork of 'next-pwa' that has app directory support
7 | * @see https://github.com/shadowwalker/next-pwa/issues/424#issuecomment-1332258575
8 | */
9 | const withPWA = require('@ducanh2912/next-pwa').default({
10 | dest: 'public',
11 | disable: process.env.NODE_ENV === 'development',
12 | })
13 |
14 | const nextConfig = {
15 | // uncomment the following snippet if using styled components
16 | // compiler: {
17 | // styledComponents: true,
18 | // },
19 | reactStrictMode: true, // Recommended for the `pages` directory, default in `app`.
20 | images: {},
21 | webpack(config, { isServer }) {
22 | if (!isServer) {
23 | // We're in the browser build, so we can safely exclude the sharp module
24 | config.externals.push('sharp')
25 | }
26 | // audio support
27 | config.module.rules.push({
28 | test: /\.(ogg|mp3|wav|mpe?g)$/i,
29 | exclude: config.exclude,
30 | use: [
31 | {
32 | loader: require.resolve('url-loader'),
33 | options: {
34 | limit: config.inlineImageLimit,
35 | fallback: require.resolve('file-loader'),
36 | publicPath: `${config.assetPrefix}/_next/static/images/`,
37 | outputPath: `${isServer ? '../' : ''}static/images/`,
38 | name: '[name]-[hash].[ext]',
39 | esModule: config.esModule || false,
40 | },
41 | },
42 | ],
43 | })
44 |
45 | // shader support
46 | config.module.rules.push({
47 | test: /\.(glsl|vs|fs|vert|frag)$/,
48 | exclude: /node_modules/,
49 | use: ['raw-loader', 'glslify-loader'],
50 | })
51 |
52 | return config
53 | },
54 | }
55 |
56 | const KEYS_TO_OMIT = ['webpackDevMiddleware', 'configOrigin', 'target', 'analyticsId', 'webpack5', 'amp', 'assetPrefix']
57 |
58 | module.exports = (_phase, { defaultConfig }) => {
59 | const plugins = [[withPWA], [withBundleAnalyzer, {}]]
60 |
61 | const wConfig = plugins.reduce((acc, [plugin, config]) => plugin({ ...acc, ...config }), {
62 | ...defaultConfig,
63 | ...nextConfig,
64 | })
65 |
66 | const finalConfig = {}
67 | Object.keys(wConfig).forEach((key) => {
68 | if (!KEYS_TO_OMIT.includes(key)) {
69 | finalConfig[key] = wConfig[key]
70 | }
71 | })
72 |
73 | return finalConfig
74 | }
75 |
--------------------------------------------------------------------------------
/src/components/canvas/Examples.jsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useGLTF } from '@react-three/drei'
4 | import { useFrame } from '@react-three/fiber'
5 | import * as THREE from 'three'
6 | import { useMemo, useRef, useState } from 'react'
7 | import { Line, useCursor, MeshDistortMaterial } from '@react-three/drei'
8 | import { useRouter } from 'next/navigation'
9 |
10 | export const Blob = ({ route = '/', ...props }) => {
11 | const router = useRouter()
12 | const [hovered, hover] = useState(false)
13 | useCursor(hovered)
14 | return (
15 | router.push(route)}
17 | onPointerOver={() => hover(true)}
18 | onPointerOut={() => hover(false)}
19 | {...props}>
20 |
21 |
22 |
23 | )
24 | }
25 |
26 | export const Logo = ({ route = '/blob', ...props }) => {
27 | const mesh = useRef(null)
28 | const router = useRouter()
29 |
30 | const [hovered, hover] = useState(false)
31 | const points = useMemo(() => new THREE.EllipseCurve(0, 0, 3, 1.15, 0, 2 * Math.PI, false, 0).getPoints(100), [])
32 |
33 | useCursor(hovered)
34 | useFrame((state, delta) => {
35 | const t = state.clock.getElapsedTime()
36 | mesh.current.rotation.y = Math.sin(t) * (Math.PI / 8)
37 | mesh.current.rotation.x = Math.cos(t) * (Math.PI / 8)
38 | mesh.current.rotation.z -= delta / 4
39 | })
40 |
41 | return (
42 |
43 | {/* @ts-ignore */}
44 |
45 | {/* @ts-ignore */}
46 |
47 | {/* @ts-ignore */}
48 |
49 | router.push(route)} onPointerOver={() => hover(true)} onPointerOut={() => hover(false)}>
50 |
51 |
52 |
53 |
54 | )
55 | }
56 |
57 | export function Duck(props) {
58 | const { scene } = useGLTF('/duck.glb')
59 |
60 | useFrame((state, delta) => (scene.rotation.y += delta))
61 |
62 | return
63 | }
64 | export function Dog(props) {
65 | const { scene } = useGLTF('/dog.glb')
66 |
67 | return
68 | }
69 |
--------------------------------------------------------------------------------
/app/head.jsx:
--------------------------------------------------------------------------------
1 | const title = 'React Three Next Starter'
2 | const url = 'https://react-three-next.vercel.app/'
3 | const description = 'The easiest and fastest way to create a 3D website using React Three Fiber and NextJS'
4 | const author = 'Author'
5 | const twitter = '@pmndrs'
6 |
7 | export default function Head() {
8 | return (
9 | <>
10 | {/* Recommended Meta Tags */}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {/* Search Engine Optimization Meta Tags */}
19 | {title}
20 |
21 |
25 |
26 |
27 | {/*
28 | Facebook Open Graph meta tags
29 | documentation: https://developers.facebook.com/docs/sharing/opengraph */}
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | {/* Meta Tags for HTML pages on Mobile */}
46 | {/*
47 | */}
48 |
49 |
50 |
51 |
52 | {/*
53 | Twitter Summary card
54 | documentation: https://dev.twitter.com/cards/getting-started
55 | Be sure validate your Twitter card markup on the documentation site. */}
56 |
57 |
58 | >
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/create-r3f-app) [](https://discord.gg/ZZjjNvJ)
2 |
3 | # :japanese_castle: React-Three-Next starter
4 |
5 | A minimalist starter for NextJS, @react-three/fiber and Threejs.
6 |
7 | 
8 |
9 | - TTL ~ 100ms
10 | - First load JS ~ 79kb
11 | - Lighthouse score of 100 (Performance, Accessibility, Best Practices, SEO)
12 |
13 | This starter allows you to navigate seamlessly between pages with dynamic dom and/or canvas content without reloading or creating a new canvas every time. 3D components are usable anywhere in the dom. The events, dom, viewport, everything is synchronized!
14 |
15 | ### ⚫ Demo :
16 |
17 | [](https://react-three-next.vercel.app/)
18 |
19 | ### How to use
20 |
21 | #### Installation
22 |
23 | _Tailwind is the default style. styled-components (styled) are also available._
24 |
25 | ```sh
26 | yarn create r3f-app next my-app
27 | # yarn create r3f-app my-app ? -ts?
28 | # npx create-r3f-app next my-app
29 | ```
30 |
31 | ### :passport_control: Typescript
32 |
33 | For typescript add the parameter `-ts` or `--typescript`:
34 |
35 | ```sh
36 | yarn create r3f-app next my-app -ts
37 | # npx create-r3f-app next my-app -ts
38 | ```
39 |
40 | ### :mount_fuji: Features
41 |
42 | - [x] GLSL imports
43 | - [x] Canvas is not getting unmounted while navigating between pages
44 | - [x] Canvas components usable in any div of the page
45 | - [x] Based on the App directory architecture
46 | - [x] PWA Support
47 |
48 | ### :bullettrain_side: Architecture
49 |
50 | Thanks to [tunnel-rat](https://github.com/pmndrs/tunnel-rat) the starter can portal components between separate renderers. Anything rendered inside the ` ` component of the starter will be rendered in the 3D Context. For better performances it uses gl.scissor to cut the viewport into segments.
51 |
52 | ```jsx
53 |
54 |
55 |
56 | // Some 3D components will be rendered here
57 |
58 |
59 | ```
60 |
61 | ### :control_knobs: Available Scripts
62 |
63 | - `yarn dev` - Next dev
64 | - `yarn analyze` - Generate bundle-analyzer
65 | - `yarn lint` - Audit code quality
66 | - `yarn build` - Next build
67 | - `yarn start` - Next start
68 |
69 | ### ⬛ Stack
70 |
71 | - [`create-r3f-app`](https://github.com/utsuboco/create-r3f-app) – Command line tool to simplify the installation.
72 | - [`threejs`](https://github.com/mrdoob/three.js/) – A lightweight, 3D library with a default WebGL renderer.
73 | - [`@react-three/fiber`](https://github.com/pmndrs/react-three-fiber) – A React renderer for Threejs on the web and react-native.
74 | - [`@react-three/drei` - Optional](https://github.com/pmndrs/drei) – useful helpers for react-three-fiber
75 | - [`@react-three/a11y` - Optional](https://github.com/pmndrs/react-three-a11y/) – Accessibility tools for React Three Fiber
76 | - [`r3f-perf` - Optional](https://github.com/RenaudRohlinger/r3f-perf) – Tool to easily monitor react threejs performances.
77 |
78 | ### How to contribute :
79 |
80 | ```bash
81 | git clone https://github.com/pmndrs/react-three-next
82 | && cd react-three-next && yarn install
83 | ```
84 |
85 | ### Maintainers :
86 |
87 | - [`twitter 🐈⬛ @onirenaud`](https://twitter.com/onirenaud)
88 |
--------------------------------------------------------------------------------
/src/templates/hooks/usePostprocess.jsx:
--------------------------------------------------------------------------------
1 | import { useFrame, useThree } from '@react-three/fiber'
2 | import { useEffect, useMemo } from 'react'
3 | import * as THREE from 'three'
4 |
5 | function getFullscreenTriangle() {
6 | const geometry = new THREE.BufferGeometry()
7 | const vertices = new Float32Array([-1, -1, 3, -1, -1, 3])
8 | const uvs = new Float32Array([0, 0, 2, 0, 0, 2])
9 |
10 | geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 2))
11 | geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2))
12 |
13 | return geometry
14 | }
15 |
16 | // Basic shader postprocess based on the template https://gist.github.com/RenaudRohlinger/bd5d15316a04d04380e93f10401c40e7
17 | // USAGE: Simply call usePostprocess hook in your r3f component to apply the shader to the canvas as a postprocess effect
18 | const usePostProcess = () => {
19 | const [{ dpr }, size, gl] = useThree((s) => [s.viewport, s.size, s.gl])
20 |
21 | const [screenCamera, screenScene, screen, renderTarget] = useMemo(() => {
22 | let screenScene = new THREE.Scene()
23 | const screenCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
24 | const screen = new THREE.Mesh(getFullscreenTriangle())
25 | screen.frustumCulled = false
26 | screenScene.add(screen)
27 |
28 | const renderTarget = new THREE.WebGLRenderTarget(512, 512, { samples: 4, encoding: gl.encoding })
29 | renderTarget.depthTexture = new THREE.DepthTexture() // fix depth issues
30 |
31 | // use ShaderMaterial for linearToOutputTexel
32 | screen.material = new THREE.RawShaderMaterial({
33 | uniforms: {
34 | diffuse: { value: null },
35 | time: { value: 0 },
36 | },
37 | vertexShader: /* glsl */ `
38 |
39 | in vec2 uv;
40 | in vec3 position;
41 | precision highp float;
42 |
43 | out vec2 vUv;
44 | void main() {
45 | vUv = uv;
46 | gl_Position = vec4( position, 1.0 );
47 |
48 | }
49 | `,
50 | fragmentShader: /* glsl */ `
51 | precision highp float;
52 | out highp vec4 pc_fragColor;
53 | uniform sampler2D diffuse;
54 | uniform float time;
55 | in vec2 vUv;
56 |
57 | // based on https://www.shadertoy.com/view/llGXzR
58 | float radial(vec2 pos, float radius)
59 | {
60 | float result = length(pos)-radius;
61 | result = fract(result*1.0);
62 | float result2 = 1.0 - result;
63 | float fresult = result * result2;
64 | fresult = pow((fresult*3.),7.);
65 | return fresult;
66 | }
67 |
68 | void main() {
69 | vec2 c_uv = vUv * 2.0 - 1.0;
70 | vec2 o_uv = vUv * 0.8;
71 | float gradient = radial(c_uv, time*0.8);
72 | vec2 fuv = mix(vUv,o_uv,gradient);
73 | pc_fragColor = texture(diffuse, fuv);
74 | }
75 | `,
76 | glslVersion: THREE.GLSL3,
77 | })
78 | screen.material.uniforms.diffuse.value = renderTarget.texture
79 |
80 | return [screenCamera, screenScene, screen, renderTarget]
81 | }, [gl.encoding])
82 | useEffect(() => {
83 | const { width, height } = size
84 | const { w, h } = {
85 | w: width * dpr,
86 | h: height * dpr,
87 | }
88 | renderTarget.setSize(w, h)
89 | }, [dpr, size, renderTarget])
90 |
91 | useFrame(({ scene, camera, gl }, delta) => {
92 | gl.setRenderTarget(renderTarget)
93 | gl.render(scene, camera)
94 |
95 | gl.setRenderTarget(null)
96 | if (screen) screen.material.uniforms.time.value += delta
97 |
98 | gl.render(screenScene, screenCamera)
99 | }, 1)
100 | return null
101 | }
102 |
103 | export default usePostProcess
104 |
--------------------------------------------------------------------------------
/app/page.jsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import dynamic from 'next/dynamic'
4 | import { Suspense } from 'react'
5 |
6 | const Logo = dynamic(() => import('@/components/canvas/Examples').then((mod) => mod.Logo), { ssr: false })
7 | const Dog = dynamic(() => import('@/components/canvas/Examples').then((mod) => mod.Dog), { ssr: false })
8 | const Duck = dynamic(() => import('@/components/canvas/Examples').then((mod) => mod.Duck), { ssr: false })
9 | const View = dynamic(() => import('@/components/canvas/View').then((mod) => mod.View), {
10 | ssr: false,
11 | loading: () => (
12 |
22 | ),
23 | })
24 | const Common = dynamic(() => import('@/components/canvas/View').then((mod) => mod.Common), { ssr: false })
25 |
26 | export default function Page() {
27 | return (
28 | <>
29 |
30 | {/* jumbo */}
31 |
32 |
Next + React Three Fiber
33 |
Next 3D Starter
34 |
A minimalist starter for React, React-three-fiber and Threejs.
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | {/* first row */}
49 |
50 |
Events are propagated
51 |
Drag, scroll, pinch, and rotate the canvas to explore the 3D scene.
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | {/* second row */}
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
Dom and 3D are synchronized
72 |
73 | 3D Divs are renderer through the View component. It uses gl.scissor to cut the viewport into segments. You
74 | tie a view to a tracking div which then controls the position and bounds of the viewport. This allows you to
75 | have multiple views with a single, performant canvas. These views will follow their tracking elements,
76 | scroll along, resize, etc.
77 |
78 |
79 |
80 | >
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
will contain the components returned by the nearest parent
14 | head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
15 | */}
16 |