code: package org.sunflow.core.shader;
  
  import org.sunflow.SunflowAPI;
  import org.sunflow.core.ParameterList;
  import org.sunflow.core.Ray;
  import org.sunflow.core.Shader;
  import org.sunflow.core.ShadingState;
  import org.sunflow.image.Color;
  import org.sunflow.math.Vector3;
  
  public class GlassShader implements Shader {
      private float eta; // refraction index ratio
      private float f0; // fresnel normal incidence
      private Color color;
      private float absorptionDistance;
      private Color absorptionColor;
  
      public GlassShader() {
          eta = 1.3f;
          color = Color.WHITE;
          absorptionDistance = 0; // disabled by default
          absorptionColor = Color.GRAY; // 50% absorbtion
      }
  
      public boolean update(ParameterList pl, SunflowAPI api) {
          color = pl.getColor("color", color);
          eta = pl.getFloat("eta", eta);
          f0 = (1 - eta) / (1 + eta);
          f0 = f0 * f0;
          absorptionDistance = pl.getFloat("absorption.distance", absorptionDistance);
          absorptionColor = pl.getColor("absorption.color", absorptionColor);
          return true;
      }
  
      public Color getRadiance(ShadingState state) {
          if (!state.includeSpecular())
              return Color.BLACK;
          Vector3 reflDir = new Vector3();
          Vector3 refrDir = new Vector3();
          state.faceforward();
          float cos = state.getCosND();
          boolean inside = state.isBehind();
          float neta = inside ? eta : 1.0f / eta;
  
          float dn = 2 * cos;
          reflDir.x = (dn * state.getNormal().x) + state.getRay().getDirection().x;
          reflDir.y = (dn * state.getNormal().y) + state.getRay().getDirection().y;
          reflDir.z = (dn * state.getNormal().z) + state.getRay().getDirection().z;
  
          // refracted ray
          float arg = 1 - (neta * neta * (1 - (cos * cos)));
          boolean tir = arg < 0;
          if (tir)
              refrDir.x = refrDir.y = refrDir.z = 0;
          else {
              float nK = (neta * cos) - (float) Math.sqrt(arg);
              refrDir.x = (neta * state.getRay().dx) + (nK * state.getNormal().x);
              refrDir.y = (neta * state.getRay().dy) + (nK * state.getNormal().y);
              refrDir.z = (neta * state.getRay().dz) + (nK * state.getNormal().z);
          }
  
          // compute Fresnel terms
          float cosTheta1 = Vector3.dot(state.getNormal(), reflDir);
          float cosTheta2 = -Vector3.dot(state.getNormal(), refrDir);
  
          float pPara = (cosTheta1 - eta * cosTheta2) / (cosTheta1 + eta * cosTheta2);
          float pPerp = (eta * cosTheta1 - cosTheta2) / (eta * cosTheta1 + cosTheta2);
          float kr = 0.5f * (pPara * pPara + pPerp * pPerp);
          float kt = 1 - kr;
  
          Color absorbtion = null;
          if (inside && absorptionDistance > 0) {
              // this ray is inside the object and leaving it
              // compute attenuation that occured along the ray
              absorbtion = Color.mul(-state.getRay().getMax() / absorptionDistance, absorptionColor.copy().opposite()).exp();
              if (absorbtion.isBlack())
                  return Color.BLACK; // nothing goes through
          }
          // refracted ray
          Color ret = Color.black();
          if (!tir) {
              ret.madd(kt, state.traceRefraction(new Ray(state.getPoint(), refrDir), 0)).mul(color);
          }
          if (!inside || tir)
              ret.add(Color.mul(kr, state.traceReflection(new Ray(state.getPoint(), reflDir), 0)).mul(color));
          return absorbtion != null ? ret.mul(absorbtion) : ret;
      }
  
      public void scatterPhoton(ShadingState state, Color power) {
          Color refr = Color.mul(1 - f0, color);
          Color refl = Color.mul(f0, color);
          float avgR = refl.getAverage();
          float avgT = refr.getAverage();
          double rnd = state.getRandom(0, 0, 1);
          if (rnd < avgR) {
              state.faceforward();
              // don't reflect internally
              if (state.isBehind())
                  return;
              // photon is reflected
              float cos = state.getCosND();
              power.mul(refl).mul(1.0f / avgR);
              float dn = 2 * cos;
              Vector3 dir = new Vector3();
              dir.x = (dn * state.getNormal().x) + state.getRay().getDirection().x;
              dir.y = (dn * state.getNormal().y) + state.getRay().getDirection().y;
              dir.z = (dn * state.getNormal().z) + state.getRay().getDirection().z;
              state.traceReflectionPhoton(new Ray(state.getPoint(), dir), power);
          } else if (rnd < avgR + avgT) {
              state.faceforward();
              // photon is refracted
              float cos = state.getCosND();
              float neta = state.isBehind() ? eta : 1.0f / eta;
              power.mul(refr).mul(1.0f / avgT);
              float wK = -neta;
              float arg = 1 - (neta * neta * (1 - (cos * cos)));
              Vector3 dir = new Vector3();
              if (state.isBehind() && absorptionDistance > 0) {
                  // this ray is inside the object and leaving it
                  // compute attenuation that occured along the ray
                  power.mul(Color.mul(-state.getRay().getMax() / absorptionDistance, absorptionColor.copy().opposite()).exp());
              }
              if (arg < 0) {
                  // TIR
                  float dn = 2 * cos;
                  dir.x = (dn * state.getNormal().x) + state.getRay().getDirection().x;
                  dir.y = (dn * state.getNormal().y) + state.getRay().getDirection().y;
                  dir.z = (dn * state.getNormal().z) + state.getRay().getDirection().z;
                  state.traceReflectionPhoton(new Ray(state.getPoint(), dir), power);
              } else {
                  float nK = (neta * cos) - (float) Math.sqrt(arg);
                  dir.x = (-wK * state.getRay().dx) + (nK * state.getNormal().x);
                  dir.y = (-wK * state.getRay().dy) + (nK * state.getNormal().y);
                  dir.z = (-wK * state.getRay().dz) + (nK * state.getNormal().z);
                  state.traceRefractionPhoton(new Ray(state.getPoint(), dir), power);
              }
          }
      }
  }