git@yoqi.me 4 years ago
commit
e2f988dfe6
9 changed files with 363 additions and 0 deletions
  1. 11 0
      .prettierrc
  2. 29 0
      package.json
  3. 42 0
      public/index.html
  4. BIN
      src/assets/233.jpg
  5. BIN
      src/assets/diamond.glb
  6. 26 0
      src/backface-material/index.js
  7. 121 0
      src/index.js
  8. 74 0
      src/refraction-material/index.js
  9. 60 0
      src/styles.css

+ 11 - 0
.prettierrc

@@ -0,0 +1,11 @@
+{
+  "printWidth": 160,
+  "tabWidth": 2,
+  "useTabs": false,
+  "semi": false,
+  "singleQuote": false,
+  "trailingComma": "none",
+  "bracketSpacing": true,
+  "jsxBracketSameLine": true,
+  "fluid": false
+}

+ 29 - 0
package.json

@@ -0,0 +1,29 @@
+{
+  "name": "floating-diamonds",
+  "version": "1.0.0",
+  "description": "react-three-fiber, threejs, react",
+  "keywords": [],
+  "main": "src/index.js",
+  "dependencies": {
+    "react": "16.11.0",
+    "react-dom": "16.11.0",
+    "react-scripts": "3.2.0",
+    "react-three-fiber": "3.0.8",
+    "three": "0.110.0"
+  },
+  "devDependencies": {
+    "typescript": "3.3.3"
+  },
+  "scripts": {
+    "start": "react-scripts start",
+    "build": "react-scripts build",
+    "test": "react-scripts test --env=jsdom",
+    "eject": "react-scripts eject"
+  },
+  "browserslist": [
+    ">0.2%",
+    "not dead",
+    "not ie <= 11",
+    "not op_mini all"
+  ]
+}

+ 42 - 0
public/index.html

@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
+    <meta name="theme-color" content="#000000" />
+    <!--
+      manifest.json provides metadata used when your web app is added to the
+      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
+    -->
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
+    <link href="https://fonts.googleapis.com/css?family=Roboto:900&display=swap" rel="stylesheet" />
+    <!--
+      Notice the use of %PUBLIC_URL% in the tags above.
+      It will be replaced with the URL of the `public` folder during the build.
+      Only files inside the `public` folder can be referenced from the HTML.
+
+      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
+      work correctly both with client-side routing and a non-root public URL.
+      Learn how to configure a non-root public URL by running `npm run build`.
+    -->
+    <title>React App</title>
+  </head>
+
+  <body>
+    <noscript>
+      You need to enable JavaScript to run this app.
+    </noscript>
+    <div id="root"></div>
+    <!--
+      This HTML file is a template.
+      If you open it directly in the browser, you will see an empty page.
+
+      You can add webfonts, meta tags, or analytics to this file.
+      The build step will place the bundled scripts into the <body> tag.
+
+      To begin the development, run `npm start` or `yarn start`.
+      To create a production bundle, use `npm run build` or `yarn build`.
+    -->
+  </body>
+</html>

BIN
src/assets/233.jpg


BIN
src/assets/diamond.glb


+ 26 - 0
src/backface-material/index.js

@@ -0,0 +1,26 @@
+import { ShaderMaterial, BackSide } from "three"
+
+export default class RefractionMaterial extends ShaderMaterial {
+  constructor(options) {
+    super({
+      vertexShader: `varying vec3 worldNormal;
+      void main() {
+
+        vec4 transformedNormal = vec4(normal, 0.);
+        vec4 transformedPosition = vec4(position, 1.0);
+        #ifdef USE_INSTANCING
+          transformedNormal = instanceMatrix * transformedNormal;
+          transformedPosition = instanceMatrix * transformedPosition;
+        #endif
+
+        worldNormal = normalize( modelViewMatrix * transformedNormal).xyz;
+        gl_Position = projectionMatrix * modelViewMatrix * transformedPosition;
+      }`,
+      fragmentShader: `varying vec3 worldNormal;
+      void main() {
+        gl_FragColor = vec4(worldNormal, 1.0);
+      }`,
+      side: BackSide
+    })
+  }
+}

+ 121 - 0
src/index.js

