How can I create a mipmap for a planet using three.js?

Recently, I delved into the realm of mipmapping and its definition, but I find myself uncertain about how to effectively implement this technique in three.js.

After exploring a couple of examples like:

and also this one:

Both examples appear to utilize mipmapping. The first example showcases a section of code:

function mipmap( size, color ) {

            var imageCanvas = document.createElement( "canvas" ),
                context = imageCanvas.getContext( "2d" );

            imageCanvas.width = imageCanvas.height = size;

            context.fillStyle = "#444";
            context.fillRect( 0, 0, size, size );

            context.fillStyle = color;
            context.fillRect( 0, 0, size / 2, size / 2 );
            context.fillRect( size / 2, size / 2, size / 2, size / 2 );
            return imageCanvas;

        }

        var canvas = mipmap( 128, '#f00' );
        var textureCanvas1 = new THREE.CanvasTexture( canvas );
        textureCanvas1.mipmaps[ 0 ] = canvas;
        textureCanvas1.mipmaps[ 1 ] = mipmap( 64, '#0f0' );
        textureCanvas1.mipmaps[ 2 ] = mipmap( 32, '#00f' );
        textureCanvas1.mipmaps[ 3 ] = mipmap( 16, '#400' );
        textureCanvas1.mipmaps[ 4 ] = mipmap( 8,  '#040' );
        textureCanvas1.mipmaps[ 5 ] = mipmap( 4,  '#004' );
        textureCanvas1.mipmaps[ 6 ] = mipmap( 2,  '#044' );
        textureCanvas1.mipmaps[ 7 ] = mipmap( 1,  '#404' );
        textureCanvas1.repeat.set( 1000, 1000 );
        textureCanvas1.wrapS = THREE.RepeatWrapping;
        textureCanvas1.wrapT = THREE.RepeatWrapping;

        var textureCanvas2 = textureCanvas1.clone();
        textureCanvas2.magFilter = THREE.NearestFilter;
        textureCanvas2.minFilter = THREE.NearestMipMapNearestFilter;

        materialCanvas1 = new THREE.MeshBasicMaterial( { map: textureCanvas1 } );
        materialCanvas2 = new THREE.MeshBasicMaterial( { color: 0xffccaa, map: textureCanvas2 } );

        var geometry = new THREE.PlaneBufferGeometry( 100, 100 );

        var meshCanvas1 = new THREE.Mesh( geometry, materialCanvas1 );
        meshCanvas1.rotation.x = -Math.PI / 2;
        meshCanvas1.scale.set(1000, 1000, 1000);

        var meshCanvas2 = new THREE.Mesh( geometry, materialCanvas2 );
        meshCanvas2.rotation.x = -Math.PI / 2;
        meshCanvas2.scale.set( 1000, 1000, 1000 );

My confusion lies with:

textureCanvas1.mipmaps[ 1 ] = mipmap( 64, '#0f0' );

and the utilization of a 2D context.

Given these examples, I am still unsure about how to properly mipmap a planet. Specifically, I would need my sphere to consist of distinct sections so that each piece of the divided texture can be placed on different parts of the sphere. After creating power-of-2 size variations, what comes next?

Hence, my query is, how does mipmapping in three.js manifest when applied to shapes like cubes, spheres, etc.? A simplified demonstration would immensely help as existing examples (which are scarce) sometimes prove too convoluted or lack documentation.

EDIT: Another user on stackoverflow shared the following snippet:

var texture = THREE.ImageUtils.loadTexture( 'images/512.png', undefined, function() {
    texture.repeat.set( 1, 1 );
    texture.mipmaps[ 0 ] = texture.image;
    texture.generateMipmaps = true;
    texture.needsUpdate = true;
};

The importance of `texture.mipmaps[]` seems evident here. However, the individual only specified one image. Shouldn't we provide various images and allow the system to determine the appropriate one based on distance? Uncertain about how this mipmapping mechanism functions.

Answer №1

Understanding Mipmapping Techniques

Mipmapping serves as a valuable texture rendering method that operates on a per-texture basis. Essentially, when mipmapping is activated, the GPU utilizes smaller versions of a texture to render a surface based on its distance from the camera.

To implement mipmapping, it is essential to possess a series of mipmaps corresponding to your texture; these mipmaps are essentially scaled-down variations of your original texture. While in earlier times, creating these mipmaps manually may have been necessary, modern graphics APIs (such as OpenGL >= 3.0) can auto-generate them. In most cases, manual generation of mipmaps is not required, especially if you are simply applying a basic texture map to an object's surface.

It's important to note that mipmapping does not depend on the 3D shape of the object being textured. Whether you're texturing a cube, sphere, or any other model, the steps involved in enabling mipmapping remain consistent for programmers. Enabling mipmapping is not mandatory for rendering textures, but it often enhances their visual appeal.

Application within the Realm of Three.js

Notably, three.js eliminates the need for manual mipmap generation with default settings. In reference to the three.js documentation for Texture, the generateMipmaps attribute controls automatic mipmap creation and is set to true by default. This feature is integrated into the renderer here. Therefore, generating mipmapped textures becomes quite straightforward:

var texture1 = THREE.ImageUtils.loadTexture("surface.png");
// mipmaps will now be generated automatically!

Additionally, there exists a mipmaps property which can be stocked manually with mipmap images. Interestingly, if this array remains non-empty, it disables the automatic mipmap generation. The source code for this functionality can be viewed here.

Analyzing a Sample Scenario

In the initial example showcasing a painted tiled floor, the mipmap() function draws a 2D texture onto an HTML canvas. This function plays a pivotal role in displaying the tiled texture visible on the ground plane. Subsequently, these textures are loaded as mipmaps by populating them within the mipmaps array, enabling their rendition in 3D using three.js.

var canvas = mipmap(128, '#f00');
var textureCanvas1 = new THREE.CanvasTexture(canvas);
// establishment of mipmaps manually
textureCanvas1.mipmaps[0] = canvas;
textureCanvas1.mipmaps[1] = mipmap(64, '#0f0');
textureCanvas1.mipmaps[2] = mipmap(32, '#00f');
textureCanvas1.mipmaps[3] = mipmap(16, '#400');
textureCanvas1.mipmaps[4] = mipmap(8, '#040');
textureCanvas1.mipmaps[5] = mipmap(4, '#004');
textureCanvas1.mipmaps[6] = mipmap(2, '#044');
textureCanvas1.mipmaps[7] = mipmap(1, '#404');

Observe how each subsequent mipmap diminishes in size by half. For instance, the initial texture placed in mipmaps[0] is 128x128, while the following ones reduce to 64x64, 32x32, and so forth. The distinctive colors like #0f0, #00f, #400, etc., create a rainbow effect denoting the boundaries between different mipmaps.

Exploring Anisotropy as a Bonus Feature

The second scenario introduces anisotropic filtering, a further enhancement beyond mipmapping. This advanced technique selects the appropriate texture size based not just on the distance from the camera, but also on the viewing angle towards the camera. Consequently, distant tilted textures tend to appear more visually appealing.

var maxAnisotropy = renderer.getMaxAnisotropy();

var texture1 = THREE.ImageUtils.loadTexture("textures/crate.gif");
// automatic mipmap generation without additional effort
texture1.anisotropy = maxAnisotropy;
texture1.wrapS = texture1.wrapT = THREE.RepeatWrapping;
texture1.repeat.set(512, 512);

Notice the stark contrast in sharpness between the crate textures depicted on the left (texture1) versus the right (texture2)?

End Result Analysis

To delve deeper into these concepts, a comprehensive example has been assembled in a plunker, aiming to elucidate the intricacies across various scenarios:

  • The top left display (no mipmapping) showcases a pronounced moire pattern upon tilting the camera upwards, underscoring the necessity of mipmapping.
  • The top right illustration (with mipmapping) exhibits improved visuals but retains some blur at distances. Why is this so?
  • The bottom left visualization (colored mipmap) offers insights into the blurring effect induced by linearly interpolating all mipmaps. As objects recede from the camera, smaller images get utilized as indicated by color variations.
  • Finally, the bottom right integration (anisotropic filtering) promises the finest results, ensuring crisp textures regardless of their distance from the viewpoint.

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

What is the reason for the return of undefined with getElementsByClassName() in puppeteer?

Currently, I am utilizing puppeteer to fetch certain elements from a webpage, specifically class items (divs). Although I understand that getElementsByClassName returns a list that needs to be looped through, the function always returns undefined for me, e ...

Using Ternary Operators in React Components for Styling

Extracting data from a json file and attempting to assign a specific class only when certain criteria are met: render: function() { var gameList = this.props.data.map(function(game) { return ( <li key={game.id} className="l ...

What is the reason for recursion not producing a new object as output?

Trying to filter out nodes in a recursion function that iterates through a tree based on the registry property. function reduceNodesRegistry(source: any) { if (!source.registry) return source; return { ...source, children: s ...

`The challenge of properly handling quotation marks within quotation marks in JavaScript string literals

I am having trouble displaying text in a JavaScript tooltip Despite my efforts to escape the quotes and eliminate any line breaks, I keep encountering unterminated string literals. The specific text I want to show is: "No, we can't. This is going t ...

Testing the mongoose.connect callback method in Jest: A step-by-step guide

Currently working on a new Express application and utilizing Jest as the testing framework. All sections of code have been successfully covered, except for the callback of the mongoose.connect method: https://i.sstatic.net/SNrep.png I attempted to spy o ...

Add the current date plus 48 hours to the content on a webpage (JavaScript maybe?)

Hello there, I am currently in the process of setting up a small online store for a farm using Squarespace. One thing I would like to implement is specifying that items will be available for pickup two days after they are purchased online, instead of imme ...

What could be causing the "Cannot POST /api/" error to occur when attempting to submit a form?

Having issues with my basic website and struggling to find a solution. As a complete beginner in this field, I am stuck and need some guidance. Accessing http://localhost:3000/class/create works perfectly fine when running the server. However, trying to a ...

Sending a `refresh` to a Context

I'm struggling to pass a refetch function from a useQuery() hook into a context in order to call it within the context. I've been facing issues with type mismatches, and sometimes the app crashes with an error saying that refetch() is not a funct ...

Tips for resolving the error "Binding element has no default value and initializer provides no value in TypeScript"

I am currently in the process of converting a JavaScript Apollo GraphQL API project to TypeScript. During this migration, I encountered an error related to a user code block: var idArg: any Initializer provides no value for this binding element and the ...

Designing a MongoDB document structure that includes subdocuments with similar properties

Currently, I am in the process of developing a referral feature and I am relatively new to MongoDB. My main issue lies in figuring out how to construct a document that contains multiple similar subdocuments - specifically, 4 email addresses. The challenge ...

Setting up multiple versions of npm application

Can I have multiple versions of npm installed for different projects on Windows 10, or are npm installations always global? I attempted to install different versions using https://github.com/marcelklehr/nodist, but it only affected the node version and no ...

Using Strapi and Next.js to retrieve user information

After searching for similar questions with no luck, I'm reaching out for help. Building an authentication system using Strapi and Next.js, I'm faced with a task that seems simple but eludes me. The main question is: How can the client retrieve u ...

What is the method by which Morgan middleware consistently displays the output on the console following the execution of all other middleware

Utilizing the express library along with the logging library known as morgan Provided below is a snippet of working code that can be used to reproduce this in nodejs: const express = require('express') const morgan = require('morgan') ...

The document.ready function does not seem to be functioning properly within an iframe

In the main page, there's an embedded iframe set up like this: <iframe src="facts.php" style="width:320px; height:500px; border:hidden" id="facts"> </iframe> Within that iframe, a jQuery function is implemented as follows: <script ty ...

The error message "gulp error - _.flattenDeep is not a function when using gulp.watch" is indicating

Whenever I run a watch task, why am I encountering the TypeError: _.flattenDeep is not a function error? Below is the content of my gulpfile.js : var gulp = require('gulp'); var sass = require('gulp-sass'); var sourcemaps = require(&a ...

Creating a progress bar with blank spaces using HTML, CSS, and JavaScript

Upon running the code below, we encounter an issue when the animation starts. The desired effect is to color the first ten percent of the element, then continue coloring incrementally until reaching 80%. However, as it stands now, the animation appears mes ...

Transmit information from Ajax to CodeIgniter's controller

Right now, I am facing an issue with sending data from my JavaScript to a function in the controller using AJAX. Despite my best efforts, AJAX is not sending the data. Below, you can find the code that I have been working on: The JavaScript Function var ...

Error occurred while trying to authenticate the user "root" with the password in Linux using NodeJS, Express, and PostgreSQL

Update - Hurrah! It appears I neglected to consult the manual. Following the guidelines exactly for the environmental variables seems to be necessary. Corrected code: # PostgreSQL Database Information PGDATABASE_TEST = user_db PGDATABASE = user_db PGUSER ...

How can I ensure the carousel stays centered on a webpage even after resizing the browser window?

Currently in the process of developing a website, I have implemented a jquery-based carousel on the homepage sourced from here. However, substantial modifications were made to tailor its appearance specifically for the site. The issue at hand is that I hav ...

By default, the D3 hierarchy will collapse to the first level

I've been working on code to create an indented tree structure. My goal is to initially collapse the tree and show only the first level children or the root node. I tried a few different approaches, but none were successful. The current portion I have ...