0

经过大量工作,我终于得到了一个可以工作的 Simplex 噪声的平铺版本,但在使用 BufferedImage 时,我似乎无法让它正确记录和显示。每当我尝试创建图像时,最终都会出现黑白带或环,而不是平滑的阴影变化,这正是我所期待的。我猜我没有做一些简单的事情,但对于我的生活,我找不到它。

这是我的代码(其中相当一部分来自 Stefan Gustavson 的 Simplex 噪声实现):

import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import java.io.File
import scala.util.Random

object ImageTest {
  def main(args: Array[String]): Unit = {
    val image = generate(1024, 1024, 1)
    ImageIO.write(image, "png", new File("heightmap.png"))
  }

  def generate(width: Int, height: Int, octaves: Int) = {
    val map = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY)
    val pi2 = Math.PI * 2

    for ( x <- 0 until width;
          y <- 0 until height) {
      var total = 0.0

      for (oct <- 1 to octaves) {
        val scale = (1 - 1/Math.pow(2, oct))
        val s = x / width.toDouble
        val t = y / height.toDouble
        val dx = 1-scale
        val dy = 1-scale

        val nx = scale + Math.cos(s*pi2) * dx
        val ny = scale + Math.cos(t*pi2) * dy
        val nz = scale + Math.sin(s*pi2) * dx
        val nw = scale + Math.sin(t*pi2) * dy

        total += (((noise(nx,ny,nz,nw)+1)/2)) * Math.pow(0.5, oct)
      }

      map.setRGB(x,y, (total * 0xffffff).toInt)
    }

    map
  }

  // Simplex 4D noise generator
  // returns -1.0 <-> 1.0
  def noise(x: Double, y: Double, z: Double, w: Double) = {
    // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
    val s = (x + y + z + w) * F4; // Factor for 4D skewing
    val i = Math.floor(x+s).toInt
    val j = Math.floor(y+s).toInt
    val k = Math.floor(z+s).toInt
    val l = Math.floor(w+s).toInt
    val t = (i+j+k+l) * G4 // Factor for 4D unskewing

    val xBase = x - (i-t) // Unskew the cell space and set the x, y, z, w
    val yBase = y - (j-t) //distances from the cell origin
    val zBase = z - (k-t)
    val wBase = w - (l-t)

    // For the 4D case, the simplex is a 4D shape I won't even try to describe.
    // To find out which of the 24 possible simplices we're in, we need to
    // determine the magnitude ordering of x0, y0, z0 and w0.
    // Six pair-wise comparisons are performed between each possible pair
    // of the four coordinates, and the results are used to rank the numbers.
    var rankx = 0
    var ranky = 0
    var rankz = 0
    var rankw = 0
    if(xBase > yBase) rankx+=1 else ranky+=1
    if(xBase > zBase) rankx+=1 else rankz+=1
    if(xBase > wBase) rankx+=1 else rankw+=1
    if(yBase > zBase) ranky+=1 else rankz+=1
    if(yBase > wBase) ranky+=1 else rankw+=1
    if(zBase > wBase) rankz+=1 else rankw+=1
    // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
    // Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
    // impossible. Only the 24 indices which have non-zero entries make any sense.
    // We use a thresholding to set the coordinates in turn from the largest magnitude.
    // Rank 3 denotes the largest coordinate.
    val i1 = if (rankx >= 3) 1 else 0
    val j1 = if (ranky >= 3) 1 else 0
    val k1 = if (rankz >= 3) 1 else 0
    val l1 = if (rankw >= 3) 1 else 0
    // Rank 2 denotes the second largest coordinate.
    val i2 = if (rankx >= 2) 1 else 0
    val j2 = if (ranky >= 2) 1 else 0
    val k2 = if (rankz >= 2) 1 else 0
    val l2 = if (rankw >= 2) 1 else 0
    // Rank 1 denotes the second smallest coordinate.
    val i3 = if (rankx >= 1) 1 else 0
    val j3 = if (ranky >= 1) 1 else 0
    val k3 = if (rankz >= 1) 1 else 0
    val l3 = if (rankw >= 1) 1 else 0
    // The fifth corner has all coordinate offsets = 1, so no need to compute that.

    val xList = Array(xBase, xBase-i1+G4, xBase-i2+2*G4, xBase-i3+3*G4, xBase-1+4*G4)
    val yList = Array(yBase, yBase-j1+G4, yBase-j2+2*G4, yBase-j3+3*G4, yBase-1+4*G4)
    val zList = Array(zBase, zBase-k1+G4, zBase-k2+2*G4, zBase-k3+3*G4, zBase-1+4*G4)
    val wList = Array(wBase, wBase-l1+G4, wBase-l2+2*G4, wBase-l3+3*G4, wBase-1+4*G4)
    // Work out the hashed gradient indices of the five simplex corners
    val ii = if (i < 0) 256 + (i % 255) else i % 255
    val jj = if (j < 0) 256 + (j % 255) else j % 255
    val kk = if (k < 0) 256 + (k % 255) else k % 255
    val ll = if (l < 0) 256 + (l % 255) else l % 255
    val gradIndices = Array(
      perm(ii+perm(jj+perm(kk+perm(ll)))) % 32,
      perm(ii+i1+perm(jj+j1+perm(kk+k1+perm(ll+l1)))) % 32,
      perm(ii+i2+perm(jj+j2+perm(kk+k2+perm(ll+l2)))) % 32,
      perm(ii+i3+perm(jj+j3+perm(kk+k3+perm(ll+l3)))) % 32,
      perm(ii+1+perm(jj+1+perm(kk+1+perm(ll+1)))) % 32)
    // Calculate the contribution from the five corners
    var total = 0.0
    for (dim <- 0 until 5) {
      val (x,y,z,w) = (xList(dim), yList(dim), zList(dim), wList(dim))
      var t = 0.5 - x*x - y*y - z*z - w*w
      total += {
        if (t < 0) 0.0
        else {
          t *= t
          val g = grad4(gradIndices(dim))
          t * t * ((g.x*x)+(g.y*y)+(g.z*z)+(g.w*w))
        }
      }
    }

    // Sum up and scale the result to cover the range [-1,1]
    27.0 * total
  }

  case class Grad(x: Double, y: Double, z: Double, w: Double = 0.0)
  private lazy val grad4 = Array(
    Grad(0,1,1,1), Grad(0,1,1,-1), Grad(0,1,-1,1), Grad(0,1,-1,-1),
    Grad(0,-1,1,1),Grad(0,-1,1,-1),Grad(0,-1,-1,1),Grad(0,-1,-1,-1),
    Grad(1,0,1,1), Grad(1,0,1,-1), Grad(1,0,-1,1), Grad(1,0,-1,-1),
    Grad(-1,0,1,1),Grad(-1,0,1,-1),Grad(-1,0,-1,1),Grad(-1,0,-1,-1),
    Grad(1,1,0,1), Grad(1,1,0,-1), Grad(1,-1,0,1), Grad(1,-1,0,-1),
    Grad(-1,1,0,1),Grad(-1,1,0,-1),Grad(-1,-1,0,1),Grad(-1,-1,0,-1),
    Grad(1,1,1,0), Grad(1,1,-1,0), Grad(1,-1,1,0), Grad(1,-1,-1,0),
    Grad(-1,1,1,0),Grad(-1,1,-1,0),Grad(-1,-1,1,0),Grad(-1,-1,-1,0))

  private lazy val perm = new Array[Short](512)
  for(i <- 0 until perm.length)
    perm(i) = Random.nextInt(256).toShort

  private lazy val F4 = (Math.sqrt(5.0) - 1.0) / 4.0
  private lazy val G4 = (5.0 - Math.sqrt(5.0)) / 20.0
}

