Tips for effectively incorporating Cook-Torrance shading in three.js?

I've been working on integrating the Cook-Torrance shading algorithm into my three.js project. I've made progress, but I'm experiencing issues with the ambient light not displaying correctly. The sections of the cube that are not directly lit appear completely black. Interestingly, if I remove the "Beckmann term," the ambient light effect is visible:

By replacing Beckmann with a function that always returns 0.0, I observe:


The problem appears to be linked to the division in:

vec3 Specular = (Beckmann(NdotH) * G(NdotH, NdotV, VdotH, NdotL) * R_F(VdotH)) / ( NdotL* NdotV);

If I change NdotL * NdotV to just NdotV and update the calculation for gl_FragColor to:

gl_FragColor = vec4(beta * NdotL * (1.0-s)*Kd + beta * s*Specular + ambient*Kd, 1.0);

It seems to resolve the issue.

What's confusing me is: why? This divisor problem isn't documented anywhere, and there might still be potential issues with the remaining division in other scenarios.


Below is the complete MWE:

<html>
    <head>
        <title>Cook-Torrance BRDF computed by shader</title>
        <style>

        body {
            font-family: Monospace;
            background-color: #f0f0f0;
            margin: 0px;
            overflow: hidden;
        }

        canvas {
            width: 100%;
            height: 100%;
        }

    </style>
        <script src="lib/three.min.js"></script>
        <script src="lib/OrbitControls.js"></script>
    </head>
    <body>

        <script type="text/x-glsl" id="vertex">
        varying vec3 transformedNormal;
        varying vec3 pointPosition;
        varying vec3 lightVector;
        uniform vec3 pointLightPosition;

        void main()
        {
            transformedNormal = normalMatrix * normal;
            pointPosition = (modelViewMatrix * vec4( position, 1.0 )).xyz;
            vec4 lPosition = viewMatrix * vec4( pointLightPosition, 1.0 );
            lightVector = lPosition.xyz - pointPosition;
            gl_Position = projectionMatrix * vec4(pointPosition,1.0);
        }
        </script>

        <script type="text/x-glsl" id="ct-fragment">
            // WebGL fragment shader code goes here ...
        </script>


        <script>
            var scene = new THREE.Scene();
            var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
            camera.position = new THREE.Vector3(0,0,5);

            var renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize( window.innerWidth, window.innerHeight );
            renderer.setClearColor( 0xf0f0f0 );
            document.body.appendChild( renderer.domElement );

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

            var uniforms = {
                        // Define necessary uniforms here ...
                    };

            var vs = document.getElementById("vertex").textContent;
            var fs = document.getElementById("ct-fragment").textContent;

            var material = new THREE.ShaderMaterial({ uniforms: uniforms, vertexShader: vs, fragmentShader: fs });

            var geometry = new THREE.CubeGeometry(1, 1, 1);
            var mesh = new THREE.Mesh(geometry, material);
            scene.add(mesh);

            // Add lighting properties and values ...

            function animate() {

                requestAnimationFrame( animate );
                render();

            }

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

            animate();
        </script>
    </body>
</html>

Answer №1

The shading equation serves as a mathematical representation of the Cook-Torrance shading model. Creating an actual shader involves considering that not all float operations behave identically to true mathematical operations within the equation.

In this instance, division by 0 leads to issues. Specifically, the problem arises from defining Specular as dividing by 0, which ultimately results in multiplying NdotL again during the assignment to gl_FragColor, yielding 0 * inf = NaN. Interestingly, NaN appears to be interpreted as a zero or negative number by the GPU, resulting in a black display.


For guidance, the accurate version of main() is:

void main()
{
    vec3  n = normalize( transformedNormal );
    vec3  v = normalize( -pointPosition );
    vec3  l = normalize( lightVector );
    vec3  h = normalize( v + l );

    vec3 specular = vec(0.0, 0.0, 0.0);           
    float  NdotH = max(0.0, dot( n, h ));
    float  VdotH = max(0.0, dot( v, h ));
    float  NdotV = max(0.0, dot( n, v ));
    float  NdotL = max(0.0, dot( n, l ));
    if (NdotL > 0 && NdotV > 0) 
    {
        specular = (Beckmann(NdotH) * G(NdotH, NdotV, VdotH, NdotL) * R_F(VdotH)) / ( NdotL * NdotV);
    }
    vec3 beta = lightPower / (4.0 * PI * pow(length(lightVector), 2.0));
    gl_FragColor = vec4(beta * NdotL * ((1.0 - s) * Kd + s * specular) + ambient * Kd, 1.0);
}

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

Tips for extracting a value from a geojson response using a specific key

When analyzing the geojson response below, I am trying to access the following: Type and Segments To achieve this, I attempted the following: return data["type"] //does not work, error received return data["features"][0]["properties"]["segments"] ...

Is there a way for me to adjust the image dimensions so that it doesn't surpass the width of its parent container?

