<script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>     
body {margin:0px; padding:0px; overflow: hidden}
let program;
let active = false, cnt = 0;

function setup() {
  pixelDensity(1);
  const canvas = createCanvas(windowWidth, windowHeight,WEBGL);
  rectMode(CENTER);
  noStroke();
  fill(1);
  program = createShader(vert,frag);
}

function draw() {
  if (!active && frameCount > 1) {return;}
  
  shader(program);
  background(0);
  program.setUniform('res',[width,height]);
  program.setUniform('cnt', cnt);
  program.setUniform('roughness', mouseX / width);
  program.setUniform('metallic', 1.0 - mouseY / height);
  rect(0,0,width,height);
  cnt ++;
}

function mouseClicked() {
  active = !active;
}

const vert=`
#ifdef GL_ES
precision highp float;
precision highp int;
#endif

#extension GL_OES_standard_derivatives : enable
attribute vec3 aPosition;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
uniform mat3 uNormalMatrix;
void main() {
  gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aPosition, 1.0);
}`;




const frag=`
#ifdef GL_ES
precision highp float;
#endif

// https://learnopengl.com/PBR/Theory
// https://learnopengl.com/PBR/Lighting

#define PI 3.14159265358979323846

uniform vec2 res;
uniform float cnt;
uniform float roughness;
uniform float metallic;

vec3 fresnelSchlick(float cosTheta, vec3 F0) {
    return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}  


float DistributionGGX(vec3 N, vec3 H, float roughness)
{
    float a      = roughness*roughness;
    float a2     = a*a;
    float NdotH  = max(dot(N, H), 0.0);
    float NdotH2 = NdotH*NdotH;
  
    float num   = a2;
    float denom = (NdotH2 * (a2 - 1.0) + 1.0);
    denom = PI * denom * denom;
  
    return num / denom;
}

float GeometrySchlickGGX(float NdotV, float roughness)
{
    float r = (roughness + 1.0);
    float k = (r*r) / 8.0;

    float num   = NdotV;
    float denom = NdotV * (1.0 - k) + k;
  
    return num / denom;
}

float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
    float NdotV = max(dot(N, V), 0.0);
    float NdotL = max(dot(N, L), 0.0);
    float ggx2  = GeometrySchlickGGX(NdotV, roughness);
    float ggx1  = GeometrySchlickGGX(NdotL, roughness);
  
    return ggx1 * ggx2;
}

mat2 rotate2d(float _angle){
    return mat2(cos(_angle),-sin(_angle),
                sin(_angle),cos(_angle));
}

vec3 shade(vec3 V, vec3 P, vec3 N) {
  vec3 F0 = vec3(0.04); 
  vec3 goldBaseColor = vec3(1.00, 0.71, 0.29);
  vec3 plasticBaseColor = vec3(1.00, 0.1, 0.01);
  vec3 albedo = mix(plasticBaseColor, goldBaseColor, metallic);
  
  F0 = mix(F0, albedo, metallic);
  vec3 lightPositions[4];
  lightPositions[0] = vec3(4.0, 0.0, 4.0);
  lightPositions[1] = vec3(-4.0, 0.0, 4.0);
  lightPositions[2] = vec3(4.0, 0.0, -4.0);
  lightPositions[3] = vec3(-4.0, 0.0, -4.0);
  vec3 lightColors[4];
  lightColors[0] = vec3(23.47, 21.31, 20.79) * 8.0;
  lightColors[1] = vec3(23.47, 21.31, 20.79) * 4.0;
  lightColors[2] = vec3(23.47, 21.31, 20.79) * 2.0;
  lightColors[3] = vec3(23.47, 21.31, 20.79) * 1.0;
  
  // reflectance equation
  vec3 Lo = vec3(0.0);
  
  for(int i = 0; i < 4; ++i) {
      // calculate per-light radiance
      vec3 lp = lightPositions[i];
      lp.xz *= rotate2d(cnt / 180.0 * PI);
      vec3 L = normalize(lp - P);
      vec3 H = normalize(V + L);
      float distance    = length(lp - P);
      float attenuation = 1.0 / (distance * distance);
      vec3 radiance     = lightColors[i] * attenuation;        

      // cook-torrance brdf
      float NDF = DistributionGGX(N, H, roughness);        
      float G   = GeometrySmith(N, V, L, roughness);      
      vec3 F    = fresnelSchlick(max(dot(H, V), 0.0), F0);   
      vec3 kS = F; // Specular
      vec3 kD = vec3(1.0) - kS; // Diffuse
      kD *= 1.0 - metallic; // Reduce the diffuse component according to the metallic parameter

      vec3 numerator    = NDF * G * F;
      float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001;
      vec3 specular     = numerator / denominator;  
      // add to outgoing radiance Lo
      float NdotL = max(dot(N, L), 0.0);                
      Lo += (kD * albedo / PI + specular) * radiance * NdotL; 
  }
  
  vec3 ambient = vec3(0.03) * albedo * metallic;
  vec3 color = ambient + Lo;
  
  return color;
}

float SDF(vec3 p) {
    float radius = 0.25;
    return length(p)-radius;
}

vec3 normal(vec3 P) {
  vec2 h = vec2(0.001, 0.0);
  return normalize(vec3(SDF(P + h.xyy) - SDF(P - h.xyy),
                        SDF(P + h.yxy) - SDF(P - h.yxy),
                        SDF(P + h.yyx) - SDF(P - h.yyx)));
}

float rayHitDist(vec3 eye, vec3 rayDir) {
    float dist = 0.0;
    float threshold = 0.005;
    for(int i = 0 ; i < 16 ; ++i) {
        float d = SDF(eye + rayDir * dist);
        if(d < threshold) { return dist; }
        dist += d;
    }
    return -1.0;
}

void main(void)
{
  vec2 crd = (gl_FragCoord.xy - res * 0.5) / min(res.x, res.y);
  
  vec3 eye = vec3(0.0, 0.0, -2.5);
  vec3 V = normalize(eye - vec3(crd, 0.0));
    
  float dist = rayHitDist(eye, -V);
  
  vec3 color = vec3(0.05);
  if (dist >= 0.0) {
      vec3 P = eye - V * dist;
      vec3 N = normal(P);
      color = mix(color, shade(V, P, N), smoothstep(0.0, 0.1, dot(V, N)));
  }
  
  
  color = color / (color + vec3(1.0));
  color = pow(color, vec3(1.0/2.2));  
    
  gl_FragColor = vec4(color, 1.0);
}`;

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.