How can I enhance the sharpness of shadows on MeshLambertMaterial in Three.js?

I am currently working on a project involving the creation of 3D images of the moon at different phases, incorporating libration effects. Here is a snapshot of my first quarter moon image:

https://i.sstatic.net/MTb4f.png

While the image looks good, I am facing an issue with the gradual transition from light to shadow. Since the moon lacks an atmosphere, the transition should be more abrupt.

Here is the code I have developed so far:

  // Insert your code here

The above code utilizes an equirectangular moon map (provided by NASA) like this:

https://i.sstatic.net/vE4iK.png

...and maps it onto a sphere to allow for rotation and illumination in different ways.

Despite my research efforts, I have only come across information about objects casting shadows on other objects. I have not found any resources on adjusting shadows on a single object with a main light source.

I have incorporated some ambient lighting in my code to represent "earth shine," ensuring that the shadowed part of the moon is not completely dark. However, removing this ambient lighting does not significantly enhance the sharpness of the shadow boundary.

Could anyone provide insights on what adjustments I should make to achieve the sharper shadow boundaries I desire?

Answer №1

Upon experimentation, I discovered that MeshPhongMaterial, which I initially overlooked due to its association with glossy surfaces and specular highlights, actually yields better results for me when reflectivity and shininess are set to zero.

Moreover, a crucial adjustment that significantly improved image contrast and gamma was setting

this.renderer.outputEncoding = sRGBEncoding
.

Another subtle modification I made was adjusting the Earth shine from its maximum at new moon to its minimum at full moon, as it naturally should be.

https://i.sstatic.net/4E8Vs.gif

  private renderMoonWebGL(context: CanvasRenderingContext2D, solarSystem: SolarSystem, time_JDE: number,
           cx: number, cy: number, size: number, pixelsPerArcSec: number, pixelRatio: number,
           parallacticAngle?: Angle, observer?: ISkyObserver, _showEclipses?: boolean): void {
    if (!this.renderer)
      this.initializeRenderer();

    if (size === 0)
      size = MAX_LUNAR_ANGULAR_DIAMETER * pixelsPerArcSec * 60;

    if (this.webGlRendererSize !== size) {
      this.renderer.setSize(size, size);
      this.webGlRendererSize = size;
    }

    const phase = solarSystem.getLunarPhase(time_JDE);
    const libration = solarSystem.getLunarLibration(time_JDE, observer);

    this.camera.position.z = libration.D * KM_PER_AU;
    this.camera.rotation.z = (parallacticAngle ? parallacticAngle.radians : 0);
    this.globeMesh.rotation.y = to_radian(-libration.l);
    this.globeMesh.rotation.x = to_radian(libration.b);
    this.sun.position.x = 93000000 * sin_deg(phase);
    this.sun.position.z = -93000000 * cos_deg(phase);
    this.earthShine.intensity = 0.025 + cos_deg(phase) * 0.0125;
    this.renderer.render(this.scene, this.camera);
    context.drawImage(this.renderer.domElement, cx - size / 2, cy - size / 2);
  }

  private initializeRenderer(): void {
    const globe = new SphereGeometry(MOON_RADIUS, 50, 50);

    globe.rotateY(-PI / 2);
    this.camera = new PerspectiveCamera(MAX_LUNAR_ANGULAR_DIAMETER / 60, 1, 0.1, 500000);
    this.scene = new Scene();
    this.globeMesh = new Mesh(globe, new MeshPhongMaterial({ map: new CanvasTexture(this.moonImageForWebGL),
      reflectivity: 0, shininess: 0 }));
    this.scene.add(this.globeMesh);
    this.renderer = new WebGLRenderer({ alpha: true, antialias: true });
    this.renderer.outputEncoding = sRGBEncoding;
    this.sun = new DirectionalLight('white', 1.5);
    this.sun.position.y = 0;
    this.scene.add(this.sun);
    this.earthShine = new AmbientLight('white');
    this.scene.add(this.earthShine);
  }

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

Establishing the Default Volume within a script

I'm looking to adjust the default volume of music on my website. Currently, it plays too loudly on load, but I have a slider bar that allows users to change the volume. How can I set the default volume to be 25% of the slider? CHECK OUT MY WEBSITE! ...

Printing an array from JavaScript to an EJS template: Tips and Tricks

I'm currently working on a database-driven website that focuses on searching through people's profiles. When attempting to display information from a database query, I am encountering the issue of receiving [object Object]. Below are snippets o ...

Ways to automatically style the child divs within a parent div

