Fragment shader error occurs when multiple lights casting shadows are used in three.js

Imagine a scenario where you have a street with numerous streetlights (over 20) and you position an object nearby, expecting to see a shadow.

The lights are represented as:

var light = new THREE.PointLight(0xffffff, 0.5, 6.0);

Only the street has .receiveShadow = true and only the car has .castShadow = true (in addition to later including the lights)

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

In three.js, setting .castShadow = true for all the lights leads to the following error:

THREE.WebGLProgram: shader error:  0 gl.VALIDATE_STATUS false 
gl.getProgramInfoLog Fragment shader sampler count exceeds MAX_TEXTURE_IMAGE_UNITS (16).

Fortunately, in our scenario, we only require a few lights (maximum of 4) to cast shadows, as most of the lights are out of range anyway.

I attempted two approaches:

  1. Iterating through all the lights and dynamically setting .castShadow = true or .castShadow = false.

  2. Additionallly, adding and removing the lights completely while configuring them with or without shadows.

Both methods resulted in the same error message.

Is there another approach that could be effective?

Update

@neeh created a Fiddle demonstrating this issuehere (to replicate the error, adjust var numLightRows = 8; to a higher value). Note that there will be a separate error when too many lights are added, which is not related to the initial problem.

It was also noted that from the source code found here, a pointShadowMap is generated even if not utilized. This clarifies why there is no improvement with a more efficient method. This issue directly relates to the GLSL code.

Therefore, we are restricted by the GPU, which in some cases may only support 16 IMAGE_UNITS (though different GPUs may vary - my CPU functions well with more). You can verify your system's capabilities using

renderer.capabilities.maxTextures
. Nonetheless, as mentioned earlier, only 4 lights are truly necessary.

The challenge persists.

Answer №1

The Issue at Hand

Indeed, a new shadow map is not necessarily created for every light with the castShadow attribute set to true. For more information on this, refer to this specific issue. A shadow map essentially serves as the canvas where shadows are computed and then applied onto a surface.

An error message may arise: "gl.getProgramInfoLog Fragment shader sampler count exceeds MAX_TEXTURE_IMAGE_UNITS (16)."

This indicates that your device can handle a maximum of 16 textures per draw call. Usually, drawing a single object like a car or a street requires one draw call.

In order to render an object that receives shadows, all the corresponding shadow maps need to be combined with the diffuse map. This means using N+1 texture units for a single draw call, where N represents the number of lights casting shadows.

If you delve into the Three.js shaders, you will encounter the following snippet:

#ifdef USE_SHADOWMAP

    #if NUM_DIR_LIGHTS > 0

        // Reserving NUM_DIR_LIGHTS texture units
        uniform sampler2D directionalShadowMap[ NUM_DIR_LIGHTS ];
        varying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];

    #endif

    ...

#endif

To determine your browser's capability in handling texture units, utilize this tool and check the 'Fragment shader' section under 'Max Texture Image Units'.


A Potential Solution

Creating and deleting lights dynamically can be inefficient due to the memory-intensive nature of allocating shadow maps.

Instead, as suggested by gaitat, opt to enable shadows solely for the closest lights. Implement the following steps within your rendering loop:

  1. Disable all shadows: light.castShadow = false;
  2. Determine the nearest lights
  3. Enable shadow for the N nearest lights: light.castShadow = true;

Potential Enhancement

The above algorithm has drawbacks since it assigns one shadow map per light, leading to potential freezes when encountering new unallocated shadow maps.

The proposed approach involves reusing existing shadow maps for nearby lights. Manage shadow maps with the following technique:

// create a new shadow map
var shadowMapCamera = new THREE.PerspectiveCamera(90, 1, 0.5, 500);
var shadow = new THREE.LightShadow(shadowMapCamera);

// use the shadow map on a light
light.shadow = shadow;
shadow.camera.position.copy(light.position);
light.castShadow = true;

You can access the maximum number of texture units through

renderer.capabilities.maxTextures
. Use this value to determine how many shadow maps to create, while ensuring adequate allocation for regular maps such as diffuseMap and normalMap.

View an illustrative implementation in this fiddle example (utilizing only 4 shadow maps).

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 jQuery to manipulate HTML elements that are dynamically loaded via ajax

