Screen Tearing in Threejs Custom Shader Implementation

For my tilemap implementation using Threejs and the custom shaders by Brandon Jones found here, I am utilizing a THREE.Plane geometry for each layer. The face of the plane is painted with the following vertex and fragment shaders:

Vertex Shader:

var tilemapVS = [
    "varying vec2 pixelCoord;",
    "varying vec2 texCoord;",

    "uniform vec2 mapSize;",
    "uniform vec2 inverseTileTextureSize;",
    "uniform float inverseTileSize;",

    "void main(void) {",
    "    pixelCoord = (uv * mapSize);",
    "    texCoord = pixelCoord * inverseTileTextureSize * inverseTileSize;",
    "    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
    "}"
].join("\n");

Fragment Shader:

var tilemapFS = [
    "varying vec2 pixelCoord;",
    "varying vec2 texCoord;",

    "uniform sampler2D tiles;",
    "uniform sampler2D sprites;",

    "uniform vec2 inverseTileTextureSize;",
    "uniform vec2 inverseSpriteTextureSize;",
    "uniform float tileSize;",
    "uniform int repeatTiles;",

    "void main(void) {",
    "    vec4 tile = texture2D(tiles, texCoord);", //load this pixel of the tilemap
    "    if(tile.x == 1.0 && tile.y == 1.0) { discard; }", //discard if R is 255 and G is 255
    "    vec2 spriteOffset = floor(tile.xy * 256.0) * tileSize;", //generate the offset in the tileset this pixel represents
    "    vec2 spriteCoord = mod(pixelCoord, tileSize);",
    "    vec4 texture = texture2D(sprites, (spriteOffset + spriteCoord) * inverseSpriteTextureSize);",
    "    gl_FragColor = texture;",
    "}"
].join("\n");

The texturing setup for each texture is as follows:

//Setup Tilemap
this.tilemap.magFilter = THREE.NearestFilter;
this.tilemap.minFilter = THREE.NearestMipMapNearestFilter;
//tilemap.flipY = false;
if(this.repeat) {
    this.tilemap.wrapS = this.tilemap.wrapT = THREE.RepeatWrapping;
} else {
    this.tilemap.wrapS = this.tilemap.wrapT = THREE.ClampToEdgeWrapping;
}

//Setup Tileset
this.tileset.wrapS = this.tileset.wrapT = THREE.ClampToEdgeWrapping;
this.tileset.flipY = false;
if(this.filtered) {
    this.tileset.magFilter = THREE.LinearFilter;
    this.tileset.minFilter = THREE.LinearMipMapLinearFilter;
} else {
    this.tileset.magFilter = THREE.NearestFilter;
    this.tileset.minFilter = THREE.NearestMipMapNearestFilter;
}

The uniforms used in the shaders are set up like this:

//setup shader uniforms
this._uniforms = {
    mapSize: { type: 'v2', value: new THREE.Vector2(this.tilemap.image.width * this.tileSize, this.tilemap.image.height * this.tileSize) },
    inverseSpriteTextureSize: { type: 'v2', value: new THREE.Vector2(1/this.tileset.image.width, 1/this.tileset.image.height) },
    tileSize: { type: 'f', value: this.tileSize },
    inverseTileSize: { type: 'f', value: 1/this.tileSize },

    tiles: { type: 't', value: this.tilemap },
    sprites: { type: 't', value: this.tileset },

    inverseTileTextureSize: { type: 'v2', value: new THREE.Vector2(1/this.tilemap.image.width, 1/this.tilemap.image.height) },
    repeatTiles: { type: 'i', value: this.repeat ? 1 : 0 }
};

The geometry and mesh creation takes place as follows:

//create the shader material
this._material = new THREE.ShaderMaterial({
    uniforms: this._uniforms,
    vertexShader: tilemapVS,
    fragmentShader: tilemapFS,
    transparent: false
});

this._plane = new THREE.PlaneGeometry(
    this.tilemap.image.width * this.tileSize * this.tileScale,
    this.tilemap.image.height * this.tileSize * this.tileScale
);

