Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • SEARCH
  • Home
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 8745007
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: June 13, 20262026-06-13T11:56:08+00:00 2026-06-13T11:56:08+00:00

Note that much of this code as changed as of edit 3 below. So

  • 0

Note that much of this code as changed as of edit 3 below.

So I really like a blog post by Brandon Jones (found here). I wanted to convert his code to Three.js, but I am having some issues. You can find his full code here. Here is my attempt so far, with a couple comments for questions I have:

// Shader
var tilemapVS = [
    "attribute vec2 pos;",
    "attribute vec2 texture;",

    "varying vec2 pixelCoord;",
    "varying vec2 texCoord;",

    "uniform vec2 viewOffset;",
    "uniform vec2 viewportSize;",
    "uniform vec2 inverseTileTextureSize;",
    "uniform float inverseTileSize;",

    "void main(void) {",
    "   pixelCoord = (texture * viewportSize) + viewOffset;",
    "   texCoord = pixelCoord * inverseTileTextureSize * inverseTileSize;",
    "   gl_Position = vec4(pos, 0.0, 1.0);",
    "}"
].join("\n");

var tilemapFS = [
    "precision highp float;",

    "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) {",
    "   if(repeatTiles == 0 && (texCoord.x < 0.0 || texCoord.x > 1.0 || texCoord.y < 0.0 || texCoord.y > 1.0)) { discard; }",
    "   vec4 tile = texture2D(tiles, texCoord);",
    "   if(tile.x == 1.0 && tile.y == 1.0) { discard; }",
    "   vec2 spriteOffset = floor(tile.xy * 256.0) * tileSize;",
    "   vec2 spriteCoord = mod(pixelCoord, tileSize);",
    "   gl_FragColor = texture2D(sprites, (spriteOffset + spriteCoord) * inverseSpriteTextureSize);",
    //"   gl_FragColor = tile;",
    "}"
].join("\n");

this.material = new THREE.ShaderMaterial({
    attributes: {
        //not really sure what to use here, he uses some quadVertBuffer
        //for these values, but not sure how to translate.
        pos: { type: 'v2', value: new THREE.Vector2(0, 0) },
        texture: { type: 'v2', value: new THREE.Vector2(0, 0) }
    },
    uniforms: {
        viewportSize: { type: 'v2', value: new THREE.Vector2(viewport.width() / this.tileScale, viewport.height() / this.tileScale) },
        inverseSpriteTextureSize: { type: 'v2', value: new THREE.Vector2(1/tileset.image.width, 1/tileset.image.height) },
        tileSize: { type: 'f', value: this.tileSize },
        inverseTileSize: { type: 'f', value: 1/this.tileSize },

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

        viewOffset: { type: 'v2', value: new THREE.Vector2(Math.floor(0), Math.floor(0)) },
        inverseTileTextureSize: { type: 'v2', value: new THREE.Vector2(1/tilemap.image.width, 1/tilemap.image.height) },
        //is 'i' the correct type for an int?
        repeatTiles: { type: 'i', value: 1 }
    },
    vertexShader: tilemapVS,
    fragmentShader: tilemapFS,
    transparent: false
});

/*this.material = new THREE.MeshBasicMaterial({
    color: 0xCC0000
})*/

this.plane = new THREE.PlaneGeometry(
    tilemap.image.width * this.tileSize * this.tileScale, //width
    tilemap.image.height * this.tileSize * this.tileScale//, //height
    //tilemap.image.width * this.tileScale, //width-segments
    //tilemap.image.height * this.tileScale //height-segments
);

this.plane.dynamic = true;

this.mesh = new THREE.Mesh(this.plane, this.material);

Once I load the page I get the following error:

TypeError: v1 is undefined
    customAttribute.array[ offset_custom ] = v1.x;

I’m sure this has to do with how I set the attributes, but i’m not sure what they should be. Any help is appreciated as there is little to no documentation on Custom Shaders in Three.js.

EDIT: Here is the code used in the blog post to fill the 2 attributes of the vertex shader (pos, and texture):

//in ctor
var quadVerts = [
    //x  y  u  v
    -1, -1, 0, 1,
     1, -1, 1, 1,
     1,  1, 1, 0,

    -1, -1, 0, 1,
     1,  1, 1, 0,
    -1,  1, 0, 0
];

this.quadVertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(quadVerts), gl.STATIC_DRAW);

this.tilemapShader = GLUtil.createProgram(gl, tilemapVS, tilemapFS);

