Skip to content

Commit 92ddad6

Browse files
authored
feat: expose Caustics materials and types as public API (#64)
This PR exposes Caustics materials and types as public API
1 parent 3823ad8 commit 92ddad6

File tree

2 files changed

+163
-90
lines changed

2 files changed

+163
-90
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,33 @@ export type CausticsType = {
378378
}
379379
```
380380

381+
##### Integrating with frontend frameworks
382+
383+
If you are using a frontend framework, the construction of `Caustics` effect by calling `Caustics()` _might not_ be enough due to how frameworks handle the component life-cycle, changes when props change, and content projection / rendering children.
384+
385+
To accommodate this use-case, `@pmndrs/vanilla` exports the following symbols to help you integrate the caustics effect with your frontend framework:
386+
387+
- `CausticsProjectionMaterial`: A material that projects the caustics onto the catcher plane.
388+
- `CausticsMaterial`: A material that renders the caustics.
389+
- `createCausticsUpdate`: A function that accepts an `updateParameters` function/getter and creates an `update` function for the caustics effect. This function should be called in the animation loop implementation of your framework, and `updateParameters` should return the latest value of the parameters based on your framework's state management.
390+
391+
```ts
392+
export function createCausticsUpdate(
393+
updateParameters: () => {
394+
params: Omit<CausticsProps, 'color'>
395+
scene: THREE.Scene
396+
group: THREE.Group
397+
camera: THREE.OrthographicCamera
398+
plane: THREE.Mesh<PlaneGeometry, InstanceType<typeof CausticsProjectionMaterial>>
399+
normalTarget: THREE.WebGLRenderTarget
400+
normalTargetB: THREE.WebGLRenderTarget
401+
causticsTarget: THREE.WebGLRenderTarget
402+
causticsTargetB: THREE.WebGLRenderTarget
403+
helper?: THREE.CameraHelper | null
404+
}
405+
): (gl: THREE.WebGLRenderer) => void
406+
```
407+
381408
#### Cloud
382409

383410
[![storybook](https://img.shields.io/badge/-storybook-%23ff69b4)](https://pmndrs.github.io/drei-vanilla/?path=/story/staging-clouds--cloud-story)

src/core/Caustics.ts

Lines changed: 136 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import * as THREE from 'three'
22
import { shaderMaterial } from './shaderMaterial'
33
import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass'
44
import { useFBO } from './useFBO'
5+
import { PlaneGeometry } from 'three'
56

67
const isVector3 = (object: any): object is THREE.Vector3 => object?.isVector3
78

8-
type CausticsProjectionMaterialType = THREE.MeshNormalMaterial & {
9+
export type CausticsProjectionMaterialType = THREE.MeshNormalMaterial & {
910
viewMatrix: { value?: THREE.Matrix4 }
1011
color?: THREE.Color
1112
causticsTexture?: THREE.Texture
@@ -14,7 +15,7 @@ type CausticsProjectionMaterialType = THREE.MeshNormalMaterial & {
1415
lightViewMatrix?: THREE.Matrix4
1516
}
1617

17-
type CausticsProps = {
18+
export type CausticsProps = {
1819
/** How many frames it will render, set it to Infinity for runtime, default: 1 */
1920
frames?: number
2021
/** Will display caustics only and skip the models, default: false */
@@ -60,15 +61,15 @@ function createNormalMaterial(side: THREE.Side = THREE.FrontSide) {
6061
})
6162
}
6263

63-
type CausticsProjectionShaderType = {
64+
export type CausticsProjectionShaderType = {
6465
causticsTexture?: THREE.Texture | null
6566
causticsTextureB?: THREE.Texture | null
6667
color?: THREE.Color
6768
lightProjMatrix?: THREE.Matrix4
6869
lightViewMatrix?: THREE.Matrix4
6970
}
7071

71-
const CausticsProjectionMaterial = shaderMaterial<CausticsProjectionShaderType>(
72+
export const CausticsProjectionMaterial = shaderMaterial<CausticsProjectionShaderType>(
7273
{
7374
causticsTexture: null,
7475
causticsTextureB: null,
@@ -101,7 +102,7 @@ const CausticsProjectionMaterial = shaderMaterial<CausticsProjectionShaderType>(
101102
}`
102103
)
103104

