FDNavigate back to the homepage

Realtime Effects Utilizing Canvas Textures - Expanding Cracked Floor

Rick
November 26th, 2023 · 4 min read

The idea is simple.. we generate a expanding greyscale image on canvas, this can then be used as a displacement map which in defined as a grey scale image!

Instead of having a positive displacement if we tweak it we can get negative displacement, which when played around with - can give very nice cracked look.

Something which seems like it shouldnt be possible live on the web is very accessible to devs wanting to make their scenes more interactive.

All the code is below, go have a look and get some inspiration

General Overview

So the idea is that over time we expand a crack or some kind of Lshape system. We do this on a standard none threejs webgl canvas element, then position it off screen absolutely.

Why and how can this be used?

Well think of it like this we need two things:

  • Timings to be accurate and correct
  • A way to transfer this canvas texture to our ground mesh

And both of these are simple when you think about it, time is constant in both worlds and there is a thing called a CanvasTexture in Threejs which lets us consume a canvas, and we can do this in realtime. Providing the real time vfx effect.

Canvas Texture and generating the cracks

I got the idea from this codepen and then made a prototype.

And then played around to get a prototype of the cracks/lightning on a jsfiddle before trying to integrate into R3F on a codesandbox.

 

Take note.. the logic for the animated 2D canvas texture is in, createCracks.js:

1import * as THREE from "three";
2
3export const render = (
4 lightning,
5 canvas,
6 ctx,
7 vfxPlaneRef,
8 center,
9 size,
10 currentUVs
11) => {
12 let i = 0;
13
14 var brightness = 0.8;
15
16 var counter = setInterval(function () {
17 console.log({ i });
18 const gradient = lightning[i].y / window.innerHeight;
19
20 const grayValue = Math.round(255 * gradient);
21
22 const fillColor = `rgb(${grayValue * brightness}, ${
23 grayValue * brightness
24 }, ${grayValue * brightness})`;
25
26 ctx.beginPath();
27 ctx.fillStyle = fillColor;
28 // /* ctx.arc(lightning[i].x - 2, lightning[i].y - 2, Math.min(Math.max(i, 30.0), 40.0 ) / 5.0, 0, 2 * Math.PI, false);*/
29 ctx.rect(lightning[i].x, lightning[i].y, i / 5.0, i / 5.0);
30 ctx.closePath();
31 ctx.stroke();
32 ctx.fill();
33
34 if (vfxPlaneRef.current) {
35 const canvasTexture = new THREE.CanvasTexture(canvas);
36 canvasTexture.center = new THREE.Vector2(0.5, 0.5);
37 if (currentUVs.x < 0.3) {
38 canvasTexture.rotation = Math.PI / 3;
39 } else if (currentUVs.x > 0.3 && currentUVs.x < 0.7) {
40 canvasTexture.rotation = 0;
41 } else {
42 canvasTexture.rotation = -Math.PI / 3;
43 }
44
45 if (canvasTexture) {
46 vfxPlaneRef.current.material.displacementMap = canvasTexture;
47 // vfxPlaneRef.current.material.map = canvasTexture;
48 }
49 }
50 if (i > lightning.length / 2.0) {
51 clearInterval(counter);
52 }
53 i += 1;
54 }, 20);
55
56 /* requestAnimationFrame(render) */
57};
58
59export const createLightning = (center, size) => {
60 var minSegmentHeight = 16.0;
61 var groundHeight = window.innerHeight + 290;
62 var roughness = 1.91;
63 var maxDifference = window.innerHeight / 5;
64 var segmentHeight = groundHeight - center.height;
65 var lightning = [];
66 console.log({ size });
67 for (let i = 0; i < 1; i++) {
68 lightning.push({ x: center.x, y: center.y });
69 lightning.push({
70 x: Math.random() * (window.innerWidth - 100) + 50,
71 y: groundHeight + (Math.random() - 0.9) * 50
72 });
73 }
74
75 var currDiff = maxDifference;
76 while (segmentHeight > minSegmentHeight) {
77 var newSegments = [];
78 for (var i = 0; i < lightning.length - 1; i++) {
79 var start = lightning[i];
80 var end = lightning[i + 1];
81 var midX = (start.x + end.x) / 2;
82 var newX = midX + (Math.random() * 2 - 1) * currDiff;
83 newSegments.push(start, { x: newX, y: (start.y + end.y) / 2 });
84 }
85
86 newSegments.push(lightning.pop());
87 lightning = newSegments;
88
89 currDiff /= roughness;
90 segmentHeight /= 2;
91 }
92 return lightning;
93};

