Fixed light source creates a steady illumination for the normal map background image on the website

I am interested in incorporating normal maps into the background images on my website. After exploring various implementations, I find that this example on CodePen utilizing three.js stands out as the best option. Despite being new to three.js, I am eager to learn and experiment.

The specific features I hope to achieve are:

I would prefer the light source to remain static rather than follow the cursor's movement.

This preference stems from the fact that mouse-based interactions do not translate well to mobile devices. Additionally, effects tied to cursor movements can often be distracting. Instead, positioning the light source at a fixed location (such as the center of the page) and allowing it to interact with the background as users scroll seems like a more effective approach.

I wish to confine the background image within a smaller div container, even when it does not need to span the entire page.

While this may seem straightforward, the current implementation appears to target either the body element or the entire window. Further investigation is needed...

I aim to be able to incorporate multiple normal map-enabled background images that share consistent lighting for scene coherence.

If the position of the light remains relative to the window dimensions, achieving this might not be overly complex.

It would be beneficial to have control over whether the image textures are tiled.

In traditional web development, this task is accomplished through the background-repeat CSS property. However, the process could differ in three.js. This aspect requires further exploration...


Note: The following points 5 and 6 are optional, but I will outline them in case there are insights on their implementation.

Implementing the capability to illuminate the "scene" (comprising all normal map-enabled background images on the page) using an image instead of point lights, known as HDR image-based lighting. To prevent page bloat, low-resolution images must be utilized, yet the improvement in aesthetics could be substantial compared to point lights. Initial findings suggest that IBL support exists in three.js, though ongoing research is necessary.

While challenging to implement, introducing a subtle tilt in the background image or light source position responsive to motions detected by a mobile device accelerometer would be intriguing. However, given limitations in accessing accelerometer data by default on iOS as highlighted here, this idea may remain unfeasible. As an alternative, incorporating parallax motion linked to scrolling behavior could provide a comparable enhancement.

Answer №1

…please pardon my lack of knowledge and lofty ambitions.

Initially, take note of the creation date of this CodePen, which was done in version R71. A lot has evolved since then. Additionally, if you are delving into Three.js as you mentioned, commencing with a custom light implemented in a custom shader might not be the most ideal way to kickstart your journey with WebGL... 😉

Your needs can be fulfilled using various approaches, both simpler and more complex. The example you provided in the CodePen is one of the (much) more challenging ones.

Type "multiple" in examples and explore numerous results showcasing how you can achieve what you desire.

https://threejs.org/examples/?q=multiple#webgl_multiple_elements

Focusing back on your points...

① Prefer the light source to remain fixed instead of following the cursor.

There isn't a specific technique here; simply disable tracking mouse movements and position the chosen light statically.

② Desire the ability to place the background image within a smaller div for cases where it doesn't cover the entire page.

Edit the canvas size as per your requirements, position it in another div as desired. For dynamic resizing (usually recommended), set up a listener for screen size alterations. Refer to the example below for inserting textures...

Wish to define multiple normal map enabled background images illuminated by shared lighting (for seamless scene continuity).

You can designate the light as shared or isolate it for a specific canvas. As evident from the example you shared.

Want to specify that the image textures be tiled.

The example showcases 4 tiles. Of course, you can divide the viewport as needed, but it's advisable to invest some time assessing performance closely. Using render scissors might be the most suitable solution. https://threejs.org/docs/#api/en/renderers/WebGLRenderer.setScissor

Generally, for versatility, you can have one canvas with several scenes or opt for a separate canvas for each scene. It all boils down to individual project requirements and adjusting the efficiency-effect balance.

⑤ …In essence, HDR image-based lighting

Take a look here…

A subtle tilt of the background image position (or light source position) in response to movements detected by the mobile device accelerometer would be cool…

About Sensors and Parallax, refer to here and here.

Example

body { margin: 0; overflow: hidden; }
.background-div { position: absolute; width: 50vw; height: 50vh; }
#background1 { top: 0; left: 0; }
#background2 { top: 0; left: 50vw; }
#background3 { top: 50vh; left: 0; }
#background4 { top: 50vh; left: 50vw; }
<div class="background-div" id="background1"></div>
<div class="background-div" id="background2"></div>
<div class="background-div" id="background3"></div>
<div class="background-div" id="background4"></div>

<script type="importmap">
  {
"imports": {
  "three": "https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="5a2e32283f3f1a6a746b6c6c746a">[email protected]</a>/build/three.module.js",
  "three/addons/": "https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="50243822353510607e6166667e60">[email protected]</a>/examples/jsm/"
}
  }
</script>

<script type="module">
import * as THREE from "three";
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

