Skip to content

Domain Repetition

Video Lecture

Section Video Links
Domain Repetition Domain Repetition Domain Repetition

Description

We can get a shape and repeat it infinitely in all directions.

In this section we will repeat a shape,

  • in all or some of the X, Y, Z directions
  • limit the boundaries of the X, Y, Z directions
  • change and animate the sizes of each repeated shape
  • change and animate the positions of each repeated shape
  • and more

Working Example

<>

Start Scripts

./src/SDFScene.ts

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { Break, float, If, Loop, normalize, uniform, vec2, vec3, Fn, positionLocal, abs } from 'three/tsl'
import { atmosphericScattering } from './AtmosphericScattering'
import Fog from './Fog'
import GUI from 'three/examples/jsm/libs/lil-gui.module.min.js'
import Fresnel from './Fresnel'
import Thing from './Thing'

export default class SDFScene {
  private static options = {
    maxSteps: 384,
    surfaceDistance: 0.005,
    cameraNear: 0.01,
    cameraFar: 192.0
  }

  private static maxSteps = uniform(this.options.maxSteps)
  private static surfaceDistance = uniform(this.options.surfaceDistance)
  private static cameraNear = uniform(this.options.cameraNear)
  private static cameraFar = uniform(this.options.cameraFar)

  // @ts-ignore
  private static scene = Fn(([position]) => {
    const distance = vec2(Thing.scene(position), 0).toVar()

    return distance
  })

  // @ts-ignore
  private static getNormal = Fn(([position, distance]) => {
    const offset = vec2(0.0001, 0)

    return normalize(
      distance.sub(
        vec3(
          this.scene(position.sub(offset.xyy)).x,
          this.scene(position.sub(offset.yxy)).x,
          this.scene(position.sub(offset.yyx)).x
        )
      )
    )
  })

  // @ts-ignore
  static render = Fn(([rayOrigin_immutable]) => {
    const rayOrigin = rayOrigin_immutable.toVar()

    const p = positionLocal

    const rayDirection = normalize(p).toVar()

    //const t = time.div(5)
    const lightPosition = vec3(0, 50, this.cameraFar.negate())
    //const lightPosition = vec3(0, sin(t).mul(30).add(43), this.cameraFar.negate())
    // /const lightPosition = vec3(sin(t).mul(this.cameraFar), 50, cos(t).mul(this.cameraFar))
    //const lightPosition = vec3(sin(t).mul(this.cameraFar), sin(t).mul(40).add(50), cos(t).mul(this.cameraFar))
    const lightDirection = normalize(lightPosition.sub(p))

    const skyColour = atmosphericScattering(vec3(p.x, p.y, p.z), normalize(lightPosition)).toVar()
    const finalColour = skyColour.toVar()

    const accumulatedDistance = float(this.cameraNear).toVar()
    const fogDistance = accumulatedDistance.toVar()

    const distance = vec2(0).toVar()
    const position = vec3(0).toVar()

    // @ts-ignore
    Loop({ start: 0, end: this.maxSteps }, () => {
      position.assign(rayOrigin.add(rayDirection.mul(accumulatedDistance)))
      distance.assign(this.scene(position))

      If(abs(distance.x).lessThan(this.surfaceDistance).or(accumulatedDistance.greaterThan(this.cameraFar)), () => {
        Break()
      })

      accumulatedDistance.addAssign(distance.x)
      fogDistance.addAssign(distance.x)
    })

    const normal = this.getNormal(position, distance.x).toVar()

    If(accumulatedDistance.lessThan(this.cameraFar), () => {
      finalColour.assign(Thing.render(finalColour, position, normal, rayDirection, lightPosition, lightDirection))

      // fog
      finalColour.assign(Fog.render(skyColour, finalColour, fogDistance))
    })

    return finalColour
  })

  static setGUI(gui: GUI) {
    const folder = gui.addFolder('SDF Scene')
    folder
      .add(this.options, 'maxSteps', 1, 512, 1)
      .name('Raymarch Max Steps')
      .onChange((v) => {
        this.maxSteps.value = v
      })
    folder
      .add(this.options, 'surfaceDistance', 0, 0.01, 0.0001)
      .name('Surface Distance')
      .onChange((v) => {
        this.surfaceDistance.value = v
      })
    folder
      .add(this.options, 'cameraNear', 0.0001, 10, 0.1)
      .name('Camera Near')
      .onChange((v) => {
        this.cameraNear.value = v
      })
    folder
      .add(this.options, 'cameraFar', 1, 512, 0.1)
      .name('Camera Far')
      .onChange((v) => {
        this.cameraFar.value = v
      })
    folder.close()

    Fresnel.setGUI(gui)
    Thing.setGUI(gui)
    Fog.setGUI(gui)
  }
}

