1

I have been trying to procedurally generate a sphere's surface using simplex noise, and I figured that in order to get smooth, non-distorted noise I need to map each uv pixel to an xyz coordinate. I have tried a few different algorithms, with the following being my favourite:

function convert2d3d(r1, r2, x, y) {
    let z = -1 + 2 * x / r1;
    let phi = 2 * Math.PI * y / r1;
    let theta = Math.asin(z);
    return {
        x: r2 * Math.cos(theta) * Math.cos(phi),
        y: r2 * Math.cos(theta) * Math.sin(phi),
        z: r2 * z,
    }
}

While the points generated look continuous, there is severe distortion around the texture seams, and where the texture is stretched the most:

simplex noise

I am aware what I am trying to do is called UV mapping, yet I'm struggling to implement it correctly. Either I get severe distortion, or ugly seams. To render the sphere I am using Three.JS MeshPhongMaterial and for the noise I am using noisejs.

Matthew
  • 160
  • 9

2 Answers2

1

Do you want something like THIS?
In the gui at the top right under scene -> geometry select the sphere.

No need to mess with UVs :)

Vertex shader from the demo linked above:

varying vec3 vPosition;
void main() {
  vPosition = normalize(position);
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}

Fragment Shader from the demo linked above:

varying vec3 vPosition;
uniform float scale;

//
// Description : Array and textureless GLSL 2D/3D/4D simplex 
//               noise functions.
//      Author : Ian McEwan, Ashima Arts.
//  Maintainer : ijm
//     Lastmod : 20110822 (ijm)
//     License : Copyright (C) 2011 Ashima Arts. All rights reserved.
//               Distributed under the MIT License. See LICENSE file.
//               https://github.com/ashima/webgl-noise
// 

vec3 mod289(vec3 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 mod289(vec4 x) {
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 permute(vec4 x) {
     return mod289(((x*34.0)+1.0)*x);
}

vec4 taylorInvSqrt(vec4 r)
{
  return 1.79284291400159 - 0.85373472095314 * r;
}

float snoise(vec3 v)
  { 
  const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;
  const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);

// First corner
  vec3 i  = floor(v + dot(v, C.yyy) );
  vec3 x0 =   v - i + dot(i, C.xxx) ;

// Other corners
  vec3 g = step(x0.yzx, x0.xyz);
  vec3 l = 1.0 - g;
  vec3 i1 = min( g.xyz, l.zxy );
  vec3 i2 = max( g.xyz, l.zxy );

  //   x0 = x0 - 0.0 + 0.0 * C.xxx;
  //   x1 = x0 - i1  + 1.0 * C.xxx;
  //   x2 = x0 - i2  + 2.0 * C.xxx;
  //   x3 = x0 - 1.0 + 3.0 * C.xxx;
  vec3 x1 = x0 - i1 + C.xxx;
  vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
  vec3 x3 = x0 - D.yyy;      // -1.0+3.0*C.x = -0.5 = -D.y

// Permutations
  i = mod289(i); 
  vec4 p = permute( permute( permute( 
             i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
           + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 
           + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));

// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
  float n_ = 0.142857142857; // 1.0/7.0
  vec3  ns = n_ * D.wyz - D.xzx;

  vec4 j = p - 49.0 * floor(p * ns.z * ns.z);  //  mod(p,7*7)

  vec4 x_ = floor(j * ns.z);
  vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)

  vec4 x = x_ *ns.x + ns.yyyy;
  vec4 y = y_ *ns.x + ns.yyyy;
  vec4 h = 1.0 - abs(x) - abs(y);

  vec4 b0 = vec4( x.xy, y.xy );
  vec4 b1 = vec4( x.zw, y.zw );

  //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
  //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
  vec4 s0 = floor(b0)*2.0 + 1.0;
  vec4 s1 = floor(b1)*2.0 + 1.0;
  vec4 sh = -step(h, vec4(0.0));

  vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
  vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;

  vec3 p0 = vec3(a0.xy,h.x);
  vec3 p1 = vec3(a0.zw,h.y);
  vec3 p2 = vec3(a1.xy,h.z);
  vec3 p3 = vec3(a1.zw,h.w);

//Normalise gradients
  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
  p0 *= norm.x;
  p1 *= norm.y;
  p2 *= norm.z;
  p3 *= norm.w;

// Mix final noise value
  vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
  m = m * m;
  return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 
                                dot(p2,x2), dot(p3,x3) ) );
  }

void main() {
  float n = snoise(vPosition * scale);
  gl_FragColor = vec4(1.0 * n, 1.0 * n, 1.0 * n, 1.0);
}

The above takes a scale uniform of type float.

var uniforms = {
    scale: { type: "f", value: 10.0 }
};

More ShaderMaterial demos

2pha
  • 9,798
  • 2
  • 29
  • 43
  • A good answer, I didn't consider using WebGL with Three.JS as I didn't even think it possible, hence the convolution. For now I'm going to continue with generating textures, as it allows me to more easily use the data in javascript. As far as I'm aware, random access would be difficult with WebGL. – Matthew Jan 15 '17 at 19:48
  • 1
    Sending data to a shader is easy enough via the uniforms as you can see when playing with the values in [this matrix shader](http://blog.2pha.com/demos/threejs/shaders/matrix.html) plus you would get much better performance if animating. – 2pha Jan 15 '17 at 21:12
1

Weighing the pros and cons of WebGL vs Textures, I decided on using the following function:

function convert2d3d(r, x, y) {
    let lat  = y / r * Math.PI - Math.PI / 2;
    let long = x / r * 2 * Math.PI - Math.PI;

    return {
        x: Math.cos(lat) * Math.cos(long),
        y: Math.sin(lat),
        z: Math.cos(lat) * Math.sin(long),
    }
}

Given a point on a square texture of size r×r, convert to lat/long, returning a 3d coordinate where the texture is mapped onto a sphere of radius 1.

I adapted the function from a blog post on inear.se, converting it to use degrees throughout. I wouldn't of managed it without 2phas answer showing me an alternative, and helping me look for what I needed. Here is what it looks like:

green-black globe

Right now it's ugly, but this was the first step.

Matthew
  • 160
  • 9