Im not going to go through and explain everything, although the concepts require a pretty fluent understanding of JS. There are 2 functions one which creates data for the cracks and then one which renders them over time to the canvas texture - the premise is as follows:

  • A displacement map, by nature, is a black/white and greyscale image/texture. So this is only going to work if we stick to rgba values which remain consistent across color channels i.e.
1// r/g/b
2const color = new Color(0.3, 0.3, 0.3);
3```glsl
4// r/g/b (or remember.. x/y/z);
5vec3 color = vec3(0.3, 0.3, 0.3);

So as long as we have a consistent grey scale then we can create the displacement map. And we can apply this to a MeshStandardMaterial or ShaderMaterial manually in the vertex shader. The general steps in this

  • We determine where to start on the canvas texture
  • Create a gradient of white as you go further down the map
  • Randomly move along the map joining these points
  • Creating a cracked/lightning shape

The main setup

Below is all the code for the main file:

1// https://codesandbox.io/s/react-postprocessing-dof-blob-pqrpl?file=/src/App.js
2// https://codesandbox.io/s/the-three-graces-0n9it?file=/src/App.js:1531-1620
3import "./styles.css";
4import React, { useEffect, useCallback, useRef } from "react";
5import { Canvas, useFrame, useLoader, useThree } from "@react-three/fiber";
6import {
7 OrbitControls,
8 Environment,
9 Lightformer,
10 Stats
11} from "@react-three/drei";
12import {
13 EffectComposer,
14 DepthOfField,
15 Bloom,
16 Noise,
17 Vignette
18} from "@react-three/postprocessing";
19import * as THREE from "three";
20import { easing } from "maath";
21import { render, createLightning } from "./createCracks";
22import { CanvasTexture } from "three";
23
24const Plane = () => {
25 const { size } = useThree();
26 const SIZE_OF_VFX_PLANE_DIMENSIONS = 100;
27 const emptyLookAtRef = useRef(null);
28 const vfxPlaneRef = useRef(null);
29 const canvas = useRef(document.getElementById("canvas"));
30 const defaultCanvasTex = useRef(
31 new THREE.CanvasTexture(document.getElementById("canvas"))
32 );
33 const xzMoveToVFXOrigin = useRef({ x: 0.0, z: 0.0 });
34 const xzUVSMoveToVFXOrigin = useRef({ x: 0.0, z: 0.0 });
35 const tempUvVec2 = new THREE.Vector2();
36 let vfxPlaneClicked = useRef(false);
37
38 const { camera } = useThree();
39
40 const roughnessMap = useLoader(
41 THREE.TextureLoader,
42 "https://pub-8f66c3b1ef444eb3bd205620d22e9ccd.r2.dev/worley-noise.jpg"
43 );
44 roughnessMap.wrapS = roughnessMap.wrapT = THREE.RepeatWrapping;
45
46 useFrame(({ delta, pointer }) => {
47 // console.log({ pointer });
48 if (vfxPlaneRef.current) {
49 const shader = vfxPlaneRef.current.userData.shader;
50
51 if (shader) {
52 tempUvVec2.x = xzUVSMoveToVFXOrigin.current.x;
53 tempUvVec2.y = xzUVSMoveToVFXOrigin.current.y;
54 shader.uniforms.vfxOriginUVs = {
55 value: tempUvVec2
56 };
57 }
58 }
59 easing.damp3(
60 emptyLookAtRef.current.position,
61 [xzMoveToVFXOrigin.current.x, 1.0, xzMoveToVFXOrigin.current.z],
62 0.2,
63 delta
64 );
65 });
66
67 const fireVFX = (point) => {
68 var color = "rgb(0,0,0)";
69
70 var c = document.getElementById("canvas");
71 c.width = size.width;
72 c.height = size.height;
73 var ctx = c.getContext("2d");
74
75 ctx.filter = "blur(4px)";
76 ctx.globalCompositeOperation = "lighter";
77 ctx.filter = "blur(4px)";
78 ctx.globalCompositeOperation = "lighter";
79
80 ctx.strokeStyle = color;
81 ctx.shadowColor = color;
82
83 ctx.fillStyle = color;
84 ctx.fillRect(0, 0, size.width, size.height);
85 ctx.fillStyle = "hsla(0, 0%, 0%, 1.0)";
86
87 const center = new THREE.Vector2(size.width / 2, 30);
88
89 for (let i = 0; i < 4; i++) {
90 const lightning = createLightning(center, size, canvas.current);
91
92 console.log({ lightning });
93
94 if (canvas) {
95 render(
96 lightning,
97 canvas.current,
98 ctx,
99 vfxPlaneRef,
100 point,
101 size,
102 xzUVSMoveToVFXOrigin.current
103 );
104 }
105
106 vfxPlaneClicked.current = false;
107 }
108 };
109
110 const OBC = (shader) => {
111 // shaders...
112 };
113
114 return (
115 <group>
116 <mesh dispose={null} ref={emptyLookAtRef} position={[0, 1.0, 0.0]}>
117 <sphereGeometry />
118 <meshPhysicalMaterial
119 roughness={0.0}
120 reflectivity={0.0}
121 color="black"
122 roughnessMap={roughnessMap}
123 />
124 </mesh>
125 <mesh
126 dispose={null}
127 ref={vfxPlaneRef}
128 rotation={[-Math.PI / 2, 0, Math.PI / 2]}
129 position={[0, 0.0, 1.0]}
130 onPointerMove={({ point, uv }) => {
131 if (emptyLookAtRef.current) {
132 if (
133 point.x > -50.0 &&
134 point.x < 10.0 &&
135 point.z < 50.0 &&
136 point.z > -50.0
137 ) {
138 xzMoveToVFXOrigin.current = {
139 x: point.x,
140 z: point.z
141 };
142
143 // emptyLookAtRef.current.position.x = point.x;
144 // emptyLookAtRef.current.position.z = point.z;
145 emptyLookAtRef.current.lookAt(new THREE.Vector3(100, -1, 0));
146 }
147 }
148 }}
149 onClick={({ uv, intersections }) => {
150 const point = intersections[0].point;
151 xzUVSMoveToVFXOrigin.current = {
152 x: uv.x,
153 y: 1.0 - uv.y
154 };
155 console.log({ vfxPlaneClicked });
156 if (!vfxPlaneClicked.current) {
157 fireVFX(point);
158 }
159 vfxPlaneClicked = true;
160 }}
161 >
162 <planeGeometry
163 args={[
164 SIZE_OF_VFX_PLANE_DIMENSIONS,
165 SIZE_OF_VFX_PLANE_DIMENSIONS,
166 100,
167 100
168 ]}
169 />
170 {/* <meshStandardMaterial
171 attach="material"
172 side={THREE.DoubleSide}
173 color="#171717"
174 metalness={1.0}
175 roughness={0}
176 /> */}
177 <meshPhysicalMaterial
178 roughness={0.2}
179 roughnessMap={roughnessMap}
180 fog={true}
181 // displacementMap={null}
182 onBeforeCompile={OBC}
183 displacementScale={-20.3}
184 >
185 <canvasTexture
186 attach="displacementMap"
187 image={defaultCanvasTex.current}
188 dispose={null}
189 />
190 <canvasTexture
191 attach="map"
192 dispose={null}
193 image={defaultCanvasTex.current}
194 />
195 </meshPhysicalMaterial>
196 </mesh>
197
198 <mesh
199 rotation-y={-Math.PI / 2}
200 rotation-z={-Math.PI / 2}
201 position={[100, -1, 0]}
202 scale={[1000, 52.0, 1]}
203 dispose={null}
204 >
205 <planeGeometry />
206 <meshStandardMaterial
207 color="#bef0ff"
208 fog={false}
209 emissive="#bef0ff"
210 emissiveIntensity={1.6}
211 roughnessMap={roughnessMap}
212 />
213 </mesh>
214
215 <Environment>
216 <Lightformer
217 intensity={6.75}
218 rotation-y={-Math.PI / 2}
219 rotation-z={-Math.PI / 2}
220 position={[4, 0, 0]}
221 scale={[10, 2.0, 1]}
222 color="#bef0ff"
223 />
224 </Environment>
225 </group>
226 );
227};
228
229export default function App() {
230 return (
231 <div className="App">
232 <div class="details">
233 <p>- Rick Thompson -</p>
234 <a href="mailto:[email protected]">(theFrontDev)</a>
235 </div>
236 <canvas id="canvas" />
237 <Canvas
238 camera={{ position: [-20, 1, 0] }}
239 onCreated={({ gl }) => {
240 gl.antiAliasing = true;
241 gl.useLegacyLights = true;
242 }}
243 >
244 <OrbitControls
245 enableDamping
246 minAzimuthAngle={-Math.PI / 1.7}
247 maxAzimuthAngle={-Math.PI / 2.4}
248 minPolarAngle={Math.PI / 2.6}
249 maxPolarAngle={Math.PI - Math.PI / 1.8}
250 target={[4, 1, 0]}
251 maxDistance={15.0}
252 minDistance={5.0}
253 />
254
255 {/* <OrbitControls /> */}
256
257 <Plane />
258 {/* <gridHelper /> */}
259 <fog attach="fog" color="black" near={0.1} far={72} />
260 <color attach="background" args={["black"]} />
261 <Stats />
262 {/* <axesHelper scale={[20, 20, 20]} /> */}
263 {/* <ambientLight intensity={100} /> */}
264
265 <EffectComposer multisampling={0} disableNormalPass={true}>
266 <Bloom
267 luminanceThreshold={0.004}
268 luminanceSmoothing={4.9}
269 height={300}
270 opacity={1.0}
271 />
272 <Noise opacity={0.2} />
273 <Vignette eskil={false} offset={0.07} darkness={1.1} />
274 </EffectComposer>
275 </Canvas>
276 </div>
277 );
278}

There are a few things:

  • Plane for the door and increassed emissive properties for the postprocessing bloom

  • And then the moving ball

  • Floor is a mixture of a roughness map, noise and shininess

  • Cracks setup

The plane / door

The door is a plane with some emissive properties set on it to give it a blue tinge, with bloom:

1<mesh
2 rotation-y={-Math.PI / 2}
3 rotation-z={-Math.PI / 2}
4 position={[100, -1, 0]}
5 scale={[1000, 52.0, 1]}
6 dispose={null}
7>
8 <planeGeometry />
9 <meshStandardMaterial
10 color="#bef0ff"
11 fog={false}
12 emissive="#bef0ff"
13 emissiveIntensity={1.6}
14 roughnessMap={roughnessMap}
15 />
16</mesh>
17
18// more code...
19
20<EffectComposer multisampling={0} disableNormalPass={true}>
21 <Bloom
22 luminanceThreshold={0.004}
23 luminanceSmoothing={4.9}
24 height={300}
25 opacity={1.0}
26 />
27 <Noise opacity={0.2} />
28 <Vignette eskil={false} offset={0.07} darkness={1.1} />
29</EffectComposer>

This gives the plane its colourful tinge material look and then some glow via postprocessing.

The Moving Ball

A ball is used as a prop to initiate the cracks in the floor. The origin of where it is, is kept and used to move the sphere / passed to the shader for calculations. And the moving is dampened by this library easing. Which gives its smooth look and feel.

1const xzMoveToVFXOrigin = useRef({ x: 0.0, z: 0.0 });
2const xzUVSMoveToVFXOrigin = useRef({ x: 0.0, z: 0.0 });
3const tempUvVec2 = new THREE.Vector2();
4
5// code...
6
7useFrame(({ delta, pointer }) => {
8 if (vfxPlaneRef.current) {
9 // code..
10
11 if (shader) {
12 tempUvVec2.x = xzUVSMoveToVFXOrigin.current.x;
13 tempUvVec2.y = xzUVSMoveToVFXOrigin.current.y;
14 shader.uniforms.vfxOriginUVs = {
15 value: tempUvVec2,
16 };
17 }
18 }
19 easing.damp3(
20 emptyLookAtRef.current.position,
21 [xzMoveToVFXOrigin.current.x, 1.0, xzMoveToVFXOrigin.current.z],
22 0.2,
23 delta,
24 );
25});
26
27// code..
28
29<mesh dispose={null} ref={emptyLookAtRef} position={[0, 1.0, 0.0]}>
30 <sphereGeometry />
31 <meshPhysicalMaterial
32 roughness={0.0}
33 reflectivity={0.0}
34 color="black"
35 roughnessMap={roughnessMap}
36 />
37</mesh>

The Floor

So this is a pretty basic setup we have modified the material by using onBeforeCompile. This way we can have all the MeshStandardMaterial properties and modify them slighly for the vfx.

The roughness map is just a grey scaled noise texture which can then be scaled if you want to. Loaded like so:

1const roughnessMap = useLoader(
2 THREE.TextureLoader,
3 "https://pub-8f66c3b1ef444eb3bd205620d22e9ccd.r2.dev/worley-noise.jpg",
4);

The shinniness and reflectivity has been drastically increased on the material. Then the roughness map can be provided to the material in the roughnessMap property and gives patchy less shiny areas.

And the beauty of this is that because we are using onBeforeCompile all we need to do to modify the vertices or roughness is find this shader chunk or relevant code in the main shader file on github and replace this code snippet with itself and some modified code. I will explain later what has been achieved through the shader.

Cracks Setup

The setup consists of creating an arbitary number of cracks per click. And then passing the canvas texture to the maps properties declartively. There are two functions which create the data and then the render function which consumes this data over time, allowing for fine control over animating the off screen canvas texture.

1//Refs:
2const defaultCanvasTex = useRef(
3 new THREE.CanvasTexture(document.getElementById("canvas")),
4);

The default canvas is just the offscreen canvas where we are going to animate the cracks, and we can either just position out of the viewport or just create the element without adding to the DOM or JSX.

Then we Define the maps like so below (this took alot of trial and error to get working):

1<mesh
2 dispose={null}
3 ref={vfxPlaneRef}
4 rotation={[-Math.PI / 2, 0, Math.PI / 2]}
5 position={[0, 0.0, 1.0]}
6 onPointerMove={({ point, uv }) => {
7 if (emptyLookAtRef.current) {
8 if (
9 point.x > -50.0 &&
10 point.x < 10.0 &&
11 point.z < 50.0 &&
12 point.z > -50.0
13 ) {
14 xzMoveToVFXOrigin.current = {
15 x: point.x,
16 z: point.z,
17 };
18
19 // emptyLookAtRef.current.position.x = point.x;
20 // emptyLookAtRef.current.position.z = point.z;
21 emptyLookAtRef.current.lookAt(new THREE.Vector3(100, -1, 0));
22 }
23 }
24 }}
25 onClick={({ uv, intersections }) => {
26 const point = intersections[0].point;
27 xzUVSMoveToVFXOrigin.current = {
28 x: uv.x,
29 y: 1.0 - uv.y,
30 };
31
32 if (!vfxPlaneClicked.current) {
33 fireVFX(point);
34 }
35 vfxPlaneClicked = true;
36 }}
37>
38 <planeGeometry
39 args={[
40 SIZE_OF_VFX_PLANE_DIMENSIONS,
41 SIZE_OF_VFX_PLANE_DIMENSIONS,
42 100,
43 100,
44 ]}
45 />
46 {/* <meshStandardMaterial
47 attach="material"
48 side={THREE.DoubleSide}
49 color="#171717"
50 metalness={1.0}
51 roughness={0}
52/> */}
53 <meshPhysicalMaterial
54 roughness={0.2}
55 roughnessMap={roughnessMap}
56 fog={true}
57 // displacementMap={null}
58 onBeforeCompile={OBC}
59 displacementScale={-20.3}
60 >
61 <canvasTexture
62 attach="displacementMap"
63 image={defaultCanvasTex.current}
64 dispose={null}
65 />
66 <canvasTexture
67 attach="map"
68 dispose={null}
69 image={defaultCanvasTex.current}
70 />
71 </meshPhysicalMaterial>
72</mesh>

I suppose the key bit here is declaring the canvas textures declaritively:

1<meshPhysicalMaterial
2 roughness={0.2}
3 roughnessMap={roughnessMap}
4 fog={true}
5 onBeforeCompile={OBC}
6 displacementScale={-20.3}
7>
8 <canvasTexture
9 attach="displacementMap"
10 image={defaultCanvasTex.current}
11 dispose={null}
12 />
13 <canvasTexture
14 attach="map"
15 dispose={null}
16 image={defaultCanvasTex.current}
17 />
18</meshPhysicalMaterial>

I found putting this in the colour map and the displacementMap slot to work well.

Shaders

The shaders are modifiying existing code in the material. The shader code looks like so:

1shader.uniforms = {
2 ...shader.uniforms,
3 ...{
4 vfxOriginUVs: {
5 value: new THREE.Vector2(),
6 },
7 },
8};
1shader.vertexShader = shader.vertexShader.replace(
2 `#include <clipping_planes_pars_vertex>`,
3 `
4 #include <clipping_planes_pars_vertex>
5 varying vec3 vPosition;
6 `,
7);
8
9shader.vertexShader = shader.vertexShader.replace(
10 `#include <displacementmap_pars_vertex>`,
11 `
12 #ifdef USE_DISPLACEMENTMAP
13
14 uniform sampler2D displacementMap;
15 uniform float displacementScale;
16 uniform float displacementBias;
17 uniform vec2 vfxOriginUVs;
18
19 #endif
20 `,
21);
22
23shader.vertexShader = shader.vertexShader.replace(
24 `#include <displacementmap_vertex>`,
25 `
26 #ifdef USE_DISPLACEMENTMAP
27
28 transformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv).x * displacementScale + displacementBias );
29 vPosition = transformed;
30 #endif
31 `,
32);