104-
type CausticsMaterialType = {
105+
export type CausticsMaterialType = {
105106
cameraMatrixWorld: THREE.Matrix4
106107
cameraProjectionMatrixInv: THREE.Matrix4
107108
normalTexture: THREE.Texture | null
@@ -120,7 +121,7 @@ type CausticsMaterialType = {
120121
intensity: number
121122
}
122123

123-
const CausticsMaterial = shaderMaterial<CausticsMaterialType>(
124+
export const CausticsMaterial = shaderMaterial<CausticsMaterialType>(
124125
{
125126
cameraMatrixWorld: new THREE.Matrix4(),
126127
cameraProjectionMatrixInv: new THREE.Matrix4(),
@@ -277,86 +278,27 @@ export type CausticsType = {
277278
causticsTargetB: THREE.WebGLRenderTarget
278279
}
279280

280-
export const Caustics = (
281-
renderer: THREE.WebGLRenderer,
282-
{
283-
frames = 1,
284-
causticsOnly = false,
285-
ior = 1.1,
286-
backside = false,
287-
backsideIOR = 1.1,
288-
worldRadius = 0.3125,
289-
color = new THREE.Color('white'),
290-
intensity = 0.05,
291-
resolution = 2024,
292-
lightSource = new THREE.Vector3(1, 1, 1),
293-
near = 0.1,
294-
far = 0, // auto calculates if zero
295-
}: CausticsProps = {}
296-
): CausticsType => {
297-
const params = {
298-
frames,
299-
ior,
300-
color,
301-
causticsOnly,
302-
backside,
303-
backsideIOR,
304-
worldRadius,
305-
intensity,
306-
resolution,
307-
lightSource,
308-
near,
309-
far,
281+
export function createCausticsUpdate(
282+
updateParameters: () => {
283+
params: Omit<CausticsProps, 'color'>
284+
scene: THREE.Scene
285+
group: THREE.Group
286+
camera: THREE.OrthographicCamera
287+
plane: THREE.Mesh<PlaneGeometry, InstanceType<typeof CausticsProjectionMaterial>>
288+
normalTarget: THREE.WebGLRenderTarget
289+
normalTargetB: THREE.WebGLRenderTarget
290+
causticsTarget: THREE.WebGLRenderTarget
291+
causticsTargetB: THREE.WebGLRenderTarget
292+
helper?: THREE.CameraHelper | null
310293
}
311-
const group = new THREE.Group()
312-
group.name = 'caustics_group'
313-
const ref = group
314-
315-
const camera = new THREE.OrthographicCamera()
316-
317-
const scene = new THREE.Scene()
318-
scene.name = 'caustics_scene'
319-
320-
const gl = renderer
321-
322-
const helper = new THREE.CameraHelper(camera)
323-
helper.name = 'caustics_helper'
324-
325-
// Buffers for front and back faces
326-
const res = params.resolution
327-
const normalTarget = useFBO(res, res, NORMALPROPS)
328-
const normalTargetB = useFBO(res, res, NORMALPROPS)
329-
const causticsTarget = useFBO(res, res, CAUSTICPROPS)
330-
const causticsTargetB = useFBO(res, res, CAUSTICPROPS)
294+
) {
331295
// Normal materials for front and back faces
332296
const normalMat = createNormalMaterial()
333297
const normalMatB = createNormalMaterial(THREE.BackSide)
334298
// The quad that catches the caustics
335299
const causticsMaterial = new CausticsMaterial()
336300
const causticsQuad = new FullScreenQuad(causticsMaterial)
337301

338-
const plane = new THREE.Mesh(
339-
new THREE.PlaneGeometry(1, 1),
340-
new CausticsProjectionMaterial({
341-
transparent: true,
342-
color: params.color,
343-
causticsTexture: causticsTarget.texture,
344-
causticsTextureB: causticsTargetB.texture,
345-
blending: THREE.CustomBlending,
346-
blendSrc: THREE.OneFactor,
347-
blendDst: THREE.SrcAlphaFactor,
348-
depthWrite: false,
349-
})
350-
)
351-
352-
plane.name = 'caustics_plane'
353-
plane.rotation.x = -Math.PI / 2
354-
plane.renderOrder = 2
355-
group.add(scene, plane)
356-
// scene.add(activeModel) //add glb to caustics scene
357-
// mainObjects.add(group, helper) // add entire group to scene
358-
group.updateWorldMatrix(false, true)
359-
360302
let count = 0
361303

362304
const v = new THREE.Vector3()
@@ -383,10 +325,26 @@ export const Caustics = (
383325
lightDirs.push(new THREE.Vector3())
384326
}
385327

386-
const update = () => {
387-
if (params.frames === Infinity || count++ < params.frames) {
388-
if (isVector3(lightSource)) lightDir.copy(lightSource).normalize()
389-
else lightDir.copy(ref.worldToLocal(lightSource.getWorldPosition(v)).normalize())
328+
return function update(gl: THREE.WebGLRenderer) {
329+
const {
330+
params,
331+
helper,
332+
camera,
333+
plane,
334+
normalTarget,
335+
normalTargetB,
336+
causticsTarget,
337+
causticsTargetB,
338+
scene,
339+
group,
340+
} = updateParameters()
341+
342+
if (params.frames === Infinity || (params.frames && count++ < params.frames)) {
343+
if (isVector3(params.lightSource)) {
344+
lightDir.copy(params.lightSource).normalize()
345+
} else if (params.lightSource) {
346+
lightDir.copy(group.worldToLocal(params.lightSource.getWorldPosition(v)).normalize())
347+
}
390348

391349
lightDirInv.copy(lightDir).multiplyScalar(-1)
392350

@@ -422,7 +380,7 @@ export const Caustics = (
422380
camera.right = radius
423381
camera.top = radius
424382
camera.bottom = -radius
425-
camera.near = params.near
383+
if (params.near) camera.near = params.near
426384
if (params.far) {
427385
camera.far = params.far
428386
} else {
@@ -449,7 +407,7 @@ export const Caustics = (
449407
plane.scale.setScalar(maxSize)
450408
plane.position.copy(centerPos)
451409

452-
if (helper.parent) helper.update()
410+
if (helper?.parent) helper.update()
453411

454412
// Inject uniforms
455413
normalMatB.viewMatrix.value = normalMat.viewMatrix.value = camera.matrixWorldInverse
@@ -467,10 +425,11 @@ export const Caustics = (
467425

468426
causticsMaterial.near = camera.near
469427
causticsMaterial.far = camera.far
470-
causticsMaterial.resolution = params.resolution
428+
if (params.resolution) causticsMaterial.resolution = params.resolution
471429
causticsMaterial.size = radius
472-
causticsMaterial.intensity = params.intensity
473-
causticsMaterial.worldRadius = params.worldRadius
430+
431+
if (params.intensity) causticsMaterial.intensity = params.intensity
432+
if (params.worldRadius) causticsMaterial.worldRadius = params.worldRadius
474433

475434
// Switch the scene on
476435
scene.visible = true
@@ -492,7 +451,7 @@ export const Caustics = (
492451
// Remove the override material
493452
scene.overrideMaterial = null
494453
// Render front face caustics
495-
causticsMaterial.ior = params.ior
454+
if (params.ior) causticsMaterial.ior = params.ior
496455
plane.material.lightProjMatrix = camera.projectionMatrix
497456
plane.material.lightViewMatrix = camera.matrixWorldInverse
498457
causticsMaterial.normalTexture = normalTarget.texture
@@ -502,7 +461,7 @@ export const Caustics = (
502461
causticsQuad.render(gl)
503462

504463
// Render back face caustics, if enabled
505-
causticsMaterial.ior = params.backsideIOR
464+
if (params.backsideIOR) causticsMaterial.ior = params.backsideIOR
506465
causticsMaterial.normalTexture = normalTargetB.texture
507466
causticsMaterial.depthTexture = normalTargetB.depthTexture
508467
gl.setRenderTarget(causticsTargetB)
@@ -516,13 +475,100 @@ export const Caustics = (
516475
if (params.causticsOnly) scene.visible = false
517476
}
518477
}
478+
}
479+
480+
export const Caustics = (
481+
renderer: THREE.WebGLRenderer,
482+
{
483+
frames = 1,
484+
causticsOnly = false,
485+
ior = 1.1,
486+
backside = false,
487+
backsideIOR = 1.1,
488+
worldRadius = 0.3125,
489+
color = new THREE.Color('white'),
490+
intensity = 0.05,
491+
resolution = 2024,
492+
lightSource = new THREE.Vector3(1, 1, 1),
493+
near = 0.1,
494+
far = 0, // auto calculates if zero
495+
}: CausticsProps = {}
496+
): CausticsType => {
497+
const params = {
498+
frames,
499+
ior,
500+
color,
501+
causticsOnly,
502+
backside,
503+
backsideIOR,
504+
worldRadius,
505+
intensity,
506+
resolution,
507+
lightSource,
508+
near,
509+
far,
510+
}
511+
const group = new THREE.Group()
512+
group.name = 'caustics_group'
513+
514+
const camera = new THREE.OrthographicCamera()
515+
516+
const scene = new THREE.Scene()
517+
scene.name = 'caustics_scene'
518+
519+
const gl = renderer
520+
521+
const helper = new THREE.CameraHelper(camera)
522+
helper.name = 'caustics_helper'
523+
524+
// Buffers for front and back faces
525+
const res = params.resolution
526+
const normalTarget = useFBO(res, res, NORMALPROPS)
527+
const normalTargetB = useFBO(res, res, NORMALPROPS)
528+
const causticsTarget = useFBO(res, res, CAUSTICPROPS)
529+
const causticsTargetB = useFBO(res, res, CAUSTICPROPS)
530+
531+
const plane = new THREE.Mesh(
532+
new THREE.PlaneGeometry(1, 1),
533+
new CausticsProjectionMaterial({
534+
transparent: true,
535+
color: params.color,
536+
causticsTexture: causticsTarget.texture,
537+
causticsTextureB: causticsTargetB.texture,
538+
blending: THREE.CustomBlending,
539+
blendSrc: THREE.OneFactor,
540+
blendDst: THREE.SrcAlphaFactor,
541+
depthWrite: false,
542+
})
543+
)
544+
545+
plane.name = 'caustics_plane'
546+
plane.rotation.x = -Math.PI / 2
547+
plane.renderOrder = 2
548+
group.add(scene, plane)
549+
// scene.add(activeModel) //add glb to caustics scene
550+
// mainObjects.add(group, helper) // add entire group to scene
551+
group.updateWorldMatrix(false, true)
552+
553+
const update = createCausticsUpdate(() => ({
554+
params,
555+
scene,
556+
group,
557+
camera,
558+
plane,
559+
normalTarget,
560+
normalTargetB,
561+
causticsTarget,
562+
causticsTargetB,
563+
helper,
564+
}))
519565

520566
return {
521567
scene,
522568
group,
523569
helper,
524570
params,
525-
update,
571+
update: update.bind({}, gl),
526572

527573
normalTarget,
528574
normalTargetB,

0 commit comments

Comments
 (0)