@@ -0,0 +1,121 @@
+import { TextureLoader, WebGLRenderTarget, Object3D, LinearFilter } from "three"
+import React, { Suspense, useMemo, useRef } from "react"
+import ReactDOM from "react-dom"
+import { Canvas, useLoader, useThree, useFrame } from "react-three-fiber"
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"
+import BackfaceMaterial from "./backface-material"
+import RefractionMaterial from "./refraction-material"
+import diamondUrl from "./assets/diamond.glb"
+import textureUrl from "./assets/233.jpg"
+import "./styles.css"
+
+function Background() {
+  const { viewport, aspect } = useThree()
+  const texture = useLoader(TextureLoader, textureUrl)
+  useMemo(() => (texture.minFilter = LinearFilter), [])
+  // Calculates a plane filling the screen similar to background-size: cover
+  const adaptedHeight = 3800 * (aspect > 5000 / 3800 ? viewport.width / 5000 : viewport.height / 3800)
+  const adaptedWidth = 5000 * (aspect > 5000 / 3800 ? viewport.width / 5000 : viewport.height / 3800)
+  return (
+    <mesh layers={1} scale={[adaptedWidth, adaptedHeight, 1]}>
+      <planeBufferGeometry attach="geometry" />
+      <meshBasicMaterial attach="material" map={texture} depthTest={false} />
+    </mesh>
+  )
+}
+
+function Diamonds() {
+  const { size, viewport, gl, scene, camera, clock } = useThree()
+  const model = useRef()
+  const gltf = useLoader(GLTFLoader, diamondUrl)
+
+  // Create Fbo's and materials
+  const [envFbo, backfaceFbo, backfaceMaterial, refractionMaterial] = useMemo(() => {
+    const envFbo = new WebGLRenderTarget(size.width, size.height)
+    const backfaceFbo = new WebGLRenderTarget(size.width, size.height)
+    const backfaceMaterial = new BackfaceMaterial()
+    const refractionMaterial = new RefractionMaterial({ envMap: envFbo.texture, backfaceMap: backfaceFbo.texture, resolution: [size.width, size.height] })
+    return [envFbo, backfaceFbo, backfaceMaterial, refractionMaterial]
+  }, [size])
+
+  // Create random position data
+  const dummy = useMemo(() => new Object3D(), [])
+  const diamonds = useMemo(
+    () =>
+      new Array(80).fill().map((_, i) => ({
+        position: [i < 5 ? 0 : viewport.width / 2 - Math.random() * viewport.width, 40 - Math.random() * 40, i < 5 ? 26 : 10 - Math.random() * 20],
+        factor: 0.1 + Math.random(),
+        direction: Math.random() < 0.5 ? -1 : 1,
+        rotation: [Math.sin(Math.random()) * Math.PI, Math.sin(Math.random()) * Math.PI, Math.cos(Math.random()) * Math.PI]
+      })),
+    []
+  )
+
+  // Render-loop
+  useFrame(() => {
+    // Update instanced diamonds
+    diamonds.forEach((data, i) => {
+      const t = clock.getElapsedTime()
+      data.position[1] -= (data.factor / 5) * data.direction
+      if (data.direction === 1 ? data.position[1] < -50 : data.position[1] > 50)
+        data.position = [i < 5 ? 0 : viewport.width / 2 - Math.random() * viewport.width, 50 * data.direction, data.position[2]]
+      const { position, rotation, factor } = data
+      dummy.position.set(position[0], position[1], position[2])
+      dummy.rotation.set(rotation[0] + t * factor, rotation[1] + t * factor, rotation[2] + t * factor)
+      dummy.scale.set(1 + factor, 1 + factor, 1 + factor)
+      dummy.updateMatrix()
+      model.current.setMatrixAt(i, dummy.matrix)
+    })
+    model.current.instanceMatrix.needsUpdate = true
+    // Render env to fbo
+    gl.autoClear = false
+    camera.layers.set(1)
+    gl.setRenderTarget(envFbo)
+    gl.render(scene, camera)
+    // Render cube backfaces to fbo
+    camera.layers.set(0)
+    model.current.material = backfaceMaterial
+    gl.setRenderTarget(backfaceFbo)
+    gl.clearDepth()
+    gl.render(scene, camera)
+    // Render env to screen
+    camera.layers.set(1)
+    gl.setRenderTarget(null)
+    gl.render(scene, camera)
+    gl.clearDepth()
+    // Render cube with refraction material to screen
+    camera.layers.set(0)
+    model.current.material = refractionMaterial
+    gl.render(scene, camera)
+  }, 1)
+
+  return (
+    <instancedMesh ref={model} args={[null, null, diamonds.length]}>
+      <bufferGeometry dispose={false} attach="geometry" {...gltf.__$[1].geometry} />
+      <meshBasicMaterial attach="material" />
+    </instancedMesh>
+  )
+}
+
+function App() {
+  return (
+    <>
+      <Canvas camera={{ fov: 50, position: [0, 0, 30] }}>
+        <Suspense fallback={null}>
+          <Background />
+          <Diamonds />
+        </Suspense>
+      </Canvas>
+      <h1>
+        three
+        <br />
+        zero
+        <br />
+        seven.
+      </h1>
+      <a href="https://github.com/drcmda/react-three-fiber">Github</a>
+    </>
+  )
+}
+
+ReactDOM.render(<App />, document.getElementById("root"))

