You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
356 lines
11 KiB
356 lines
11 KiB
/*
|
|
* Copyright 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <GLES3/gl3.h>
|
|
#include <math/vec2.h>
|
|
#include <math/vec3.h>
|
|
#include <math/vec4.h>
|
|
|
|
static const char* VERTEX_SHADER = R"SHADER__(#version 300 es
|
|
precision highp float;
|
|
|
|
layout(location = 0) in vec4 mesh_position;
|
|
|
|
void main() {
|
|
gl_Position = mesh_position;
|
|
}
|
|
)SHADER__";
|
|
|
|
static const char* FRAGMENT_SHADER = R"SHADER__(#version 300 es
|
|
precision highp float;
|
|
|
|
layout(location = 0) uniform vec4 resolution;
|
|
layout(location = 1) uniform float time;
|
|
layout(location = 2) uniform vec3[4] SPHERICAL_HARMONICS;
|
|
|
|
layout(location = 0) out vec4 fragColor;
|
|
|
|
#define saturate(x) clamp(x, 0.0, 1.0)
|
|
#define PI 3.14159265359
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Distance field functions
|
|
//------------------------------------------------------------------------------
|
|
|
|
float sdPlane(in vec3 p) {
|
|
return p.y;
|
|
}
|
|
|
|
float sdSphere(in vec3 p, float s) {
|
|
return length(p) - s;
|
|
}
|
|
|
|
float sdTorus(in vec3 p, in vec2 t) {
|
|
return length(vec2(length(p.xz) - t.x, p.y)) - t.y;
|
|
}
|
|
|
|
vec2 opUnion(vec2 d1, vec2 d2) {
|
|
return d1.x < d2.x ? d1 : d2;
|
|
}
|
|
|
|
vec2 scene(in vec3 position) {
|
|
vec2 scene = opUnion(
|
|
vec2(sdPlane(position), 1.0),
|
|
vec2(sdSphere(position - vec3(0.0, 0.4, 0.0), 0.4), 12.0)
|
|
);
|
|
return scene;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Ray casting
|
|
//------------------------------------------------------------------------------
|
|
|
|
float shadow(in vec3 origin, in vec3 direction, in float tmin, in float tmax) {
|
|
float hit = 1.0;
|
|
|
|
for (float t = tmin; t < tmax; ) {
|
|
float h = scene(origin + direction * t).x;
|
|
if (h < 0.001) return 0.0;
|
|
t += h;
|
|
hit = min(hit, 10.0 * h / t);
|
|
}
|
|
|
|
return clamp(hit, 0.0, 1.0);
|
|
}
|
|
|
|
vec2 traceRay(in vec3 origin, in vec3 direction) {
|
|
float tmin = 0.02;
|
|
float tmax = 20.0;
|
|
|
|
float material = -1.0;
|
|
float t = tmin;
|
|
|
|
for ( ; t < tmax; ) {
|
|
vec2 hit = scene(origin + direction * t);
|
|
if (hit.x < 0.002 || t > tmax) break;
|
|
t += hit.x;
|
|
material = hit.y;
|
|
}
|
|
|
|
if (t > tmax) {
|
|
material = -1.0;
|
|
}
|
|
|
|
return vec2(t, material);
|
|
}
|
|
|
|
vec3 normal(in vec3 position) {
|
|
vec3 epsilon = vec3(0.001, 0.0, 0.0);
|
|
vec3 n = vec3(
|
|
scene(position + epsilon.xyy).x - scene(position - epsilon.xyy).x,
|
|
scene(position + epsilon.yxy).x - scene(position - epsilon.yxy).x,
|
|
scene(position + epsilon.yyx).x - scene(position - epsilon.yyx).x);
|
|
return normalize(n);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// BRDF
|
|
//------------------------------------------------------------------------------
|
|
|
|
float pow5(float x) {
|
|
float x2 = x * x;
|
|
return x2 * x2 * x;
|
|
}
|
|
|
|
float D_GGX(float linearRoughness, float NoH, const vec3 h) {
|
|
// Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces"
|
|
float oneMinusNoHSquared = 1.0 - NoH * NoH;
|
|
float a = NoH * linearRoughness;
|
|
float k = linearRoughness / (oneMinusNoHSquared + a * a);
|
|
float d = k * k * (1.0 / PI);
|
|
return d;
|
|
}
|
|
|
|
float V_SmithGGXCorrelated(float linearRoughness, float NoV, float NoL) {
|
|
// Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"
|
|
float a2 = linearRoughness * linearRoughness;
|
|
float GGXV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2);
|
|
float GGXL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2);
|
|
return 0.5 / (GGXV + GGXL);
|
|
}
|
|
|
|
vec3 F_Schlick(const vec3 f0, float VoH) {
|
|
// Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"
|
|
return f0 + (vec3(1.0) - f0) * pow5(1.0 - VoH);
|
|
}
|
|
|
|
float F_Schlick(float f0, float f90, float VoH) {
|
|
return f0 + (f90 - f0) * pow5(1.0 - VoH);
|
|
}
|
|
|
|
float Fd_Burley(float linearRoughness, float NoV, float NoL, float LoH) {
|
|
// Burley 2012, "Physically-Based Shading at Disney"
|
|
float f90 = 0.5 + 2.0 * linearRoughness * LoH * LoH;
|
|
float lightScatter = F_Schlick(1.0, f90, NoL);
|
|
float viewScatter = F_Schlick(1.0, f90, NoV);
|
|
return lightScatter * viewScatter * (1.0 / PI);
|
|
}
|
|
|
|
float Fd_Lambert() {
|
|
return 1.0 / PI;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Indirect lighting
|
|
//------------------------------------------------------------------------------
|
|
|
|
vec3 Irradiance_SphericalHarmonics(const vec3 n) {
|
|
return max(
|
|
SPHERICAL_HARMONICS[0]
|
|
+ SPHERICAL_HARMONICS[1] * (n.y)
|
|
+ SPHERICAL_HARMONICS[2] * (n.z)
|
|
+ SPHERICAL_HARMONICS[3] * (n.x)
|
|
, 0.0);
|
|
}
|
|
|
|
vec2 PrefilteredDFG_Karis(float roughness, float NoV) {
|
|
// Karis 2014, "Physically Based Material on Mobile"
|
|
const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
|
|
const vec4 c1 = vec4( 1.0, 0.0425, 1.040, -0.040);
|
|
|
|
vec4 r = roughness * c0 + c1;
|
|
float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;
|
|
|
|
return vec2(-1.04, 1.04) * a004 + r.zw;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Tone mapping and transfer functions
|
|
//------------------------------------------------------------------------------
|
|
|
|
vec3 Tonemap_ACES(const vec3 x) {
|
|
// Narkowicz 2015, "ACES Filmic Tone Mapping Curve"
|
|
const float a = 2.51;
|
|
const float b = 0.03;
|
|
const float c = 2.43;
|
|
const float d = 0.59;
|
|
const float e = 0.14;
|
|
return (x * (a * x + b)) / (x * (c * x + d) + e);
|
|
}
|
|
|
|
vec3 OECF_sRGBFast(const vec3 linear) {
|
|
return pow(linear, vec3(1.0 / 2.2));
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Rendering
|
|
//------------------------------------------------------------------------------
|
|
|
|
vec3 render(in vec3 origin, in vec3 direction, out float distance) {
|
|
// Sky gradient
|
|
vec3 color = vec3(0.65, 0.85, 1.0) + direction.y * 0.72;
|
|
|
|
// (distance, material)
|
|
vec2 hit = traceRay(origin, direction);
|
|
distance = hit.x;
|
|
float material = hit.y;
|
|
|
|
// We've hit something in the scene
|
|
if (material > 0.0) {
|
|
vec3 position = origin + distance * direction;
|
|
|
|
vec3 v = normalize(-direction);
|
|
vec3 n = normal(position);
|
|
vec3 l = normalize(vec3(0.6, 0.7, -0.7));
|
|
vec3 h = normalize(v + l);
|
|
vec3 r = normalize(reflect(direction, n));
|
|
|
|
float NoV = abs(dot(n, v)) + 1e-5;
|
|
float NoL = saturate(dot(n, l));
|
|
float NoH = saturate(dot(n, h));
|
|
float LoH = saturate(dot(l, h));
|
|
|
|
vec3 baseColor = vec3(0.0);
|
|
float roughness = 0.0;
|
|
float metallic = 0.0;
|
|
|
|
float intensity = 2.0;
|
|
float indirectIntensity = 0.64;
|
|
|
|
if (material < 4.0) {
|
|
// Checkerboard floor
|
|
float f = mod(floor(6.0 * position.z) + floor(6.0 * position.x), 2.0);
|
|
baseColor = 0.4 + f * vec3(0.6);
|
|
roughness = 0.1;
|
|
} else if (material < 16.0) {
|
|
// Metallic objects
|
|
baseColor = vec3(0.3, 0.0, 0.0);
|
|
roughness = 0.2;
|
|
}
|
|
|
|
float linearRoughness = roughness * roughness;
|
|
vec3 diffuseColor = (1.0 - metallic) * baseColor.rgb;
|
|
vec3 f0 = 0.04 * (1.0 - metallic) + baseColor.rgb * metallic;
|
|
|
|
float attenuation = shadow(position, l, 0.02, 2.5);
|
|
|
|
// specular BRDF
|
|
float D = D_GGX(linearRoughness, NoH, h);
|
|
float V = V_SmithGGXCorrelated(linearRoughness, NoV, NoL);
|
|
vec3 F = F_Schlick(f0, LoH);
|
|
vec3 Fr = (D * V) * F;
|
|
|
|
// diffuse BRDF
|
|
vec3 Fd = diffuseColor * Fd_Burley(linearRoughness, NoV, NoL, LoH);
|
|
|
|
color = Fd + Fr;
|
|
color *= (intensity * attenuation * NoL) * vec3(0.98, 0.92, 0.89);
|
|
|
|
// diffuse indirect
|
|
vec3 indirectDiffuse = Irradiance_SphericalHarmonics(n) * Fd_Lambert();
|
|
|
|
vec2 indirectHit = traceRay(position, r);
|
|
vec3 indirectSpecular = vec3(0.65, 0.85, 1.0) + r.y * 0.72;
|
|
if (indirectHit.y > 0.0) {
|
|
if (indirectHit.y < 4.0) {
|
|
vec3 indirectPosition = position + indirectHit.x * r;
|
|
// Checkerboard floor
|
|
float f = mod(floor(6.0 * indirectPosition.z) + floor(6.0 * indirectPosition.x), 2.0);
|
|
indirectSpecular = 0.4 + f * vec3(0.6);
|
|
} else if (indirectHit.y < 16.0) {
|
|
// Metallic objects
|
|
indirectSpecular = vec3(0.3, 0.0, 0.0);
|
|
}
|
|
}
|
|
|
|
// indirect contribution
|
|
vec2 dfg = PrefilteredDFG_Karis(roughness, NoV);
|
|
vec3 specularColor = f0 * dfg.x + dfg.y;
|
|
vec3 ibl = diffuseColor * indirectDiffuse + indirectSpecular * specularColor;
|
|
|
|
color += ibl * indirectIntensity;
|
|
}
|
|
|
|
return color;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Setup and execution
|
|
//------------------------------------------------------------------------------
|
|
|
|
mat3 setCamera(in vec3 origin, in vec3 target, float rotation) {
|
|
vec3 forward = normalize(target - origin);
|
|
vec3 orientation = vec3(sin(rotation), cos(rotation), 0.0);
|
|
vec3 left = normalize(cross(forward, orientation));
|
|
vec3 up = normalize(cross(left, forward));
|
|
return mat3(left, up, forward);
|
|
}
|
|
|
|
void main() {
|
|
// Normalized coordinates
|
|
vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
|
|
// Aspect ratio
|
|
p.x *= resolution.x / resolution.y;
|
|
|
|
// Camera position and "look at"
|
|
vec3 origin = vec3(0.0, 1.0, 0.0);
|
|
vec3 target = vec3(0.0);
|
|
|
|
origin.x += 2.0 * cos(time * 0.2);
|
|
origin.z += 2.0 * sin(time * 0.2);
|
|
|
|
mat3 toWorld = setCamera(origin, target, 0.0);
|
|
vec3 direction = toWorld * normalize(vec3(p.xy, 2.0));
|
|
|
|
// Render scene
|
|
float distance;
|
|
vec3 color = render(origin, direction, distance);
|
|
|
|
// Tone mapping
|
|
color = Tonemap_ACES(color);
|
|
|
|
// Exponential distance fog
|
|
color = mix(color, 0.8 * vec3(0.7, 0.8, 1.0), 1.0 - exp2(-0.011 * distance * distance));
|
|
|
|
// Gamma compression
|
|
color = OECF_sRGBFast(color);
|
|
|
|
fragColor = vec4(color, 1.0);
|
|
}
|
|
)SHADER__";
|
|
|
|
static const android::vec3 SPHERICAL_HARMONICS[4] =
|
|
{{0.754554516862612, 0.748542953903366, 0.790921515418539},
|
|
{-0.083856548007422, 0.092533500963210, 0.322764661032516},
|
|
{0.308152705331738, 0.366796330467391, 0.466698181299906},
|
|
{-0.188884931542396, -0.277402551592231, -0.377844212327557}};
|
|
|
|
static const android::vec4 TRIANGLE[3] = {{-1.0f, -1.0f, 1.0f, 1.0f},
|
|
{3.0f, -1.0f, 1.0f, 1.0f},
|
|
{-1.0f, 3.0f, 1.0f, 1.0f}};
|