The phenomenon of lithophane in Three.js

Is there a way to achieve the Lithophane effect using three.js?

.

I've experimented with different materials featuring transparency and opacity, but haven't been successful.

    <html lang="en">
<head>
    <title>Lith (Three.js)</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">

</head>
<body>

<script src="js/three.min.js"></script>
<script src="./js/dat.gui.min.js"></script>
<script src="./js/STLLoader.js"></script>
<script src="js/Detector.js"></script>
<script src="js/OrbitControls.js"></script>
<script src="js/SkyShader.js"></script>
<script src="js/THREEx.WindowResize.js"></script>

<div id="ThreeJS" style="position: absolute; left:0px; top:0px"></div>
<script>
var container, scene, camera, renderer, controls, stats;
var clock = new THREE.Clock();
var cube;

init();
animate();

function init() 
{
    // SCENE
    scene = new THREE.Scene();
    // CAMERA
    var SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight;
    var VIEW_ANGLE = 45, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 20000;
    camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR);
    scene.add(camera);
    camera.position.set(0,150,400);
    camera.lookAt(scene.position);  
    // RENDERER
    if ( Detector.webgl )
        renderer = new THREE.WebGLRenderer( {antialias:true} );
    else
        renderer = new THREE.CanvasRenderer(); 
    renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
    renderer.setClearColor( 0x999999 );
    container = document.getElementById( 'ThreeJS' );
    container.appendChild( renderer.domElement );
    // EVENTS
    THREEx.WindowResize(renderer, camera);

    controls = new THREE.OrbitControls( camera, renderer.domElement );

    // SKYBOX/FOG
    var skyBoxGeometry = new THREE.CubeGeometry( 10000, 10000, 10000 );
    var skyBoxMaterial = new THREE.MeshBasicMaterial( { color: 0x9999ff, side: THREE.BackSide } );
    var skyBox = new THREE.Mesh( skyBoxGeometry, skyBoxMaterial );
    // scene.add(skyBox);
    scene.fog = new THREE.FogExp2( 0x9999ff, 0.00025 );

    ////////////
    // CUSTOM //
    ////////////

    // must enable shadows on the renderer 
    renderer.shadowMapEnabled = true;

    // "shadow cameras" show the light source and direction

    // spotlight #1 -- yellow, dark shadow
    var spotlight = new THREE.SpotLight(0xffff00);
    spotlight.position.set(0,150,-50);
    spotlight.shadowCameraVisible = true;
    spotlight.shadowDarkness = 0.8;
    spotlight.intensity = 2;
    // must enable shadow casting ability for the light
    spotlight.castShadow = true;
    scene.add(spotlight);

    var sphereSize = 10;
    var pointLightHelper = new THREE.SpotLightHelper( spotlight, sphereSize );
    scene.add( pointLightHelper );


    var light = new THREE.SpotLight(0x999999);
    light.intensity = 0.6;
    camera.add(light);

    var loader = new THREE.STLLoader();
    loader.load('./TestOriginal.stl', function(object) {
        meshObject = object;
        var color = new THREE.Color( 0xffffff );
        var material = new THREE.MeshPhongMaterial({
                    color: color,//'white',
                    side: THREE.DoubleSide,
                    //shading: THREE.SmoothShading, 
                    opacity: 0.6, 
                    transparent: true
                });
        this.mesh = new THREE.Mesh(object, material);

        mesh.position.set(0,0,0);
        scene.add(mesh);

        mesh.position.set(0,0,0);
        var newScale = 1;
        mesh.geometry.computeBoundingBox();
        boundingBox = mesh.geometry.boundingBox;
        mesh.translateX(-((boundingBox.max.x + boundingBox.min.x) * newScale) / 2);
        mesh.translateY(-((boundingBox.max.y + boundingBox.min.y) * newScale) / 2);
        mesh.translateZ(-((boundingBox.max.z + boundingBox.min.z) * newScale) / 2);
    });

    // floor: mesh to receive shadows
    var floorTexture = new THREE.ImageUtils.loadTexture( './checkerboard.jpg' );
    floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping; 
    floorTexture.repeat.set( 10, 10 );
    // Note the change to Lambert material.
    var floorMaterial = new THREE.MeshLambertMaterial( { map: floorTexture, side: THREE.DoubleSide } );
    var floorGeometry = new THREE.PlaneGeometry(1000, 1000, 100, 100);
    var floor = new THREE.Mesh(floorGeometry, floorMaterial);
    floor.position.y = -80.5;
    floor.rotation.x = Math.PI / 2;
    floor.receiveShadow = true;
    scene.add(floor);
}

function animate() 
{
    requestAnimationFrame( animate );
    render();       
    update();
}

function update()
{   
    controls.update();
}

function render() 
{
    renderer.render( scene, camera );
}

</script>

</body>
</html>

My output is similar to:

I have attempted using a shader material, resulting in something like this: https://i.sstatic.net/gRk69.png

What I aim for is having the light from the backside of the object while making the engraved part of the object glow in accordance to its depth.

Answer №1

Although not a definitive solution, I strongly believe that creating a custom shader is the most effective approach to this issue. It's important to note that shader programming is quite complex, as it involves low-level languages and requires a deep understanding of geometry and mathematics.


Upon investigation, it seems that a shader capable of achieving Sub-Surface Scattering (SSS) is required for this particular effect. SSS simulates how light interacts with translucent objects based on their thickness and other properties:

To achieve a Lithophane effect, you'll need to generate a "thickness map" that corresponds to your object's mesh. The custom shader will then use this map to accurately scatter light and create the desired effect.

I learned about this process from a straightforward presentation by a Lead Rendering Programmer at a game developer. Here's an example slide from the presentation:

This shader can produce a similar effect in real time, based on the object's thickness:

If you're serious about implementing this effect, I recommend studying Cg or GLSL shader programming. Personally, I prefer Cg due to its compatibility with Unity3D. A useful resource for learning Cg is this wikibook. Although geared towards Unity, the fundamental concepts remain applicable.

Best of luck in your endeavors. I hope this information proves helpful!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The close button on my modal isn't functioning properly

There seems to be an issue with the close button functionality. <div class="modal fade show " id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" style="display: block;left: -6.5%;"> <div class="modal-dialog" ...

Issue with ng-grid in AngularJS: Data does not appear after resizing columns

Currently, I am encountering a problem where ng-grid data is not being displayed when column resizing occurs after changing the column names. To demonstrate this issue, I have created a plunkr at - http://plnkr.co/edit/eV9baoDOV9A46FZmKW8G?p=preview I wo ...

execute a function for every regex match found within a string

When working with WordPress or PHP in general, I came across this interesting recommendation for email obfuscation, and I would like to: Convert email addresses to mailto links Encode the mailto links using str_13() Decode them client-side using JavaScri ...

Exploring the Implementation of Box Icons in a Vuejs For Loop

I am currently exploring the box icons web component and I am trying to integrate the icons into my link array for each item. However, I am uncertain about the best approach to achieve this. <div class="container"> <div class="l ...

Formik Alert: Update depth limit reached

Currently, I am working on an EDIT formik form that is displayed as a MODAL. The issue arises when the setState function is triggered with setState(true), causing the form to appear without any onClick event. Despite changing onClick={editStory} to onClick ...

Discover the magic of triggering events that dynamically alter CSS styles

I am trying to implement an eventBus in the App.vue component that allows me to change a modal's CSS based on a payload object. For example, if I pass { type: 'success' }, the border of the modal should turn green, and if I pass { type: &apo ...

Loading external JSON and images located beyond the root directory

Currently, I am attempting to load a JSON file and a set of images from a directory located above my root directory. Although the code I have works, I believe there may be a more efficient way to achieve this. $.getJSON("http://localhost/folder1/folder2/f ...

Transfer a JSON object to a Java Class without relying on a servlet

I have a form in HTML where I collect user input and store it as an object in JavaScript. Here is how I am creating the object: var dataObject = { Name: getName(), Age : getAge() } Now, I want to send this object using Ajax to a b ...

Exploring the dynamic duo of Github and vue.js

As I am new to programming and currently learning JavaScript and Vue.js, I have been trying to deploy my first Vue.js app without success. Despite spending hours on it, I still cannot figure it out and need to seek assistance. Every time I try to deploy, ...

AngularJS: ng-show causing flickering issue upon page refresh

Recently, I encountered an issue with my code snippet: <body> <ng-view></ng-view> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script> <script src="http://ajax.googleapis.com/ajax/ ...

Opening a new tab on any webpage with C# Selenium

Is there a foolproof way to open a new tab when using Selenium? Trying to use an action method has proven unsuccessful Actions a = new Actions(driver); a.KeyDown(OpenQA.Selenium.Keys.LeftControl); a.SendKeys("t"); a.KeyUp(OpenQA.Selenium.Keys.L ...

Disabling the child element from triggering the parent element's onclick function, while still allowing it to respond to its own content

My list elements have onclick-functions that change the display of each element and its child div, moving them to the top of the list. Clicking again reverses this effect. The issue arises because the child div contains search fields, clickable pictures, ...

Count the number of checkboxes in a div

In my current project, I am working on incorporating three div elements with multiple checkboxes in each one. Successfully, I have managed to implement a counter that tracks the number of checkboxes selected within individual divs. However, I now aspire to ...

Updating information within a for loop with jQuery in a Django environment

I'm currently developing a website that enables users to search for papers and interact with them by liking or disliking. The like count should update dynamically as users click the like button. To handle the dynamic updating of the like count, I&apo ...

HTML5 Canvas Border

Hey Everyone, I've been working on an HTML5 canvas project where I set the canvas width to $(window).width(). Everything was going smoothly until I added a border, which caused a horizontal scroll to appear. Important: I attempted to use the innerWid ...

Arranging a JSON Object Array in JavaScript by its alphanumeric key attribute order

I need assistance sorting this JSON array by unitId var array = [ { id: 10, unitId: 'unit17' }, { id: 11, unitId: 'unit19' }, { id: 13, unitId: 'unit27' }, { id: 12, unitId: 'unit2' }, { id: 13, unitId: 'unit ...

How can we prevent other components from re-rendering while using setInterval and updating state within useEffect?

I am working with two components, List.js and Counter.js. In App.js, after the DOM is rendered, I want to use setInterval to display an incremental count every second. Additionally, there is a list that should only update when manually submitted. App.js e ...

Unraveling deeply nested array objects in JSON with Java Script/jQuery

I need help working with a JSON file that looks like the following: {[ {"name":"avc"}, {"name":"Anna"}, {"name":"Peter"}, {"Folder":[ {"name":"John"}, {"name":"Anna"}, {"Folder":[ {"name":"gg"}, ...

Steps for adding a row as the penultimate row in a table

There aren't many solutions out there that don't rely on jQuery, but I'm looking to avoid it. The row content needs to be generated dynamically. Here is my flawed approach: function addRow() { let newRow = '<tr><td>B ...

Coverage report not generated when running jest

I need to include some functions (sum, mul, sub, div) in the test file as shown below, import {describe, expect} from "@jest/globals"; function sum(a,b) { return a+b; } const div = (a,b) => { return a/b; } const mul = (a,b) => { ...