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}}; 356