./src/Thing.ts

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { clamp, cos, dot, float, Fn, sin, uniform, vec3, length, round, mix, Loop, time } from 'three/tsl'
import GUI from 'three/examples/jsm/libs/lil-gui.module.min.js'
import Fresnel from './Fresnel'

export default class Thing {
  private static options = {
    octaves: 3,
    lacunarity: 3.3,
    gain: 0.34,
    factorA: 1,
    factorB: 6,
    distance: 5
  }

  private static octaves = uniform(this.options.octaves)
  private static lacunarity = uniform(this.options.lacunarity)
  private static gain = uniform(this.options.gain)
  private static factorA = uniform(this.options.factorA)
  private static factorB = uniform(this.options.factorB)
  private static distance = uniform(this.options.distance)

  // @ts-ignore
  private static noise = Fn(([position]) => {
    return sin(position.x).add(sin(position.y))
  })

  // @ts-ignore
  private static fbm = Fn(([position]) => {
    const p = position.toVar()
    const accumulator = float(0.0).toVar()
    const amplitude = float(this.gain).toVar()

    Loop({ start: 0, end: this.octaves, condition: '<' }, () => {
      accumulator.addAssign(amplitude.mul(this.noise(p)))
      amplitude.mulAssign(this.gain)
      p.mulAssign(this.lacunarity)
    })

    return accumulator
  })

  // @ts-ignore
  private static Sphere = Fn(([position, radius]) => {
    return length(position).sub(radius)
  })

  // @ts-ignore
  static scene = Fn(([position]) => {
    const p = position.toVar()
    //const d = float(this.distance)
    //const index = round(position.div(d))

    //p.subAssign(d.mul(index))

    //p.x.subAssign(d.mul(index.x))
    //p.y.subAssign(d.mul(index.y))
    //p.z.subAssign(d.mul(index.z))

    // p.x.assign(clamp(p.x, -5.5, 5.5).sub(d.mul(index.x)))
    // p.y.assign(clamp(p.y, -5.5, 5.5).sub(d.mul(index.y)))
    // p.z.assign(clamp(p.z, -5.5, 5.5).sub(d.mul(index.z)))

    //p.x.addAssign(sin(index.z).mul(0.5))
    //p.y.addAssign(sin(index.z.add(time)).mul(0.5))
    //p.z.addAssign(sin(index.x).mul(0.5))

    const distance = this.Sphere(
      p,
      float(1) //.sub(cos(index.x).mul(0.75))
      //.sub(cos(index.x.add(time)).mul(0.75))
      //.add(this.fbm(position.mul(this.factorA).add(this.factorB)))
    )

    return distance
  })

  // @ts-ignore
  static render = Fn(([baseColour, position, normal, rayDirection, lightPosition, lightDirection]) => {
    const diffuse = clamp(dot(normal, lightDirection), 0.5, 1).toVar()
    const colour = vec3(1).toVar()

    colour.assign(mix(colour, baseColour, Fresnel.render(rayDirection.negate(), normal)))

    return colour.mul(diffuse)
  })

  // @ts-ignore
  static setGUI(gui: GUI) {
    const folder = gui.addFolder('Thing')
    folder
      .add(this.options, 'octaves', 0, 10, 1)
      .name('Octaves')
      .onChange((v) => {
        this.octaves.value = v
      })
    folder
      .add(this.options, 'lacunarity', 1, 10, 0.01)
      .name('Lacunarity')
      .onChange((v) => {
        this.lacunarity.value = v
      })
    folder
      .add(this.options, 'gain', 0.01, 1.99, 0.01)
      .name('Gain')
      .onChange((v) => {
        this.gain.value = v
      })
    folder
      .add(this.options, 'factorA', 0, 10, 1)
      .name('Factor A')
      .onChange((v) => {
        this.factorA.value = v
      })
    folder
      .add(this.options, 'factorB', 0, 10, 1)
      .name('Factor B')
      .onChange((v) => {
        this.factorB.value = v
      })
    folder
      .add(this.options, 'distance', 1, 20, 0.01)
      .name('Distance')
      .onChange((v) => {
        this.distance.value = v
      })
    folder.close()
  }
}

Palm Trees

Corrugated Trees

FBM Trees