1#version 130 2// Compatibility #ifdefs needed for parameters 3#ifdef GL_ES 4#define COMPAT_PRECISION mediump 5#else 6#define COMPAT_PRECISION 7#endif 8 9// Parameter lines go here: 10#pragma parameter RETRO_PIXEL_SIZE "Retro Pixel Size" 0.84 0.0 1.0 0.01 11#ifdef PARAMETER_UNIFORM 12// All parameter floats need to have COMPAT_PRECISION in front of them 13uniform COMPAT_PRECISION float RETRO_PIXEL_SIZE; 14#else 15#define RETRO_PIXEL_SIZE 0.84 16#endif 17 18#if defined(VERTEX) 19 20#if __VERSION__ >= 130 21#define COMPAT_VARYING out 22#define COMPAT_ATTRIBUTE in 23#define COMPAT_TEXTURE texture 24#else 25#define COMPAT_VARYING varying 26#define COMPAT_ATTRIBUTE attribute 27#define COMPAT_TEXTURE texture2D 28#endif 29 30#ifdef GL_ES 31#define COMPAT_PRECISION mediump 32#else 33#define COMPAT_PRECISION 34#endif 35 36COMPAT_ATTRIBUTE vec4 VertexCoord; 37COMPAT_ATTRIBUTE vec4 COLOR; 38COMPAT_ATTRIBUTE vec4 TexCoord; 39COMPAT_VARYING vec4 COL0; 40COMPAT_VARYING vec4 TEX0; 41// out variables go here as COMPAT_VARYING whatever 42 43vec4 _oPosition1; 44uniform mat4 MVPMatrix; 45uniform COMPAT_PRECISION int FrameDirection; 46uniform COMPAT_PRECISION int FrameCount; 47uniform COMPAT_PRECISION vec2 OutputSize; 48uniform COMPAT_PRECISION vec2 TextureSize; 49uniform COMPAT_PRECISION vec2 InputSize; 50 51// compatibility #defines 52#define vTexCoord TEX0.xy 53#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize 54#define OutSize vec4(OutputSize, 1.0 / OutputSize) 55 56void main() 57{ 58 gl_Position = MVPMatrix * VertexCoord; 59 TEX0.xy = VertexCoord.xy; 60// Paste vertex contents here: 61} 62 63#elif defined(FRAGMENT) 64 65#if __VERSION__ >= 130 66#define COMPAT_VARYING in 67#define COMPAT_TEXTURE texture 68out vec4 FragColor; 69#else 70#define COMPAT_VARYING varying 71#define FragColor gl_FragColor 72#define COMPAT_TEXTURE texture2D 73#endif 74 75#ifdef GL_ES 76#ifdef GL_FRAGMENT_PRECISION_HIGH 77precision highp float; 78#else 79precision mediump float; 80#endif 81#define COMPAT_PRECISION mediump 82#else 83#define COMPAT_PRECISION 84#endif 85 86uniform COMPAT_PRECISION int FrameDirection; 87uniform COMPAT_PRECISION int FrameCount; 88uniform COMPAT_PRECISION vec2 OutputSize; 89uniform COMPAT_PRECISION vec2 TextureSize; 90uniform COMPAT_PRECISION vec2 InputSize; 91uniform sampler2D Texture; 92uniform sampler2D iChannel0; 93uniform sampler2D iChannel1; 94vec2 iChannelResolution = vec2(64.0, 64.0); 95COMPAT_VARYING vec4 TEX0; 96// in variables go here as COMPAT_VARYING whatever 97 98// compatibility #defines 99#define Source Texture 100#define vTexCoord TEX0.xy 101 102#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize 103#define OutSize vec4(OutputSize, 1.0 / OutputSize) 104 105// delete all 'params.' or 'registers.' or whatever in the fragment 106float iGlobalTime = float(FrameCount)*0.025; 107vec2 iResolution = OutputSize.xy; 108 109#define PI 3.14159 110 111#define VOXEL_NONE 0 112#define VOXEL_WATER 1 113#define VOXEL_SAND 2 114#define VOXEL_EARTH 3 115#define VOXEL_STONE 4 116#define VOXEL_GRASS 5 117 118#define SUN_DIRECTION normalize(vec3(0.4, 0.6, 0.7)) 119 120struct VoxelHit 121{ 122 ivec3 mapPos; // world coords 123 int terrainType; // terrain type 124 vec2 volAccum; // sum of (fog, water) along the ray path 125 vec3 hitRel; // position of intersect relative to center of voxel 126 vec3 hitNormal; // surface normal at intersect 127 float weight; // contribution to the ray (fractional values come from anti-aliasing) 128}; 129 130struct VoxelMarchResult 131{ 132 // we store the first two intersects for two purposes: 133 // 1) it allows the first voxel hit to be non-cube shaped (e.g. rounded edges) 134 // 2) it allows cheap anti-aliasing 135 VoxelHit first; 136 VoxelHit second; 137}; 138 139// from https://www.shadertoy.com/view/4sfGzS 140float noise( in vec3 x ) 141{ 142 vec3 p = floor(x); 143 vec3 f = fract(x); 144 f = f*f*(3.0-2.0*f); 145 146 vec2 uv = p.xy + f.xy; 147 vec2 rg = vec2(texture( iChannel0, (uv+vec2(37.0,17.0)*p.z+0.5)/256.0, -100.0 ).x, 148 texture( iChannel0, (uv+vec2(37.0,17.0)*(p.z+1.0)+0.5)/256.0, -100.0 ).x ); 149 return mix( rg.x, rg.y, f.z ); 150} 151 152void getVoxelAndOcclusionsAt(ivec3 ip, out int terrainType, out vec2 occlusions) 153{ 154 terrainType = VOXEL_NONE; 155 156 float cloudiness = noise(vec3(ip)/8.0); 157 occlusions = vec2(smoothstep(0.6, 0.7, cloudiness)*0.3 + 0.1, 0.0); 158 159 if (ip.y <= 1) {terrainType = VOXEL_WATER; occlusions = vec2(0.0, 1.0);} 160 161 // so this is like, grabbing the texture as a heightmap and 162 // then like twisting it in random directions as it goes up 163 // umm... 164 vec3 p = vec3(vec3(ip) + 0.5); 165 float theta = noise(p / 16.0) * PI * 2.0; 166 vec2 disp = vec2(cos(theta), sin(theta)) * p.y; 167 vec3 terr = texture(iChannel1, (p.xz + disp) / 128.0).rgb; 168 169 bvec3 contains = lessThanEqual(vec3(0.0), (terr - p.y/16.0)); 170 if (contains.x && contains.y && !contains.z) terrainType = VOXEL_SAND; 171 else if (contains.x && contains.z) terrainType = VOXEL_GRASS; 172 else if (contains.y && contains.z && !contains.x) terrainType = VOXEL_STONE; 173 else if (contains.x || contains.y || contains.z) terrainType = VOXEL_EARTH; 174} 175 176float dfVoxel(vec3 p, int terrainType) 177{ 178 float r = 0.1; 179 if (terrainType == VOXEL_WATER) r = 0.0; 180 return length(max(abs(p)-vec3(0.5-r),0.0))-r; 181} 182 183vec3 nrmVoxel(vec3 p, int terrainType) 184{ 185 vec2 dd = vec2(0.001,0.0); 186 float base = dfVoxel(p, terrainType); 187 return normalize(vec3( 188 dfVoxel(p+dd.xyy, terrainType) - base, 189 dfVoxel(p+dd.yxy, terrainType) - base, 190 dfVoxel(p+dd.yyx, terrainType) - base 191 )); 192} 193 194VoxelMarchResult voxelMarch(vec3 ro, vec3 rd) 195{ 196 ivec3 mapPos = ivec3(floor(ro)); 197 vec3 deltaDist = abs(vec3(length(rd)) / rd); 198 ivec3 rayStep = ivec3(sign(rd)); 199 vec3 sideDist = (sign(rd) * (vec3(mapPos) - ro) + (sign(rd) * 0.5) + 0.5) * deltaDist; 200 vec2 volAccum = vec2(0.0); 201 float prevDist = 0.0; 202 203 VoxelMarchResult result; 204 205 for (int i = 0; i < 96; i++) { 206 207 // check current position for voxel 208 vec2 occlusions; int terrainType; 209 getVoxelAndOcclusionsAt(mapPos, terrainType, occlusions); 210 211 // if intersected, save 212 if (terrainType != VOXEL_NONE) { 213 VoxelHit newVoxelHit = VoxelHit(mapPos, terrainType, volAccum, vec3(0.0), vec3(0.0), 0.0); 214 if (result.first.terrainType == VOXEL_NONE) { 215 result.first = newVoxelHit; 216 } else if (result.first.terrainType != VOXEL_WATER || terrainType != VOXEL_WATER) { 217 result.second = newVoxelHit; 218 break; // two intersections, stop stepping! 219 } 220 } 221 222 // march forward to next position 223 float newDist = min( sideDist.x, min(sideDist.y, sideDist.z )); 224 vec3 mi = step( sideDist.xyz, sideDist.yzx ); 225 vec3 mm = mi*(1.0-mi.zxy); 226 sideDist += mm * vec3(rayStep) / rd; 227 mapPos += ivec3(mm)*rayStep; 228 229 // accumulate occlusions 230 volAccum += occlusions * (newDist - prevDist); 231 prevDist = newDist; 232 } 233 234 // last result should always have max fog 235 result.second.volAccum = volAccum; 236 237 // if there was no intersection, set accumulated fog on first hit and return 238 if (result.first.terrainType == VOXEL_NONE) { 239 result.first.volAccum = volAccum; 240 result.first.weight = 1.0; 241 return result; 242 } 243 244 // distance march to intersect first voxel 245 vec3 hitVoxelCenter = vec3(result.first.mapPos) + 0.5; 246 vec3 cubeIntersect = (hitVoxelCenter - ro - 0.5*sign(rd))/rd; 247 float dist = max(cubeIntersect.x, max(cubeIntersect.y, cubeIntersect.z)); 248 float diff; float mindiff = 1.0; float finaldist = 0.0; 249 for (int i=0; i<8; i++) { 250 vec3 p = ro + rd * dist; 251 diff = dfVoxel(p - hitVoxelCenter, result.first.terrainType); 252 if (diff < mindiff) { 253 mindiff = diff; 254 finaldist = dist; 255 } 256 dist += diff; 257 } 258 259 float pixSizeApprox = 2.0/iResolution.x * finaldist; // the FOV is actually about 1 radian :) 260 result.first.weight = smoothstep(pixSizeApprox, 0.0, mindiff - 0.01); // anti-alias blend 261 result.first.hitRel = ro + rd * finaldist - hitVoxelCenter; 262 result.first.hitNormal = nrmVoxel(result.first.hitRel, result.first.terrainType); 263 264 // if it was water, adjust weight for surface reflection 265 if (result.first.terrainType == VOXEL_WATER) result.first.weight = 0.5; 266 267 // do a cube intersection for the second voxel 268 hitVoxelCenter = vec3(result.second.mapPos) + 0.5; 269 cubeIntersect = (hitVoxelCenter - ro - 0.5*sign(rd))/rd; 270 dist = max(cubeIntersect.x, max(cubeIntersect.y, cubeIntersect.z)); 271 result.second.hitRel = ro + rd * dist - hitVoxelCenter; 272 273 // attempt to improve a little with distance marching 274 for (int i=0; i<4; i++) { 275 vec3 p = ro + rd * dist; 276 diff = dfVoxel(p - hitVoxelCenter, result.first.terrainType); 277 dist += diff; 278 } 279 if (diff < 0.05) result.second.hitRel = ro + rd * dist - hitVoxelCenter; 280 281 result.second.weight = 1.0 - result.first.weight; 282 result.second.hitNormal = nrmVoxel(result.second.hitRel, result.second.terrainType); 283 284 return result; 285} 286 287float marchShadowCheck(VoxelHit hit) 288{ 289 vec3 ro = hit.hitRel + vec3(hit.mapPos) + 0.5; 290 vec3 rd = SUN_DIRECTION; 291 ro += rd*0.11; 292 293 ivec3 mapPos = ivec3(floor(ro)); 294 vec3 deltaDist = abs(vec3(length(rd)) / rd); 295 ivec3 rayStep = ivec3(sign(rd)); 296 vec3 sideDist = (sign(rd) * (vec3(mapPos) - ro) + (sign(rd) * 0.5) + 0.5) * deltaDist; 297 float fogAccum = 0.0; 298 float prevDist = 0.0; 299 300 for (int i = 0; i < 16; i++) { 301 302 // check current position for voxel 303 vec2 occlusions; int terrainType; 304 getVoxelAndOcclusionsAt(mapPos, terrainType, occlusions); 305 306 // if intersected, finish 307 if (terrainType != VOXEL_NONE) { 308 return 1.0; 309 } 310 311 // march forward to next position 312 float newDist = min( sideDist.x, min(sideDist.y, sideDist.z )); 313 vec3 mi = step( sideDist.xyz, sideDist.yzx ); 314 vec3 mm = mi*(1.0-mi.zxy); 315 sideDist += mm * vec3(rayStep) / rd; 316 mapPos += ivec3(mm)*rayStep; 317 318 // accumulate fog 319 fogAccum += occlusions.x * (newDist - prevDist); 320 prevDist = newDist; 321 } 322 323 // no intersection 324 return fogAccum / 5.0; 325} 326 327float calcAmbientOcclusion(VoxelHit hit) 328{ 329 float ambientOcc = 0.0; 330 331 // for each of the 28 voxels surrounding the hit voxel 332 for (int i=-1; i<=1; i++) for (int j=-1; j<=1; j++) for (int k=-1; k<=1; k++) { 333 if (i == 0 && j == 0 && k == 0) continue; // skip the hit voxel 334 ivec3 offset = ivec3(i, j, k); 335 // TODO: find some way to skip these voxels 336 // if (dot(hit.hitRel, vec3(offset)) < 0.0) continue; 337 338 int terrainType; vec2 occlusions; 339 getVoxelAndOcclusionsAt(hit.mapPos + offset, terrainType, occlusions); 340 if (terrainType != VOXEL_NONE && terrainType != VOXEL_WATER) { 341 342 // use the distance from just above the intersection to estimate occlusion 343 float dist = dfVoxel(hit.hitRel + hit.hitNormal*0.5 - vec3(offset), terrainType); 344 ambientOcc += smoothstep(1.0, 0.0, dist); 345 } 346 } 347 348 return ambientOcc / 8.0; 349} 350 351vec3 doColoring(VoxelHit hit, vec3 rd) 352{ 353 // global position for non-repeating noise 354 vec3 hitGlobal = vec3(hit.mapPos) + hit.hitRel + 0.5; 355 float f1 = noise(hitGlobal*19.0); 356 float f2 = noise(hitGlobal*33.0); 357 float f3 = noise(hitGlobal*71.0); 358 359 vec3 color = vec3(0.0); 360 if (hit.terrainType == VOXEL_WATER) { 361 color = vec3(0.4, 0.4, 0.8) * (0.8 + f1*0.1 + f2*0.05 + f3*0.05); 362 } else if (hit.terrainType == VOXEL_EARTH) { 363 color = vec3(1.0, 0.7, 0.3) * (f1*0.13 + f2*0.13 + f3*0.1 + 0.3); 364 } else if (hit.terrainType == VOXEL_SAND) { 365 color = vec3(1.0, 1.0, 0.6) * (f1*0.07 + f2*0.07 + f3*0.2 + 0.5); 366 } else if (hit.terrainType == VOXEL_STONE) { 367 color = vec3(0.5) * (f1*0.3 + f2*0.1 + 0.6); 368 } else if (hit.terrainType == VOXEL_GRASS) { 369 color = vec3(0.3, 0.7, 0.4) * (f1*0.1 + f3*0.1 + 0.6); 370 } else if (hit.terrainType == VOXEL_NONE) { 371 color = vec3(0.0, 1.0, 1.0); 372 color += vec3(5.0, 3.0, 0.0)*pow(max(dot(rd, SUN_DIRECTION), 0.0), 128.0); 373 } 374 375 float shadow = min(marchShadowCheck(hit), 1.0); 376 float ambient = 1.0 - calcAmbientOcclusion(hit); 377 float diffuse = max(dot(SUN_DIRECTION, hit.hitNormal), 0.0); 378 diffuse = diffuse*(1.0-shadow); 379 380 color *= diffuse * 0.6 + ambient * 0.4; 381 382 vec2 occlusions = smoothstep(vec2(0.0), vec2(10.0, 3.0), hit.volAccum); 383 color = mix(color, vec3(0.3, 0.3, 0.5), occlusions.y); // water 384 color = mix(color, vec3(0.6), occlusions.x); // cloud 385 386 // blend with other intersection. will be fractional when anti-aliasing or underwater 387 color *= hit.weight; 388 389 return color; 390} 391 392 393VoxelHit marchReflection(VoxelHit hit, vec3 prevrd) 394{ 395 vec3 ro = hit.hitRel + vec3(hit.mapPos) + 0.5; 396 vec3 rd = reflect(prevrd, hit.hitNormal); 397 ro += 0.01*rd; 398 399 ivec3 mapPos = ivec3(floor(ro)); 400 vec3 deltaDist = abs(vec3(length(rd)) / rd); 401 ivec3 rayStep = ivec3(sign(rd)); 402 vec3 sideDist = (sign(rd) * (vec3(mapPos) - ro) + (sign(rd) * 0.5) + 0.5) * deltaDist; 403 vec2 volAccum = hit.volAccum; 404 float prevDist = 0.0; 405 406 for (int i = 0; i < 16; i++) { 407 408 // check current position for voxel 409 vec2 occlusions; int terrainType; 410 getVoxelAndOcclusionsAt(mapPos, terrainType, occlusions); 411 412 // if intersected, finish 413 if (terrainType != VOXEL_NONE) { 414 vec3 hitVoxelCenter = vec3(mapPos) + 0.5; 415 vec3 cubeIntersect = (hitVoxelCenter - ro - 0.5*sign(rd))/rd; 416 float dist = max(cubeIntersect.x, max(cubeIntersect.y, cubeIntersect.z)); 417 vec3 hitRel = ro + rd * dist - hitVoxelCenter; 418 return VoxelHit(mapPos, terrainType, volAccum, hitRel, nrmVoxel(hitRel, terrainType), 1.0); 419 } 420 421 // march forward to next position 422 float newDist = min( sideDist.x, min(sideDist.y, sideDist.z )); 423 vec3 mi = step( sideDist.xyz, sideDist.yzx ); 424 vec3 mm = mi*(1.0-mi.zxy); 425 sideDist += mm * vec3(rayStep) / rd; 426 mapPos += ivec3(mm)*rayStep; 427 428 // accumulate occlusions 429 volAccum += occlusions * (newDist - prevDist); 430 prevDist = newDist; 431 } 432 433 // no intersection 434 return VoxelHit(mapPos, VOXEL_NONE, volAccum, vec3(0.0), vec3(0.0), 1.0); 435} 436 437void mainImage( out vec4 fragColor, in vec2 fragCoord ) 438{ 439 // camera stolen from Shane :) https://www.shadertoy.com/view/ll2SRy 440 vec2 uv = (fragCoord - iResolution.xy*.5 )/iResolution.y; 441 vec3 rd = normalize(vec3(uv, (1.-dot(uv, uv)*.5)*.5)); 442 vec3 ro = vec3(0., 10., iGlobalTime*10.0); 443 float t = sin(iGlobalTime * 0.2) + noise(ro/32.0); 444 ro.y += 4.0*t; 445 float cs = cos( t ), si = sin( t ); 446 rd.yz = mat2(cs, si,-si, cs)*rd.yz; 447 rd.xz = mat2(cs, si,-si, cs)*rd.xz; 448 449 // voxel march into the scene storing up to four intersections 450 VoxelMarchResult result = voxelMarch(ro, rd); 451 452 // if first intersection is with water surface, march reflection 453 if (result.first.terrainType == VOXEL_WATER) { 454 result.first = marchReflection(result.first, rd); 455 result.first.weight = 0.5; 456 result.second.weight = 0.5; 457 } 458 459 // color 460 vec3 color1 = doColoring(result.first, rd); 461 vec3 color2 = doColoring(result.second, rd); 462 vec3 color = color1 + color2; 463 464 fragColor = vec4(color,1.0); 465} 466 467 void main(void) 468{ 469 //just some shit to wrap shadertoy's stuff 470 vec2 FragCoord = vTexCoord.xy*OutputSize.xy; 471 mainImage(FragColor,FragCoord); 472} 473#endif 474