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