this._mesh = new THREE.Mesh(this._plane, this._material);
this._mesh.z = this.zIndex;

The variables this.tileSize and this.tileScale have values of 16 and 4 respectively. However, I am encountering some tearing around the edges of the 16x16 tiles:

Interestingly, this tearing only occurs sporadically when moving along the y-axis. On my Linux machine, the issue is more pronounced and affects the x-axis as well.

It seems like there might be a slight misalignment of the 16x16 tiles caused by my vertex shader, but I'm unsure of the root cause. Any assistance on resolving this issue would be greatly appreciated. Thank you!

Edit

Here's an enhanced image showing the tearing, particularly noticeable on grassy areas:

As evident from the image, the tearing occurs along the edges of the scaled 16x16 tiles (scaled by 4).

Answer №1

Your implementation involves using mipmapped textures with discontinuous texture coordinates at the edges of tiles:

"    vec2 spriteOffset = floor(tile.xy * 256.0) * tileSize;", //calculate the offset in the tileset for this pixel
"    vec2 spriteCoord = mod(pixelCoord, tileSize);",
"    vec4 texture = texture2D(sprites, (spriteOffset + spriteCoord) * inverseSpriteTextureSize);",

Due to the floor and mod functions used, there is a sudden jump in texture coordinates between pixels, resulting in a different mipmap level being utilized at tile edges. This causes a visible disconnect as the chosen mipmap may not match adjacent pixels.

To address this issue quickly, you can opt to disable mipmapping by setting one of these options:

texture.minFilter = THREE.LinearFilter;
texture.minFilter = THREE.NearestFilter;

If mipmaps are necessary, you can adjust the bias parameter in the texture2D function. Use a value of 0.0 by default, but when encountering tile edges, set it to a negative number equal to the desired number of mipmaps to traverse back up. For example, -11.0 would backtrack 11 mipmaps from the smallest resolution mipmap to the full-sized texture at 2048x2048. The precise value will depend on factors like zoom level, image size, and texture dimensions.

Answer №2

It seems like the potential source of your problems lies in the camera position interfering with the sprite uv mapping, leading to errors in rendering. Essentially, neighboring sprites from the sprite sheet may be appearing on the sprite you are trying to display.

The line that stands out is: `vec2 spriteOffset = floor(tile.xy * 256.0) * tileSize;"

This specific code snippet could be responsible for causing the offset discrepancies. To investigate further, try adding a distinct bright color around your sprites to see if any anomalies become visible.

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

Submit the form without displaying any output in the browser, not even in the view source code

I have a basic form that needs to be submitted multiple times, but I want the submission process to be hidden from the browser. Simply using "hidden" or "display:none" won't completely hide the form code when viewing the page source. I tried using PHP ...

Creating an object in JavaScript using variables

Is it possible to create an object with keys coming from a variable parameter? Let's say 'prod_id' holds a value, and I want to create an object with key 'prod_id' and value 1. Can this be achieved? If so, how? Thank you! var cart ...

Tips for streamlining the filter function using replace and split:

Currently, I am utilizing a filter function alongside replace() and split(). Here is the code snippet: jQuery('table .abc').filter((i, el) => jQuery(el).text(jQuery(el).text().replace('a', 'b').split(' ')[0] + &ap ...

Trouble displaying MongoDB data on Meteor template

As I embarked on building my first app with Meteor, everything seemed to be going smoothly until I encountered an issue where a collection was no longer displaying in a template. Here is the code snippet: App.js Tasks = new Mongo.Collection("tasks"); i ...

Customize your Angular UI Bootstrap Datepicker with unique buttons - here's how!

Currently, I have a datepicker with clear and close buttons. I tried using the append function to add more buttons to this datepicker, but it didn't work because the content is not loaded until we click the icon since it's a popup datepicker. Is ...

How can I change the background color of the initial word in a textbox?

In my HTML, I have a text box input. While I am familiar with how to use CSS to set the background color of the entire textbox using background-color, I am wondering if it is possible to specifically target and change the background color of only the first ...

Refresh the Disqus comment count

Utilizing AJAX, I am fetching articles. To display their comment counts, I utilize the following code: DISQUSWIDGETS.getCount(); Initially, this method works fine. However, upon loading additional articles and re-calling the function, it fails to exhibit ...

Is it normal for the protractor cucumber tests to pass without observing any browser interactions taking place?

Having recently started using protractor cucumber, I have created the following feature. Upon launching protractor protractor.conf.js, the browser opens and immediately closes, displaying that my tests have passed. Is this the expected testing behavior? Sh ...

Tips for controlling the embla carousel based on breakpoints in a React application

Trying to toggle the Embla Carousel on and off based on different breakpoints using the React version. I have attempted to initialize it for mobile and destroy it for tablet upwards, but it seems like the reinitialization is not working as expected. I su ...

Tips for generating a <span> element using the img alt tag and inserting it following the <img> element

I have a set of images with 'alt' tags and I would like to extract the 'alt' tag for each img and create a <span> + alt tag + </span> line after each image. I am unsure of how to achieve this using jQuery. The desired outpu ...

Randomly reorganize elements in a JavaScript array

I am facing an unusual issue while shuffling an array in JavaScript and I am unable to identify the root cause. Can someone provide assistance? When attempting to shuffle an array, the output I receive is unexpected: [1,2,3,4,5,6,7,8,9,10] Instead of ...

Should code in Vuejs be spread out among multiple components or consolidated into a single component?

After spending a significant amount of time working with Vue, I find myself facing a dilemma now that my app has grown in size. Organizing it efficiently has become a challenge. I grasp the concept of components and their usefulness in scenarios where the ...

Absence of information from the localStorage is evident

I am currently working on a project using ReactJs and utilizing a rich text editor. I have encountered an issue where the data stored in localStorage is not showing up in the content area after refreshing the page, even though I have successfully saved the ...

Create a PDF document and provide a reply

Recently, I encountered an issue while trying to generate a PDF using KnpSnappyBundle on Symfony. Upon running a route through AJAX, the code executes without errors but fails to produce the PDF file. The objective is to create a PDF in a new tab or wind ...

Encountering an error with an unexpected token while attempting to type a function with destructuring assignment parameters in Create-React

I am currently in the process of defining the type for a function where the parameters utilize the destructuring assignment syntax like this: type somefunc = ({name} : {name: string}) => boolean; An error is popping up during compilation: ./src/App ...

Clickable Href in jquery autocomplete

These are the codes I am currently using: <link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css"> <script src="//code.jquery.com/jquery-1.10.2.js"></script> <script src="//code.jquery.com/ui/1.1 ...

What is the best way to group a Pie Chart by a string field in a .csv file using dc.js, d3.js, and crossfilter.js in a Node environment?

I've successfully set up several Dimensions and groups, but I'm encountering an issue with a Pie Chart that needs to be grouped based on domain names like bing.com. Each domain name is parsed consistently to xxxx.xxx format and the data is clean. ...

Guide on implementing Webpack and Gulp for transpiling application and test directories with multiple entry points

For a project I'm working on, I decided to build a basic blog. The focus is on honing my skills with React, ES6, and the Mocha test framework. However, I've hit a roadblock when it comes to transpiling my ES6 tests and application code using the ...

Is there a way to apply multiple nested criteria to filter an object effectively?

I'm dealing with a specific object and my goal is to filter and retrieve players from both groups based on certain criteria like "show me all players where franchise = Marvel, and power= flight" but I am struggling with the filtering process at multip ...

Adjust the marginLeft and marginRight values in a JavaScript statement within a Highcharts configuration

Is there a way to adjust the chart marginLeft and marginRight using Highcharts, and then redraw it in JavaScript? I am looking to change the chart margin dynamically at different points in my code. http://jsfiddle.net/ovh9dwqc/ I attempted to do this wit ...