| 1 | //------------------------------------------------------------------------------ |
| 2 | // Signed-distance-field raymarching shaders, see: |
| 3 | // https://iquilezles.org/articles/mandelbulb |
| 4 | // https://www.shadertoy.com/view/ltfSWn |
| 5 | //------------------------------------------------------------------------------ |
| 6 | |
| 7 | //--- vertex shader |
| 8 | @vs vs |
| 9 | uniform vs_params { |
| 10 | float aspect; |
| 11 | float time; |
| 12 | }; |
| 13 | in vec4 position; |
| 14 | |
| 15 | out vec2 pos; |
| 16 | out vec3 eye; |
| 17 | out vec3 up; |
| 18 | out vec3 right; |
| 19 | out vec3 fwd; |
| 20 | |
| 21 | // compute eye position (orbit around center) |
| 22 | vec3 eye_pos(float time, vec3 center) { |
| 23 | return center + vec3(sin(time * 0.05) * 3.0, sin(time * 0.1) * 2.0, cos(time * 0.05) * 3.0); |
| 24 | } |
| 25 | |
| 26 | // a lookat function |
| 27 | void lookat(vec3 eye, vec3 center, vec3 up, out vec3 out_fwd, out vec3 out_right, out vec3 out_up) { |
| 28 | out_fwd = normalize(center - eye); |
| 29 | out_right = normalize(cross(out_fwd, up)); |
| 30 | out_up = cross(out_right, out_fwd); |
| 31 | } |
| 32 | |
| 33 | void main() { |
| 34 | gl_Position = position; |
| 35 | pos.x = position.x * aspect; |
| 36 | pos.y = position.y; |
| 37 | const vec3 center = vec3(0.0, 0.0, 0.0); |
| 38 | const vec3 up_vec = vec3(0.0, 1.0, 0.0); |
| 39 | eye = eye_pos(time * 5, center); |
| 40 | lookat(eye, center, up_vec, fwd, right, up); |
| 41 | } |
| 42 | @end |
| 43 | |
| 44 | //--- fragment shader |
| 45 | @fs fs |
| 46 | in vec2 pos; |
| 47 | in vec3 eye; |
| 48 | in vec3 up; |
| 49 | in vec3 right; |
| 50 | in vec3 fwd; |
| 51 | |
| 52 | out vec4 frag_color; |
| 53 | |
| 54 | float sd_sphere(vec3 p, float s) { |
| 55 | return length(p) - s; |
| 56 | } |
| 57 | |
| 58 | float sd_mandelbulb(vec3 p, out vec4 res_color) { |
| 59 | vec3 w = p; |
| 60 | float m = dot(w,w); |
| 61 | |
| 62 | vec4 trap = vec4(abs(w),m); |
| 63 | float dz = 1.0; |
| 64 | |
| 65 | for( int i=0; i<4; i++ ) { |
| 66 | float m2 = m*m; |
| 67 | float m4 = m2*m2; |
| 68 | dz = 8.0*sqrt(m4*m2*m)*dz + 1.0; |
| 69 | |
| 70 | float x = w.x; float x2 = x*x; float x4 = x2*x2; |
| 71 | float y = w.y; float y2 = y*y; float y4 = y2*y2; |
| 72 | float z = w.z; float z2 = z*z; float z4 = z2*z2; |
| 73 | |
| 74 | float k3 = x2 + z2; |
| 75 | float k2 = inversesqrt( k3*k3*k3*k3*k3*k3*k3 ); |
| 76 | float k1 = x4 + y4 + z4 - 6.0*y2*z2 - 6.0*x2*y2 + 2.0*z2*x2; |
| 77 | float k4 = x2 - y2 + z2; |
| 78 | |
| 79 | w.x = p.x + 64.0*x*y*z*(x2-z2)*k4*(x4-6.0*x2*z2+z4)*k1*k2; |
| 80 | w.y = p.y + -16.0*y2*k3*k4*k4 + k1*k1; |
| 81 | w.z = p.z + -8.0*y*k4*(x4*x4 - 28.0*x4*x2*z2 + 70.0*x4*z4 - 28.0*x2*z2*z4 + z4*z4)*k1*k2; |
| 82 | |
| 83 | trap = min( trap, vec4(abs(w),m) ); |
| 84 | |
| 85 | m = dot(w,w); |
| 86 | if( m > 256.0 ) { |
| 87 | break; |
| 88 | } |
| 89 | } |
| 90 | res_color = vec4(m,trap.yzw); |
| 91 | return 0.25*log(m)*sqrt(m)/dz; |
| 92 | } |
| 93 | |
| 94 | float d_scene(vec3 p, out vec4 res_color) { |
| 95 | float d = sd_sphere(p, 1.1); |
| 96 | if (d < 0.1) { |
| 97 | d = sd_mandelbulb(p, res_color); |
| 98 | } |
| 99 | else { |
| 100 | res_color = vec4(0.0); |
| 101 | } |
| 102 | return d; |
| 103 | } |
| 104 | |
| 105 | // surface normal estimation |
| 106 | vec3 surface_normal(vec3 p, float dp) { |
| 107 | const float eps = 0.001; |
| 108 | const vec2 d = vec2(eps, 0); |
| 109 | vec4 tra; |
| 110 | float x = d_scene(p + d.xyy, tra) - dp; |
| 111 | float y = d_scene(p + d.yxy, tra) - dp; |
| 112 | float z = d_scene(p + d.yyx, tra) - dp; |
| 113 | return normalize(vec3(x, y, z)); |
| 114 | } |
| 115 | |
| 116 | vec3 calc_color(vec3 ro, vec3 rd, float t, vec4 tra) { |
| 117 | const vec3 light1 = vec3( 0.577, 0.577, -0.577); |
| 118 | const vec3 light2 = vec3(-0.707, 0.000, 0.707); |
| 119 | |
| 120 | vec3 pos = ro + rd * t; |
| 121 | vec3 nrm = surface_normal(pos, t); |
| 122 | vec3 hal = normalize(light1 - rd); |
| 123 | float occ = clamp(0.05 * log(tra.x), 0.0, 1.0); |
| 124 | float fac = clamp(1.0 + dot(rd, nrm), 0.0, 1.0); |
| 125 | |
| 126 | // sun |
| 127 | float dif1 = clamp(dot( light1, nrm), 0.0, 1.0); |
| 128 | float spe1 = pow(clamp(dot(nrm, hal), 0.0, 1.0), 32.0 )*dif1*(0.04+0.96*pow(clamp(1.0-dot(hal,light1),0.0,1.0),5.0)); |
| 129 | // bounce |
| 130 | float dif2 = clamp( 0.5 + 0.5*dot( light2, nrm ), 0.0, 1.0 )*occ; |
| 131 | // sky |
| 132 | float dif3 = (0.7+0.3*nrm.y)*(0.2+0.8*occ); |
| 133 | |
| 134 | vec3 col = vec3(0.01); |
| 135 | col = mix(col, vec3(0.10,0.20,0.30), clamp(tra.y,0.0,1.0) ); |
| 136 | col = mix(col, vec3(0.02,0.10,0.30), clamp(tra.z*tra.z,0.0,1.0) ); |
| 137 | col = mix(col, vec3(0.30,0.10,0.02), clamp(pow(tra.w,6.0),0.0,1.0) ); |
| 138 | |
| 139 | vec3 lin = vec3(0.0); |
| 140 | lin += 7.0*vec3(1.50,1.10,0.70)*dif1; |
| 141 | lin += 4.0*vec3(0.25,0.20,0.15)*dif2; |
| 142 | lin += 1.5*vec3(0.10,0.20,0.30)*dif3; |
| 143 | lin += 2.5*vec3(0.35,0.30,0.25)*(0.05+0.95*occ); // ambient |
| 144 | lin += 4.0*fac*occ; // fake SSS |
| 145 | col *= lin; |
| 146 | col = pow( col, vec3(0.7,0.9,1.0)); // fake SSS |
| 147 | col += spe1*15.0; |
| 148 | |
| 149 | // gamma |
| 150 | col = sqrt(col); |
| 151 | |
| 152 | return col; |
| 153 | } |
| 154 | |
| 155 | void main() { |
| 156 | const float epsilon = 0.001; |
| 157 | const float focal_length = 1.8; |
| 158 | |
| 159 | vec3 ray_origin = eye + fwd * focal_length + right * pos.x + up * pos.y; |
| 160 | vec3 ray_direction = normalize(ray_origin - eye); |
| 161 | |
| 162 | vec4 tra; |
| 163 | vec4 color = vec4(0.10,0.20,0.30,1.0); |
| 164 | float t = 0.0; |
| 165 | for (int i = 0; i < 96; i++) { |
| 166 | vec3 p = ray_origin + ray_direction * t; |
| 167 | float d = d_scene(p, tra); |
| 168 | if (d < epsilon) { |
| 169 | color.xyz = calc_color(p, ray_direction, d, tra); |
| 170 | break; |
| 171 | } |
| 172 | else { |
| 173 | color.xyz += vec3(0.003, 0.001, 0.0) * i; |
| 174 | } |
| 175 | if (t > 3) { |
| 176 | break; |
| 177 | } |
| 178 | t += d; |
| 179 | } |
| 180 | frag_color = color; |
| 181 | } |
| 182 | @end |
| 183 | |
| 184 | @program sdf vs fs |
| 185 | |