When working with images, it can be tricky to set the original width while also ensuring it fits within a parent container. For example, if the parent container has a width of 1000px, you may want the image to have a max-width of 100%, but not exceed 1000p ...

Submitting form data in Angular is a straightforward process

I'm currently using the ng-flow library to upload images to my server. After a user drags and drops an image, I can successfully retrieve the HTML 5 file image in my controller. However, when trying to download it to the server along with other parame ...

Exploring Sensor Information with Vis.js

In my work with Wireless Sensor Networks, I have been collecting sensor addresses and their corresponding temperature readings in JSON format. The data looks like this: {"eui":"c10c00000000007b","count":0"tmp102":" 0.0000 C"} When it comes to the network ...

Struggling with setting up and installing the MDX transformer plugin (as well as its dependencies) on a Gatsby site

Task: Currently working on setting up a basic blog using Gatsby Results: Expected Result: Following the tutorial guide as expected Actual Result: Encountering Dependency tree error at certain steps & also receiving a warning message stating 34 vulnerab ...

Guide to reading JSON files with a personalized approach

Below is a JSON object structure that I am working with: { "db": { "name": "db", "connector": "memory" }, "MySql": { "host": "localhost", "port": 3306, "database": "users", "username": "root", "password": "", "name": "MySql", ...

What is the best way to measure the distance between two countries, between a country and a city, and between two cities?

What is the best method for determining the distance between two countries, between a country and a city, and between two cities? ...

Exploring Vue.js: Navigating through highlighted search terms with previous and next buttons

Is there a way to allow users to navigate through highlighted search results using 'next / previous' arrows? Something similar to the feature in Google Docs shown below: https://i.sstatic.net/44LpL.png I already have the code to highlight all o ...

What is the best way to create a CSS class for a list element in React?

I am facing an issue with styling buttons in my UI. These buttons represent different domains and are dynamically generated based on data fetched from the server using the componentDidMount() method. Since I do not know the quantity of buttons at the time ...

Steps for building a progressive v-file-input component in Vuetify

I am utilizing a v-file-input to manage certain documents. However, whenever a user attempts to add additional files by selecting them, it ends up wiping out the previous files. Ideally, I would like the v-file-input to be able to accumulate files and only ...

Accessing services from the main page's popover callback in Ionic 2 is essential for enhancing user interaction and

After spending some time working with angularJs, I recently made the switch to angular 2. I am facing an issue with calling a popover from a page and fetching data from an API when an item is selected from the popover. The problem arises when I try to acce ...

Adjusting color of fixed offcanvas navbar to become transparent while scrolling

After creating a navbar with a transparent background, I am now using JavaScript to attempt changing the navigation bar to a solid color once someone scrolls down. The issue is that when scrolling, the box-shadow at the bottom of the navbar changes inste ...

How to hide bullet points in a list when the links are hidden, utilizing either Jquery or CSS

In my attempt to hide bullet points from a list when links are hidden, the first and second links have been hidden using backend code in C#. I am trying to make sure that the bullets are only displayed if there are actual links present. <div class="l ...

Is it possible to incorporate both Matter and Arcade physics into the Player object?

I attempted to instantiate a player object export default class Player extends Phaser.Physics.Matter.Sprite { constructor(data) { let { scene, x, y, texture, frame } = data; super(scene.matter.world, x, y, texture, frame); this. ...

Is it possible to transfer the JSON data received from a POST request to the client?

I have a Node.js server running with Express that listens to incoming requests. I want to update an HTML file when I receive a GET request in my server, but the data sent to my server is from an API triggered by an external event asynchronously. I'm s ...

What is the connection between serialization and JSON?

Can you explain serialization? Serialization is the process of converting an object into a stream of bytes, allowing it to be sent over a network or stored in a file. This allows the object to be reconstructed later on. What exactly is JSON? JSON stands ...

Having trouble with loading VRML files in three.js?

I attempted to load a VRML or WRL file using the vrmlloader.js script from the three.js library. However, after loading the file, all I see is a blank white page with no content displayed. You can find the content of the VRML file here: http://jsfiddle.ne ...

Is it possible to test the JEST configuration for the node_modules even when it is turned

I'm currently integrating Jest tests into an existing codebase, but I've encountered an error that's giving me some trouble. Jest came across an unexpected token Typically, this means you're trying to import a file that Jest can& ...

.value not showing correct data in JavaScript

I am attempting to retrieve the value entered by the user in an HTML input field. While I can display a fixed, predetermined value with ease, this is not my intention. My goal is for users to input a numerical value and for that value to be displayed/adj ...

Utilizing a personalized service within an extended RouterOutlet component in Angular 2 for streamlined authentication

In my quest to establish authentication in Angular 2, I turned to an insightful article (as well as a previous inquiry on SO) where I learned how to create a custom extended RouterOutlet: export class LoggedInRouterOutlet extends RouterOutlet { public ...