2012-10-22 83 views
3

Note that much of this code as changed as of edit 3 below.Three.js Custom Shader

所以我非常喜歡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); 

我真的不完全瞭解到底是什麼發生在這裏,但如果我是正確的,我認爲它是填充2 Float32Array s與每個​​的數據的一半。我不知道爲什麼,我不確定我是否正確,也不知道如何將其轉換爲Three.js方法。


EDIT2:現在我使用的是平面顯示(2D)的背景下,我應該使用一個精靈呢?


EDIT3

所以我有點遠,當我意識到three.js所將設置爲我的位置和紫外線向量(這似乎是相似的,如果不一樣的位置/紋理在上面的例子中)。我也注意到我可能有一些類型錯誤,因爲我有很多'v2'類型(調用uniform2f)實際上是通過uniform2fv加載的,所以我將它們更改爲'v2v'並更新了值。現在我沒有得到這個錯誤,並且它的確畫了,只是不太像瓷磚地圖。

這裏是更新的頂點着色器:

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

這裏是結果,我得到:

enter image description here

任何想法是受歡迎的!


編輯4

如果我改變頂點着色器使用什麼我發現是「three.js所法」設置gl_Position我可以得到更接近的,但偏移精靈表錯誤。我認爲pixelCoord變化是錯誤的(因爲uv與我認爲的texture略有不同)。

我改變了頂點着色器的主要功能:

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

,現在我得到實際的瓷磚形成質地表,但它選擇的實際區塊是錯誤的:

enter image description here

越來越更接近,任何幫助仍然感激。


編輯5

我懷疑這將是我最後一次更新,因爲我接近有一個答案。在設置tileset.flipY = false;之後,其中tileset是實際紋理貼圖,而不是紅色貼圖。我把所有正確的瓷磚着陸在正確的地方;除了他們都倒過來!

下面是它看起來像這樣改變之後:

enter image description here

是否有某種方式來翻轉Y軸每一個人的質地(沒有編輯地形設置圖像)?我覺得有一些簡單的矢量數學可以添加到我的着色器中,以翻轉它繪製的每個紋理並最終確定它。

我注意到,如果我不翻轉兩個(tilemap.flipY = false;tileset.flipY = false;),我會得到正確的紋理,在正確的位置,正確地裝配在一起。但整個地圖是顛倒的!如此接近......

enter image description here

+0

任何機會,你可以分享鏈接* *你的*完整的代碼?如果你有一個工作鏈接,我們可以調試錯誤,那更好。 – mrdoob

+0

@mrdoob當然,https://github.com/englercj/lttp-webgl是代碼。您可以將其克隆並打開index.html。選擇「加載資源」,然後選擇「啓動遊戲」。相關代碼主要可以在'js/game/lib/core/TileMap.js'中找到,它被實例化並添加到'js/game/lib/core/Engine.js'中的場景中。 'resources'對象中的所有屬性都是'THREE.Texture',加載了'THREE.TextureLoader'。謝謝! – Chad

+0

我也有brandon的代碼,以供參考,儘管它們沒有被使用(它們的前綴是'brandons_')。 – Chad

回答

1

我設法得到這個「工作」雖然我不認爲這是「固定」。

我翻轉tilemap的(tilemap.flipY = true),unflipped的地形設置(tileset.flipY = false),然後修改mapmaker.html(即東治寫道,將創建這些tilemaps)來繪製每個圖塊在精靈表(地形設置)倒置。

我會更喜歡不同的答案,而不是像這樣解決問題,但現在這是我的解決方案。

以下是完整的相關代碼。

着色器:

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.Texture S):

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

如果任何人有不同的回答,請不要害羞發佈它。