First of we declare a varying to be passed to the fragment shader vPosition. The we do another replace and add some displacement uniforms into the #ifdef for the original displacement uniforms.

The key bit is that on the mesh code where we define the displacement map we define this aswell:

1<meshPhysicalMaterial
2 roughness={0.2}
3 roughnessMap={roughnessMap}
4 fog={true}
5 onBeforeCompile={OBC}
6 displacementScale={-20.3}
7>

So we want cracks / negative displacement and not positive, so…

1displacementScale={-20.3}

The fragmentShader modifications:

1shader.fragmentShader = shader.fragmentShader.replace(
2 `#include <clipping_planes_pars_fragment>`,
3 `
4 #include <clipping_planes_pars_fragment>
5 varying vec3 vPosition;
6 `,
7);
8
9// Modify the roughness calculation to account for Y threshold
10shader.fragmentShader = shader.fragmentShader.replace(
11 `#include <roughnessmap_fragment>`,
12 `
13
14 float roughnessFactor = roughness;
15
16 #ifdef USE_ROUGHNESSMAP
17
18 vec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv * 4.0 );
19
20 // reads channel G, compatible with a combined OcclusionRoughnessMetallic (RGB) texture
21 roughnessFactor *= texelRoughness.g;
22
23 #endif
24
25 if (vPosition.z < 0.0) {
26 roughnessFactor = mix(0.01, roughnessFactor +0.6, abs(vPosition.z) );
27 }
28 `,
29);
30
31shader.fragmentShader = shader.fragmentShader.replace(
32 `#include <dithering_fragment>`,
33 `
34 #include <dithering_fragment>
35
36
37 if (vPosition.z < -0.5) {
38 gl_FragColor.r = mix(mix(0.006,0.008,abs(vPosition.z + 0.5) / 2.0), 0.0017, abs(vPosition.z + 0.6) / 2.3);
39 }
40 `,
41);

