How to make diamond looks real?

Something like this, I guess… I found this code in an old repo online

image

image

1 Like

updated the sandbox with the solution from n8programms: https://twitter.com/N8Programs/status/1569865007042646018

could you guys check if that’s good or is your version faster? here’s the sandboxbox again

it looks pretty damn good :smile:

7 Likes

Yeah, the effect looks very good. I must spend a lot of time learning these. Thank you very much! :smile:

if anyone needs it, i published the raw shader: UNPKG - @react-three/drei (npm: import { MeshRefractionMaterial } from '@react-three/drei/materials/MeshRefractionMaterial'). it will not pull anything from react, it’s just the namespace.

to use it you need to give it a couple of defines without which it can’t work, and a bvh index, you can see how to add both here: drei/MeshRefractionMaterial.tsx at 66750d85d3c3ed5d1289d779acf9377eee159bb1 · pmndrs/drei · GitHub

3 Likes

Any luck making it look more white?

Not sure if this is relivant to the later provided resources but on android the diamond seems to be not rendered at all in the earlier sandbox…

Try adding glare to the diamond, check diamond project, it looks so real

Do we have the source code for the diamond project?

I also cannot render the diamond on the sandbox… any update on this?

any errors you can catch in the console?

Jus this:

THREE.WebGLProgram: Shader Error 0 - VALIDATE_STATUS false
Program Info Log: Could not pack varying viewDirection

there is a post from 2011 that says this means too many varyings. sounds like you will need to ask @drcmda to optimize his shaders :sweat_smile:

i think it’s fixed now, cody made it a few varyings less.

1 Like

@drcmda : I have been trying to incorporate the mesh refraction material in a react project, but we are not using react-three or react-fibre or drei. I just wanted to use the raw shader from unpkg in my typescript project to replace a material for diamonds on a mesh.
I tried to add it to the DOM with useEffect hook, but I am getting an error -

Refused to execute https://unpkg.com/browse/@react-three/drei@9.92.7/materials/MeshRefractionMaterial.js as script because "X-Content-Type-Options: nosniff" was given and its Content-Type is not a script MIME type.

Any idea how I can use this properly and where do I add the defines (I understood how I can pass the bvh)? The example you have mentioned is for react-three/react-fibre. I am using typescript.

this is about unpkg serving it, it’s not a drei thing. i would suggest you copy this material into your own project. then you still need the glue code drei/src/core/MeshRefractionMaterial.tsx at aa2dd0f99fcb33562eff69b57fcb51dbe1d65ff3 · pmndrs/drei · GitHub

first you set the material up, keep in mind that drei uses a shaderMaterial helper that creates auto setter/getters for uniforms. best copy drei/shaderMaterial into your project as well drei/src/core/shaderMaterial.tsx at aa2dd0f99fcb33562eff69b57fcb51dbe1d65ff3 · pmndrs/drei · GitHub.

const material = new MeshRefractionMaterial()
material.resolution = new THREE.Vector2(size.width, size.height)
material.aberrationStrength = aberrationStrength
material.envMap=envMap

the useMemo creates the defines, just copy the contents of it into your code and apply them:

    const temp = {}
    // Sampler2D and SamplerCube need different defines
    const isCubeMap = isCubeTexture(envMap)
    const w = (isCubeMap ? envMap.image[0]?.width : envMap.image.width) ?? 1024
    const cubeSize = w / 4
    const _lodMax = Math.floor(Math.log2(cubeSize))
    const _cubeSize = Math.pow(2, _lodMax)
    const width = 3 * Math.max(_cubeSize, 16 * 7)
    const height = 4 * _cubeSize
    if (isCubeMap) temp.ENVMAP_TYPE_CUBEM = ''
    temp.CUBEUV_TEXEL_WIDTH = `${1.0 / width}`
    temp.CUBEUV_TEXEL_HEIGHT = `${1.0 / height}`
    temp.CUBEUV_MAX_MIP = `${_lodMax}.0`
    // Add defines from chromatic aberration
    if (aberrationStrength > 0) temp.CHROMATIC_ABERRATIONS = ''
    if (fastChroma) temp.FAST_CHROMA = ''
    materal.defines = themp

the useLayoutEffect is about applying the geometry

      material.bvh = new MeshBVHUniformStruct()
      material.bvh.updateFrom(
        new MeshBVH(geometry.clone().toNonIndexed(), { lazyGeneration: false, strategy: SAH })
      )

then the material needs a ticker, these are uniforms as well