Currently, I am enhancing my basic HTML tables with the help of the jQuery plugin called DataTables. The following code allows me to load the DataTables plugin for all pages: <script> $(document).ready(function() { $('table. ...

How to delete an array within an array in MongoDB if it exists

{ "_id" : ObjectId("5cca927ed5494b0"), "userName": "1234", "rcReviews": [{ "userName": "qwert", "finalReview": "qtrwyw", "dField": [{ "name": "t2", "status": "Not Verified", "reviewCom ...

Selection Change Event for Dropdown Menu

Just starting to learn JavaScript and currently working with 3 select elements on a JSP page: <select id="railwayServiceList" name="railwayService_id" onchange="changeCompaniesCombo()"></select> <select id="companyList" name="company_i ...

Dealing with the validation of two forms on a single webpage: Strategies and Solutions

In a popup, there are two forms that alternate display - one for editing (loaded via ajax) and one for creation. Using jQuery validation, I aim to show hints for both editing and field submission. The validation includes ensuring time spans do not overla ...

Issue with the loop function

When I try to loop through my code, I keep getting the same "y" value (5) and it doesn't change. What I actually want is to make the ajax call repeat X times [all], passing both the response and the current call number through an anonymous function. A ...

A Step-by-Step Guide to Setting Up a Scoreboard for Your Rock Paper Scissors Game

I'm new to JavaScript and I want to create a rock, paper, scissors game from scratch. My Game Plan: Once the user clicks on an option (rock, paper, scissors), the game will display their score and the computer's score along with the result of t ...

Passing parent HTML attributes to child components in Angular 2

Is there a way to pass HTML attributes directly from parent to child without creating variables in the parent's .ts class first? In the sample code below, I am trying to pass the "type=number" attribute from the parent to the app-field-label component ...

Tips for including a permanent button at the bottom of a material ui dialog box

I want to implement a dialog popup in my react js application. When the user clicks on a button, the dialog should open up. Inside the dialog, there is a form with input fields. After the user fills out all the inputs, they can submit the information by cl ...

developing a dynamic structure that can store multiple levels of data

I am grappling with the task of creating a multidimensional array in JavaScript to send data via an Ajax call to PHP. My expertise in JS is limited, especially when it comes to this particular task... I have shared the code on JSFiddle The desired struct ...

Web Browser cross-origin AJAX requests

Recently, I developed an Internet Explorer extension that injects a script every time a page is accessed. The purpose of this script is to send an AJAX request to my localhost. However, I have encountered a roadblock where the request only works if it&apos ...

Instructions for sending an array of integers as an argument from JavaScript to Python

I have a JavaScript function that extracts the values of multiple checkboxes and stores them in an array: var selectedValues = $('.item:checked').map(function(){return parseInt($(this).attr('name'));}).get(); My goal is to pass this a ...

Tips for adjusting line placement on a leaflet map

My leaflet map displays two lines, but sometimes they appear identical, causing the map to show only one line due to overlap. To address this issue, I am attempting to shift one of the lines slightly so that both are visible on the map. One method I cons ...

Steps to configure ExpressJS to listen on a custom domain

I'm trying to set up ExpressJS to listen on a specific domain, as I just acquired a new domain. I want Express.JS to be able to listen on this domain without specifying any ports. Can anyone provide guidance on how to accomplish this? If Express doesn ...

JavaScript: Repeated Depth First Exploration for hierarchical rearrangement implemented with d3's recursion()

I'm attempting to perform a depth-first search on a complex JSON object, such as the one available through this link. My goal is to generate a modified object structure that looks like this: [ { name: "Original Object", children : [ ...

Explore through descendant elements and locate attributes (up to a specified number of levels)

Consider the JSON data below: var responseData = { "Status": "Met", "Text": "Text1", "Id": "AAA", "ContentItems": [ { "Selected": true, "Text": "Text2", "Id": "BBB" }, { "Status": "Met", "Text": "Text3", ...

Utilitarian pool method yields the output from the specified callback function

As a beginner in nodejs, I am currently experimenting with generic-pool and mariasql. My code is functioning well. However, I recently enclosed my code within a function, and I am unsure about the proper way to handle events in nodejs to retrieve the resul ...

Exploring how to read class decorator files in a Node.js environment

I've developed a custom class decorator that extracts permissions for an Angular component. decorator.ts function extractPermissions(obj: { [key: 'read' | 'write' | 'update' | 'delete']: string }[]) { re ...

How can I implement SSO and Auth for multiple sub-domains using PHP?

Is it possible to implement SSO using PHP between two different sub-domains (e.g. parappawithfries.com and *.parappawithfries.com)? My current configuration is causing issues as I use Cloudflare to direct my subs to a different 000webhost website. While I ...

Creating an HTML element that can zoom, using dimensions specified in percentages but appearing as if they were specified in pixels

This question may seem simple, but I have been searching for an answer and haven't found one yet. Imagine we have an HTML element with dimensions specified in pixels: <div style="width:750px; height: 250px"></div> We can easily resize i ...

Tips for incorporating a new column into your JavaScript code

function generateGrid(){ var result = ''; var num1 = document.getElementById('num1').value; var counter = 1; for(var x=1; x<=num1;x++){ for(var y=0 ; y<n ...