Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Terrain mipmapping #172

Open
Karang opened this issue Mar 22, 2021 · 2 comments
Open

Terrain mipmapping #172

Karang opened this issue Mar 22, 2021 · 2 comments

Comments

@Karang
Copy link
Collaborator

Karang commented Mar 22, 2021

Add mipmapping to remove moiré patterns, especially visible in VR but also when moving the camera.

This is not a trivial issue. Setting the minFilter of the texture to THREE.NearestMipmapNearestFilter solve the moiré issue but handle very badly transparent blocks and cause issue with the alpha testing.

A correct solution would be to generate the mipmaps manually and use a better algorithm to interpolate the alpha (max neighbor?). I tried to do that but it wasn't very concluant, may revisit this solution in the future.

An other solution could be to generate a binary alphaMap that doesnt have the mipmap and use the mipmap on the texture.

@Karang
Copy link
Collaborator Author

Karang commented Mar 22, 2021

(Buggy?) code to generate the mipmaps manually:

function mipmap(img, scale = false) {
  const imageCanvas = document.createElement( "canvas" )
  const context = imageCanvas.getContext( "2d" )
  if (!scale) {
    imageCanvas.width = imageCanvas.height = img.width
    context.drawImage(img, 0, 0)
  } else {
    imageCanvas.width = imageCanvas.height = img.width / 2
    const inData = img.getContext('2d').getImageData(0, 0, img.width, img.height).data
    const out = context.getImageData(0, 0, imageCanvas.width, imageCanvas.height)
    for (let i=0 ; i<imageCanvas.height; i++) {
      for (let j=0 ; j<imageCanvas.width; j++) {
        let in0 = (i*2 * imageCanvas.width + j*2) * 4
        let in1 = in0 + 4
        let in2 = in0 + img.width
        let in3 = in0 + img.width + 4

        let a0 = inData[in0+3]
        let a1 = inData[in1+3]
        let a2 = inData[in2+3]
        let a3 = inData[in3+3]

        let r = (inData[in0] * a0 + inData[in1] * a1 + inData[in2] * a2 + inData[in3] * a3) / 1020
        let g = (inData[in0+1] * a0 + inData[in1+1] * a1 + inData[in2+1] * a2 + inData[in3+1] * a3) / 1020
        let b = (inData[in0+2] * a0 + inData[in1+2] * a1 + inData[in2+2] * a2 + inData[in3+2] * a3) / 1020
        let a = Math.max(Math.max(a0, a1), Math.max(a2, a3))
        let outIdx = (i * imageCanvas.width + j) * 4
        out.data[outIdx++] = r
        out.data[outIdx++] = g
        out.data[outIdx++] = b
        out.data[outIdx++] = a
      }
    }
    context.putImageData(out, 0, 0)
  }
  return imageCanvas
}

function loadTextureMipmap(path, cb) {
  let img = new Image()
  img.onload = () => {
    const canvas = mipmap(img)
    const texture = new THREE.CanvasTexture(canvas)
    texture.mipmaps[0] = canvas
    let i=0
    while (texture.mipmaps[i].width != 1) {
      i++
      texture.mipmaps[i] = mipmap(texture.mipmaps[i-1], true)
    }

    texture.wrapS = THREE.ClampToEdgeWrapping
    texture.wrapT = THREE.ClampToEdgeWrapping
    texture.magFilter = THREE.NearestFilter
    texture.minFilter = THREE.NearestMipmapNearestFilter
    texture.flipY = false

    cb(texture)
  }
  img.src = path
}

@Karang
Copy link
Collaborator Author

Karang commented Mar 26, 2021

Debugged mipmap generator:

function mipmap(img, scale = false) {
  const imageCanvas = document.createElement( "canvas" )
  const context = imageCanvas.getContext( "2d" )
  if (!scale) {
    imageCanvas.width = imageCanvas.height = img.width
    context.drawImage(img, 0, 0)
  } else {
    imageCanvas.width = imageCanvas.height = img.width / 2
    const inData = img.getContext('2d').getImageData(0, 0, img.width, img.height).data
    const out = context.getImageData(0, 0, imageCanvas.width, imageCanvas.height)
    for (let i=0 ; i<imageCanvas.height; i++) {
      for (let j=0 ; j<imageCanvas.width; j++) {
        let in0 = (i*2 * img.width + j*2) * 4
        let in1 = in0 + 4
        let in2 = in0 + img.width * 4
        let in3 = in0 + img.width * 4 + 4

        let a0 = inData[in0 + 3]
        let a1 = inData[in1 + 3]
        let a2 = inData[in2 + 3]
        let a3 = inData[in3 + 3]

        let r = g = b = a = 0
        let div = 0

        if (a0 > 0) {
          r += inData[in0]
          g += inData[in0 + 1]
          b += inData[in0 + 2]
          div++
        }

        if (a1 > 0) {
          r += inData[in1]
          g += inData[in1 + 1]
          b += inData[in1 + 2]
          div++
        }

        if (a2 > 0) {
          r += inData[in2]
          g += inData[in2 + 1]
          b += inData[in2 + 2]
          div++
        }

        if (a3 > 0) {
          r += inData[in3]
          g += inData[in3 + 1]
          b += inData[in3 + 2]
          div++
        }

        if (div > 0) {
          r /= div
          g /= div
          b /= div
        }
        a = Math.max(Math.max(a0, a1), Math.max(a2, a3))

        let outIdx = (i * imageCanvas.width + j) * 4
        out.data[outIdx++] = r
        out.data[outIdx++] = g
        out.data[outIdx++] = b
        out.data[outIdx++] = a
      }
    }
    context.putImageData(out, 0, 0)
  }
  return imageCanvas
}

function loadTextureMipmap(path, cb) {
  let img = new Image()
  img.onload = () => {
    const canvas = mipmap(img)
    const texture = new THREE.CanvasTexture(canvas)
    texture.mipmaps[0] = canvas
    let i=0
    while (texture.mipmaps[i].width != 1) {
      i++
      texture.mipmaps[i] = mipmap(texture.mipmaps[i-1], true)
    }

    texture.wrapS = THREE.ClampToEdgeWrapping
    texture.wrapT = THREE.ClampToEdgeWrapping
    texture.magFilter = THREE.NearestFilter
    texture.minFilter = THREE.NearestMipmapNearestFilter
    texture.flipY = false

    cb(texture)
  }
  img.src = path
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants