【问题标题】:Three.js Custom ShaderThree.js 自定义着色器
【发布时间】:2012-10-12 09:27:44
【问题描述】:

请注意,从下面的编辑 3 开始,此代码的大部分内容都已更改。

所以我真的很喜欢 Brandon Jones (found here) 的博客文章。我想将他的代码转换为 Three.js,但我遇到了一些问题。你可以找到他的完整代码here。到目前为止,这是我的尝试,我有几个问题:

// 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);

加载页面后出现以下错误:

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

我确定这与我设置属性的方式有关,但我不确定它们应该是什么。感谢任何帮助,因为在 Three.js 中几乎没有关于自定义着色器的文档。

编辑:这是博文中用于填充顶点着色器的 2 个属性(postexture)的代码:

//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);

我真的不完全理解这里到底发生了什么,但如果我是正确的,我认为它会用每个 quadVertBuffer 的一半数据填充 2 个Float32Arrays。我不仅不知道为什么,我不确定我是否正确,也不知道如何将其转换为 Three.js 方法。


EDIT2:现在我正在使用平面来显示(2D)背景,我应该使用精灵来代替吗?


EDIT3

所以当我意识到 Three.js 将为我设置位置和 uv 向量时,我走得更远了(如果与上面示例中的位置/纹理不同,这似乎是相似的)。我还注意到我可能有一些类型错误,因为我拥有的许多'v2' 类型(调用uniform2f)实际上是通过uniform2fv 加载的,所以我将它们更改为'v2v' 并更新了值。现在我没有收到错误,它确实绘制了 something,只是不完全是 tilemap。

这是更新后的顶点着色器:

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");

以及更新的着色器材质:

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
});

这是我得到的结果:

欢迎提出任何想法!


编辑 4

如果我更改顶点着色器以使用我发现的设置gl_Position 的“Three.js 方法”,我可以更接近,但精灵表中的偏移量是错误的。我认为 pixelCoord 变量设置错误(因为我认为 uv 的值与 texture 略有不同)。

我将顶点着色器的主要功能改为:

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

现在我从纹理表中得到了实际的瓷砖,但它选择的实际瓷砖是错误的:

越来越近,仍然感谢任何帮助。


编辑 5

我怀疑这将是我的最后一次更新,因为我即将得到答案。设置tileset.flipY = false;后,其中tileset是实际的贴图贴图,不是红色贴图。我将所有正确的瓷砖放在正确的位置;除了它们都是颠倒的!

这是更改后的样子:

有没有办法在 Y 轴上翻转每个单独的纹理(不编辑图块集图像)?我觉得有一些简单的矢量数学可以添加到我的着色器中来翻转它绘制的每个纹理并最终确定它。

我确实注意到,如果我不翻转两者(tilemap.flipY = false;tileset.flipY = false;),我会得到正确的纹理,在正确的位置,正确地组合在一起。但是整个地图是颠倒的!如此接近......

【问题讨论】:

  • 您是否有机会分享一个指向您的完整代码的链接?如果您有一个可以调试错误的有效链接,那就更好了。
  • @mrdoob 当然,github.com/englercj/lttp-webgl 是代码。您可以克隆它并打开 index.html。选择“加载资源”,然后选择“启动游戏”。有问题的代码主要可以在js/game/lib/core/TileMap.js中找到,在js/game/lib/core/Engine.js中实例化并添加到场景中。 resources 对象中的所有属性都是用THREE.TextureLoader 加载的THREE.Textures。谢谢!
  • 我也有brandon的代码供参考,虽然它们没有被使用(它们以brandons_为前缀)。
  • @mrdoob 自从您上次提出要求以来,代码库已经发生了显着变化。您应该能够从编辑 4 和 5 中获得您需要的所有信息,但是如果您仍然需要完整的代码,那么它在该 github 链接上是最新的。我目前正在 master 分支上工作。
  • @Toji 这就是.flipY 属性(我设置为false)的作用。来自_gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );,但生成的纹理仍然是颠倒的。如果我没有在 tilemaptileset 纹理上设置此标志,则地图不会对齐并且使用了不正确的纹理。

标签: javascript shader webgl three.js


【解决方案1】:

我设法让这个“工作”,虽然我不认为它“固定”。

我翻转了 tilemap (tilemap.flipY = true),unflipped tileset (tileset.flipY = false),然后修改了 mapmaker.html(Toji 编写的将创建这些 tilemaps)以在 sprite 上绘制每个 tile倒置工作表(图块集).

我更喜欢一个实际解决问题的不同答案,而不是像这样解决问题,但现在这是我的解决方案。

下面是完整的相关代码。

着色器:

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");

着色器材质(tilemaptilesetTHREE.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);

修改后的地图制作部分:

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);*/
};

如果有人有不同的答案,请不要害羞地发布它。

【讨论】:

    猜你喜欢
    • 2012-09-28
    • 2019-07-29
    • 1970-01-01
    • 2012-09-19
    • 2012-10-20
    • 1970-01-01
    • 1970-01-01
    • 2016-09-20
    • 2017-01-17
    相关资源
    最近更新 更多