So we get the vPosition, this is passed as a varying and then use to create a gradient with this code along the edges of the cracks so its not a sharp change and looks more natural int erms of colours with the scene. Based on how far the vertex (via vPosition) is below the ground mesh. The gradient is as follows:

1if (vPosition.z < -0.5) {
2 gl_FragColor.r = mix(
3 mix(0.006,0.008,abs(vPosition.z + 0.5) / 2.0),
4 0.0017,
5 abs(vPosition.z + 0.6) / 2.3
6 );
7}

The mix has two edges, the further up sections (light blue on the cracks) mix(0.006,0.008,abs(vPosition.z + 0.5) / 2.0) and then the blackish deep bits to the cracks 0.0017. And then mix these on how deep the crack is: abs(vPosition.z + 0.6) / 2.3, only using the absolute value to simplify things.

And finally we do some slight modifications and multiplications to the roughness code and also store the roughness value into a roughnessFactor to use in creating a roughness gradient aswell as a colour gradient.

1float roughnessFactor = roughness;
2
3#ifdef USE_ROUGHNESSMAP
4
5 vec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv * 4.0 );
6
7 // reads channel G, compatible with a combined OcclusionRoughnessMetallic (RGB) texture
8 roughnessFactor *= texelRoughness.g;
9
10#endif

