Marissa Wall | 713b63f | 2018-10-17 15:42:43 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2018 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #pragma once |
| 18 | |
| 19 | #include <GLES3/gl3.h> |
| 20 | #include <math/vec2.h> |
| 21 | #include <math/vec3.h> |
| 22 | #include <math/vec4.h> |
| 23 | |
| 24 | static const char* VERTEX_SHADER = R"SHADER__(#version 300 es |
| 25 | precision highp float; |
| 26 | |
| 27 | layout(location = 0) in vec4 mesh_position; |
| 28 | |
| 29 | void main() { |
| 30 | gl_Position = mesh_position; |
| 31 | } |
| 32 | )SHADER__"; |
| 33 | |
| 34 | static const char* FRAGMENT_SHADER = R"SHADER__(#version 300 es |
| 35 | precision highp float; |
| 36 | |
| 37 | layout(location = 0) uniform vec4 resolution; |
| 38 | layout(location = 1) uniform float time; |
| 39 | layout(location = 2) uniform vec3[4] SPHERICAL_HARMONICS; |
| 40 | |
| 41 | layout(location = 0) out vec4 fragColor; |
| 42 | |
| 43 | #define saturate(x) clamp(x, 0.0, 1.0) |
| 44 | #define PI 3.14159265359 |
| 45 | |
| 46 | //------------------------------------------------------------------------------ |
| 47 | // Distance field functions |
| 48 | //------------------------------------------------------------------------------ |
| 49 | |
| 50 | float sdPlane(in vec3 p) { |
| 51 | return p.y; |
| 52 | } |
| 53 | |
| 54 | float sdSphere(in vec3 p, float s) { |
| 55 | return length(p) - s; |
| 56 | } |
| 57 | |
| 58 | float sdTorus(in vec3 p, in vec2 t) { |
| 59 | return length(vec2(length(p.xz) - t.x, p.y)) - t.y; |
| 60 | } |
| 61 | |
| 62 | vec2 opUnion(vec2 d1, vec2 d2) { |
| 63 | return d1.x < d2.x ? d1 : d2; |
| 64 | } |
| 65 | |
| 66 | vec2 scene(in vec3 position) { |
| 67 | vec2 scene = opUnion( |
| 68 | vec2(sdPlane(position), 1.0), |
| 69 | vec2(sdSphere(position - vec3(0.0, 0.4, 0.0), 0.4), 12.0) |
| 70 | ); |
| 71 | return scene; |
| 72 | } |
| 73 | |
| 74 | //------------------------------------------------------------------------------ |
| 75 | // Ray casting |
| 76 | //------------------------------------------------------------------------------ |
| 77 | |
| 78 | float shadow(in vec3 origin, in vec3 direction, in float tmin, in float tmax) { |
| 79 | float hit = 1.0; |
| 80 | |
| 81 | for (float t = tmin; t < tmax; ) { |
| 82 | float h = scene(origin + direction * t).x; |
| 83 | if (h < 0.001) return 0.0; |
| 84 | t += h; |
| 85 | hit = min(hit, 10.0 * h / t); |
| 86 | } |
| 87 | |
| 88 | return clamp(hit, 0.0, 1.0); |
| 89 | } |
| 90 | |
| 91 | vec2 traceRay(in vec3 origin, in vec3 direction) { |
| 92 | float tmin = 0.02; |
| 93 | float tmax = 20.0; |
| 94 | |
| 95 | float material = -1.0; |
| 96 | float t = tmin; |
| 97 | |
| 98 | for ( ; t < tmax; ) { |
| 99 | vec2 hit = scene(origin + direction * t); |
| 100 | if (hit.x < 0.002 || t > tmax) break; |
| 101 | t += hit.x; |
| 102 | material = hit.y; |
| 103 | } |
| 104 | |
| 105 | if (t > tmax) { |
| 106 | material = -1.0; |
| 107 | } |
| 108 | |
| 109 | return vec2(t, material); |
| 110 | } |
| 111 | |
| 112 | vec3 normal(in vec3 position) { |
| 113 | vec3 epsilon = vec3(0.001, 0.0, 0.0); |
| 114 | vec3 n = vec3( |
| 115 | scene(position + epsilon.xyy).x - scene(position - epsilon.xyy).x, |
| 116 | scene(position + epsilon.yxy).x - scene(position - epsilon.yxy).x, |
| 117 | scene(position + epsilon.yyx).x - scene(position - epsilon.yyx).x); |
| 118 | return normalize(n); |
| 119 | } |
| 120 | |
| 121 | //------------------------------------------------------------------------------ |
| 122 | // BRDF |
| 123 | //------------------------------------------------------------------------------ |
| 124 | |
| 125 | float pow5(float x) { |
| 126 | float x2 = x * x; |
| 127 | return x2 * x2 * x; |
| 128 | } |
| 129 | |
| 130 | float D_GGX(float linearRoughness, float NoH, const vec3 h) { |
| 131 | // Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces" |
| 132 | float oneMinusNoHSquared = 1.0 - NoH * NoH; |
| 133 | float a = NoH * linearRoughness; |
| 134 | float k = linearRoughness / (oneMinusNoHSquared + a * a); |
| 135 | float d = k * k * (1.0 / PI); |
| 136 | return d; |
| 137 | } |
| 138 | |
| 139 | float V_SmithGGXCorrelated(float linearRoughness, float NoV, float NoL) { |
| 140 | // Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs" |
| 141 | float a2 = linearRoughness * linearRoughness; |
| 142 | float GGXV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2); |
| 143 | float GGXL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2); |
| 144 | return 0.5 / (GGXV + GGXL); |
| 145 | } |
| 146 | |
| 147 | vec3 F_Schlick(const vec3 f0, float VoH) { |
| 148 | // Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering" |
| 149 | return f0 + (vec3(1.0) - f0) * pow5(1.0 - VoH); |
| 150 | } |
| 151 | |
| 152 | float F_Schlick(float f0, float f90, float VoH) { |
| 153 | return f0 + (f90 - f0) * pow5(1.0 - VoH); |
| 154 | } |
| 155 | |
| 156 | float Fd_Burley(float linearRoughness, float NoV, float NoL, float LoH) { |
| 157 | // Burley 2012, "Physically-Based Shading at Disney" |
| 158 | float f90 = 0.5 + 2.0 * linearRoughness * LoH * LoH; |
| 159 | float lightScatter = F_Schlick(1.0, f90, NoL); |
| 160 | float viewScatter = F_Schlick(1.0, f90, NoV); |
| 161 | return lightScatter * viewScatter * (1.0 / PI); |
| 162 | } |
| 163 | |
| 164 | float Fd_Lambert() { |
| 165 | return 1.0 / PI; |
| 166 | } |
| 167 | |
| 168 | //------------------------------------------------------------------------------ |
| 169 | // Indirect lighting |
| 170 | //------------------------------------------------------------------------------ |
| 171 | |
| 172 | vec3 Irradiance_SphericalHarmonics(const vec3 n) { |
| 173 | return max( |
| 174 | SPHERICAL_HARMONICS[0] |
| 175 | + SPHERICAL_HARMONICS[1] * (n.y) |
| 176 | + SPHERICAL_HARMONICS[2] * (n.z) |
| 177 | + SPHERICAL_HARMONICS[3] * (n.x) |
| 178 | , 0.0); |
| 179 | } |
| 180 | |
| 181 | vec2 PrefilteredDFG_Karis(float roughness, float NoV) { |
| 182 | // Karis 2014, "Physically Based Material on Mobile" |
| 183 | const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022); |
| 184 | const vec4 c1 = vec4( 1.0, 0.0425, 1.040, -0.040); |
| 185 | |
| 186 | vec4 r = roughness * c0 + c1; |
| 187 | float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y; |
| 188 | |
| 189 | return vec2(-1.04, 1.04) * a004 + r.zw; |
| 190 | } |
| 191 | |
| 192 | //------------------------------------------------------------------------------ |
| 193 | // Tone mapping and transfer functions |
| 194 | //------------------------------------------------------------------------------ |
| 195 | |
| 196 | vec3 Tonemap_ACES(const vec3 x) { |
| 197 | // Narkowicz 2015, "ACES Filmic Tone Mapping Curve" |
| 198 | const float a = 2.51; |
| 199 | const float b = 0.03; |
| 200 | const float c = 2.43; |
| 201 | const float d = 0.59; |
| 202 | const float e = 0.14; |
| 203 | return (x * (a * x + b)) / (x * (c * x + d) + e); |
| 204 | } |
| 205 | |
| 206 | vec3 OECF_sRGBFast(const vec3 linear) { |
| 207 | return pow(linear, vec3(1.0 / 2.2)); |
| 208 | } |
| 209 | |
| 210 | //------------------------------------------------------------------------------ |
| 211 | // Rendering |
| 212 | //------------------------------------------------------------------------------ |
| 213 | |
| 214 | vec3 render(in vec3 origin, in vec3 direction, out float distance) { |
| 215 | // Sky gradient |
| 216 | vec3 color = vec3(0.65, 0.85, 1.0) + direction.y * 0.72; |
| 217 | |
| 218 | // (distance, material) |
| 219 | vec2 hit = traceRay(origin, direction); |
| 220 | distance = hit.x; |
| 221 | float material = hit.y; |
| 222 | |
| 223 | // We've hit something in the scene |
| 224 | if (material > 0.0) { |
| 225 | vec3 position = origin + distance * direction; |
| 226 | |
| 227 | vec3 v = normalize(-direction); |
| 228 | vec3 n = normal(position); |
| 229 | vec3 l = normalize(vec3(0.6, 0.7, -0.7)); |
| 230 | vec3 h = normalize(v + l); |
| 231 | vec3 r = normalize(reflect(direction, n)); |
| 232 | |
| 233 | float NoV = abs(dot(n, v)) + 1e-5; |
| 234 | float NoL = saturate(dot(n, l)); |
| 235 | float NoH = saturate(dot(n, h)); |
| 236 | float LoH = saturate(dot(l, h)); |
| 237 | |
| 238 | vec3 baseColor = vec3(0.0); |
| 239 | float roughness = 0.0; |
| 240 | float metallic = 0.0; |
| 241 | |
| 242 | float intensity = 2.0; |
| 243 | float indirectIntensity = 0.64; |
| 244 | |
| 245 | if (material < 4.0) { |
| 246 | // Checkerboard floor |
| 247 | float f = mod(floor(6.0 * position.z) + floor(6.0 * position.x), 2.0); |
| 248 | baseColor = 0.4 + f * vec3(0.6); |
| 249 | roughness = 0.1; |
| 250 | } else if (material < 16.0) { |
| 251 | // Metallic objects |
| 252 | baseColor = vec3(0.3, 0.0, 0.0); |
| 253 | roughness = 0.2; |
| 254 | } |
| 255 | |
| 256 | float linearRoughness = roughness * roughness; |
| 257 | vec3 diffuseColor = (1.0 - metallic) * baseColor.rgb; |
| 258 | vec3 f0 = 0.04 * (1.0 - metallic) + baseColor.rgb * metallic; |
| 259 | |
| 260 | float attenuation = shadow(position, l, 0.02, 2.5); |
| 261 | |
| 262 | // specular BRDF |
| 263 | float D = D_GGX(linearRoughness, NoH, h); |
| 264 | float V = V_SmithGGXCorrelated(linearRoughness, NoV, NoL); |
| 265 | vec3 F = F_Schlick(f0, LoH); |
| 266 | vec3 Fr = (D * V) * F; |
| 267 | |
| 268 | // diffuse BRDF |
| 269 | vec3 Fd = diffuseColor * Fd_Burley(linearRoughness, NoV, NoL, LoH); |
| 270 | |
| 271 | color = Fd + Fr; |
| 272 | color *= (intensity * attenuation * NoL) * vec3(0.98, 0.92, 0.89); |
| 273 | |
| 274 | // diffuse indirect |
| 275 | vec3 indirectDiffuse = Irradiance_SphericalHarmonics(n) * Fd_Lambert(); |
| 276 | |
| 277 | vec2 indirectHit = traceRay(position, r); |
| 278 | vec3 indirectSpecular = vec3(0.65, 0.85, 1.0) + r.y * 0.72; |
| 279 | if (indirectHit.y > 0.0) { |
| 280 | if (indirectHit.y < 4.0) { |
| 281 | vec3 indirectPosition = position + indirectHit.x * r; |
| 282 | // Checkerboard floor |
| 283 | float f = mod(floor(6.0 * indirectPosition.z) + floor(6.0 * indirectPosition.x), 2.0); |
| 284 | indirectSpecular = 0.4 + f * vec3(0.6); |
| 285 | } else if (indirectHit.y < 16.0) { |
| 286 | // Metallic objects |
| 287 | indirectSpecular = vec3(0.3, 0.0, 0.0); |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | // indirect contribution |
| 292 | vec2 dfg = PrefilteredDFG_Karis(roughness, NoV); |
| 293 | vec3 specularColor = f0 * dfg.x + dfg.y; |
| 294 | vec3 ibl = diffuseColor * indirectDiffuse + indirectSpecular * specularColor; |
| 295 | |
| 296 | color += ibl * indirectIntensity; |
| 297 | } |
| 298 | |
| 299 | return color; |
| 300 | } |
| 301 | |
| 302 | //------------------------------------------------------------------------------ |
| 303 | // Setup and execution |
| 304 | //------------------------------------------------------------------------------ |
| 305 | |
| 306 | mat3 setCamera(in vec3 origin, in vec3 target, float rotation) { |
| 307 | vec3 forward = normalize(target - origin); |
| 308 | vec3 orientation = vec3(sin(rotation), cos(rotation), 0.0); |
| 309 | vec3 left = normalize(cross(forward, orientation)); |
| 310 | vec3 up = normalize(cross(left, forward)); |
| 311 | return mat3(left, up, forward); |
| 312 | } |
| 313 | |
| 314 | void main() { |
| 315 | // Normalized coordinates |
| 316 | vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy; |
| 317 | // Aspect ratio |
| 318 | p.x *= resolution.x / resolution.y; |
| 319 | |
| 320 | // Camera position and "look at" |
| 321 | vec3 origin = vec3(0.0, 1.0, 0.0); |
| 322 | vec3 target = vec3(0.0); |
| 323 | |
| 324 | origin.x += 2.0 * cos(time * 0.2); |
| 325 | origin.z += 2.0 * sin(time * 0.2); |
| 326 | |
| 327 | mat3 toWorld = setCamera(origin, target, 0.0); |
| 328 | vec3 direction = toWorld * normalize(vec3(p.xy, 2.0)); |
| 329 | |
| 330 | // Render scene |
| 331 | float distance; |
| 332 | vec3 color = render(origin, direction, distance); |
| 333 | |
| 334 | // Tone mapping |
| 335 | color = Tonemap_ACES(color); |
| 336 | |
| 337 | // Exponential distance fog |
| 338 | color = mix(color, 0.8 * vec3(0.7, 0.8, 1.0), 1.0 - exp2(-0.011 * distance * distance)); |
| 339 | |
| 340 | // Gamma compression |
| 341 | color = OECF_sRGBFast(color); |
| 342 | |
| 343 | fragColor = vec4(color, 1.0); |
| 344 | } |
| 345 | )SHADER__"; |
| 346 | |
| 347 | static const android::vec3 SPHERICAL_HARMONICS[4] = |
| 348 | {{0.754554516862612, 0.748542953903366, 0.790921515418539}, |
| 349 | {-0.083856548007422, 0.092533500963210, 0.322764661032516}, |
| 350 | {0.308152705331738, 0.366796330467391, 0.466698181299906}, |
| 351 | {-0.188884931542396, -0.277402551592231, -0.377844212327557}}; |
| 352 | |
| 353 | static const android::vec4 TRIANGLE[3] = {{-1.0f, -1.0f, 1.0f, 1.0f}, |
| 354 | {3.0f, -1.0f, 1.0f, 1.0f}, |
| 355 | {-1.0f, 3.0f, 1.0f, 1.0f}}; |