function yourFrameLoop() {
    material.viewMatrixInverse = camera.matrixWorld
    material.projectionMatrixInverse = camera.projectionMatrixInverse

this is the reason we haven’t included it into drei-vanilla GitHub - pmndrs/drei-vanilla: 🍦 drei-inspired helpers for threejs it’s way too complex for a simple class and without the component paradigm there would be all this extra glue code the user would have to manage.

Hey @drcmda,

I am trying to implement the code you have suggested. Everything seems to be in place, and everything compiles. No errors in console, but the material is rendering black.

Two questions I have that might have caused issue-

  1. The ‘lazyGeneration’ is not a part of the MeshBVHOptions. I had to remove this parameter in order to compile. Where is this supposed to be defined?
  2. I have added the code for the ticker in Animate function of ThreeJS. Is this correct?

Apart from this, everything is where it supposed to be, I think.

Material Initialisation -

    const aberrationStrength = 0.01;
    const fastCroma = true;
    const envMap = this.scene.environment;

    const isCubeTexture = (def: THREE.CubeTexture | THREE.Texture): def is THREE.CubeTexture => def && (def as THREE.CubeTexture).isCubeTexture

    diamondMaterial.uniforms.aberrationStrength.value = aberrationStrength;
    // diamondMaterial.uniforms.resolution.value = new THREE.Vector2(node.size.width, node.size.height);
    diamondMaterial.uniforms.envMap.value = envMap;

    const temp = {} as { [key: string]: string };
    // Sampler2D and SamplerCube need different defines
    const isCubeMap = isCubeTexture(envMap as THREE.Texture);
    const w = (isCubeMap ? envMap?.image[0]?.width : envMap?.image.width) ?? 1024;

    const cubeSize = w / 4;
    const _lodMax = Math.floor(Math.log2(cubeSize));
    const _cubeSize = Math.pow(2, _lodMax);
    const width = 3 * Math.max(_cubeSize, 16 * 7);
    const height = 4 * _cubeSize;
    if (isCubeMap) temp.ENVMAP_TYPE_CUBEM = '';
    temp.CUBEUV_TEXEL_WIDTH = `${1.0 / width}`;
    temp.CUBEUV_TEXEL_HEIGHT = `${1.0 / height}`;
    temp.CUBEUV_MAX_MIP = `${_lodMax}.0`;
    // Add defines from chromatic aberration
    if (aberrationStrength > 0) temp.CHROMATIC_ABERRATIONS = '';
    if (fastCroma) temp.FAST_CHROMA = '';
    diamondMaterial.defines = temp;

On node detection -

if (node.material && node.name == "Diamonds") {

          diamondMaterial.uniforms.bvh.value = new MeshBVHUniformStruct();
          diamondMaterial.uniforms.bvh.value.updateFrom(
            new MeshBVH(node.geometry.clone().toNonIndexed(), { strategy: SAH })
          )

          node.material = diamondMaterial;
        }

inside animate = ()

      ...
      diamondMaterial.uniforms.viewMatrixInverse.value = this.camera.matrixWorld;
      diamondMaterial.uniforms.projectionMatrixInverse.value = this.camera.projectionMatrixInverse;
      ...

1 Like

i think it already works, it now just needs en environment. sorry i forgot about that part because this is applied in userland in react also.

the jsx looks like this:

const texture = useLoader(RGBELoader, "/textures/royal_esplanade_1k.hdr")
return (
  <mesh geometry={diamondGeometry} {...props}>
    <MeshRefractionMaterial envMap={texture} />

you could also film the scene and drop it into the envMap prop, this way the scene itself would refract, but it only looks good if it’s filled and has a background. in react it would look like this:

<CubeCamera>
  {(texture) => (
    <mesh geometry={diamondGeometry} {...props}>
      <MeshRefractionMaterial envMap={texture} />
    </mesh>
  )}
</CubeCamera>

i would start with the first, get RGBELoader, load your favourite hdri, drop it right into the envMap uniform.

btw if you did use drei/shaderMaterial you dont have to write material.uniforms.foo.value because it creates auto getters/setters, material.foo = value would suffice.

ps make sure all diamonds are joined into one geometry in blender, otherwise it’s going to get very expensive. similar to this demo

Thanks for quick response. I do have an environment map. That’s how the glasses are lit.

    const rgbeLoader = new RGBELoader();
    rgbeLoader.load(
      "https://sprie-jarvis-public.s3.eu-west-2.amazonaws.com/royal_esplanade_1k_desaturated.hdr",
      (environmentMap: any) => {
        const pmremGenerator = new THREE.PMREMGenerator(this.renderer!);
        pmremGenerator.compileCubemapShader();
        const hdrCubeRenderTarget =
          pmremGenerator.fromEquirectangular(environmentMap).texture;

        this.scene.environment = hdrCubeRenderTarget;
        pmremGenerator.dispose();
      }
    );

   const envMap = this.scene.environment;

using material.foo gives me compile time errors. e.g. - diamondMaterial.envMap = envMap; gives me -

Property ‘envMap’ does not exist on type ‘ShaderMaterial’

All geometry for these diamonds is in a single mesh. Hence I am only searching for it by actual node name.

FYI, I am using js and d.ts files from the Unpkg build for MeshRefractionMaterial, shaderMaterial and constants

envMap is a uniform on MeshRefractionMaterial drei/src/materials/MeshRefractionMaterial.tsx at aa2dd0f99fcb33562eff69b57fcb51dbe1d65ff3 · pmndrs/drei · GitHub it has to be set or else it will be black. better copy from drei directly than from unpkg. with shaderMaterial all these uniforms are directly available on the material

imo the way you set up the envmap is incorrect, you don’t need the PMREMGenerator stuff

const rgbeLoader = new RGBELoader()
rgbeLoader.load(
  "https://sprie-jarvis-public.s3.eu-west-2.amazonaws.com/royal_esplanade_1k_desaturated.hdr",
  (environmentMap) => {
    this.scene.environment = environmentMap
    ...
    const material = new MeshRefractionMaterial()
    material.envMap = environmentMap