+ 74 - 0
src/refraction-material/index.js

@@ -0,0 +1,74 @@
+import { ShaderMaterial } from "three"
+
+export default class RefractionMaterial extends ShaderMaterial {
+  constructor(options) {
+    super({
+      vertexShader: `varying vec3 worldNormal;
+      varying vec3 viewDirection;
+      void main() {
+
+        vec4 transformedNormal = vec4(normal, 0.);
+        vec4 transformedPosition = vec4(position, 1.0);
+        #ifdef USE_INSTANCING
+          transformedNormal = instanceMatrix * transformedNormal;
+          transformedPosition = instanceMatrix * transformedPosition;
+        #endif
+
+        vec4 worldPosition = modelMatrix * vec4( position, 1.0);
+        worldNormal = normalize( modelViewMatrix * transformedNormal).xyz;
+        viewDirection = normalize(worldPosition.xyz - cameraPosition);;
+        gl_Position = projectionMatrix * modelViewMatrix * transformedPosition;
+      }`,
+      fragmentShader: `uniform sampler2D envMap;
+      uniform sampler2D backfaceMap;
+      uniform vec2 resolution;
+      
+      varying vec3 worldNormal;
+      varying vec3 viewDirection;
+      
+      float ior = 1.5;
+      float a = 0.33;
+      
+      vec3 fogColor = vec3(1.0);
+      vec3 reflectionColor = vec3(1.0);
+      
+      float fresnelFunc(vec3 viewDirection, vec3 worldNormal) {
+        return pow( 1.08 + dot( viewDirection, worldNormal), 10.0 );
+      }
+      
+      void main() {
+        // screen coordinates
+        vec2 uv = gl_FragCoord.xy / resolution;
+      
+        // sample backface data from texture
+        vec3 backfaceNormal = texture2D(backfaceMap, uv).rgb;
+      
+        // combine backface and frontface normal
+        vec3 normal = worldNormal * (1.0 - a) - backfaceNormal * a;
+      
+        // calculate refraction and apply to uv
+        vec3 refracted = refract(viewDirection, normal, 1.0/ior);
+        uv += refracted.xy;
+      
+        // sample environment texture
+        vec4 tex = texture2D(envMap, uv);
+      
+        // calculate fresnel
+        float fresnel = fresnelFunc(viewDirection, normal);
+      
+        vec4 color = tex;
+      
+        // apply fresnel
+        color.rgb = mix(color.rgb, reflectionColor, fresnel);
+      
+        gl_FragColor = vec4(color.rgb, 1.0);
+      }`
+    })
+
+    this.uniforms = {
+      envMap: { value: options.envMap },
+      backfaceMap: { value: options.backfaceMap },
+      resolution: { value: options.resolution }
+    }
+  }
+}

+ 60 - 0
src/styles.css

@@ -0,0 +1,60 @@
+* {
+  box-sizing: border-box;
+}
+
+html,
+body,
+#root {
+  width: 100%;
+  height: 100%;
+  margin: 0;
+  padding: 0;
+  -webkit-touch-callout: none;
+  -webkit-user-select: none;
+  -khtml-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  overflow: hidden;
+}
+
+#root {
+  overflow: auto;
+}
+
+body {
+  position: fixed;
+  overflow: hidden;
+  overscroll-behavior-y: none;
+  font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, ubuntu, roboto, noto, segoe ui, arial, sans-serif;
+  color: black;
+}
+
+h1 {
+  position: absolute;
+  top: 60px;
+  left: 50px;
+  font-family: "Roboto", sans-serif;
+  font-size: 12em;
+  margin: 0;
+  color: white;
+  line-height: 0.58em;
+  letter-spacing: -5px;
+}
+
+@media only screen and (max-width: 800px) {
+  h1 {
+    font-size: 6em;
+    letter-spacing: -1px;
+  }
+}
+
+a {
+  position: absolute;
+  bottom: 50px;
+  right: 50px;
+  margin: 0;
+  color: white;
+  text-decoration: none;
+  font-size: 1.2em;
+}