Trigger the Lightning

There is a simple onClick callback which gets fired on the floor mesh:

1onClick={({ uv, intersections }) => {
2 const point = intersections[0].point;
3 xzUVSMoveToVFXOrigin.current = {
4 x: uv.x,
5 y: 1.0 - uv.y,
6 };
7
8 if (!vfxPlaneClicked.current) {
9 fireVFX(point);
10 }
11 vfxPlaneClicked = true;
12}}

The origin of the VFX cracks for the shader is set by passing the uvs to a ref value.

Then pass the point at which the click occured on the floor to the fire function shown above, which kicks of the animated canvas texture and also gets passed into the material declaritively.

 

More articles from theFrontDev

Pulsating Volume in r3f and three

This was a experiment producing a volume which was dynamic and moving. This piece involved using planes, noise and transparency. The final result being a pulsating colourful volume using psuedo normals to calculate how the light interacts with the planes in a shaderMaterial.

November 18th, 2023 · 2 min read

Order Indpendent transparency, webgl and opaqueness

In our latest blog post, we dissect the transparency issue within WebGl, shedding light on the technical intricacies that govern rendering. We explore the nuances of prioritizing opaque meshes, revealing how this hierarchy can impact the visual landscape. Discover how to navigate this digital terrain, ensuring that your web graphics and visualizations maintain fidelity in the face of transparency challenges, even when utilizing lights.

November 11th, 2023 · 7 min read
© 2021–2024 theFrontDev
Link to $https://twitter.com/TheFrontDevLink to $https://github.com/Richard-ThompsonLink to $https://www.linkedin.com/in/richard-thompson-248ba3111/