I'm trying to figure out how to float a parent div with child divs of different widths and heights while maximizing the use of space and not being affected by absolutely positioned elements. For reference, here's an example: http://jsfiddle.net ...

Creating a factory function in TypeScript to generate union types

I have developed a unique Factory type that allows me to create factory functions. export type Factory<T> = (state?: Partial<T>) => T; Within my <Avatar /> React component, I have implemented a prop with a union type to accommodate fo ...

Assign a new value to a controller scope variable without relying on angular.element in AngularJS

I have the following div: <div ng-controller="MyController as MC" id="div1"> <a href="#" id="1" ng-init="MC.EntityId = 1 , MC. ...

Tips for inserting a delay between two separate javascript commands within the same onchange event

Within my HTML code, I have an input field that triggers an onchange event: <input type="text" value="2014-01-01" class="datepicker dateDashboard" onchange="repartiteur('DatesDashboard',$(this));document.location.href='index.php?menu=17| ...

Is it possible to determine whether a path leads to a directory or a file?

Is it possible to distinguish between a file and a directory in a given path? I need to log the directory and file separately, and then convert them into a JSON object. const testFolder = './data/'; fs.readdir(testFolder, (err, files) => { ...

Navigation buttons that move the user either up or down the page

I am a beginner in programming and I wanted to create two buttons for my webpage. One button should appear when the page is scrolled more than 300px and take the user to the top, while the other button should be displayed immediately and scroll the user to ...

Unable to change the value of selected items in checkbox event within a React context

Today marks the beginning of my journey into developing a React application. I am currently faced with the challenge of retrieving the row ID of a checked checkbox in a React table. Utilizing hooks, I have managed to transfer the states to another compone ...

Customize date filtering in KendoUI grid

I am trying to modify the date format in the filter of my kendo grid. For example, I would like to change 1/30/2015 to Jan 30, 2015 I have successfully changed the date format for Start Date field: "StartDate", title: " ...

Why are users not visible in the Node.js application?

Good day! I am a newcomer to this community and I'm hoping to get some guidance in the right direction with my inquiries. Recently, I came across some code on Github that I am trying to improve upon. The code can be found at https://github.com/voroni ...

Toggle between resizing a set of boxes and fading in/out their images

I have a series of clickable boxes that I want to expand and hide the image when clicked. Additionally, I need to be able to close any previously opened box by returning it to its original height and width while fading back in its image content. The .info ...

Vue.js CSS class interpolation

Is there a way to apply CSS classes dynamically in Vue.js? {{ category.icon }} --> "icon1" I am trying to achieve the following: <i :class="category.icon" class="icon icon-"></i> The desired output should be: ...

What are common pitfalls to avoid when modifying state with createSlice in Redux-Toolkit?

I am currently working with the createSlice function, specifically with the reducer setCustomEquipment that updates the state. My question is whether the = method is an acceptable way to update the state when using createSlice, or if I should always use ...

Tips for implementing server-side rendering in Jade using an Array of JSON objects instead of just a single JSON object

In my Node.js server, I am working with an array of JavaScript objects retrieved from a MySQL query. To pass this array to my Jade template, I use the following code in my router.js: data = JSON.stringify(rows[0]); res.render('yourUploads', {fro ...

HTML5 Drag and Drop: How to Stop Drag and Drop Actions from Occurring Between a Browser and Browser Windows

Is it possible to restrict HTML5 Drag & Drop functionality between different browsers or windows? I am currently working on an Angular2 app that utilizes native HTML5 Drag and Drop feature. Is there a way to prevent users from dragging items out of th ...

Does anyone have a solution for resetting the ID numbers in a flatlist after removing an item from it?

As the title suggests, I am facing an issue with the following scenario: { id: '1', name: 'one' }, { id: '2', name: 'two' }, { id: '3', name: 'three' }, { id: '4', name: &apo ...

Interacting with a third-party application via OAuth using Node server to send REST requests

https://i.stack.imgur.com/Znrp0.png I've been working on a server to manage chat messages and need to integrate with a JIRA instance. I'm currently using the passport-atlassian-oauth strategy for authentication and BearerStrategy for requests. H ...

Achieving Efficiency with Handlebars: Streamlining Remote Template Organization and

I am looking for a way to better organize my HB template by splitting it into different HTML files. In this case, I have created a file called helpers.html. This file contains two script tags: <script id='alert' type='text/template>... ...

Various web browsers may exhibit varying behaviors when processing extremely lengthy hyperlinks

I am curious to understand why browsers handle long URLs differently based on how they are accessed. Let me explain further: Within my application, there is a link to a specific view that may have a URL exceeding 2000 characters in length. I am aware that ...