// SimplexCellularNoiseマップ作成シェーダ // ver 1.0 2020/06/07 // by yarunashi@dooon // // Code repository for GPU noise development blog // http://briansharpe.wordpress.com // https://github.com/BrianSharpe // // I'm not one for copyrights. Use the code however you wish. // All I ask is that credit be given back to the blog or myself when appropriate. // And also to let me know if you come up with any changes, improvements, thoughts or interesting uses for this stuff. :) // Thanks! // // Brian Sharpe // brisharpe CIRCLE_A yahoo DOT com // http://briansharpe.wordpress.com // https://github.com/BrianSharpe // void FAST32_hash_3D( vec3 gridcell, vec3 v1_mask, // user definable v1 and v2. ( 0's and 1's ) vec3 v2_mask, out vec4 hash_0, out vec4 hash_1, out vec4 hash_2 ) // generates 3 random numbers for each of the 4 3D cell corners. cell corners: v0=0,0,0 v3=1,1,1 the other two are user definable { // gridcell is assumed to be an integer coordinate // TODO: these constants need tweaked to find the best possible noise. // probably requires some kind of brute force computational searching or something.... const vec2 OFFSET = vec2( 50.0, 161.0 ); const float DOMAIN = 69.0; const vec3 SOMELARGEFLOATS = vec3( 635.298681, 682.357502, 668.926525 ); const vec3 ZINC = vec3( 48.500388, 65.294118, 63.934599 ); // truncate the domain gridcell.xyz = gridcell.xyz - floor(gridcell.xyz * ( 1.0 / DOMAIN )) * DOMAIN; vec3 gridcell_inc1 = step( gridcell, vec3( DOMAIN - 1.5 ) ) * ( gridcell + 1.0 ); // compute x*x*y*y for the 4 corners vec4 P = vec4( gridcell.xy, gridcell_inc1.xy ) + OFFSET.xyxy; P *= P; vec4 V1xy_V2xy = mix( P.xyxy, P.zwzw, vec4( v1_mask.xy, v2_mask.xy ) ); // apply mask for v1 and v2 P = vec4( P.x, V1xy_V2xy.xz, P.z ) * vec4( P.y, V1xy_V2xy.yw, P.w ); // get the lowz and highz mods vec3 lowz_mods = vec3( 1.0 / ( SOMELARGEFLOATS.xyz + gridcell.zzz * ZINC.xyz ) ); vec3 highz_mods = vec3( 1.0 / ( SOMELARGEFLOATS.xyz + gridcell_inc1.zzz * ZINC.xyz ) ); // apply mask for v1 and v2 mod values v1_mask = ( v1_mask.z < 0.5 ) ? lowz_mods : highz_mods; v2_mask = ( v2_mask.z < 0.5 ) ? lowz_mods : highz_mods; // compute the final hash hash_0 = fract( P * vec4( lowz_mods.x, v1_mask.x, v2_mask.x, highz_mods.x ) ); hash_1 = fract( P * vec4( lowz_mods.y, v1_mask.y, v2_mask.y, highz_mods.y ) ); hash_2 = fract( P * vec4( lowz_mods.z, v1_mask.z, v2_mask.z, highz_mods.z ) ); } // Given an arbitrary 3D point this calculates the 4 vectors from the corners of the simplex pyramid to the point // It also returns the integer grid index information for the corners // void Simplex3D_GetCornerVectors( vec3 P, // input point out vec3 Pi, // integer grid index for the origin out vec3 Pi_1, // offsets for the 2nd and 3rd corners. ( the 4th = Pi + 1.0 ) out vec3 Pi_2, out vec4 v1234_x, // vectors from the 4 corners to the intput point out vec4 v1234_y, out vec4 v1234_z ) { // // Simplex math from Stefan Gustavson's and Ian McEwan's work at... // http://github.com/ashima/webgl-noise // // simplex math constants const float SKEWFACTOR = 1.0/3.0; const float UNSKEWFACTOR = 1.0/6.0; const float SIMPLEX_CORNER_POS = 0.5; const float SIMPLEX_PYRAMID_HEIGHT = 0.70710678118654752440084436210485; // sqrt( 0.5 ) height of simplex pyramid. P *= SIMPLEX_PYRAMID_HEIGHT; // scale space so we can have an approx feature size of 1.0 ( optional ) // Find the vectors to the corners of our simplex pyramid Pi = floor( P + dot( P, vec3( SKEWFACTOR) ) ); vec3 x0 = P - Pi + dot(Pi, vec3( UNSKEWFACTOR ) ); vec3 g = step(x0.yzx, x0.xyz); vec3 l = 1.0 - g; Pi_1 = min( g.xyz, l.zxy ); Pi_2 = max( g.xyz, l.zxy ); vec3 x1 = x0 - Pi_1 + UNSKEWFACTOR; vec3 x2 = x0 - Pi_2 + SKEWFACTOR; vec3 x3 = x0 - SIMPLEX_CORNER_POS; // pack them into a parallel-friendly arrangement v1234_x = vec4( x0.x, x1.x, x2.x, x3.x ); v1234_y = vec4( x0.y, x1.y, x2.y, x3.y ); v1234_z = vec4( x0.z, x1.z, x2.z, x3.z ); } // convert a 0.0->1.0 sample to a -1.0->1.0 sample weighted towards the extremes vec4 Cellular_weight_samples( vec4 samples ) { samples = samples * 2.0 - 1.0; //return (1.0 - samples * samples) * sign(samples); // square return (samples * samples * samples) - sign(samples); // cubic (even more variance) } // // SimplexCellular3D // cellular noise over a simplex (tetrahedron) grid // Return value range of 0.0->~1.0 // http://briansharpe.files.wordpress.com/2012/01/simplexcellularsample.jpg // // TODO: scaling of return value to strict 0.0->1.0 range // float SimplexCellular3D( vec3 P ) { // calculate the simplex vector and index math vec3 Pi; vec3 Pi_1; vec3 Pi_2; vec4 v1234_x; vec4 v1234_y; vec4 v1234_z; Simplex3D_GetCornerVectors( P, Pi, Pi_1, Pi_2, v1234_x, v1234_y, v1234_z ); // generate the random vectors // ( various hashing methods listed in order of speed ) vec4 hash_x; vec4 hash_y; vec4 hash_z; FAST32_hash_3D( Pi, Pi_1, Pi_2, hash_x, hash_y, hash_z ); //SGPP_hash_3D( Pi, Pi_1, Pi_2, hash_x, hash_y, hash_z ); // push hash values to extremes of jitter window const float INV_SIMPLEX_PYRAMID_HEIGHT = 1.4142135623730950488016887242097; // 1.0 / sqrt( 0.5 ) This scales things so to a nice 0.0->1.0 range const float JITTER_WINDOW = ( 0.0597865779345250670558198111 * INV_SIMPLEX_PYRAMID_HEIGHT) ; // this will guarentee no artifacts. hash_x = Cellular_weight_samples( hash_x ) * JITTER_WINDOW; hash_y = Cellular_weight_samples( hash_y ) * JITTER_WINDOW; hash_z = Cellular_weight_samples( hash_z ) * JITTER_WINDOW; // offset the vectors. v1234_x *= INV_SIMPLEX_PYRAMID_HEIGHT; v1234_y *= INV_SIMPLEX_PYRAMID_HEIGHT; v1234_z *= INV_SIMPLEX_PYRAMID_HEIGHT; v1234_x += hash_x; v1234_y += hash_y; v1234_z += hash_z; // calc the distance^2 to the closest point vec4 distsq = v1234_x*v1234_x + v1234_y*v1234_y + v1234_z*v1234_z; return min( min( distsq.x, distsq.y ), min( distsq.z, distsq.w ) ); } // xs_begin // author : 'yarunashi@dooon' // arg : { id = '0' name = 'xscale' value = '2' range = '0.0 100.0' step = '0.5' decimal = '1' } // arg : { id = '1' name = 'yscale' value = '2' range = '0.0 100.0' step = '0.5' decimal = '1' } // arg : { id = '2' name = 'seed' value = '0' range = '0.0 100.0' step = '0.1' decimal = '1' } // arg : { id = '3' name = 'numcolors' value = '255' range = '1 255' step = '1' decimal = '0' } // arg : { id = '4' name = 'seamless' value = '0' range = '0 1' step = '1' decimal = '0' } // arg : { id = '5' name = 'fbm' value = '0' range = '0 1' step = '1' decimal = '0' } // arg : { id = '6' name = 'hscale' value = '1.0' range = '0.1 100.0' step = '0.1' decimal = '1' } // arg : { id = '7' name = 'thickness' value = '1' range = '1 255' step = '1' decimal = '0' } // xs_end float xscale = (i_args[0] == 0) ? 2.0 : iArgs[0] ; float yscale = (i_args[1] == 0) ? 2.0 : iArgs[1] ; float seed = (i_args[2] == 0) ? 0.0 : iArgs[2] ; float step = (i_args[3] == 0) ? 255.0 : iArgs[3] ; float seamless = (i_args[4] == 0) ? 0.0 : 1.0; float fbm = (i_args[5] == 0) ? 0.0 : 1.0; float hscale = (iArgs[6] == 0) ? 1.0 : iArgs[6] ; float thickness = (iArgs[7] == 0) ? 1.0 : iArgs[7] ; // シームありノイズ生成 float smnoise(vec2 v) { vec3 p = vec3(v.x * xscale / i_volume_size.x, v.y * yscale / i_volume_size.y, seed); float r = SimplexCellular3D(p); return r; } //フラクショナルブラウン運動 #define NUM_OCTAVES 5 float fsmnoise(vec2 x) { float v = 0.0; float a = 0.5; vec2 shift = vec2(100); // Rotate to reduce axial bias mat2 rot = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.50)); for (int i = 0; i < NUM_OCTAVES; ++i) { v += a * smnoise(x); x = rot * x * 2.0 + shift; a *= 0.5; } return v; } // シームレスノイズ生成 float smlessnoise(vec2 v){ vec2 p = mod(v, i_volume_size.xy); vec2 q = p / i_volume_size.xy; vec2 r = i_volume_size.xy; return smnoise(vec2(p.x, p.y )) * q.x * q.y + smnoise(vec2(p.x, p.y + r.y)) * q.x * (1.0 - q.y) + smnoise(vec2(p.x + r.x, p.y )) * (1.0 - q.x) * q.y + smnoise(vec2(p.x + r.x, p.y + r.y)) * (1.0 - q.x) * (1.0 - q.y); } float fsmlessnoise(vec2 v){ vec2 p = mod(v, i_volume_size.xy); vec2 q = p / i_volume_size.xy; vec2 r = i_volume_size.xy; return fsmnoise(vec2(p.x, p.y )) * q.x * q.y + fsmnoise(vec2(p.x, p.y + r.y)) * q.x * (1.0 - q.y) + fsmnoise(vec2(p.x + r.x, p.y )) * (1.0 - q.x) * q.y + fsmnoise(vec2(p.x + r.x, p.y + r.y)) * (1.0 - q.x) * (1.0 - q.y); } float makemap(vec3 v) { float r = 0.0; if (seamless >= 1.0) { // seamless noise if (fbm >= 1.0) { r = fsmlessnoise(v.xy); } else { r = smlessnoise(v.xy); } } else { if (fbm >= 1.0) { r = fsmnoise(v.xy); } else { r = smnoise(v.xy); } } r = r * 255; return r - mod(r, 255.0 / step) + 1; } float map(vec3 v) { //マップの色1-255を取得 float index = makemap(v); //色の高さを反転 float height = 256.0 - index; //空間の高さに合わせる height = (height / 255.0) * i_volume_size.z * hscale; if (i_volume_size.z > 1.0) { //色の幅を調整 index = 256.0 - height; index = index - mod(index, thickness) + 1; } return ( floor(v.z) < height ? index : 0.0 ); }