我已经检查了我正在使用的噪声函数的输出值,它还没有返回除 (-1, 1) 独占之外的任何内容。对于单个八度音阶,我提供给图像 ( total) 的值也没有超出 (0,1) 的独占性。

我唯一能看到的问题是 BufferedImage 类型设置不正确,或者total在设置图像中的值时乘以错误的十六进制值。

我查看了 BufferedImage 上的 Javadocs 以了解有关它们接受的类型和值的信息,尽管我发现的任何内容在我的代码中似乎都不合适(不过,我通常对使用 BufferedImage 相当陌生)。而且我尝试过更改十六进制值,但似乎都没有改变任何东西。我发现唯一有影响的是如果我将该(total * 0xffffff).toInt值除以 256,这似乎会使波段变暗,并且在应该的区域上出现轻微的渐变,但是如果我将除法增加太多,图像只是变成黑色。因此,到目前为止,我一直在纠结可能是什么问题。

4

1 回答 1

0

(total * 0xffffff).toInt似乎没有意义。您正在使用单次乘法从灰度浮点创建 ARGB 值?

我想你想要这样的东西:

  val i   = (total * 0xFF).toInt
  val rgb = 0xFF000000 | (i << 16) | (i << 8) | i

这给了我一个平滑的随机纹理,虽然对比度非常低 - 使用 1 个八度音程,你的总和似乎在 0.2 到 0.3 之间变化,所以你可能需要稍微调整一下比例。


我不确定如何获得 16 位灰度分辨率。也许您需要直接设置栅格数据而不是使用setRGB(这迫使您降低到 8 位)。此问题下方的评论建议您直接使用栅格。

于 2013-05-30T09:02:59.180 回答