//...

//then on the draw method
gl.bindBuffer(gl.ARRAY_BUFFER, this.quadVertBuffer);

gl.enableVertexAttribArray(shader.attribute.position);
gl.enableVertexAttribArray(shader.attribute.texture);
gl.vertexAttribPointer(shader.attribute.position, 2, gl.FLOAT, false, 16, 0);
gl.vertexAttribPointer(shader.attribute.texture, 2, gl.FLOAT, false, 16, 8);

I really don’t fully understand exactly what is happening here, but if I am correct I think it is filling 2 Float32Arrays with half the data of the quadVertBuffer in each. Not only am I not sure why, I am not sure if I’m correct, nor do I know how t convert this to the Three.js method.


EDIT2: Right now I am using a plane to display the (2D) background, should I be using a sprite instead?


EDIT3:

So I got a little farther when I realized that Three.js will set position and uv vectors for me (which seems to be similar if not the same as position/texture in the above example). I also noticed that I may have had some types wrong since many of the 'v2' types I had (which invoke uniform2f) were actually being loaded via uniform2fv, so I changed those to 'v2v' and updated the value. Now I don’t get the error, and it does paint something, just not quite the tilemap.

Here is the updated Vertex Shader:

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

    "uniform vec2 viewOffset;",
    "uniform vec2 viewportSize;",
    "uniform vec2 inverseTileTextureSize;",
    "uniform float inverseTileSize;",

    "void main(void) {",
    "   pixelCoord = (uv * viewportSize) + viewOffset;",
    "   texCoord = pixelCoord * inverseTileTextureSize * inverseTileSize;",
    "   gl_Position = vec4(position.x, position.y, 0.0, 1.0);",
    "}"
].join("\n");

and the updated Shader Material:

this._material = new THREE.ShaderMaterial({
    uniforms: {
        viewportSize: { type: 'v2v', value: [new THREE.Vector2(viewport.width() / this.tileScale, viewport.height() / this.tileScale)] },
        inverseSpriteTextureSize: { type: 'v2v', value: [new THREE.Vector2(1/tileset.image.width, 1/tileset.image.height)] },
        tileSize: { type: 'f', value: this.tileSize },
        inverseTileSize: { type: 'f', value: 1/this.tileSize },

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

        viewOffset: { type: 'v2', value: new THREE.Vector2(0, 0) },
        inverseTileTextureSize: { type: 'v2v', value: [new THREE.Vector2(1/tilemap.image.width, 1/tilemap.image.height)] },
        repeatTiles: { type: 'i', value: 1 }
    },
    vertexShader: tilemapVS,
    fragmentShader: tilemapFS,
    transparent: false
});

And here is the result that I get:

enter image description here

Any ideas are welcome!


EDIT 4:

If I change the Vertex shader to use what I have found to be the “Three.js method” of setting gl_Position I can get even closer, but the offset is wrong in the sprite sheet. I think the pixelCoord varying is set wrong (since uv has slightly different values than texture I think).

I changed the Vertex Shader’s main function to:

void main(void) {
   pixelCoord = (uv * viewportSize) + viewOffset;
   texCoord = pixelCoord * inverseTileTextureSize * inverseTileSize;
   gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

and now I get actual tiles form the texture sheet, but the actual tile it chooses is wrong:

enter image description here

getting closer, any help is still appreciated.


EDIT 5:

I suspect this will be my last update, as I am close to having an answer. After setting tileset.flipY = false;, where tileset is the actual texture tiles, not the red map. I get all the right tiles landing in the right places; except they are all upside down!

Here is what it looks like after this change:

enter image description here

Is there some way to flip each individual texture over the Y axis (without editing the tileset image)? I feel like there is some simple vector math I could add to my shader to flip each texture it draws and finalize this.

I do note that if I don’t flip both (tilemap.flipY = false; and tileset.flipY = false;) I get the right textures, int the right spots, fitting together correctly. But the entire map is upside down! so close…

enter image description here

  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. Editorial Team
    Editorial Team
    2026-06-13T11:56:09+00:00Added an answer on June 13, 2026 at 11:56 am

    I managed to get this “working” although I do not consider it “fixed”.

    I flipped the tilemap (tilemap.flipY = true), unflipped the tileset (tileset.flipY = false) and then modified the mapmaker.html (that Toji wrote that will create these tilemaps) to draw each tile upside down on the sprite sheet (tileset).

    I would MUCH prefer a different answer that actually fixes the problem instead of working around it like this, but for now this is my solution.

    Below is the full relevant code.

    Shader:

    var tilemapVS = [
        "varying vec2 pixelCoord;",
        "varying vec2 texCoord;",
    
        "uniform vec2 viewOffset;",
        "uniform vec2 viewportSize;",
        "uniform vec2 inverseTileTextureSize;",
        "uniform float inverseTileSize;",
    
        "void main(void) {",
        "    pixelCoord = (uv * viewportSize) + viewOffset;",
        "    texCoord = pixelCoord * inverseTileTextureSize * inverseTileSize;",
        "    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
        "}"
    ].join("\n");
    
    var tilemapFS = [
        //"precision highp float;",
    
        "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");
    

    Shader Material (where tilemap and tileset are THREE.Textures):

    //Setup Tilemap
    tilemap.magFilter = THREE.NearestFilter;
    tilemap.minFilter = THREE.NearestMipMapNearestFilter;
    if(this.repeat) {
        tilemap.wrapS = tilemap.wrapT = THREE.RepeatWrapping;
    } else {
        tilemap.wrapS = tilemap.wrapT = THREE.ClampToEdgeWrapping;
    }
    
    //Setup Tileset
    tileset.wrapS = tileset.wrapT = THREE.ClampToEdgeWrapping;
    tileset.flipY = false;
    if(this.filtered) {
        tileset.magFilter = THREE.LinearFilter;
        tileset.minFilter = THREE.LinearMipMapLinearFilter;
    } else {
        tileset.magFilter = THREE.NearestFilter;
        tileset.minFilter = THREE.NearestMipMapNearestFilter;
    }
    
    //setup shader uniforms
    this.offset = new THREE.Vector2(0, 0);
    this._uniforms = {
        viewportSize: { type: 'v2', value: new THREE.Vector2(viewport.width / this.tileScale, viewport.height / this.tileScale) },
        inverseSpriteTextureSize: { type: 'v2', value: new THREE.Vector2(1/tileset.image.width, 1/tileset.image.height) },
        tileSize: { type: 'f', value: this.tileSize },
        inverseTileSize: { type: 'f', value: 1/this.tileSize },
    
        tiles: { type: 't', value: tilemap },
        sprites: { type: 't', value: tileset },
    
        viewOffset: { type: 'v2', value: this.offset },
        inverseTileTextureSize: { type: 'v2', value: new THREE.Vector2(1/tilemap.image.width, 1/tilemap.image.height) },
        repeatTiles: { type: 'i', value: this.repeat ? 1 : 0 }
    };
    
    //create the shader material
    this._material = new THREE.ShaderMaterial({
        uniforms: this._uniforms,
        vertexShader: tilemapVS,
        fragmentShader: tilemapFS,
        transparent: false
    });
    
    this._plane = new THREE.PlaneGeometry(viewport.width, viewport.height, this.tileSize, this.tileSize);
    
    this._mesh = new THREE.Mesh(this._plane, this._material);
    

    Modified Mapmaker portion:

    MapMaker.prototype.processTile = function(x, y) {
        //rotate upside down, and draw
        this.tileCtx.save();
        this.tileCtx.translate(0, this.tileSize);
        this.tileCtx.scale(1, -1);
        this.tileCtx.drawImage(this.srcImage, 
            x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize, 
            0, 0, this.tileSize, this.tileSize);
    
        var sprite = this.cacheSprite();
    
        this.tileCtx.restore();
    
        this.mapCtx.fillStyle="rgb(" + sprite.x + "," + sprite.y + ", 0)";
        this.mapCtx.fillRect(x,y,1,1);
    
        /* Why was this thing drawing 2 times?
        this.tileCtx.drawImage(this.srcImage, 
            x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize, 
            0, 0, this.tileSize, this.tileSize);*/
    };
    

    If anyone has a different answer, please do not be shy to post it.

    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Related Questions

How would you do this PHP switch statement? Also note that these are much
(note that this question is not about CAS, it's about the May fail spuriously
Note that all the code is a simplified example in order to only communicate
Note that this is for an ApiController in MVC 4 although I think it
Warning: Note that this is a dumb question of something that I would probably
Edit : Note that, as Daniel and latkin noted in an answer and a
[Please note that this is a different question from the already answered How to
This is my current code that does a MultiField Query ''# Variables used by
--- Note to moderators: Today (July 15), I've noticed that someone already faced this
I have 3 sets in Linq, like this: struct Index { string code; int

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.