function fourPlanes(container) {
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
camera.position.z = 20;

const renderer = new THREE.WebGLRenderer();
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.enabled = true;

const light = new THREE.PointLight(0xFFFF00, 777, 100);
light.position.set(40, 0, 10);
scene.add(light);

function createTexturedPlane(textureURL, normalMapURL, position, scene) {
    const textureLoader = new THREE.TextureLoader();
    const texture = textureLoader.load(textureURL);
    const normalMap = textureLoader.load(normalMapURL);
    texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
    normalMap.wrapS = normalMap.wrapT = THREE.RepeatWrapping;
    texture.repeat.set(4, 4);
    normalMap.repeat.set(4, 4);

const material = new THREE.MeshStandardMaterial({
        map: texture,
        normalMap: normalMap,
    });

    const geometry = new THREE.PlaneGeometry(10, 10);
    const plane = new THREE.Mesh(geometry, material);
    plane.position.copy(position);

    scene.add(plane);
    
}
createTexturedPlane('https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/water.jpg', 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/textures/waternormals.jpg', new THREE.Vector3(0, 0, 0), scene);

function animate() {
    requestAnimationFrame(animate);
    controls.update();
    renderer.render(scene, camera);
}
animate();

window.addEventListener('resize', () => {
    camera.aspect = container.clientWidth / container.clientHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(container.clientWidth, container.clientHeight);
});
}

const background1 = document.getElementById('background1');
const background2 = document.getElementById('background2');
const background3 = document.getElementById('background3');
const background4 = document.getElementById('background4');

fourPlanes(background1);
fourPlanes(background2);
fourPlanes(background3);
fourPlanes(background4);

</script>

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

Using THREE.js to animate between two specific points on a spherical object

Seeking a solution on how to rotate a sphere in order to align one specific point with another on the surface, like illustrated in the image below. An illustration of the desired movement While I've come across tutorials explaining this process i ...

Utilizing AngularJS, implement ng-form and ng-repeat to seamlessly handle input validation and submission actions

Angular 1.6.2 I am experimenting with iterating over elements of a collection inputted by the user, checking their validity, and enabling the submit action if validation passes. I have tried using ng-form for this purpose and have come up with two differ ...

Issue with Angular 5: Bootstrap Tooltip not functioning as expected

Where am I going wrong? I've been attempting to implement a Bootstrap Tooltip in Angular 5, but I'm facing some challenges. We have already set up the Javascript library through the footer of our index.html page as per the recommendations. The B ...

"Enhance the functionality of material-table by incorporating a multi-select feature

My data management has been made easier with Material-Table, but I have encountered a small issue. The code below shows how I currently get a select menu for my data. However, I am looking to have a multiselect menu instead, allowing me to save more than o ...

Mastering the Art of Manipulating SkinnedMesh Bones in THREE.JS

Currently, I am immersed in a 3D project utilizing THREE.JS and endeavoring to animate a straightforward character reminiscent of Minecraft. To achieve this, I have exported the character from Blender (including bones) and then displayed it using THREE.JS ...

Tips for avoiding variable overwriting while utilizing it in a for loop to color objects

I am looking to enhance the efficiency of my function in creating HTML objects using native JS script. The current function paints text, but I want it to optimize. When running the function, I expect different iterations of text to be written in HTML thro ...

Using React and Material-UI to dynamically populate a table with data retrieved from

Material ui table utilizes data in a specific format. rows: [ createData(1, "dashboard", "details"), createData(2, "product", "product details"), ].sort((a, b) => (a.id < b.id ? -1 : 1)) When the API responds with data stored in st ...

I am experiencing difficulty scrolling down on my website. Can anyone offer suggestions on how to resolve this issue?

I'm having trouble scrolling on my website and I can't seem to figure out how to add more content. I've tried adjusting the height in CSS, but it didn't work. I'm new to web development and would appreciate any help! Maybe the issu ...

The correct way to create a Reaction Collection

I'm trying to set up an event where a user triggers a reaction with either a Check mark or X. However, I keep encountering an error that the function doesn't exist for the message object. I attempted to switch back to awaitReactions but it didn& ...

Create a dynamic animation of an image expanding and shifting towards the center of its parent container

Is it possible to animate an SVG image within a div to grow to 80% screen height and move to the center of the parent div when a specific function is called? If so, how can this be achieved? function openNav(){ $("#topnav").animate({height:'100%& ...

Prevent HTML Link from being accessed again after being clicked

My goal is to prevent a link from submitting my form multiple times when clicked. I need this to avoid duplicate requests being sent by the same user. Below is the code I've tried, but unfortunately, it does not seem to be effective. <a id="submit ...

Helper for Three.js edges

As a beginner in Threejs, I have been using the EdgesHelper to achieve the desired look. However, I have encountered two questions: How is the default edge width calculated? Is it possible to change the edge width? After searching extensively, it seems ...

Seamless Integration of Hosted Fields by Braintree

I am currently struggling with setting up Braintree hosted fields on my registration form. Unfortunately, there are significant gaps between the fields which doesn't look appealing. Despite referring to the braintree documentation and guides, I find t ...

What is the best way to achieve dolly or zoom effects with React three fiber or threejs?

I'm currently working on a react-three-fiber application where I have implemented OrbitControls for my camera controls. My goal is to add on-screen buttons that allow users to manually zoom in or out, similar to how the middle mouse button functions ...

Switch from using a select tag to a check box to easily switch between a dark and light theme

I have implemented a feature on my website that allows users to toggle between dark and light themes using the select tag with jQuery. Here is the code snippet that I am currently using: $(function() { // Code for changing stylesheets based on select ...

Executing a function on a dynamically generated JavaScript button

Is there a way to create a button that triggers an onClick event calling a function with the signature deleteEntry(entry)? I'm encountering an error stating that entry is undefined even though I have used it in the same function. Adding escape charact ...

Exploring the implementation of an if condition within a button tag

Currently, I am a beginner in the field of web development and I have encountered an issue while trying to implement a certain logic. Here is the button tag that I am working with: <td> <input style="word-wrap: break-word;white-space: normal; ...

displaying a pair of inputs next to each other

Is it possible to display two input fields side by side? I am using Angular's matInput forms, but struggling to position the second input next to the first. What I would like to achieve is to have "input1 , input2" on the same line. Here is my code: ...

Data retrieved from API not displaying in Angular Material table

I've hit a roadblock trying to understand why my mat-table isn't displaying the data for me. I'm working with Angular 15 and using Angular Material 15. Below is my HTML component code: <mat-divider></mat-divider> <table mat-t ...

Double-checked in p:commandButton, encountering a race condition with oncomplete

When I execute the server action using p:commandButton, I then refresh the form to display hidden fields and show a dialog. Attempting to refresh only the panels with hidden buttons yields the same result: After I call dialog.show(), a second refresh is t ...