Skip to content

Commit

Permalink
Support texture atlases for terrain
Browse files Browse the repository at this point in the history
 * adds `atlasIndex` option to `registry.registerMaterial`
 * `terrainMesher` now adds atlasIndex data to terrain meshes if needed
 * `terrainMaterials` can now create StandardMaterials that support
   texture atlases (via a material plugin)
  • Loading branch information
fenomas committed Jul 16, 2022
1 parent 8689b89 commit 70d13dd
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 13 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Recent changes:

* `v0.33`:
* Signature of `noa.registry.registerMaterial` changed to take an options object
* Terrain now supports texture atlases! See `registry.registerMaterial`.
* Key binding with `noa.inputs.bind` now uses [KeyboardEvent.code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code) strings
* Binding to mouse buttons now uses `Mouse1`, `Mouse2`..
* Mouse move/scroll data (`dx,dy,scrollx,scrolly`) are moved from
Expand Down
8 changes: 5 additions & 3 deletions docs/history.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ This is a summary of new features and breaking changes in recent `noa` versions.
### 0.33.0

* Signature of `noa.registry.registerMaterial` changed to take an options object
* Terrain now supports texture atlases! Merge your textures into a vertical strip atlas, then call `noa.registry.registerMaterial` with that texture and specify an `atlasIndex` options property.
* Babylon version updated
* Modernization updates to `noa.inputs`. Breaking changes:
* Key bindings should now use [KeyboardEvent.code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code) strings, like `KeyA`, `Shift`, etc.
* Mouse button bindings should use `Mouse1`, `Mouse2`..
* Mouse move/scroll values (`dx,dy,scrollx,scrolly`) are moved from
* Key bindings should now use [KeyboardEvent.code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code) strings, like `KeyA`, `Shift`, etc.
* Mouse button bindings should use `Mouse1`, `Mouse2`..
* Mouse move/scroll values (`dx,dy,scrollx,scrolly`) are moved from
`noa.inputs.state` to `noa.inputs.pointerState`

### 0.32.0
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"voxel-physics-engine": "^0.12.0"
},
"peerDependencies": {
"@babylonjs/core": "^5.2.0"
"@babylonjs/core": "^5.14.1"
},
"devDependencies": {
"eslint": "^8.3.0",
Expand Down
6 changes: 4 additions & 2 deletions src/lib/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class Registry {
* @prop {number} alpha
* @prop {string} texture
* @prop {boolean} texHasAlpha
* @prop {number} atlasIndex
* @prop {*} renderMat
*/
/** @type {MatDef[]} */
Expand Down Expand Up @@ -183,6 +184,7 @@ export class Registry {
alpha,
texture: opts.textureURL ? this._texturePath + opts.textureURL : '',
texHasAlpha: !!opts.texHasAlpha,
atlasIndex: opts.atlasIndex,
renderMat: opts.renderMaterial,
}
return matID
Expand Down Expand Up @@ -395,9 +397,9 @@ function MaterialOptions() {
*/
this.textureURL = null
/** Whether the texture image has alpha */
this.texHasTransparency = false
/** Whether the texture image has alpha */
this.texHasAlpha = false
/** Index into a (vertical strip) texture atlas, if applicable */
this.atlasIndex = -1
/**
* An optional Babylon.js `Material`. If specified, terrain for this voxel
* will be rendered with the supplied material (this can impact performance).
Expand Down
105 changes: 103 additions & 2 deletions src/lib/terrainMaterials.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

import { MaterialPluginBase, Texture } from '@babylonjs/core/Materials'
import { Engine } from '@babylonjs/core/Engines/engine'
import { MaterialPluginBase, RawTexture2DArray, Texture } from '@babylonjs/core/Materials'

/**
* @module
Expand Down Expand Up @@ -138,9 +139,109 @@ function createTerrainMat(self, blockMatID = 0) {
var scene = self.noa.rendering.getScene()
var mat = self.noa.rendering.makeStandardMaterial('terrain-textured-' + blockMatID)
var url = matInfo.texture
var tex = new Texture(url, scene, true, false, Texture.NEAREST_SAMPLINGMODE)
var sampling = Texture.NEAREST_SAMPLINGMODE
var tex = new Texture(url, scene, true, false, sampling)
if (matInfo.texHasAlpha) tex.hasAlpha = true
mat.diffuseTexture = tex

// it texture is an atlas, apply material plugin
if (matInfo.atlasIndex >= 0) new TerrainMaterialPlugin(mat, tex)
return mat
}











/**
*
* Babylon material plugin - twiddles the defines/shaders/etc so that
* a standard material can use textures from a 2D texture atlas.
*
*/

class TerrainMaterialPlugin extends MaterialPluginBase {
constructor(material, texture) {
var priority = 200
var defines = { 'NOA_TWOD_ARRAY_TEXTURE': false }
super(material, 'TestPlugin', priority, defines)
this._enable(true)
this._atlasTextureArray = null

texture.onLoadObservable.add((tex) => {
this.setTextureArrayData(tex)
})
}

setTextureArrayData(texture) {
var { width, height } = texture.getSize()
var numLayers = Math.round(height / width)
height = width
var data = texture._readPixelsSync()

var format = Engine.TEXTUREFORMAT_RGBA
var genMipMaps = true
var invertY = false
var mode = Texture.NEAREST_SAMPLINGMODE
var scene = texture.getScene()

this._atlasTextureArray = new RawTexture2DArray(
data, width, height, numLayers,
format, scene, genMipMaps, invertY, mode,
)
}

prepareDefines(defines, scene, mesh) {
defines['NOA_TWOD_ARRAY_TEXTURE'] = true
}

getClassName() {
return 'TerrainMaterialPluginName'
}

getSamplers(samplers) {
samplers.push('atlasTexture')
}

getAttributes(attributes) {
attributes.push('texAtlasIndices')
}

getUniforms() {
return { ubo: [] }
}

bindForSubMesh(uniformBuffer, scene, engine, subMesh) {
if (this._atlasTextureArray) {
uniformBuffer.setTexture('atlasTexture', this._atlasTextureArray)
}
}

getCustomCode(shaderType) {
if (shaderType === 'vertex') return {
'CUSTOM_VERTEX_MAIN_BEGIN': `
texAtlasIndex = texAtlasIndices;
`,
'CUSTOM_VERTEX_DEFINITIONS': `
uniform highp sampler2DArray atlasTexture;
attribute float texAtlasIndices;
varying float texAtlasIndex;
`,
}
if (shaderType === 'fragment') return {
'!baseColor\\=texture2D\\(diffuseSampler,vDiffuseUV\\+uvOffset\\);':
`baseColor = texture(atlasTexture, vec3(vDiffuseUV, texAtlasIndex));`,
'CUSTOM_FRAGMENT_DEFINITIONS': `
uniform highp sampler2DArray atlasTexture;
varying float texAtlasIndex;
`,
}
return null
}
}
25 changes: 21 additions & 4 deletions src/lib/terrainMesher.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ function GeometryData(terrainID) {
this.normals = [0.5] // raw data, 12 normals per quad
this.colors = [0.5] // raw data, 16 colors per quad
this.uvs = [0.5] // raw data, 8 uvs per quad
this.atlasIndices = [0] // indices into vertical strip texture atlas
}
GeometryData.prototype.dispose = function () {
this.quadMaterials = null
Expand All @@ -208,6 +209,7 @@ GeometryData.prototype.dispose = function () {
this.normals = null
this.colors = null
this.uvs = null
this.atlasIndices = null
}


Expand All @@ -234,10 +236,6 @@ GeometryData.prototype.dispose = function () {
/** @param {import('../index').Engine} noa */
function MeshBuilder(noa, terrainMatManager) {

// var matCache = {}
// var multiMatCache = {}


// core
this.build = function (chunk, geomDataSet, ignoreMaterials) {
var scene = noa.rendering.getScene()
Expand All @@ -262,6 +260,11 @@ function MeshBuilder(noa, terrainMatManager) {
vdat.uvs = geomData.uvs
vdat.applyToMesh(mesh)

// meshes using a texture atlas need atlasIndices
if (geomData.atlasIndices.length > 1) {
mesh.setVerticesData('texAtlasIndices', geomData.atlasIndices, false, 1)
}

// materials wrangled by external module
mesh.material = terrainMatManager.getMaterial(geomData.terrainID)

Expand Down Expand Up @@ -318,6 +321,7 @@ function MeshBuilder(noa, terrainMatManager) {
*
*/

/** @param {import('../index').Engine} noa */
function GreedyMesher(noa, terrainMatManager) {

var maskCache = new Int16Array(16)
Expand All @@ -326,6 +330,7 @@ function GreedyMesher(noa, terrainMatManager) {
var solidLookup = noa.registry._solidityLookup
var opacityLookup = noa.registry._opacityLookup
var matIDtoTerrainID = (matID) => terrainMatManager.getTerrainMatId(matID)
var matIDtoAtlasIndex = (matID) => noa.registry.getMaterialData(matID).atlasIndex


this.mesh = function (voxels, getMaterial, getColor, doAO, aoValues, revAoVal, edgesOnly) {
Expand Down Expand Up @@ -528,6 +533,12 @@ function GreedyMesher(noa, terrainMatManager) {
// we're now ready to push a quad worth of geometry data
var nq = geomData.numQuads

// if block material is a texture atlas, add indices to it
var atlasIndex = matIDtoAtlasIndex(matID)
if (atlasIndex >= 0) {
addAtlasIndices(geomData.atlasIndices, nq * 4, atlasIndex)
}

// add colors into geomData
// tridir is boolean for which way to split the quad into triangles
var colorsArr = geomData.colors
Expand Down Expand Up @@ -616,6 +627,12 @@ function GreedyMesher(noa, terrainMatManager) {
D: [3, 1, 0, 3, 2, 1],
}

function addAtlasIndices(indArr, offset, atlasIndex) {
for (var i = 0; i < 4; i++) {
indArr[offset + i] = atlasIndex
}
}




Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"MovementState",
"PhysicsState",
"PositionState",
"MatDef",
]
},
}
}

0 comments on commit 70d13dd

Please sign in to comment.