Source file: /examples/material/standard/reflection_refraction.js

import Stats from "stats.js";
import { GUI } from "dat.gui";
import {
  WebGLRenderer,
  PerspectiveProjection,
  GLTFLoader,
  Camera,
  WebGLRenderDevice,
  MeshMaterialPlugin,
  MeshMaterial3D,
  OrbitCameraControls,
  TextureType,
  SkyBox,
  TextureLoader,
  DirectionalLight,
  LightPlugin,
  AmbientLight,
  CanvasTarget,
  SkyboxPlugin,
  CameraPlugin,
  EnvironmentMap,
  StandardMaterial,
} from "chorama";

const canvas = document.createElement("canvas")
const renderTarget = new CanvasTarget(canvas)
const renderDevice = new WebGLRenderDevice(canvas, {
  depth: true
})
const renderer = new WebGLRenderer({
  plugins: [
    new CameraPlugin(),
    new LightPlugin(),
    new SkyboxPlugin(),
    new MeshMaterialPlugin(),
  ]
})

const environmentMap = renderer.getResource(EnvironmentMap)

// lighting
const ambientLight = new AmbientLight()
const directionalLight = new DirectionalLight()

directionalLight.transform.orientation
  .rotateX(-Math.PI / 4)
  .rotateZ(-Math.PI / 4)
directionalLight.intensity = 10
ambientLight.intensity = 0.1

// camera and camera controls
const camera = new Camera(renderTarget)
const cameraControls = new OrbitCameraControls(camera, canvas)

const textureLoader = new TextureLoader()
const gltfLoader = new GLTFLoader()

const day = textureLoader.load({
  paths: [
    "/images/skybox/miramar_right.png",
    "/images/skybox/miramar_left.png",
    "/images/skybox/miramar_top.png",
    "/images/skybox/miramar_bottom.png",
    "/images/skybox/miramar_back.png",
    "/images/skybox/miramar_front.png",
  ],
  type: TextureType.TextureCubeMap,
  generateMipmaps: true,
})

environmentMap?.set(day)

const skyBox = new SkyBox({
  day
})
skyBox.transform.orientation.rotateY(Math.PI)

const model = gltfLoader.load({
  paths: ["/models/glb/teapot.glb"]
})

model.transform.orientation.rotateY(-Math.PI / 8)

cameraControls.distance = 2.4
cameraControls.offset.y = 0.1
cameraControls.azimuth = -Math.PI / 4
cameraControls.elevation = 0.2

if (camera.projection instanceof PerspectiveProjection) {
  camera.projection.fov = Math.PI / 180 * 70
  camera.projection.aspect = innerWidth / innerHeight
}

document.body.append(canvas)
updateView()
addEventListener("resize", updateView)
requestAnimationFrame(update)

function update() {
  stats.begin()
  syncMaterials()
  model.transform.orientation.rotateY(Math.PI / 4000)
  renderer.render([model, skyBox, ambientLight, directionalLight, camera], renderDevice)
  cameraControls.update()
  stats.end()
  requestAnimationFrame(update)
}

function updateView() {
  canvas.style.width = innerWidth + "px"
  canvas.style.height = innerHeight + "px"
  canvas.width = innerWidth * devicePixelRatio
  canvas.height = innerHeight * devicePixelRatio

  if (camera.projection instanceof PerspectiveProjection) {
    camera.projection.aspect = innerWidth / innerHeight
  }
}

// demo-only GUI controls
const controls = new GUI()
const materialFolder = controls.addFolder("Teapot Material")
const settings = {
  reflectionStrength: 1.0,
  transmission: 1,
  thickness: 0.08,
  roughness: 0.02,
  metallic: 0.0,
  ior: 1.52
}

materialFolder
  .add(settings, "reflectionStrength", 0, 1, 0.01)
  .name("Reflection Strength")
  .onChange(syncMaterials)
materialFolder
  .add(settings, "transmission", 0, 1, 0.01)
  .name("Transmission")
  .onChange(syncMaterials)
materialFolder
  .add(settings, "thickness", 0, 20, 0.01)
  .name("Thickness")
  .onChange(syncMaterials)
materialFolder
  .add(settings, "roughness", 0, 1, 0.01)
  .name("Roughness")
  .onChange(syncMaterials)
materialFolder
  .add(settings, "metallic", 0, 1, 0.01)
  .name("Metallic")
  .onChange(syncMaterials)
materialFolder
  .add(settings, "ior", 1.0, 15, 0.01)
  .name("IOR")
  .onChange(syncMaterials)
syncMaterials()
materialFolder.open()

function syncMaterials() {
  model.traverseDFS(applyTeapotMaterial)
}

/**
 * @param {import("chorama").Object3D} child
 * @returns {boolean}
 */
function applyTeapotMaterial(child) {
  if (!(child instanceof MeshMaterial3D)) {
    return true
  }
  const material = child.material

  if (material instanceof StandardMaterial) {
    material.reflectionStrength = settings.reflectionStrength
    material.transmission = settings.transmission
    material.thickness = settings.thickness
    material.roughness = settings.roughness
    material.metallic = settings.metallic
    material.ior = settings.ior
  }

  return true
}

// demo-only performance monitor
const stats = new Stats()
stats.showPanel(1)
document.body.append(stats.dom)
stats.dom.removeAttribute("style")
stats.dom.classList.add("performance-monitor")