first commit
This commit is contained in:
+24
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+4483
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "r3f-water-drops",
|
||||||
|
"homepage": "https://nemutas.github.io/r3f-water-drops/",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"start": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"deploy": "npm run build && gh-pages -d dist"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/css": "^11.9.0",
|
||||||
|
"@react-three/cannon": "^6.3.0",
|
||||||
|
"@react-three/drei": "^9.11.0",
|
||||||
|
"@react-three/fiber": "^8.0.17",
|
||||||
|
"lil-gui": "^0.16.1",
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0",
|
||||||
|
"three": "^0.140.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.0.0",
|
||||||
|
"@types/react-dom": "^18.0.0",
|
||||||
|
"@types/three": "^0.140.0",
|
||||||
|
"@vitejs/plugin-react": "^1.3.0",
|
||||||
|
"gh-pages": "^4.0.0",
|
||||||
|
"typescript": "^4.6.3",
|
||||||
|
"vite": "^2.9.9",
|
||||||
|
"vite-plugin-glsl": "^0.1.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="165" height="165" viewBox="0 0 165 165" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M82.5 0C36.9188 0 0 36.9188 0 82.5C0 119.006 23.6156 149.841 56.4094 160.772C60.5344 161.494 62.0812 159.019 62.0812 156.853C62.0812 154.894 61.9781 148.397 61.9781 141.488C41.25 145.303 35.8875 136.434 34.2375 131.794C33.3094 129.422 29.2875 122.1 25.7812 120.141C22.8937 118.594 18.7687 114.778 25.6781 114.675C32.175 114.572 36.8156 120.656 38.3625 123.131C45.7875 135.609 57.6469 132.103 62.3906 129.938C63.1125 124.575 65.2781 120.966 67.65 118.903C49.2938 116.841 30.1125 109.725 30.1125 78.1688C30.1125 69.1969 33.3094 61.7719 38.5688 55.9969C37.7438 53.9344 34.8563 45.4781 39.3937 34.1344C39.3937 34.1344 46.3031 31.9688 62.0812 42.5906C68.6813 40.7344 75.6937 39.8063 82.7062 39.8063C89.7188 39.8063 96.7312 40.7344 103.331 42.5906C119.109 31.8656 126.019 34.1344 126.019 34.1344C130.556 45.4781 127.669 53.9344 126.844 55.9969C132.103 61.7719 135.3 69.0938 135.3 78.1688C135.3 109.828 116.016 116.841 97.6594 118.903C100.65 121.481 103.228 126.431 103.228 134.166C103.228 145.2 103.125 154.069 103.125 156.853C103.125 159.019 104.672 161.597 108.797 160.772C125.174 155.242 139.406 144.717 149.488 130.676C159.57 116.635 164.995 99.7857 165 82.5C165 36.9188 128.081 0 82.5 0Z" fill="#fff"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
@@ -0,0 +1,25 @@
|
|||||||
|
import React, { FC } from 'react';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { publicPath } from '../modules/utils';
|
||||||
|
import { LinkIconButton } from './LinkIconButton';
|
||||||
|
import { TCanvas } from './three/TCanvas';
|
||||||
|
|
||||||
|
export const App: FC = () => {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<TCanvas />
|
||||||
|
<LinkIconButton
|
||||||
|
imagePath={publicPath('/assets/icons/github.svg')}
|
||||||
|
linkPath="https://github.com/nemutas/r3f-water-drops"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
container: css`
|
||||||
|
position: relative;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
`
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import React, { FC, useState } from 'react';
|
||||||
|
|
||||||
|
type LinkIconButtonProps = {
|
||||||
|
/**
|
||||||
|
* Resource path directly under the public folder.
|
||||||
|
* @example '/assets/icons/github.svg'
|
||||||
|
*/
|
||||||
|
imagePath: string
|
||||||
|
/**
|
||||||
|
* @example 'https://github.com'
|
||||||
|
*/
|
||||||
|
linkPath: string
|
||||||
|
/**
|
||||||
|
* @default 'bottom-right'
|
||||||
|
*/
|
||||||
|
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
|
||||||
|
/**
|
||||||
|
* @default [50, 50] - width:50px, height:50px
|
||||||
|
*/
|
||||||
|
size?: [number, number]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LinkIconButton: FC<LinkIconButtonProps> = props => {
|
||||||
|
const { imagePath, linkPath, position = 'bottom-right', size = [50, 50] } = props
|
||||||
|
const [hover, setHover] = useState(false)
|
||||||
|
|
||||||
|
const publicImagePath = imagePath
|
||||||
|
|
||||||
|
let positionStyle
|
||||||
|
switch (position) {
|
||||||
|
case 'top-left':
|
||||||
|
positionStyle = styles.topLeft
|
||||||
|
break
|
||||||
|
case 'top-right':
|
||||||
|
positionStyle = styles.topRight
|
||||||
|
break
|
||||||
|
case 'bottom-left':
|
||||||
|
positionStyle = styles.bottomLeft
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
positionStyle = styles.bottomRight
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
style={positionStyle}
|
||||||
|
href={linkPath}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
onMouseEnter={() => setHover(true)}
|
||||||
|
onMouseLeave={() => setHover(false)}>
|
||||||
|
<img style={hover ? hoverStyles.img : styles.img} src={publicImagePath} alt="" width={size[0]} height={size[1]} />
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================
|
||||||
|
// styles
|
||||||
|
|
||||||
|
type Styles = { [key in string]: React.CSSProperties }
|
||||||
|
|
||||||
|
const temp: Styles = {
|
||||||
|
container: {
|
||||||
|
position: 'absolute',
|
||||||
|
fontSize: '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles: Styles = {
|
||||||
|
topLeft: {
|
||||||
|
...temp.container,
|
||||||
|
top: '10px',
|
||||||
|
left: '10px'
|
||||||
|
},
|
||||||
|
topRight: {
|
||||||
|
...temp.container,
|
||||||
|
top: '10px',
|
||||||
|
right: '10px'
|
||||||
|
},
|
||||||
|
bottomLeft: {
|
||||||
|
...temp.container,
|
||||||
|
bottom: '10px',
|
||||||
|
left: '10px'
|
||||||
|
},
|
||||||
|
bottomRight: {
|
||||||
|
...temp.container,
|
||||||
|
bottom: '10px',
|
||||||
|
right: '10px'
|
||||||
|
},
|
||||||
|
img: {
|
||||||
|
objectFit: 'cover',
|
||||||
|
opacity: '0.5',
|
||||||
|
transform: 'rotate(0deg)',
|
||||||
|
transition: 'all 0.3s'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hoverStyles: Styles = {
|
||||||
|
img: {
|
||||||
|
...styles.img,
|
||||||
|
opacity: '1',
|
||||||
|
transform: 'rotate(360deg)'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import React, { FC } from 'react';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { Plane, useTexture } from '@react-three/drei';
|
||||||
|
import { useFrame, useThree } from '@react-three/fiber';
|
||||||
|
import fragmentShader from '../../modules/glsl/raymarchingFrag.glsl';
|
||||||
|
import vertexShader from '../../modules/glsl/raymarchingVert.glsl';
|
||||||
|
import { AMOUNT, datas } from '../../modules/store';
|
||||||
|
import { publicPath } from '../../modules/utils';
|
||||||
|
|
||||||
|
export const ScreenPlane: FC = () => {
|
||||||
|
const { viewport } = useThree()
|
||||||
|
|
||||||
|
const texture = useTexture(publicPath('/assets/images/wlop.jpg'))
|
||||||
|
texture.encoding = THREE.sRGBEncoding
|
||||||
|
texture.wrapS = THREE.MirroredRepeatWrapping
|
||||||
|
texture.wrapT = THREE.MirroredRepeatWrapping
|
||||||
|
|
||||||
|
// Calculate screen size and texture aspect ratio
|
||||||
|
const textureAspect = texture.image.width / texture.image.height
|
||||||
|
const aspect = viewport.aspect
|
||||||
|
const ratio = aspect / textureAspect
|
||||||
|
const [x, y] = aspect < textureAspect ? [ratio, 1] : [1, 1 / ratio]
|
||||||
|
|
||||||
|
// Replace embedded text
|
||||||
|
const fragment = fragmentShader.replaceAll('AMOUNT', AMOUNT.toString())
|
||||||
|
|
||||||
|
const shader: THREE.Shader = {
|
||||||
|
uniforms: {
|
||||||
|
u_aspect: { value: viewport.aspect },
|
||||||
|
u_datas: { value: datas },
|
||||||
|
u_texture: { value: texture },
|
||||||
|
u_uvScale: { value: new THREE.Vector2(x, y) }
|
||||||
|
},
|
||||||
|
vertexShader,
|
||||||
|
fragmentShader: fragment
|
||||||
|
}
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
datas.forEach((data, i) => {
|
||||||
|
shader.uniforms.u_datas.value[i].position.copy(data.position)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Plane args={[1, 1]} scale={[viewport.width, viewport.height, 1]}>
|
||||||
|
<shaderMaterial args={[shader]} />
|
||||||
|
</Plane>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import React, { FC, useEffect } from 'react';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { Physics, usePlane, useSphere } from '@react-three/cannon';
|
||||||
|
import { useFrame } from '@react-three/fiber';
|
||||||
|
import { datas } from '../../modules/store';
|
||||||
|
import { Data } from '../../types/types';
|
||||||
|
|
||||||
|
export const Simulator: FC = () => {
|
||||||
|
return (
|
||||||
|
<Physics gravity={[0, 0, 0]} iterations={10} broadphase="SAP">
|
||||||
|
{/* debug mode */}
|
||||||
|
{/* <Debug color="#fff" scale={1.05}>
|
||||||
|
<Collisions />
|
||||||
|
{datas.map((data, i) => (
|
||||||
|
<PhysicalSphere key={i} data={data} />
|
||||||
|
))}
|
||||||
|
</Debug> */}
|
||||||
|
|
||||||
|
{/* product mode */}
|
||||||
|
<Collisions />
|
||||||
|
{datas.map((data, i) => (
|
||||||
|
<PhysicalSphere key={i} data={data} />
|
||||||
|
))}
|
||||||
|
</Physics>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================
|
||||||
|
const Collisions: FC = () => {
|
||||||
|
// back
|
||||||
|
usePlane(() => ({ position: [0, 0, -1], rotation: [0, 0, 0] }))
|
||||||
|
// front
|
||||||
|
usePlane(() => ({ position: [0, 0, 5], rotation: [0, -Math.PI, 0] }))
|
||||||
|
// // bottom
|
||||||
|
// usePlane(() => ({ position: [0, -4, 0], rotation: [-Math.PI / 2, 0, 0] }))
|
||||||
|
// // top
|
||||||
|
// usePlane(() => ({ position: [0, 4, 0], rotation: [Math.PI / 2, 0, 0] }))
|
||||||
|
|
||||||
|
const [, api] = useSphere(() => ({ type: 'Kinematic', args: [3] }))
|
||||||
|
|
||||||
|
useFrame(({ mouse, viewport }) => {
|
||||||
|
const x = (mouse.x * viewport.width) / 2
|
||||||
|
const y = (mouse.y * viewport.height) / 2
|
||||||
|
api.position.set(x, y, 1.5)
|
||||||
|
})
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================
|
||||||
|
const PhysicalSphere: FC<{ data: Data }> = ({ data }) => {
|
||||||
|
const scale = data.scale
|
||||||
|
|
||||||
|
const [ref, api] = useSphere(() => ({
|
||||||
|
mass: 1,
|
||||||
|
angularDamping: 0.1,
|
||||||
|
linearDamping: 0.95,
|
||||||
|
fixedRotation: true,
|
||||||
|
args: [scale],
|
||||||
|
position: data.position.toArray()
|
||||||
|
}))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const vec = new THREE.Vector3()
|
||||||
|
const unsubscribe = api.position.subscribe(p => {
|
||||||
|
data.position.set(p[0], p[1], p[2])
|
||||||
|
|
||||||
|
vec
|
||||||
|
.set(p[0], p[1], p[2])
|
||||||
|
.normalize()
|
||||||
|
.multiplyScalar(-scale * 30)
|
||||||
|
|
||||||
|
return api.applyForce(vec.toArray(), [0, 0, 0])
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribe()
|
||||||
|
}
|
||||||
|
}, [api])
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import React, { FC, Suspense } from 'react';
|
||||||
|
import { Canvas } from '@react-three/fiber';
|
||||||
|
import { PostProcessing } from './postprocessing/PostProcessing';
|
||||||
|
import { ScreenPlane } from './ScreenPlane';
|
||||||
|
import { Simulator } from './Simulator';
|
||||||
|
|
||||||
|
export const TCanvas: FC = () => {
|
||||||
|
return (
|
||||||
|
<Canvas
|
||||||
|
camera={{
|
||||||
|
position: [0, 0, 15],
|
||||||
|
fov: 50,
|
||||||
|
aspect: window.innerWidth / window.innerHeight,
|
||||||
|
near: 0.1,
|
||||||
|
far: 2000
|
||||||
|
}}
|
||||||
|
dpr={window.devicePixelRatio}>
|
||||||
|
<color attach="background" args={['#000']} />
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<Simulator />
|
||||||
|
<ScreenPlane />
|
||||||
|
<PostProcessing />
|
||||||
|
</Suspense>
|
||||||
|
</Canvas>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React, { FC } from 'react';
|
||||||
|
import { Effects } from '@react-three/drei';
|
||||||
|
import { useFrame } from '@react-three/fiber';
|
||||||
|
import { FXAA } from './pass/FXAA';
|
||||||
|
|
||||||
|
export const PostProcessing: FC = () => {
|
||||||
|
const fxaa = new FXAA()
|
||||||
|
|
||||||
|
useFrame(({ size }) => {
|
||||||
|
fxaa.update(size)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Effects>
|
||||||
|
<shaderPass ref={fxaa.ref} args={[fxaa.shader]} />
|
||||||
|
</Effects>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { createRef } from 'react';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { Pass, ShaderPass } from 'three-stdlib';
|
||||||
|
|
||||||
|
export abstract class BasePass<T extends Pass> {
|
||||||
|
public ref
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.ref = createRef<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract _initController: () => void
|
||||||
|
abstract update: (...args: any) => void
|
||||||
|
|
||||||
|
protected get pass() {
|
||||||
|
return this.ref.current
|
||||||
|
}
|
||||||
|
|
||||||
|
protected validatedPass = (enabled = true) => {
|
||||||
|
if (!this.pass) return null
|
||||||
|
this.pass.enabled = enabled
|
||||||
|
return enabled ? this.pass : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class BaseShader extends BasePass<ShaderPass> {
|
||||||
|
public shader: THREE.Shader
|
||||||
|
|
||||||
|
constructor(shader: THREE.Shader) {
|
||||||
|
super()
|
||||||
|
this.shader = shader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class BaseCustomShader extends BaseShader {
|
||||||
|
constructor(uniforms: { [uniform: string]: THREE.IUniform<any> }, vertexShader: string, fragmentShader: string) {
|
||||||
|
const shader = {
|
||||||
|
uniforms: THREE.UniformsUtils.merge([{ tDiffuse: { value: null } }, uniforms]),
|
||||||
|
vertexShader,
|
||||||
|
fragmentShader
|
||||||
|
}
|
||||||
|
super(shader)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { FXAAShader } from 'three-stdlib';
|
||||||
|
import { Size } from '@react-three/fiber';
|
||||||
|
import { BaseShader } from './Base';
|
||||||
|
|
||||||
|
export class FXAA extends BaseShader {
|
||||||
|
constructor() {
|
||||||
|
super(FXAAShader)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _initController = () => {}
|
||||||
|
|
||||||
|
update = (size: Size) => {
|
||||||
|
// validate pass
|
||||||
|
const pass = this.validatedPass()
|
||||||
|
if (!pass) return
|
||||||
|
|
||||||
|
// update uniforms
|
||||||
|
pass.uniforms.resolution.value.set(1 / size.width, 1 / size.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
|
||||||
|
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#41D1FF"/>
|
||||||
|
<stop offset="1" stop-color="#BD34FE"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FFEA83"/>
|
||||||
|
<stop offset="0.0833333" stop-color="#FFDD35"/>
|
||||||
|
<stop offset="1" stop-color="#FFA800"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
|
||||||
|
'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
font-size: 62.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import './index.css';
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import { App } from './components/App';
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
float fresnel(vec3 eye, vec3 normal) {
|
||||||
|
return pow(1.0 + dot(eye, normal), 3.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
//---------------------------------------------------------
|
||||||
|
// This function is based on the Inigo Quilez article.
|
||||||
|
// https://iquilezles.org/articles/distfunctions/
|
||||||
|
//---------------------------------------------------------
|
||||||
|
|
||||||
|
float opUnion( float d1, float d2 ) { return min(d1,d2); }
|
||||||
|
|
||||||
|
float opSubtraction( float d1, float d2 ) { return max(-d1,d2); }
|
||||||
|
|
||||||
|
float opIntersection( float d1, float d2 ) { return max(d1,d2); }
|
||||||
|
|
||||||
|
float opSmoothUnion( float d1, float d2, float k ) {
|
||||||
|
float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
|
||||||
|
return mix( d2, d1, h ) - k*h*(1.0-h);
|
||||||
|
}
|
||||||
|
|
||||||
|
float opSmoothSubtraction( float d1, float d2, float k ) {
|
||||||
|
float h = clamp( 0.5 - 0.5*(d2+d1)/k, 0.0, 1.0 );
|
||||||
|
return mix( d2, -d1, h ) + k*h*(1.0-h);
|
||||||
|
}
|
||||||
|
|
||||||
|
float opSmoothIntersection( float d1, float d2, float k ) {
|
||||||
|
float h = clamp( 0.5 - 0.5*(d2-d1)/k, 0.0, 1.0 );
|
||||||
|
return mix( d2, d1, h ) + k*h*(1.0-h);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
//---------------------------------------------------------
|
||||||
|
// This function is based on the Inigo Quilez article.
|
||||||
|
// https://iquilezles.org/articles/normalsSDF/
|
||||||
|
//---------------------------------------------------------
|
||||||
|
|
||||||
|
// this function must be define after sdf.
|
||||||
|
vec3 calcNormal(in vec3 p) {
|
||||||
|
const float h = 0.0001;
|
||||||
|
const vec2 k = vec2(1, -1) * h;
|
||||||
|
return normalize( k.xyy * sdf( p + k.xyy ) +
|
||||||
|
k.yyx * sdf( p + k.yyx ) +
|
||||||
|
k.yxy * sdf( p + k.yxy ) +
|
||||||
|
k.xxx * sdf( p + k.xxx ) );
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
//---------------------------------------------------------
|
||||||
|
// This function is based on the Inigo Quilez article.
|
||||||
|
// https://iquilezles.org/articles/distfunctions/
|
||||||
|
//---------------------------------------------------------
|
||||||
|
|
||||||
|
float sdSphere( vec3 p, float s ) {
|
||||||
|
return length(p)-s;
|
||||||
|
}
|
||||||
|
|
||||||
|
float sdBox( vec3 p, vec3 b ) {
|
||||||
|
vec3 q = abs(p) - b;
|
||||||
|
return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// AMOUNT replaces at call position
|
||||||
|
|
||||||
|
struct Data {
|
||||||
|
vec3 position;
|
||||||
|
float scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
uniform float u_aspect;
|
||||||
|
uniform Data u_datas[AMOUNT];
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform vec2 u_uvScale;
|
||||||
|
varying vec2 v_uv;
|
||||||
|
|
||||||
|
const int COUNT = AMOUNT;
|
||||||
|
|
||||||
|
#include './fresnel.glsl'
|
||||||
|
#include './raymarching/primitives.glsl'
|
||||||
|
#include './raymarching/combinations.glsl'
|
||||||
|
|
||||||
|
float sdf(vec3 p) {
|
||||||
|
vec3 correct = vec3(u_aspect, 1.0, 1.0) * vec3(0.08, 0.15, 0.2);
|
||||||
|
|
||||||
|
vec3 pos = p + -u_datas[0].position * correct;
|
||||||
|
float final = sdSphere(pos, 0.2 * u_datas[0].scale);
|
||||||
|
|
||||||
|
for(int i = 1; i < COUNT; i++) {
|
||||||
|
pos = p + -u_datas[i].position * correct;
|
||||||
|
float sphere = sdSphere(pos, 0.2 * u_datas[i].scale);
|
||||||
|
final = opSmoothUnion(final, sphere, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return final;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include './raymarching/normal.glsl'
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 centeredUV = (v_uv - 0.5) * vec2(u_aspect, 1.0);
|
||||||
|
vec3 ray = normalize(vec3(centeredUV, -1.0));
|
||||||
|
|
||||||
|
vec3 camPos = vec3(0.0, 0.0, 2.3);
|
||||||
|
vec3 rayPos = camPos;
|
||||||
|
float totalDist = 0.0;
|
||||||
|
float tMax = 5.0;
|
||||||
|
|
||||||
|
for(int i = 0; i < 256; i++) {
|
||||||
|
float dist = sdf(rayPos);
|
||||||
|
if (dist < 0.0001 || tMax < totalDist) break;
|
||||||
|
totalDist += dist;
|
||||||
|
rayPos = camPos + totalDist * ray;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 uv = (v_uv - 0.5) * u_uvScale + 0.5;
|
||||||
|
vec4 tex = texture2D(u_texture, uv);
|
||||||
|
tex = vec4(vec3((tex.r + tex.g + tex.b) / 3.0), tex.a);
|
||||||
|
vec4 outgoing = tex;
|
||||||
|
|
||||||
|
if(totalDist < tMax) {
|
||||||
|
vec3 normal = calcNormal(rayPos);
|
||||||
|
float f = fresnel(ray, normal);
|
||||||
|
|
||||||
|
float len = pow(length(normal.xy), 3.0);
|
||||||
|
uv += normal.xy * len * 0.1;
|
||||||
|
|
||||||
|
tex = texture2D(u_texture, uv);
|
||||||
|
tex += f * 0.3;
|
||||||
|
outgoing = tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_FragColor = outgoing;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
varying vec2 v_uv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
v_uv = uv;
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import GUI from 'lil-gui';
|
||||||
|
|
||||||
|
export class GUIController {
|
||||||
|
private static _instance: GUIController | null
|
||||||
|
private _gui
|
||||||
|
private _currentFolderName: string | undefined
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
this._gui = new GUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
static get instance() {
|
||||||
|
if (!this._instance) {
|
||||||
|
this._instance = new GUIController()
|
||||||
|
}
|
||||||
|
this._instance._currentFolderName = undefined
|
||||||
|
return this._instance
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getGui = (folderName: string | undefined) => {
|
||||||
|
let gui = this._gui
|
||||||
|
if (folderName) {
|
||||||
|
gui = this._folder(folderName)
|
||||||
|
} else if (this._currentFolderName) {
|
||||||
|
gui = this._folder(this._currentFolderName)
|
||||||
|
}
|
||||||
|
return gui
|
||||||
|
}
|
||||||
|
|
||||||
|
private _folder = (title: string) => {
|
||||||
|
let folder = this._gui.folders.find(f => f._title === title)
|
||||||
|
if (!folder) folder = this._gui.addFolder(title)
|
||||||
|
return folder
|
||||||
|
}
|
||||||
|
|
||||||
|
private _controller = (gui: GUI, name: string) => {
|
||||||
|
return gui.controllers.find(c => c._name === name)
|
||||||
|
}
|
||||||
|
|
||||||
|
setFolder = (name: string) => {
|
||||||
|
this._currentFolderName = name
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
open = (open: boolean) => {
|
||||||
|
this._getGui(this._currentFolderName).open(open)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add color controls
|
||||||
|
* @reference https://lil-gui.georgealways.com/#Guide#Colors
|
||||||
|
*/
|
||||||
|
addColor = (
|
||||||
|
obj: object,
|
||||||
|
propertyName: string,
|
||||||
|
rgbScale?: number | undefined,
|
||||||
|
displayName?: string | undefined,
|
||||||
|
folderName?: string | undefined
|
||||||
|
) => {
|
||||||
|
const controllerName = displayName ? displayName : propertyName
|
||||||
|
const gui = this._getGui(folderName)
|
||||||
|
|
||||||
|
let controller = this._controller(gui, controllerName)
|
||||||
|
if (!controller) {
|
||||||
|
controller = gui.addColor(obj, propertyName, rgbScale).name(controllerName)
|
||||||
|
}
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add numeric slider controls
|
||||||
|
* @reference https://lil-gui.georgealways.com/#Guide#Numbers-and-Sliders
|
||||||
|
*/
|
||||||
|
addNumericSlider = (
|
||||||
|
obj: object,
|
||||||
|
propertyName: string,
|
||||||
|
min: number,
|
||||||
|
max: number,
|
||||||
|
step: number,
|
||||||
|
displayName?: string | undefined,
|
||||||
|
folderName?: string | undefined
|
||||||
|
) => {
|
||||||
|
const controllerName = displayName ? displayName : propertyName
|
||||||
|
const gui = this._getGui(folderName)
|
||||||
|
|
||||||
|
let controller = this._controller(gui, controllerName)
|
||||||
|
if (!controller) {
|
||||||
|
controller = gui.add(obj, propertyName, min, max, step).name(controllerName)
|
||||||
|
}
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add dropdown controls
|
||||||
|
* @reference https://lil-gui.georgealways.com/#Guide#Dropdowns
|
||||||
|
*/
|
||||||
|
addDropdown = (
|
||||||
|
obj: object,
|
||||||
|
propertyName: string,
|
||||||
|
list: string[] | { [key: string]: number },
|
||||||
|
displayName?: string | undefined,
|
||||||
|
folderName?: string | undefined
|
||||||
|
) => {
|
||||||
|
const controllerName = displayName ? displayName : propertyName
|
||||||
|
const gui = this._getGui(folderName)
|
||||||
|
|
||||||
|
let controller = this._controller(gui, controllerName)
|
||||||
|
if (!controller) {
|
||||||
|
controller = gui.add(obj, propertyName, list).name(controllerName)
|
||||||
|
}
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add Button controls
|
||||||
|
* @description property given by its property name is a callback method.
|
||||||
|
* @reference https://lil-gui.georgealways.com/#Guide#Saving
|
||||||
|
*/
|
||||||
|
addButton = (
|
||||||
|
obj: object,
|
||||||
|
propertyName: string,
|
||||||
|
displayName?: string | undefined,
|
||||||
|
folderName?: string | undefined
|
||||||
|
) => {
|
||||||
|
const controllerName = displayName ? displayName : propertyName
|
||||||
|
const gui = this._getGui(folderName)
|
||||||
|
|
||||||
|
let controller = this._controller(gui, controllerName)
|
||||||
|
if (!controller) {
|
||||||
|
controller = gui.add(obj, propertyName).name(controllerName)
|
||||||
|
}
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add CheckBox controls
|
||||||
|
* @description property given by its property name is type of boolean.
|
||||||
|
* @reference https://lil-gui.georgealways.com/#Guide#Adding-Controllers
|
||||||
|
*/
|
||||||
|
addCheckBox = (
|
||||||
|
obj: object,
|
||||||
|
propertyName: string,
|
||||||
|
displayName?: string | undefined,
|
||||||
|
folderName?: string | undefined
|
||||||
|
) => {
|
||||||
|
const controllerName = displayName ? displayName : propertyName
|
||||||
|
const gui = this._getGui(folderName)
|
||||||
|
|
||||||
|
let controller = this._controller(gui, controllerName)
|
||||||
|
if (!controller) {
|
||||||
|
controller = gui.add(obj, propertyName).name(controllerName)
|
||||||
|
}
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { Data } from '../types/types';
|
||||||
|
|
||||||
|
export const AMOUNT = 20
|
||||||
|
|
||||||
|
export const datas: Data[] = [...Array(AMOUNT)].map(() => {
|
||||||
|
const position = new THREE.Vector3(THREE.MathUtils.randFloat(-5, 5), THREE.MathUtils.randFloat(-5, 5), 0)
|
||||||
|
const scale = THREE.MathUtils.randFloat(0.5, 1.5)
|
||||||
|
return { position, scale }
|
||||||
|
})
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export const publicPath = (path: string) => {
|
||||||
|
path.startsWith('/') && (path = path.substring(1))
|
||||||
|
return import.meta.env.BASE_URL + path
|
||||||
|
}
|
||||||
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
declare module '*.glsl' {
|
||||||
|
const value: string
|
||||||
|
export default value
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type Data = {
|
||||||
|
position: THREE.Vector3
|
||||||
|
scale: number
|
||||||
|
}
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": false,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import glsl from 'vite-plugin-glsl';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react(), glsl()],
|
||||||
|
base: '/r3f-water-drops/'
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user